Merge "DPM changes to support remote bugreports"
diff --git a/Android.mk b/Android.mk
index e4f40af..282b2af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -209,6 +209,7 @@
 	core/java/android/os/IBatteryPropertiesRegistrar.aidl \
 	core/java/android/os/ICancellationSignal.aidl \
 	core/java/android/os/IDeviceIdleController.aidl \
+	core/java/android/os/IMaintenanceActivityListener.aidl \
 	core/java/android/os/IMessenger.aidl \
 	core/java/android/os/INetworkActivityListener.aidl \
 	core/java/android/os/INetworkManagementService.aidl \
@@ -414,6 +415,9 @@
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
 	wifi/java/android/net/wifi/IWifiManager.aidl \
 	wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl \
+	wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl \
+	wifi/java/android/net/wifi/nan/IWifiNanManager.aidl \
+	wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
 	wifi/java/android/net/wifi/IRttManager.aidl \
@@ -484,6 +488,11 @@
 	frameworks/base/media/java/android/media/tv/TvTrackInfo.aidl \
 	frameworks/base/media/java/android/media/browse/MediaBrowser.aidl \
 	frameworks/base/wifi/java/android/net/wifi/ScanSettings.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/ConfigRequest.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/PublishData.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/SubscribeData.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/PublishSettings.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl \
diff --git a/api/current.txt b/api/current.txt
index c9d5307..0f3b099 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5789,6 +5789,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
@@ -5854,6 +5855,7 @@
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
+    method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String);
@@ -5909,6 +5911,7 @@
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
+    field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
@@ -7207,6 +7210,15 @@
     field public static final int TYPE_SCO = 2; // 0x2
   }
 
+  public class OobData implements android.os.Parcelable {
+    ctor public OobData();
+    method public int describeContents();
+    method public byte[] getSecurityManagerTk();
+    method public void setSecurityManagerTk(byte[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+  }
+
 }
 
 package android.bluetooth.le {
@@ -19362,6 +19374,7 @@
     field public static final deprecated int NUM_STREAMS = 5; // 0x5
     field public static final java.lang.String PROPERTY_OUTPUT_FRAMES_PER_BUFFER = "android.media.property.OUTPUT_FRAMES_PER_BUFFER";
     field public static final java.lang.String PROPERTY_OUTPUT_SAMPLE_RATE = "android.media.property.OUTPUT_SAMPLE_RATE";
+    field public static final java.lang.String PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED = "android.media.property.SUPPORT_AUDIO_SOURCE_UNPROCESSED";
     field public static final java.lang.String PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND = "android.media.property.SUPPORT_MIC_NEAR_ULTRASOUND";
     field public static final java.lang.String PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND = "android.media.property.SUPPORT_SPEAKER_NEAR_ULTRASOUND";
     field public static final java.lang.String RINGER_MODE_CHANGED_ACTION = "android.media.RINGER_MODE_CHANGED";
@@ -19399,9 +19412,10 @@
     method public abstract void onAudioFocusChange(int);
   }
 
-  public class AudioRecord {
+  public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method public int getAudioSource();
@@ -19425,7 +19439,8 @@
     method public int read(java.nio.ByteBuffer, int);
     method public int read(java.nio.ByteBuffer, int, int);
     method public void release();
-    method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setNotificationMarkerPosition(int);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19463,17 +19478,29 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public abstract interface AudioRouting {
+    method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+    method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+    method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+    method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+  }
+
+  public static abstract interface AudioRouting.OnRoutingChangedListener {
+    method public abstract void onRoutingChanged(android.media.AudioRouting);
+  }
+
   public final class AudioTimestamp {
     ctor public AudioTimestamp();
     field public long framePosition;
     field public long nanoTime;
   }
 
-  public class AudioTrack {
+  public class AudioTrack implements android.media.AudioRouting {
     ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int attachAuxEffect(int);
     method public void flush();
     method public int getAudioFormat();
@@ -19503,7 +19530,8 @@
     method public void play() throws java.lang.IllegalStateException;
     method public void release();
     method public int reloadStaticData();
-    method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setAuxEffectSendLevel(float);
     method public int setLoopPoints(int, int, int);
     method public int setNotificationMarkerPosition(int);
@@ -19556,8 +19584,8 @@
     method public abstract void onPeriodicNotification(android.media.AudioTrack);
   }
 
-  public static abstract interface AudioTrack.OnRoutingChangedListener {
-    method public abstract void onRoutingChanged(android.media.AudioTrack);
+  public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+    method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
   }
 
   public class CamcorderProfile {
@@ -20707,6 +20735,7 @@
     field public static final int DEFAULT = 0; // 0x0
     field public static final int MIC = 1; // 0x1
     field public static final int REMOTE_SUBMIX = 8; // 0x8
+    field public static final int UNPROCESSED = 9; // 0x9
     field public static final int VOICE_CALL = 4; // 0x4
     field public static final int VOICE_COMMUNICATION = 7; // 0x7
     field public static final int VOICE_DOWNLINK = 3; // 0x3
@@ -28476,6 +28505,7 @@
   }
 
   public class UserManager {
+    method public static android.content.Intent createUserCreationIntent(java.lang.String, java.lang.String, java.lang.String, android.os.PersistableBundle);
     method public android.os.Bundle getApplicationRestrictions(java.lang.String);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method public int getUserCount();
@@ -33617,6 +33647,7 @@
     method public abstract void onUnsubscribe(android.net.Uri);
     field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
     field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+    field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
     field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
@@ -38128,9 +38159,11 @@
 
   public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
     ctor public LocaleSpan(java.util.Locale);
+    ctor public LocaleSpan(android.util.LocaleList);
     ctor public LocaleSpan(android.os.Parcel);
     method public int describeContents();
     method public java.util.Locale getLocale();
+    method public android.util.LocaleList getLocales();
     method public int getSpanTypeId();
     method public void updateDrawState(android.text.TextPaint);
     method public void updateMeasureState(android.text.TextPaint);
@@ -38236,7 +38269,8 @@
     ctor public SuggestionSpan(android.os.Parcel);
     method public int describeContents();
     method public int getFlags();
-    method public java.lang.String getLocale();
+    method public deprecated java.lang.String getLocale();
+    method public java.util.Locale getLocaleObject();
     method public int getSpanTypeId();
     method public java.lang.String[] getSuggestions();
     method public void setFlags(int);
@@ -43386,6 +43420,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public static int getComposingSpanEnd(android.text.Spannable);
@@ -43551,6 +43586,7 @@
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
     method public abstract boolean deleteSurroundingText(int, int);
+    method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
     method public abstract boolean endBatchEdit();
     method public abstract boolean finishComposingText();
     method public abstract int getCursorCapsMode(int);
@@ -43581,6 +43617,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public int getCursorCapsMode(int);
@@ -43643,6 +43680,7 @@
   }
 
   public final class InputMethodManager {
+    method public void dispatchKeyEventFromInputMethod(android.view.View, android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
     method public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
     method public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
diff --git a/api/system-current.txt b/api/system-current.txt
index 59000f1..360a40b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5923,6 +5923,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
@@ -5997,6 +5998,7 @@
     method public boolean resetPassword(java.lang.String, int);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
     method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException;
+    method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String);
@@ -6052,6 +6054,7 @@
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
+    field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
@@ -7439,6 +7442,15 @@
     field public static final int TYPE_SCO = 2; // 0x2
   }
 
+  public class OobData implements android.os.Parcelable {
+    ctor public OobData();
+    method public int describeContents();
+    method public byte[] getSecurityManagerTk();
+    method public void setSecurityManagerTk(byte[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+  }
+
 }
 
 package android.bluetooth.le {
@@ -8216,6 +8228,7 @@
     field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
     field public static final int BIND_AUTO_CREATE = 1; // 0x1
     field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+    field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
     field public static final int BIND_IMPORTANT = 64; // 0x40
     field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
     field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -20666,6 +20679,7 @@
     field public static final deprecated int NUM_STREAMS = 5; // 0x5
     field public static final java.lang.String PROPERTY_OUTPUT_FRAMES_PER_BUFFER = "android.media.property.OUTPUT_FRAMES_PER_BUFFER";
     field public static final java.lang.String PROPERTY_OUTPUT_SAMPLE_RATE = "android.media.property.OUTPUT_SAMPLE_RATE";
+    field public static final java.lang.String PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED = "android.media.property.SUPPORT_AUDIO_SOURCE_UNPROCESSED";
     field public static final java.lang.String PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND = "android.media.property.SUPPORT_MIC_NEAR_ULTRASOUND";
     field public static final java.lang.String PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND = "android.media.property.SUPPORT_SPEAKER_NEAR_ULTRASOUND";
     field public static final java.lang.String RINGER_MODE_CHANGED_ACTION = "android.media.RINGER_MODE_CHANGED";
@@ -20703,10 +20717,11 @@
     method public abstract void onAudioFocusChange(int);
   }
 
-  public class AudioRecord {
+  public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method public int getAudioSource();
@@ -20730,7 +20745,8 @@
     method public int read(java.nio.ByteBuffer, int);
     method public int read(java.nio.ByteBuffer, int, int);
     method public void release();
-    method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setNotificationMarkerPosition(int);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -20770,17 +20786,29 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public abstract interface AudioRouting {
+    method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+    method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+    method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+    method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+  }
+
+  public static abstract interface AudioRouting.OnRoutingChangedListener {
+    method public abstract void onRoutingChanged(android.media.AudioRouting);
+  }
+
   public final class AudioTimestamp {
     ctor public AudioTimestamp();
     field public long framePosition;
     field public long nanoTime;
   }
 
-  public class AudioTrack {
+  public class AudioTrack implements android.media.AudioRouting {
     ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int attachAuxEffect(int);
     method public void flush();
     method public int getAudioFormat();
@@ -20810,7 +20838,8 @@
     method public void play() throws java.lang.IllegalStateException;
     method public void release();
     method public int reloadStaticData();
-    method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setAuxEffectSendLevel(float);
     method public int setLoopPoints(int, int, int);
     method public int setNotificationMarkerPosition(int);
@@ -20863,8 +20892,8 @@
     method public abstract void onPeriodicNotification(android.media.AudioTrack);
   }
 
-  public static abstract interface AudioTrack.OnRoutingChangedListener {
-    method public abstract void onRoutingChanged(android.media.AudioTrack);
+  public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+    method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
   }
 
   public class CamcorderProfile {
@@ -22016,6 +22045,7 @@
     field public static final int MIC = 1; // 0x1
     field public static final int RADIO_TUNER = 1998; // 0x7ce
     field public static final int REMOTE_SUBMIX = 8; // 0x8
+    field public static final int UNPROCESSED = 9; // 0x9
     field public static final int VOICE_CALL = 4; // 0x4
     field public static final int VOICE_COMMUNICATION = 7; // 0x7
     field public static final int VOICE_DOWNLINK = 3; // 0x3
@@ -30488,7 +30518,12 @@
   }
 
   public class UserManager {
+    method public void clearSeedAccountData();
+    method public static android.content.Intent createUserCreationIntent(java.lang.String, java.lang.String, java.lang.String, android.os.PersistableBundle);
     method public android.os.Bundle getApplicationRestrictions(java.lang.String);
+    method public java.lang.String getSeedAccountName();
+    method public android.os.PersistableBundle getSeedAccountOptions();
+    method public java.lang.String getSeedAccountType();
     method public long getSerialNumberForUser(android.os.UserHandle);
     method public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
@@ -35763,6 +35798,7 @@
     method public abstract void onUnsubscribe(android.net.Uri);
     field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
     field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+    field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
     field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
@@ -37561,6 +37597,35 @@
     method public abstract void onVideoQualityChanged(int);
   }
 
+  public class ParcelableCallAnalytics implements android.os.Parcelable {
+    ctor public ParcelableCallAnalytics(long, long, int, boolean, boolean, int, int, boolean, java.lang.String, boolean);
+    ctor public ParcelableCallAnalytics(android.os.Parcel);
+    method public int describeContents();
+    method public long getCallDurationMillis();
+    method public int getCallTechnologies();
+    method public int getCallTerminationCode();
+    method public int getCallType();
+    method public java.lang.String getConnectionService();
+    method public long getStartTimeMillis();
+    method public boolean isAdditionalCall();
+    method public boolean isCreatedFromExistingConnection();
+    method public boolean isEmergencyCall();
+    method public boolean isInterrupted();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CALLTYPE_INCOMING = 1; // 0x1
+    field public static final int CALLTYPE_OUTGOING = 2; // 0x2
+    field public static final int CALLTYPE_UNKNOWN = 0; // 0x0
+    field public static final int CDMA_PHONE = 1; // 0x1
+    field public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics> CREATOR;
+    field public static final int GSM_PHONE = 2; // 0x2
+    field public static final int IMS_PHONE = 4; // 0x4
+    field public static final long MILLIS_IN_1_SECOND = 1000L; // 0x3e8L
+    field public static final long MILLIS_IN_5_MINUTES = 300000L; // 0x493e0L
+    field public static final int SIP_PHONE = 8; // 0x8
+    field public static final int STILL_CONNECTED = -1; // 0xffffffff
+    field public static final int THIRD_PARTY_PHONE = 16; // 0x10
+  }
+
   public final deprecated class Phone {
     method public final void addListener(android.telecom.Phone.Listener);
     method public final boolean canAddCall();
@@ -37778,6 +37843,7 @@
     method public void cancelMissedCallsNotification();
     method public deprecated void clearAccounts();
     method public void clearPhoneAccounts();
+    method public java.util.List<android.telecom.ParcelableCallAnalytics> dumpAnalytics();
     method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean);
     method public boolean endCall();
     method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -38510,7 +38576,6 @@
     method public boolean isOffhook();
     method public boolean isRadioOn();
     method public boolean isRinging();
-    method public boolean isSimPinEnabled();
     method public boolean isSmsCapable();
     method public boolean isTtyModeSupported();
     method public boolean isVideoCallingEnabled();
@@ -40487,9 +40552,11 @@
 
   public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
     ctor public LocaleSpan(java.util.Locale);
+    ctor public LocaleSpan(android.util.LocaleList);
     ctor public LocaleSpan(android.os.Parcel);
     method public int describeContents();
     method public java.util.Locale getLocale();
+    method public android.util.LocaleList getLocales();
     method public int getSpanTypeId();
     method public void updateDrawState(android.text.TextPaint);
     method public void updateMeasureState(android.text.TextPaint);
@@ -40595,7 +40662,8 @@
     ctor public SuggestionSpan(android.os.Parcel);
     method public int describeContents();
     method public int getFlags();
-    method public java.lang.String getLocale();
+    method public deprecated java.lang.String getLocale();
+    method public java.util.Locale getLocaleObject();
     method public int getSpanTypeId();
     method public java.lang.String[] getSuggestions();
     method public void setFlags(int);
@@ -45748,6 +45816,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public static int getComposingSpanEnd(android.text.Spannable);
@@ -45913,6 +45982,7 @@
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
     method public abstract boolean deleteSurroundingText(int, int);
+    method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
     method public abstract boolean endBatchEdit();
     method public abstract boolean finishComposingText();
     method public abstract int getCursorCapsMode(int);
@@ -45943,6 +46013,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public int getCursorCapsMode(int);
@@ -46005,6 +46076,7 @@
   }
 
   public final class InputMethodManager {
+    method public void dispatchKeyEventFromInputMethod(android.view.View, android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
     method public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
     method public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
diff --git a/api/test-current.txt b/api/test-current.txt
index 33151e8..fb11fcc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5791,6 +5791,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
@@ -5856,6 +5857,7 @@
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
+    method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String);
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String);
@@ -5911,6 +5913,7 @@
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
+    field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
@@ -7209,6 +7212,15 @@
     field public static final int TYPE_SCO = 2; // 0x2
   }
 
+  public class OobData implements android.os.Parcelable {
+    ctor public OobData();
+    method public int describeContents();
+    method public byte[] getSecurityManagerTk();
+    method public void setSecurityManagerTk(byte[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+  }
+
 }
 
 package android.bluetooth.le {
@@ -19370,6 +19382,7 @@
     field public static final deprecated int NUM_STREAMS = 5; // 0x5
     field public static final java.lang.String PROPERTY_OUTPUT_FRAMES_PER_BUFFER = "android.media.property.OUTPUT_FRAMES_PER_BUFFER";
     field public static final java.lang.String PROPERTY_OUTPUT_SAMPLE_RATE = "android.media.property.OUTPUT_SAMPLE_RATE";
+    field public static final java.lang.String PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED = "android.media.property.SUPPORT_AUDIO_SOURCE_UNPROCESSED";
     field public static final java.lang.String PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND = "android.media.property.SUPPORT_MIC_NEAR_ULTRASOUND";
     field public static final java.lang.String PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND = "android.media.property.SUPPORT_SPEAKER_NEAR_ULTRASOUND";
     field public static final java.lang.String RINGER_MODE_CHANGED_ACTION = "android.media.RINGER_MODE_CHANGED";
@@ -19407,9 +19420,10 @@
     method public abstract void onAudioFocusChange(int);
   }
 
-  public class AudioRecord {
+  public class AudioRecord implements android.media.AudioRouting {
     ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int getAudioFormat();
     method public int getAudioSessionId();
     method public int getAudioSource();
@@ -19433,7 +19447,8 @@
     method public int read(java.nio.ByteBuffer, int);
     method public int read(java.nio.ByteBuffer, int, int);
     method public void release();
-    method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setNotificationMarkerPosition(int);
     method public int setPositionNotificationPeriod(int);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19471,17 +19486,29 @@
     method public abstract void onRoutingChanged(android.media.AudioRecord);
   }
 
+  public abstract interface AudioRouting {
+    method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+    method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+    method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+    method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+  }
+
+  public static abstract interface AudioRouting.OnRoutingChangedListener {
+    method public abstract void onRoutingChanged(android.media.AudioRouting);
+  }
+
   public final class AudioTimestamp {
     ctor public AudioTimestamp();
     field public long framePosition;
     field public long nanoTime;
   }
 
-  public class AudioTrack {
+  public class AudioTrack implements android.media.AudioRouting {
     ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
     ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
-    method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+    method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public int attachAuxEffect(int);
     method public void flush();
     method public int getAudioFormat();
@@ -19511,7 +19538,8 @@
     method public void play() throws java.lang.IllegalStateException;
     method public void release();
     method public int reloadStaticData();
-    method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+    method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public int setAuxEffectSendLevel(float);
     method public int setLoopPoints(int, int, int);
     method public int setNotificationMarkerPosition(int);
@@ -19564,8 +19592,8 @@
     method public abstract void onPeriodicNotification(android.media.AudioTrack);
   }
 
-  public static abstract interface AudioTrack.OnRoutingChangedListener {
-    method public abstract void onRoutingChanged(android.media.AudioTrack);
+  public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+    method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
   }
 
   public class CamcorderProfile {
@@ -20715,6 +20743,7 @@
     field public static final int DEFAULT = 0; // 0x0
     field public static final int MIC = 1; // 0x1
     field public static final int REMOTE_SUBMIX = 8; // 0x8
+    field public static final int UNPROCESSED = 9; // 0x9
     field public static final int VOICE_CALL = 4; // 0x4
     field public static final int VOICE_COMMUNICATION = 7; // 0x7
     field public static final int VOICE_DOWNLINK = 3; // 0x3
@@ -28485,6 +28514,7 @@
   }
 
   public class UserManager {
+    method public static android.content.Intent createUserCreationIntent(java.lang.String, java.lang.String, java.lang.String, android.os.PersistableBundle);
     method public android.os.Bundle getApplicationRestrictions(java.lang.String);
     method public long getSerialNumberForUser(android.os.UserHandle);
     method public int getUserCount();
@@ -33631,6 +33661,7 @@
     method public abstract void onUnsubscribe(android.net.Uri);
     field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
     field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+    field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
     field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
   }
@@ -38144,9 +38175,11 @@
 
   public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
     ctor public LocaleSpan(java.util.Locale);
+    ctor public LocaleSpan(android.util.LocaleList);
     ctor public LocaleSpan(android.os.Parcel);
     method public int describeContents();
     method public java.util.Locale getLocale();
+    method public android.util.LocaleList getLocales();
     method public int getSpanTypeId();
     method public void updateDrawState(android.text.TextPaint);
     method public void updateMeasureState(android.text.TextPaint);
@@ -38252,7 +38285,8 @@
     ctor public SuggestionSpan(android.os.Parcel);
     method public int describeContents();
     method public int getFlags();
-    method public java.lang.String getLocale();
+    method public deprecated java.lang.String getLocale();
+    method public java.util.Locale getLocaleObject();
     method public int getSpanTypeId();
     method public java.lang.String[] getSuggestions();
     method public void setFlags(int);
@@ -43402,6 +43436,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public static int getComposingSpanEnd(android.text.Spannable);
@@ -43567,6 +43602,7 @@
     method public abstract boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public abstract boolean commitText(java.lang.CharSequence, int);
     method public abstract boolean deleteSurroundingText(int, int);
+    method public abstract boolean deleteSurroundingTextInCodePoints(int, int);
     method public abstract boolean endBatchEdit();
     method public abstract boolean finishComposingText();
     method public abstract int getCursorCapsMode(int);
@@ -43597,6 +43633,7 @@
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(java.lang.CharSequence, int);
     method public boolean deleteSurroundingText(int, int);
+    method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public int getCursorCapsMode(int);
@@ -43659,6 +43696,7 @@
   }
 
   public final class InputMethodManager {
+    method public void dispatchKeyEventFromInputMethod(android.view.View, android.view.KeyEvent);
     method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
     method public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
     method public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 2449ee5..10f5d0d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1853,6 +1853,22 @@
         }.start();
     }
 
+    /**
+     * @hide
+     * Checks if the given account exists on any of the users on the device.
+     * Only the system process can call this method.
+     *
+     * @param account The account to check for existence.
+     * @return whether any user has this account
+     */
+    public boolean someUserHasAccount(@NonNull final Account account) {
+        try {
+            return mService.someUserHasAccount(account);
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+    }
+
     private void ensureNotOnMainThread() {
         final Looper looper = Looper.myLooper();
         if (looper != null && looper == mContext.getMainLooper()) {
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4af9f33..608501f 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -95,4 +95,7 @@
     /* Finish session started by startAddAccountSession(...) or startUpdateCredentialsSession(...) */
     void finishSession(in IAccountManagerResponse response, in Bundle sessionBundle,
         boolean expectActivityLaunch, in Bundle appInfo);
+
+    /* Check if an account exists on any user on the device. */
+    boolean someUserHasAccount(in Account account);
 }
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 7f9a5d3..e721de9 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -17,6 +17,7 @@
 package android.animation;
 
 import android.annotation.CallSuper;
+import android.annotation.IntDef;
 import android.os.Looper;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
@@ -25,6 +26,8 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -234,6 +237,11 @@
      * Public constants
      */
 
+    /** @hide */
+    @IntDef({RESTART, REVERSE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RepeatMode {}
+
     /**
      * When the animation reaches the end and <code>repeatCount</code> is INFINITE
      * or a positive value, the animation restarts from the beginning.
@@ -807,7 +815,7 @@
      *
      * @param value {@link #RESTART} or {@link #REVERSE}
      */
-    public void setRepeatMode(int value) {
+    public void setRepeatMode(@RepeatMode int value) {
         mRepeatMode = value;
     }
 
@@ -816,6 +824,7 @@
      *
      * @return either one of {@link #REVERSE} or {@link #RESTART}
      */
+    @RepeatMode
     public int getRepeatMode() {
         return mRepeatMode;
     }
diff --git a/core/java/android/annotation/IntDef.java b/core/java/android/annotation/IntDef.java
index a18bfa5..434a9c7 100644
--- a/core/java/android/annotation/IntDef.java
+++ b/core/java/android/annotation/IntDef.java
@@ -42,7 +42,7 @@
  * For a flag, set the flag attribute:
  * <pre><code>
  *  &#64;IntDef(
- *      flag = true
+ *      flag = true,
  *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
  * </code></pre>
  *
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 624131e..63b6825 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2798,7 +2798,8 @@
         case MOVE_TASKS_TO_FULLSCREEN_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             final int stackId = data.readInt();
-            moveTasksToFullscreenStack(stackId);
+            final boolean onTop = data.readInt() == 1;
+            moveTasksToFullscreenStack(stackId, onTop);
             reply.writeNoException();
             return true;
         }
@@ -2842,6 +2843,14 @@
             reply.writeNoException();
             return true;
         }
+        case IS_APP_FOREGROUND_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final int userHandle = data.readInt();
+            final boolean isForeground = isAppForeground(userHandle);
+            reply.writeNoException();
+            reply.writeInt(isForeground ? 1 : 0);
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -6573,11 +6582,12 @@
     }
 
     @Override
-    public void moveTasksToFullscreenStack(int fromStackId) throws RemoteException {
+    public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(fromStackId);
+        data.writeInt(onTop ? 1 : 0);
         mRemote.transact(MOVE_TASKS_TO_FULLSCREEN_STACK_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -6639,5 +6649,18 @@
         reply.recycle();
     }
 
+    @Override
+    public boolean isAppForeground(int uid) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(uid);
+        mRemote.transact(IS_APP_FOREGROUND_TRANSACTION, data, reply, 0);
+        final boolean isForeground = reply.readInt() == 1 ? true : false;
+        data.recycle();
+        reply.recycle();
+        return isForeground;
+    };
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4e55c89..2f2fbbe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4362,6 +4362,9 @@
         if (!tmp.onlyLocalRequest) {
             try {
                 ActivityManagerNative.getDefault().activityRelaunched(r.token);
+                if (r.window != null) {
+                    r.window.reportActivityRelaunched();
+                }
             } catch (RemoteException e) {
                 // If the system process has died, it's game over for everyone.
             }
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index e28fb20..af840d0 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -894,7 +894,9 @@
             final View decor = getDecor();
             if (decor != null) {
                 final ViewRootImpl viewRoot = decor.getViewRootImpl();
-                viewRoot.setPausedForTransition(false);
+                if (viewRoot != null) {
+                    viewRoot.setPausedForTransition(false);
+                }
             }
             onTransitionsComplete();
         }
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 8d7f347..bbf1607 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -23,7 +23,6 @@
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.os.Bundle;
-import android.text.format.DateUtils;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -49,19 +48,16 @@
     private static final String DAY = "day";
 
     private final DatePicker mDatePicker;
-    private final Calendar mCalendar;
 
     private OnDateSetListener mDateSetListener;
 
-    private boolean mTitleNeedsUpdate = true;
-
     /**
      * Creates a new date picker dialog for the current date using the parent
      * context's default date picker dialog theme.
      *
      * @param context the parent context
      */
-    public DatePickerDialog(Context context) {
+    public DatePickerDialog(@NonNull Context context) {
         this(context, 0);
     }
 
@@ -73,7 +69,7 @@
      *                   this dialog, or {@code 0} to use the parent
      *                   {@code context}'s default alert dialog theme
      */
-    public DatePickerDialog(Context context, @StyleRes int themeResId) {
+    public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId) {
         super(context, resolveDialogTheme(context, themeResId));
 
         final Context themeContext = getContext();
@@ -85,11 +81,10 @@
         setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
         setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
 
-        mCalendar = Calendar.getInstance();
-
-        final int year = mCalendar.get(Calendar.YEAR);
-        final int monthOfYear = mCalendar.get(Calendar.MONTH);
-        final int dayOfMonth = mCalendar.get(Calendar.DAY_OF_MONTH);
+        final Calendar calendar = Calendar.getInstance();
+        final int year = calendar.get(Calendar.YEAR);
+        final int monthOfYear = calendar.get(Calendar.MONTH);
+        final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
         mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
         mDatePicker.init(year, monthOfYear, dayOfMonth, this);
         mDatePicker.setValidationCallback(mValidationCallback);
@@ -107,7 +102,7 @@
      * @param dayOfMonth the initially selected day of month (1-31, depending
      *                   on month)
      */
-    public DatePickerDialog(@Nullable Context context, @Nullable OnDateSetListener listener,
+    public DatePickerDialog(@NonNull Context context, @Nullable OnDateSetListener listener,
             int year, int month, int dayOfMonth) {
         this(context, 0, listener, year, month, dayOfMonth);
     }
@@ -135,7 +130,7 @@
         mDatePicker.updateDate(year, month, dayOfMonth);
     }
 
-    static int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
+    static @StyleRes int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
         if (themeResId == 0) {
             final TypedValue outValue = new TypedValue();
             context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
@@ -146,9 +141,8 @@
     }
 
     @Override
-    public void onDateChanged(DatePicker view, int year, int month, int dayOfMonth) {
+    public void onDateChanged(@NonNull DatePicker view, int year, int month, int dayOfMonth) {
         mDatePicker.init(year, month, dayOfMonth, this);
-        updateTitle(year, month, dayOfMonth);
     }
 
     /**
@@ -161,7 +155,7 @@
     }
 
     @Override
-    public void onClick(DialogInterface dialog, int which) {
+    public void onClick(@NonNull DialogInterface dialog, int which) {
         switch (which) {
             case BUTTON_POSITIVE:
                 if (mDateSetListener != null) {
@@ -200,29 +194,6 @@
         mDatePicker.updateDate(year, month, dayOfMonth);
     }
 
-    private void updateTitle(int year, int month, int dayOfMonth) {
-        if (!mDatePicker.getCalendarViewShown()) {
-            mCalendar.set(Calendar.YEAR, year);
-            mCalendar.set(Calendar.MONTH, month);
-            mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
-
-            final String title = DateUtils.formatDateTime(mContext,
-                    mCalendar.getTimeInMillis(),
-                    DateUtils.FORMAT_SHOW_DATE
-                    | DateUtils.FORMAT_SHOW_WEEKDAY
-                    | DateUtils.FORMAT_SHOW_YEAR
-                    | DateUtils.FORMAT_ABBREV_MONTH
-                    | DateUtils.FORMAT_ABBREV_WEEKDAY);
-            setTitle(title);
-
-            mTitleNeedsUpdate = true;
-        } else if (mTitleNeedsUpdate) {
-            setTitle(R.string.date_picker_dialog_title);
-
-            mTitleNeedsUpdate = false;
-        }
-    }
-
     @Override
     public Bundle onSaveInstanceState() {
         final Bundle state = super.onSaveInstanceState();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1ae91a6..5bb2cf5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -577,7 +577,7 @@
 
     public void suppressResizeConfigChanges(boolean suppress) throws RemoteException;
 
-    public void moveTasksToFullscreenStack(int fromStackId) throws RemoteException;
+    public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) throws RemoteException;
 
     public int getAppStartMode(int uid, String packageName) throws RemoteException;
 
@@ -589,6 +589,8 @@
 
     public void setVrMode(IBinder token, boolean enabled) throws RemoteException;
 
+    public boolean isAppForeground(int uid) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -960,4 +962,5 @@
     int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 359;
     int GET_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 360;
     int CLEAR_GRANTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 361;
+    int IS_APP_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 362;
 }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 633f699..368b8ef 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -103,6 +103,7 @@
     boolean updateAutomaticZenRule(in AutomaticZenRule automaticZenRule);
     boolean removeAutomaticZenRule(String id);
     boolean removeAutomaticZenRules(String packageName);
+    int getRuleInstanceCount(in ComponentName owner);
 
     byte[] getBackupPayload(int user);
     void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/JobSchedulerImpl.java b/core/java/android/app/JobSchedulerImpl.java
index 09038d5..dacf4ea 100644
--- a/core/java/android/app/JobSchedulerImpl.java
+++ b/core/java/android/app/JobSchedulerImpl.java
@@ -46,6 +46,15 @@
     }
 
     @Override
+    public int scheduleAsPackage(JobInfo job, String packageName, int userId) {
+        try {
+            return mBinder.scheduleAsPackage(job, packageName, userId);
+        } catch (RemoteException e) {
+            return JobScheduler.RESULT_FAILURE;
+        }
+    }
+
+    @Override
     public void cancel(int jobId) {
         try {
             mBinder.cancel(jobId);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 9a3c820..faf5b11 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -380,6 +380,18 @@
     }
 
     /**
+     * @hide
+     */
+    public int getRuleInstanceCount(ComponentName owner) {
+        INotificationManager service = getService();
+        try {
+            return service.getRuleInstanceCount(owner);
+        } catch (RemoteException e) {
+        }
+        return 0;
+    }
+
+    /**
      * Returns AutomaticZenRules owned by the caller.
      *
      * <p>
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 89d52f2..f3b1175 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -78,6 +78,8 @@
 import android.net.wifi.RttManager;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.nan.IWifiNanManager;
+import android.net.wifi.nan.WifiNanManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager;
 import android.net.wifi.passpoint.IWifiPasspointManager;
@@ -499,6 +501,18 @@
                 return new WifiP2pManager(service);
             }});
 
+        registerService(Context.WIFI_NAN_SERVICE, WifiNanManager.class,
+                new StaticServiceFetcher<WifiNanManager>() {
+            @Override
+            public WifiNanManager createService() {
+                IBinder b = ServiceManager.getService(Context.WIFI_NAN_SERVICE);
+                IWifiNanManager service = IWifiNanManager.Stub.asInterface(b);
+                if (service == null) {
+                    return null;
+                }
+                return new WifiNanManager(service);
+            }});
+
         registerService(Context.WIFI_SCANNING_SERVICE, WifiScanner.class,
                 new CachedServiceFetcher<WifiScanner>() {
             @Override
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e27e2..bd10267 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -28,6 +28,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.view.IWindowManager;
 import android.view.InputEvent;
 import android.view.SurfaceControl;
@@ -167,9 +168,10 @@
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
+        int callingUserId = UserHandle.getCallingUserId();
         final long identity = Binder.clearCallingIdentity();
         try {
-            IBinder token = mAccessibilityManager.getWindowToken(windowId);
+            IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
             if (token == null) {
                 return false;
             }
@@ -186,9 +188,10 @@
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
+        int callingUserId = UserHandle.getCallingUserId();
         final long identity = Binder.clearCallingIdentity();
         try {
-            IBinder token = mAccessibilityManager.getWindowToken(windowId);
+            IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
             if (token == null) {
                 return null;
             }
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 44164158..56b4249 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,12 +16,16 @@
 
 package android.app;
 
+import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class provides access to the system uimode services.  These services
  * allow applications to control UI modes of the device.
@@ -92,18 +96,26 @@
      * when the user exits desk mode.
      */
     public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE";
-    
-    /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+
+    /** @hide */
+    @IntDef({MODE_NIGHT_AUTO, MODE_NIGHT_NO, MODE_NIGHT_YES})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NightMode {}
+
+    /**
+     * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * automatically switch night mode on and off based on the time.
      */
     public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4;
     
-    /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+    /**
+     * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * never run in night mode.
      */
     public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
     
-    /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+    /**
+     * Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
      * always run in night mode.
      */
     public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
@@ -195,20 +207,28 @@
     }
 
     /**
-     * Sets the night mode.  Changes to the night mode are only effective when
-     * the car or desk mode is enabled on a device.
-     *
-     * <p>The mode can be one of:
+     * Sets the night mode.
+     * <p>
+     * The mode can be one of:
      * <ul>
-     *   <li><em>{@link #MODE_NIGHT_NO}<em> - sets the device into notnight
-     *       mode.</li>
-     *   <li><em>{@link #MODE_NIGHT_YES}</em> - sets the device into night mode.
-     *   </li>
-     *   <li><em>{@link #MODE_NIGHT_AUTO}</em> - automatic night/notnight switching
-     *       depending on the location and certain other sensors.</li>
+     *   <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+     *       {@code notnight} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
+     *       {@code night} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the device's current
+     *       location and certain other sensors</li>
      * </ul>
+     * <p>
+     * <strong>Note:</strong> On API 22 and below, changes to the night mode
+     * are only effective when the {@link Configuration#UI_MODE_TYPE_CAR car}
+     * or {@link Configuration#UI_MODE_TYPE_DESK desk} mode is enabled on a
+     * device. Starting in API 23, changes to night mode are always effective.
+     *
+     * @param mode the night mode to set
+     * @see #getNightMode()
      */
-    public void setNightMode(int mode) {
+    public void setNightMode(@NightMode int mode) {
         if (mService != null) {
             try {
                 mService.setNightMode(mode);
@@ -219,11 +239,20 @@
     }
 
     /**
-     * @return the currently configured night mode. May be one of
-     *         {@link #MODE_NIGHT_NO}, {@link #MODE_NIGHT_YES},
-     *         {@link #MODE_NIGHT_AUTO}, or -1 on error.
+     * Returns the currently configured night mode.
+     * <p>
+     * May be one of:
+     * <ul>
+     *   <li>{@link #MODE_NIGHT_NO}</li>
+     *   <li>{@link #MODE_NIGHT_YES}</li>
+     *   <li>{@link #MODE_NIGHT_AUTO}</li>
+     *   <li>{@code -1} on error</li>
+     * </ul>
+     *
+     * @return the current night mode, or {@code -1} on error
+     * @see #setNightMode(int)
      */
-    public int getNightMode() {
+    public @NightMode int getNightMode() {
         if (mService != null) {
             try {
                 return mService.getNightMode();
@@ -250,9 +279,13 @@
     }
 
     /**
-     * @return If Night mode is locked or not. When Night mode is locked, changing Night mode
-     *         is only allowed to privileged system components and normal application's call
-     *         to change Night mode using {@link #setNightMode(int)} will silently fail.
+     * Returns whether night mode is locked or not.
+     * <p>
+     * When night mode is locked, only privileged system components may change
+     * night mode and calls from non-privileged applications to change night
+     * mode will fail silently.
+     *
+     * @return {@code true} if night mode is locked or {@code false} otherwise
      */
     public boolean isNightModeLocked() {
         if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ca5a923..9e39a5f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -773,12 +773,28 @@
      * have the user select a new password in order to meet the current
      * constraints. Upon being resumed from this activity, you can check the new
      * password characteristics to see if they are sufficient.
+     *
+     * If the intent is launched from within a managed profile with a profile
+     * owner built against {@link android.os.Build.VERSION_CODES#M} or before,
+     * this will trigger entering a new password for the parent of the profile.
+     * For all other cases it will trigger entering a new password for the user
+     * or profile it is launched from.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_SET_NEW_PASSWORD
             = "android.app.action.SET_NEW_PASSWORD";
 
     /**
+     * Activity action: have the user enter a new password for the parent profile.
+     * If the intent is launched from within a managed profile, this will trigger
+     * entering a new password for the parent of the profile. In all other cases
+     * the behaviour is identical to {@link #ACTION_SET_NEW_PASSWORD}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD
+            = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
+
+    /**
      * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
      * the parent profile to access intents sent from the managed profile.
      * That is, when an app in the managed profile calls
@@ -2451,6 +2467,53 @@
     }
 
     /**
+     * Called by a device or profile owner to configure an always-on VPN connection through a
+     * specific application for the current user.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link android.net.VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission#BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+     *
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     */
+    public boolean setAlwaysOnVpnPackage(@NonNull ComponentName admin,
+            @Nullable String vpnPackage) {
+        if (mService != null) {
+            try {
+                return mService.setAlwaysOnVpnPackage(admin, vpnPackage);
+            } catch (RemoteException e) {
+                Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by a device or profile owner to read the name of the package administering an
+     * always-on VPN connection for the current user.
+     * If there is no such package, or the always-on VPN is provided by the system instead
+     * of by an application, {@code null} will be returned.
+     *
+     * @return Package name of VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set.
+     */
+    public String getAlwaysOnVpnPackage(@NonNull ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getAlwaysOnVpnPackage(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called by an application that is administering the device to disable all cameras
      * on the device, for this user. After setting this, no applications running as this user
      * will be able to access any cameras on the device.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 2f33bb5..08cab88 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -146,6 +146,9 @@
     void setCertInstallerPackage(in ComponentName who, String installerPackage);
     String getCertInstallerPackage(in ComponentName who);
 
+    boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage);
+    String getAlwaysOnVpnPackage(in ComponentName who);
+
     void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
     void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
 
diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl
index f1258ae..f0c3302 100644
--- a/core/java/android/app/job/IJobScheduler.aidl
+++ b/core/java/android/app/job/IJobScheduler.aidl
@@ -24,6 +24,7 @@
   */
 interface IJobScheduler {
     int schedule(in JobInfo job);
+    int scheduleAsPackage(in JobInfo job, String packageName, int userId);
     void cancel(int jobId);
     void cancelAll();
     List<JobInfo> getAllPendingJobs();
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index b899710..9ad35d4 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -91,6 +91,7 @@
     private final long flexMillis;
     private final long initialBackoffMillis;
     private final int backoffPolicy;
+    private final int priority;
 
     /**
      * Unique job id associated with this class. This is assigned to your job by the scheduler.
@@ -113,6 +114,11 @@
         return service;
     }
 
+    /** @hide */
+    public int getPriority() {
+        return priority;
+    }
+
     /**
      * Whether this job needs the device to be plugged in.
      */
@@ -237,6 +243,7 @@
         backoffPolicy = in.readInt();
         hasEarlyConstraint = in.readInt() == 1;
         hasLateConstraint = in.readInt() == 1;
+        priority = in.readInt();
     }
 
     private JobInfo(JobInfo.Builder b) {
@@ -256,6 +263,7 @@
         backoffPolicy = b.mBackoffPolicy;
         hasEarlyConstraint = b.mHasEarlyConstraint;
         hasLateConstraint = b.mHasLateConstraint;
+        priority = b.mPriority;
     }
 
     @Override
@@ -281,6 +289,7 @@
         out.writeInt(backoffPolicy);
         out.writeInt(hasEarlyConstraint ? 1 : 0);
         out.writeInt(hasLateConstraint ? 1 : 0);
+        out.writeInt(priority);
     }
 
     public static final Creator<JobInfo> CREATOR = new Creator<JobInfo>() {
@@ -305,6 +314,7 @@
         private int mJobId;
         private PersistableBundle mExtras = PersistableBundle.EMPTY;
         private ComponentName mJobService;
+        private int mPriority;
         // Requirements.
         private boolean mRequiresCharging;
         private boolean mRequiresDeviceIdle;
@@ -338,6 +348,14 @@
         }
 
         /**
+         * @hide
+         */
+        public Builder setPriority(int priority) {
+            mPriority = priority;
+            return this;
+        }
+
+        /**
          * Set optional extras. This is persisted, so we only allow primitive types.
          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
          */
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 7ee39f5..a0a60e8 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -28,10 +28,22 @@
  */
 public class JobParameters implements Parcelable {
 
+    /** @hide */
+    public static final int REASON_CANCELED = 0;
+    /** @hide */
+    public static final int REASON_CONSTRAINTS_NOT_SATISFIED = 1;
+    /** @hide */
+    public static final int REASON_PREEMPT = 2;
+    /** @hide */
+    public static final int REASON_TIMEOUT = 3;
+    /** @hide */
+    public static final int REASON_DEVICE_IDLE = 4;
+
     private final int jobId;
     private final PersistableBundle extras;
     private final IBinder callback;
     private final boolean overrideDeadlineExpired;
+    private int stopReason; // Default value of stopReason is REASON_CANCELED
 
     /** @hide */
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
@@ -50,6 +62,14 @@
     }
 
     /**
+     * Reason onStopJob() was called on this job.
+     * @hide
+     */
+    public int getStopReason() {
+        return stopReason;
+    }
+
+    /**
      * @return The extras you passed in when constructing this job with
      * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
      * never be null. If you did not set any extras this will be an empty bundle.
@@ -78,6 +98,12 @@
         extras = in.readPersistableBundle();
         callback = in.readStrongBinder();
         overrideDeadlineExpired = in.readInt() == 1;
+        stopReason = in.readInt();
+    }
+
+    /** @hide */
+    public void setStopReason(int reason) {
+        stopReason = reason;
     }
 
     @Override
@@ -91,6 +117,7 @@
         dest.writePersistableBundle(extras);
         dest.writeStrongBinder(callback);
         dest.writeInt(overrideDeadlineExpired ? 1 : 0);
+        dest.writeInt(stopReason);
     }
 
     public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index 6e96da5..5e1a4256 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -63,6 +63,17 @@
     public abstract int schedule(JobInfo job);
 
     /**
+     *
+     * @param job The job to be scheduled.
+     * @param packageName The package on behalf of which the job is to be scheduled. This will be
+     *                    used to track battery usage and appIdleState.
+     * @param userId    User on behalf of whom this job is to be scheduled.
+     * @return {@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}
+     * @hide
+     */
+    public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId);
+
+    /**
      * Cancel a job that is pending in the JobScheduler.
      * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
      * {@link #getAllPendingJobs()}.
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index ee591d3..88ba874 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -16,7 +16,9 @@
 
 package android.app.trust;
 
+import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -54,9 +56,12 @@
      * Changes the lock status for the given user. This is only applicable to Managed Profiles,
      * other users should be handled by Keyguard.
      *
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
      * @param userId The id for the user to be locked/unlocked.
      * @param locked The value for that user's locked state.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
     public void setDeviceLockedForUser(int userId, boolean locked) {
         try {
             mService.setDeviceLockedForUser(userId, locked);
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
old mode 100644
new mode 100755
index 2e27345..74302f2
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -371,6 +371,89 @@
     }
 
     /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
+     * {@link #PRIORITY_OFF},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF &&
+                priority != BluetoothProfile.PRIORITY_ON){
+                return false;
+            }
+            try {
+                return mService.setPriority(device, priority);
+            } catch (RemoteException e) {
+                   Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                   return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.getPriority(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.PRIORITY_OFF;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.PRIORITY_OFF;
+    }
+
+    /**
+     * Check if A2DP profile is streaming music.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device BluetoothDevice device
+     */
+    public boolean isA2dpPlaying(BluetoothDevice device) {
+        if (mService != null && isEnabled()
+            && isValidDevice(device)) {
+            try {
+                return mService.isA2dpPlaying(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
      * Helper for converting a state to a string.
      *
      * For debug use only - strings are not internationalized.
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index b53a8fc..444e429 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -28,8 +30,8 @@
 import java.util.List;
 
 /**
- * This class provides the public APIs to control the Bluetooth AVRCP Controller
- * profile.
+ * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
+ * supports player information, playback support and track metadata.
  *
  *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
@@ -39,7 +41,7 @@
  */
 public final class BluetoothAvrcpController implements BluetoothProfile {
     private static final String TAG = "BluetoothAvrcpController";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
     /**
@@ -61,7 +63,63 @@
      * receive.
      */
     public static final String ACTION_CONNECTION_STATE_CHANGED =
-        "android.bluetooth.acrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+        "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in metadata state of playing track on the AVRCP
+     * AG.
+     *
+     * <p>This intent will have the two extras:
+     * <ul>
+     *    <li> {@link #EXTRA_METADATA} - {@link MediaMetadata} containing the current metadata.</li>
+     *    <li> {@link #EXTRA_PLAYBACK} - {@link PlaybackState} containing the current playback
+     *    state. </li>
+     * </ul>
+     */
+    public static final String ACTION_TRACK_EVENT =
+        "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+
+
+    /**
+     * Intent used to broadcast the change in player application setting state on AVRCP AG.
+     *
+     * <p>This intent will have the following extras:
+     * <ul>
+     *    <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
+     *    most recent player setting. </li>
+     * </ul>
+     */
+    public static final String ACTION_PLAYER_SETTING =
+        "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
+
+    public static final String EXTRA_METADATA =
+            "android.bluetooth.avrcp-controller.profile.extra.METADATA";
+
+    public static final String EXTRA_PLAYBACK =
+            "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
+
+    public static final String EXTRA_PLAYER_SETTING =
+            "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
+
+    /*
+     * KeyCoded for Pass Through Commands
+     */
+    public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
+    public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
+    public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
+    public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
+    public static final int PASS_THRU_CMD_ID_STOP = 0x45;
+    public static final int PASS_THRU_CMD_ID_FF = 0x49;
+    public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
+    public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
+    public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
+    /* Key State Variables */
+    public static final int KEY_STATE_PRESSED = 0;
+    public static final int KEY_STATE_RELEASED = 1;
+    /* Group Navigation Key Codes */
+    public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
+    public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
+
 
     private Context mContext;
     private ServiceListener mServiceListener;
@@ -69,33 +127,33 @@
     private BluetoothAdapter mAdapter;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG,"Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG,"",re);
-                            }
+        new IBluetoothStateChangeCallback.Stub() {
+            public void onBluetoothStateChange(boolean up) {
+                if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                if (!up) {
+                    if (VDBG) Log.d(TAG,"Unbinding service...");
+                    synchronized (mConnection) {
+                        try {
+                            mService = null;
+                            mContext.unbindService(mConnection);
+                        } catch (Exception re) {
+                            Log.e(TAG,"",re);
                         }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG,"Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG,"",re);
+                    }
+                } else {
+                    synchronized (mConnection) {
+                        try {
+                            if (mService == null) {
+                                if (VDBG) Log.d(TAG,"Binding service...");
+                                doBind();
                             }
+                        } catch (Exception re) {
+                            Log.e(TAG,"",re);
                         }
                     }
                 }
-        };
+            }
+      };
 
     /**
      * Create a BluetoothAvrcpController proxy object for interacting with the local
@@ -223,6 +281,104 @@
         if (mService == null) Log.w(TAG, "Proxy not attached to service");
     }
 
+    /**
+     * Gets the player application settings.
+     *
+     * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
+     */
+    public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getPlayerSettings");
+        BluetoothAvrcpPlayerSettings settings = null;
+        if (mService != null && isEnabled()) {
+            try {
+                settings = mService.getPlayerSettings(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
+                return null;
+            }
+        }
+        return settings;
+    }
+
+    /**
+     * Gets the metadata for the current track.
+     *
+     * This should be usually called when application UI needs to be updated, eg. when the track
+     * changes or immediately after connecting and getting the current state.
+     * @return the {@link MediaMetadata} or {@link null} if there is an error.
+     */
+    public MediaMetadata getMetadata(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getMetadata");
+        MediaMetadata metadata = null;
+        if (mService != null && isEnabled()) {
+            try {
+                metadata = mService.getMetadata(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
+                return null;
+            }
+        }
+        return metadata;
+    }
+
+    /**
+     * Gets the playback state for current track.
+     *
+     * When the application is first connecting it can use current track state to get playback info.
+     * For all further updates it should listen to notifications.
+     * @return the {@link PlaybackState} or {@link null} if there is an error.
+     */
+    public PlaybackState getPlaybackState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getPlaybackState");
+        PlaybackState playbackState = null;
+        if (mService != null && isEnabled()) {
+            try {
+                playbackState = mService.getPlaybackState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                    "Error talking to BT service in getPlaybackState() " + e);
+                return null;
+            }
+        }
+        return playbackState;
+    }
+
+    /**
+     * Sets the player app setting for current player.
+     * returns true in case setting is supported by remote, false otherwise
+     */
+    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+        if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.setPlayerApplicationSetting(plAppSetting);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
+                return false;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /*
+     * Send Group Navigation Command to Remote.
+     * possible keycode values: next_grp, previous_grp defined above
+     */
+    public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
+        if (mService != null && isEnabled()) {
+            try {
+                mService.sendGroupNavigationCmd(device, keyCode, keyState);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
+                return;
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+    }
+
     private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl
new file mode 100644
index 0000000..590fd63
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+parcelable BluetoothAvrcpPlayerSettings;
diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
new file mode 100644
index 0000000..927cb56
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to identify settings associated with the player on AG.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcpPlayerSettings implements Parcelable {
+    public static final String TAG = "BluetoothAvrcpPlayerSettings";
+
+    /**
+     * Equalizer setting.
+     */
+    public static final int SETTING_EQUALIZER    = 0x01;
+
+    /**
+     * Repeat setting.
+     */
+    public static final int SETTING_REPEAT       = 0x02;
+
+    /**
+     * Shuffle setting.
+     */
+    public static final int SETTING_SHUFFLE      = 0x04;
+
+    /**
+     * Scan mode setting.
+     */
+    public static final int SETTING_SCAN         = 0x08;
+
+    /**
+     * Invalid state.
+     *
+     * Used for returning error codes.
+     */
+    public static final int STATE_INVALID = -1;
+
+    /**
+     * OFF state.
+     *
+     * Denotes a general OFF state. Applies to all settings.
+     */
+    public static final int STATE_OFF = 0x00;
+
+    /**
+     * ON state.
+     *
+     * Applies to {@link SETTING_EQUALIZER}.
+     */
+    public static final int STATE_ON = 0x01;
+
+    /**
+     * Single track repeat.
+     *
+     * Applies only to {@link SETTING_REPEAT}.
+     */
+    public static final int STATE_SINGLE_TRACK = 0x02;
+
+    /**
+     * All track repeat/shuffle.
+     *
+     * Applies to {@link SETTING_REPEAT}, {@link SETTING_SHUFFLE} and {@link SETTING_SCAN}.
+     */
+    public static final int STATE_ALL_TRACK    = 0x03;
+
+    /**
+     * Group repeat/shuffle.
+     *
+     * Applies to {@link SETTING_REPEAT}, {@link SETTING_SHUFFLE} and {@link SETTING_SCAN}.
+     */
+    public static final int STATE_GROUP        = 0x04;
+
+    /**
+     * List of supported settings ORed.
+     */
+    private int mSettings;
+
+    /**
+     * Hash map of current capability values.
+     */
+    private Map<Integer, Integer> mSettingsValue = new HashMap<Integer, Integer>();
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSettings);
+        out.writeInt(mSettingsValue.size());
+        for (int k : mSettingsValue.keySet()) {
+            out.writeInt(k);
+            out.writeInt(mSettingsValue.get(k));
+        }
+    }
+
+    public static final Parcelable.Creator<BluetoothAvrcpPlayerSettings> CREATOR
+            = new Parcelable.Creator<BluetoothAvrcpPlayerSettings>() {
+        public BluetoothAvrcpPlayerSettings createFromParcel(Parcel in) {
+            return new BluetoothAvrcpPlayerSettings(in);
+        }
+
+        public BluetoothAvrcpPlayerSettings[] newArray(int size) {
+            return new BluetoothAvrcpPlayerSettings[size];
+        }
+    };
+
+    private BluetoothAvrcpPlayerSettings(Parcel in) {
+        mSettings = in.readInt();
+        int numSettings = in.readInt();
+        for (int i = 0; i < numSettings; i++) {
+            mSettingsValue.put(in.readInt(), in.readInt());
+        }
+    }
+
+    /**
+     * Create a new player settings object.
+     *
+     * @param settings a ORed value of SETTINGS_* defined above.
+     */
+    public BluetoothAvrcpPlayerSettings(int settings) {
+        mSettings = settings;
+    }
+
+    /**
+     * Get the supported settings.
+     *
+     * @return int ORed value of supported settings.
+     */
+    public int getSettings() {
+        return mSettings;
+    }
+
+    /**
+     * Add a setting value.
+     *
+     * The setting must be part of possible settings in {@link getSettings()}.
+     * @param setting setting config.
+     * @param value value for the setting.
+     * @throws IllegalStateException if the setting is not supported.
+     */
+    public void addSettingValue(int setting, int value) {
+        if ((setting & mSettings) == 0) {
+            Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+            throw new IllegalStateException("Setting not supported: " + setting);
+        }
+        mSettingsValue.put(setting, value);
+    }
+
+    /**
+     * Get a setting value.
+     *
+     * The setting must be part of possible settings in {@link getSettings()}.
+     * @param setting setting config.
+     * @return value value for the setting.
+     * @throws IllegalStateException if the setting is not supported.
+     */
+    public int getSettingValue(int setting) {
+        if ((setting & mSettings) == 0) {
+            Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+            throw new IllegalStateException("Setting not supported: " + setting);
+        }
+        Integer i = mSettingsValue.get(setting);
+        if (i == null) return -1;
+        return i;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
old mode 100644
new mode 100755
index 54bf4af..4a38287
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -283,6 +283,8 @@
     public static final int PROFILE_PANU = 4;
     /** @hide */
     public static final int PROFILE_NAP = 5;
+    /** @hide */
+    public static final int PROFILE_A2DP_SINK = 6;
 
     /**
      * Check class bits for possible bluetooth profile support.
@@ -310,6 +312,21 @@
                 default:
                     return false;
             }
+        } else if (profile == PROFILE_A2DP_SINK) {
+            if (hasService(Service.CAPTURE)) {
+                return true;
+            }
+            // By the A2DP spec, srcs must indicate the CAPTURE service.
+            // However if some device that do not, we try to
+            // match on some other class bits.
+            switch (getDeviceClass()) {
+                case Device.AUDIO_VIDEO_HIFI_AUDIO:
+                case Device.AUDIO_VIDEO_SET_TOP_BOX:
+                case Device.AUDIO_VIDEO_VCR :
+                    return true;
+                default:
+                    return false;
+            }
         } else if (profile == PROFILE_HEADSET) {
             // The render service class is required by the spec for HFP, so is a
             // pretty good signal
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index cd5c205..f43fb30 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -879,18 +879,16 @@
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
-     * @param hash - Simple Secure pairing hash
-     * @param randomizer - The random key obtained using OOB
+     * @param transport - Transport to use
+     * @param oobData - Out Of Band data
      * @return false on immediate error, true if bonding will begin
      *
      * @hide
      */
-    public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) {
-        //TODO(BT)
-        /*
+    public boolean createBondOutOfBand(int transport, OobData oobData) {
         try {
-            return sService.createBondOutOfBand(this, hash, randomizer);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}*/
+            return sService.createBondOutOfBand(this, transport, oobData);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
         return false;
     }
 
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 66f3418..74cb0f6 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -20,6 +20,7 @@
 import android.bluetooth.IBluetoothStateChangeCallback;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.OobData;
 import android.os.ParcelUuid;
 import android.os.ParcelFileDescriptor;
 
@@ -56,6 +57,7 @@
 
     BluetoothDevice[] getBondedDevices();
     boolean createBond(in BluetoothDevice device, in int transport);
+    boolean createBondOutOfBand(in BluetoothDevice device, in int transport, in OobData oobData);
     boolean cancelBondProcess(in BluetoothDevice device);
     boolean removeBond(in BluetoothDevice device);
     int getBondState(in BluetoothDevice device);
diff --git a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
old mode 100644
new mode 100755
index b7c6476..d1458246
--- a/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dpSink.aidl
@@ -31,4 +31,7 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
     BluetoothAudioConfig getAudioConfig(in BluetoothDevice device);
+    boolean setPriority(in BluetoothDevice device, int priority);
+    int getPriority(in BluetoothDevice device);
+    boolean isA2dpPlaying(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
index f917a50..f1288d0 100644
--- a/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
+++ b/core/java/android/bluetooth/IBluetoothAvrcpController.aidl
@@ -16,7 +16,10 @@
 
 package android.bluetooth;
 
+import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
+import android.media.MediaMetadata;
+import android.media.session.PlaybackState;
 
 /**
  * APIs for Bluetooth AVRCP controller service
@@ -28,4 +31,9 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
     void sendPassThroughCmd(in BluetoothDevice device, int keyCode, int keyState);
+    BluetoothAvrcpPlayerSettings getPlayerSettings(in BluetoothDevice device);
+    MediaMetadata getMetadata(in BluetoothDevice device);
+    PlaybackState getPlaybackState(in BluetoothDevice device);
+    boolean setPlayerApplicationSetting(in BluetoothAvrcpPlayerSettings plAppSetting);
+    void sendGroupNavigationCmd(in BluetoothDevice device, int keyCode, int keyState);
 }
diff --git a/core/java/android/bluetooth/OobData.aidl b/core/java/android/bluetooth/OobData.aidl
new file mode 100644
index 0000000..d831c64
--- /dev/null
+++ b/core/java/android/bluetooth/OobData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.bluetooth;
+
+parcelable OobData;
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
new file mode 100644
index 0000000..01f72ef
--- /dev/null
+++ b/core/java/android/bluetooth/OobData.java
@@ -0,0 +1,63 @@
+/*
+ * 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.util.Log;
+
+/**
+ * Out Of Band Data for Bluetooth device.
+ */
+public class OobData implements Parcelable {
+    private byte[] securityManagerTk;
+
+    public byte[] getSecurityManagerTk() {
+        return securityManagerTk;
+    }
+
+    public void setSecurityManagerTk(byte[] securityManagerTk) {
+        this.securityManagerTk = securityManagerTk;
+    }
+
+    public OobData() { }
+
+    private OobData(Parcel in) {
+        securityManagerTk = in.createByteArray();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(securityManagerTk);
+    }
+
+    public static final Parcelable.Creator<OobData> CREATOR
+            = new Parcelable.Creator<OobData>() {
+        public OobData createFromParcel(Parcel in) {
+            return new OobData(in);
+        }
+
+        public OobData[] newArray(int size) {
+            return new OobData[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e9d83eb..48d0196 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -335,6 +335,7 @@
      * calling application's package, rather than the package in which the service is declared.
      * @hide
      */
+    @SystemApi
     public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
 
     /**
@@ -2559,6 +2560,7 @@
             NETWORK_STATS_SERVICE,
             //@hide: NETWORK_POLICY_SERVICE,
             WIFI_SERVICE,
+            WIFI_NAN_SERVICE,
             WIFI_PASSPOINT_SERVICE,
             WIFI_P2P_SERVICE,
             WIFI_SCANNING_SERVICE,
@@ -3021,6 +3023,17 @@
     public static final String WIFI_P2P_SERVICE = "wifip2p";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.net.wifi.nan.WifiNanManager} for handling management of
+     * Wi-Fi NAN discovery and connections.
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.nan.WifiNanManager
+     * @hide PROPOSED_NAN_API
+     */
+    public static final String WIFI_NAN_SERVICE = "wifinan";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.wifi.WifiScanner} for scanning the wifi universe
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e7f886d..c627436 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2537,6 +2537,16 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
 
+   /**
+     * Broadcast Action:  The "Picture-in-picture (PIP) Button" was pressed.
+     * Includes a single extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+     * caused the broadcast.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PICTURE_IN_PICTURE_BUTTON =
+            "android.intent.action.PICTURE_IN_PICTURE_BUTTON";
+
     /**
      * Broadcast Action:  The "Camera Button" was pressed.  Includes a single
      * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index f611991..90a1198 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -294,6 +294,8 @@
     void restoreDefaultApps(in byte[] backup, int userId);
     byte[] getIntentFilterVerificationBackup(int userId);
     void restoreIntentFilterVerification(in byte[] backup, int userId);
+    byte[] getPermissionGrantBackup(int userId);
+    void restorePermissionGrants(in byte[] backup, int userId);
 
     /**
      * Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5113e19..ba4d14c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1902,6 +1902,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Wi-Fi Aware (NAN)
+     * networking.
+     *
+     * @hide PROPOSED_NAN_API
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WIFI_NAN = "android.hardware.wifi.nan";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: This is a device dedicated to showing UI
      * on a vehicle headunit. A headunit here is defined to be inside a
      * vehicle that may or may not be moving. A headunit uses either a
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 6d360d7..a6fec9f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -51,6 +51,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.TypedValue;
+import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.jar.StrictJarFile;
 import android.view.Gravity;
 
@@ -1022,11 +1023,56 @@
         final boolean requireCode = ((parseFlags & PARSE_ENFORCE_CODE) != 0) && hasCode;
         final String apkPath = apkFile.getAbsolutePath();
 
+        // Try to verify the APK using APK Signature Scheme v2.
+        boolean verified = false;
+        {
+            Certificate[][] allSignersCerts = null;
+            Signature[] signatures = null;
+            try {
+                allSignersCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
+                signatures = convertToSignatures(allSignersCerts);
+                // APK verified using APK Signature Scheme v2.
+                verified = true;
+            } catch (ApkSignatureSchemeV2Verifier.SignatureNotFoundException e) {
+                // No APK Signature Scheme v2 signature found
+            } catch (Exception e) {
+                // APK Signature Scheme v2 signature was found but did not verify
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "Failed to collect certificates from " + apkPath
+                                + " using APK Signature Scheme v2",
+                        e);
+            }
+
+            if (verified) {
+                if (pkg.mCertificates == null) {
+                    pkg.mCertificates = allSignersCerts;
+                    pkg.mSignatures = signatures;
+                    pkg.mSigningKeys = new ArraySet<>(allSignersCerts.length);
+                    for (int i = 0; i < allSignersCerts.length; i++) {
+                        Certificate[] signerCerts = allSignersCerts[i];
+                        Certificate signerCert = signerCerts[0];
+                        pkg.mSigningKeys.add(signerCert.getPublicKey());
+                    }
+                } else {
+                    if (!Signature.areExactMatch(pkg.mSignatures, signatures)) {
+                        throw new PackageParserException(
+                                INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                apkPath + " has mismatched certificates");
+                    }
+                }
+                // Not yet done, because we need to confirm that AndroidManifest.xml exists and,
+                // if requested, that classes.dex exists.
+            }
+        }
+
         boolean codeFound = false;
         StrictJarFile jarFile = null;
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor");
-            jarFile = new StrictJarFile(apkPath);
+            jarFile = new StrictJarFile(
+                    apkPath,
+                    !verified // whether to verify JAR signature
+                    );
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             // Always verify manifest, regardless of source
@@ -1036,6 +1082,16 @@
                         "Package " + apkPath + " has no manifest");
             }
 
+            // Optimization: early termination when APK already verified
+            if (verified) {
+                if ((requireCode) && (jarFile.findEntry(BYTECODE_FILENAME) == null)) {
+                    throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                            "Package " + apkPath + " code is missing");
+                }
+                return;
+            }
+
+            // APK's integrity needs to be verified using JAR signature scheme.
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "buildVerifyList");
             final List<ZipEntry> toVerify = new ArrayList<>();
             toVerify.add(manifestEntry);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e3050fe..9cf4675 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -145,6 +145,10 @@
         return (flags & FLAG_EPHEMERAL) == FLAG_EPHEMERAL;
     }
 
+    public boolean isInitialized() {
+        return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
+    }
+
     /**
      * Returns true if the user is a split system user.
      * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index e875864..6935174 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -534,6 +534,24 @@
     public static final String STRING_TYPE_WRIST_TILT_GESTURE = "android.sensor.wrist_tilt_gesture";
 
     /**
+     * The current orientation of the device.
+     * <p>
+     * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
+     *
+     * @hide Expected to be used internally for auto-rotate and speaker rotation.
+     *
+     */
+    public static final int TYPE_DEVICE_ORIENTATION = 27;
+
+    /**
+     * A constant string describing a device orientation sensor type.
+     *
+     * @hide
+     * @see #TYPE_DEVICE_ORIENTATION
+     */
+    public static final String STRING_TYPE_DEVICE_ORIENTATION = "android.sensor.device_orientation";
+
+    /**
      * A constant describing all sensor types.
      */
     public static final int TYPE_ALL = -1;
@@ -618,6 +636,7 @@
             1, // SENSOR_TYPE_GLANCE_GESTURE
             1, // SENSOR_TYPE_PICK_UP_GESTURE
             1, // SENSOR_TYPE_WRIST_TILT_GESTURE
+            1, // SENSOR_TYPE_DEVICE_ORIENTATION
     };
 
     /**
@@ -939,6 +958,9 @@
             case TYPE_TEMPERATURE:
                 mStringType = STRING_TYPE_TEMPERATURE;
                 return true;
+            case TYPE_DEVICE_ORIENTATION:
+                mStringType = STRING_TYPE_DEVICE_ORIENTATION;
+                return true;
             default:
                 return false;
         }
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 906c2a19..9937b2c 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -483,6 +483,20 @@
      * on it. In earlier versions, this used to be always 3 which has changed now. </p>
      *
      * @see GeomagneticField
+     *
+     * <h4> {@link android.hardware.Sensor#TYPE_DEVICE_ORIENTATION
+     * Sensor.TYPE_DEVICE_ORIENTATION}:</h4>
+     * The current device orientation will be available in values[0]. The only
+     * available values are:
+     * <ul>
+     * <li> 0: device is in default orientation (Y axis is vertical and points up)
+     * <li> 1: device is rotated 90 degrees counter-clockwise from default
+     *         orientation (X axis is vertical and points up)
+     * <li> 2: device is rotated 180 degrees from default orientation (Y axis is
+     *         vertical and points down)
+     * <li> 3: device is rotated 90 degrees clockwise from default orientation (X axis
+     *         is vertical and points down)
+     * </ul>
      */
     public final float[] values;
 
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 176e923..c4f0847 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -686,6 +687,47 @@
     }
 
     /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param userId The identifier of the user to set an always-on VPN for.
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage) {
+        try {
+            return mService.setAlwaysOnVpnPackage(userId, vpnPackage);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the package name of the currently set always-on VPN application.
+     * If there is no always-on VPN set, or the VPN is provided by the system instead
+     * of by an app, {@code null} will be returned.
+     *
+     * @return Package name of VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set.
+     * @hide
+     */
+    public String getAlwaysOnVpnPackageForUser(int userId) {
+        try {
+            return mService.getAlwaysOnVpnPackage(userId);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index ef91137..569468e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -117,6 +117,8 @@
     VpnInfo[] getAllVpnInfo();
 
     boolean updateLockdownVpn();
+    boolean setAlwaysOnVpnPackage(int userId, String packageName);
+    String getAlwaysOnVpnPackage(int userId);
 
     int checkMobileProvisioning(int suggestedTimeOutMs);
 
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 7a1a6a2..838279b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.os.IMaintenanceActivityListener;
 import android.os.UserHandle;
 
 /** @hide */
@@ -37,4 +38,6 @@
     void exitIdle(String reason);
     void downloadServiceActive(IBinder token);
     void downloadServiceInactive();
+    boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
+    void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
 }
diff --git a/core/java/android/os/IMaintenanceActivityListener.aidl b/core/java/android/os/IMaintenanceActivityListener.aidl
new file mode 100644
index 0000000..6a2581f
--- /dev/null
+++ b/core/java/android/os/IMaintenanceActivityListener.aidl
@@ -0,0 +1,22 @@
+/**
+ * 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.os;
+
+/** @hide */
+oneway interface IMaintenanceActivityListener {
+    void onMaintenanceActivityChanged(boolean active);
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9ee6228..bc2566b 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -18,6 +18,7 @@
 package android.os;
 
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.content.pm.UserInfo;
 import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
@@ -35,7 +36,7 @@
 
     UserInfo createUser(in String name, int flags);
     UserInfo createProfileForUser(in String name, int flags, int userHandle);
-    UserInfo createRestrictedProfile(String name, int parentUserId);
+    UserInfo createRestrictedProfile(String name, int parentUserHandle);
     void setUserEnabled(int userHandle);
     boolean removeUser(int userHandle);
     void setUserName(int userHandle, String name);
@@ -44,21 +45,21 @@
     UserInfo getPrimaryUser();
     List<UserInfo> getUsers(boolean excludeDying);
     List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
-    boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
+    boolean canAddMoreManagedProfiles(int userHandle, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userHandle);
-    boolean isSameProfileGroup(int userId, int otherUserId);
+    boolean isSameProfileGroup(int userHandle, int otherUserHandle);
     UserInfo getUserInfo(int userHandle);
-    String getUserAccount(int userId);
-    void setUserAccount(int userId, String accountName);
+    String getUserAccount(int userHandle);
+    void setUserAccount(int userHandle, String accountName);
     long getUserCreationTime(int userHandle);
     boolean isRestricted();
-    boolean canHaveRestrictedProfile(int userId);
+    boolean canHaveRestrictedProfile(int userHandle);
     int getUserSerialNumber(int userHandle);
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
     boolean hasBaseUserRestriction(String restrictionKey, int userHandle);
     boolean hasUserRestriction(in String restrictionKey, int userHandle);
-    void setUserRestriction(String key, boolean value, int userId);
+    void setUserRestriction(String key, boolean value, int userHandle);
     void setApplicationRestrictions(in String packageName, in Bundle restrictions,
             int userHandle);
     Bundle getApplicationRestrictions(in String packageName);
@@ -68,4 +69,11 @@
     boolean markGuestForDeletion(int userHandle);
     void setQuietModeEnabled(int userHandle, boolean enableQuietMode);
     boolean isQuietModeEnabled(int userHandle);
+    void setSeedAccountData(int userHandle, in String accountName,
+            in String accountType, in PersistableBundle accountOptions, boolean persist);
+    String getSeedAccountName();
+    String getSeedAccountType();
+    PersistableBundle getSeedAccountOptions();
+    void clearSeedAccountData();
+    boolean someUserHasSeedAccount(in String accountName, in String accountType);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d1b3f59..f01f597 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -26,6 +26,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -567,6 +568,37 @@
      */
     public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
 
+    private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
+
+    /**
+     * Extra containing a name for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_NAME = "android.os.extra.USER_NAME";
+
+    /**
+     * Extra containing account name for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_NAME = "android.os.extra.USER_ACCOUNT_NAME";
+
+    /**
+     * Extra containing account type for the user being created. Optional parameter passed to
+     * ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_TYPE = "android.os.extra.USER_ACCOUNT_TYPE";
+
+    /**
+     * Extra containing account-specific data for the user being created. Optional parameter passed
+     * to ACTION_CREATE_USER activity.
+     * @hide
+     */
+    public static final String EXTRA_USER_ACCOUNT_OPTIONS
+            = "android.os.extra.USER_ACCOUNT_OPTIONS";
+
     /** @hide */
     public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3;
     /** @hide */
@@ -766,9 +798,13 @@
      * @param user The user to retrieve the running state for.
      */
     public boolean isUserRunning(UserHandle user) {
+        return isUserRunning(user.getIdentifier());
+    }
+
+    /** {@hide} */
+    public boolean isUserRunning(int userId) {
         try {
-            return ActivityManagerNative.getDefault().isUserRunning(
-                    user.getIdentifier(), 0);
+            return ActivityManagerNative.getDefault().isUserRunning(userId, 0);
         } catch (RemoteException e) {
             return false;
         }
@@ -1138,6 +1174,137 @@
     }
 
     /**
+     * Returns an intent to create a user for the provided name and email address. The name
+     * and email address will be used when the setup process for the new user is started.
+     * If this device does not support multiple users, null is returned.
+     * <p/>
+     * The intent should be launched using startActivityForResult and the return result will
+     * indicate if the user consented to adding a new user and if the operation succeeded.
+     * <p/>
+     * The new user is created but not initialized. After switching into the user for the first
+     * time, the preferred user name and account information are used by the setup process for that
+     * user.
+     *
+     * @param userName Optional name to assign to the user.
+     * @param accountName Optional email address that will be used by the setup wizard to initialize
+     *                    the user.
+     * @param accountType Optional account type for the account to be created. This is required
+     *                    if the account name is specified.
+     * @param accountOptions Optional bundle of data to be passed in during account creation in the
+     *                       new user via {@link AccountManager#addAccount(String, String, String[],
+     *                       Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
+     *                       Handler)}.
+     * @return An Intent that can be launched from an Activity or null if creating users is not
+     *         supported on this device.
+     */
+    public static Intent createUserCreationIntent(@Nullable String userName,
+            @Nullable String accountName,
+            @Nullable String accountType, @Nullable PersistableBundle accountOptions) {
+        if (!supportsMultipleUsers() || getMaxSupportedUsers() < 2) {
+            return null;
+        }
+        Intent intent = new Intent(ACTION_CREATE_USER);
+        if (userName != null) {
+            intent.putExtra(EXTRA_USER_NAME, userName);
+        }
+        if (accountName != null && accountType == null) {
+            throw new IllegalArgumentException("accountType must be specified if accountName is "
+                    + "specified");
+        }
+        if (accountName != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_NAME, accountName);
+        }
+        if (accountType != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_TYPE, accountType);
+        }
+        if (accountOptions != null) {
+            intent.putExtra(EXTRA_USER_ACCOUNT_OPTIONS, accountOptions);
+        }
+        return intent;
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account name for user creation. Requires MANAGE_USERS permission.
+     */
+    @SystemApi
+    public String getSeedAccountName() {
+        try {
+            return mService.getSeedAccountName();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get the seed account name", re);
+            return null;
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account type for user creation. Requires MANAGE_USERS permission.
+     */
+    @SystemApi
+    public String getSeedAccountType() {
+        try {
+            return mService.getSeedAccountType();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get the seed account type", re);
+            return null;
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Returns the preferred account's options bundle for user creation. Requires MANAGE_USERS
+     * permission.
+     * @return Any options set by the requestor that created the user.
+     */
+    @SystemApi
+    public PersistableBundle getSeedAccountOptions() {
+        try {
+            return mService.getSeedAccountOptions();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get the seed account options", re);
+            return null;
+        }
+    }
+
+    /**
+     * @hide
+     *
+     * Called by a system activity to set the seed account information of a user created
+     * through the user creation intent.
+     * @param userId
+     * @param accountName
+     * @param accountType
+     * @param accountOptions
+     * @see #createUserCreationIntent(String, String, String, PersistableBundle)
+     */
+    public void setSeedAccountData(int userId, String accountName, String accountType,
+            PersistableBundle accountOptions) {
+        try {
+            mService.setSeedAccountData(userId, accountName, accountType, accountOptions,
+                    /* persist= */ true);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not set the seed account data", re);
+        }
+    }
+
+    /**
+     * @hide
+     * Clears the seed information used to create this user. Requires MANAGE_USERS permission.
+     */
+    @SystemApi
+    public void clearSeedAccountData() {
+        try {
+            mService.clearSeedAccountData();
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not clear the seed account data", re);
+        }
+    }
+
+    /**
      * @hide
      * Marks the guest user for deletion to allow a new guest to be created before deleting
      * the current user who is a guest.
@@ -1752,4 +1919,21 @@
             return 0;
         }
     }
+
+    /**
+     * @hide
+     * Checks if any uninitialized user has the specific seed account name and type.
+     *
+     * @param mAccountName The account name to check for
+     * @param mAccountType The account type of the account to check for
+     * @return whether the seed account was found
+     */
+    public boolean someUserHasSeedAccount(String accountName, String accountType) {
+        try {
+            return mService.someUserHasSeedAccount(accountName, accountType);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not check seed accounts", re);
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 83efe80..ff9c0df 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -20,16 +20,17 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
 
 /**
  * This class represents the unique id of a printer.
  */
 public final class PrinterId implements Parcelable {
 
-    private final ComponentName mServiceName;
+    private final @NonNull ComponentName mServiceName;
 
-    private final String mLocalId;
+    private final @NonNull String mLocalId;
 
     /**
      * Creates a new instance.
@@ -39,14 +40,14 @@
      *
      * @hide
      */
-    public PrinterId(ComponentName serviceName, String localId) {
+    public PrinterId(@NonNull ComponentName serviceName, @NonNull String localId) {
         mServiceName = serviceName;
         mLocalId = localId;
     }
 
-    private PrinterId(Parcel parcel) {
-        mServiceName = parcel.readParcelable(null);
-        mLocalId = parcel.readString();
+    private PrinterId(@NonNull Parcel parcel) {
+        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null));
+        mLocalId = Preconditions.checkNotNull(parcel.readString());
     }
 
     /**
@@ -56,7 +57,7 @@
      *
      * @hide
      */
-    public ComponentName getServiceName() {
+    public @NonNull ComponentName getServiceName() {
         return mServiceName;
     }
 
@@ -93,14 +94,10 @@
             return false;
         }
         PrinterId other = (PrinterId) object;
-        if (mServiceName == null) {
-            if (other.mServiceName != null) {
-                return false;
-            }
-        } else if (!mServiceName.equals(other.mServiceName)) {
+        if (!mServiceName.equals(other.mServiceName)) {
             return false;
         }
-        if (!TextUtils.equals(mLocalId, other.mLocalId)) {
+        if (!mLocalId.equals(other.mLocalId)) {
             return false;
         }
         return true;
@@ -110,8 +107,7 @@
     public int hashCode() {
         final int prime = 31;
         int hashCode = 1;
-        hashCode = prime * hashCode + ((mServiceName != null)
-                ? mServiceName.hashCode() : 1);
+        hashCode = prime * hashCode + mServiceName.hashCode();
         hashCode = prime * hashCode + mLocalId.hashCode();
         return hashCode;
     }
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index afef9c0..0d2d9f4 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -33,6 +33,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -43,6 +45,9 @@
  * major components, printer properties such as name, id, status,
  * description and printer capabilities which describe the various
  * print modes a printer supports such as media sizes, margins, etc.
+ * <p>
+ * Once {@link PrinterInfo.Builder#build() built} the objects are immutable.
+ * </p>
  */
 public final class PrinterInfo implements Parcelable {
 
@@ -62,60 +67,41 @@
     /** Printer status: the printer is not available. */
     public static final int STATUS_UNAVAILABLE = 3;
 
-    private PrinterId mId;
+    private final @NonNull PrinterId mId;
 
     /** Resource inside the printer's services's package to be used as an icon */
-    private int mIconResourceId;
+    private final int mIconResourceId;
 
     /** If a custom icon can be loaded for the printer */
-    private boolean mHasCustomPrinterIcon;
+    private final boolean mHasCustomPrinterIcon;
 
     /** The generation of the icon in the cache. */
-    private int mCustomPrinterIconGen;
+    private final int mCustomPrinterIconGen;
 
     /** Intent that launches the activity showing more information about the printer. */
-    private PendingIntent mInfoIntent;
+    private final @Nullable PendingIntent mInfoIntent;
 
-    private String mName;
+    private final @NonNull String mName;
 
-    private int mStatus;
+    private final @Status int mStatus;
 
-    private String mDescription;
+    private final @Nullable String mDescription;
 
-    private PrinterCapabilitiesInfo mCapabilities;
+    private final @Nullable PrinterCapabilitiesInfo mCapabilities;
 
-    private PrinterInfo() {
-        /* do nothing */
-    }
-
-    private PrinterInfo(PrinterInfo prototype) {
-        copyFrom(prototype);
-    }
-
-    /**
-     * @hide
-     */
-    public void copyFrom(PrinterInfo other) {
-        if (this == other) {
-            return;
-        }
-        mId = other.mId;
-        mName = other.mName;
-        mStatus = other.mStatus;
-        mDescription = other.mDescription;
-        if (other.mCapabilities != null) {
-            if (mCapabilities != null) {
-                mCapabilities.copyFrom(other.mCapabilities);
-            } else {
-                mCapabilities = new PrinterCapabilitiesInfo(other.mCapabilities);
-            }
-        } else {
-            mCapabilities = null;
-        }
-        mIconResourceId = other.mIconResourceId;
-        mHasCustomPrinterIcon = other.mHasCustomPrinterIcon;
-        mCustomPrinterIconGen = other.mCustomPrinterIconGen;
-        mInfoIntent = other.mInfoIntent;
+    private PrinterInfo(@NonNull PrinterId printerId, @NonNull String name, @Status int status,
+            int iconResourceId, boolean hasCustomPrinterIcon, String description,
+            PendingIntent infoIntent, PrinterCapabilitiesInfo capabilities,
+            int customPrinterIconGen) {
+        mId = printerId;
+        mName = name;
+        mStatus = status;
+        mIconResourceId = iconResourceId;
+        mHasCustomPrinterIcon = hasCustomPrinterIcon;
+        mDescription = description;
+        mInfoIntent = infoIntent;
+        mCapabilities = capabilities;
+        mCustomPrinterIconGen = customPrinterIconGen;
     }
 
     /**
@@ -180,7 +166,7 @@
      *
      * @return The printer name.
      */
-    public @Nullable String getName() {
+    public @NonNull String getName() {
         return mName;
     }
 
@@ -227,10 +213,51 @@
         return mCapabilities;
     }
 
+    /**
+     * Check if printerId is valid.
+     *
+     * @param printerId The printerId that might be valid
+     * @return The valid printerId
+     * @throws IllegalArgumentException if printerId is not valid.
+     */
+    private static @NonNull PrinterId checkPrinterId(PrinterId printerId) {
+        return Preconditions.checkNotNull(printerId, "printerId cannot be null.");
+    }
+
+    /**
+     * Check if status is valid.
+     *
+     * @param status The status that might be valid
+     * @return The valid status
+     * @throws IllegalArgumentException if status is not valid.
+     */
+    private static @Status int checkStatus(int status) {
+        if (!(status == STATUS_IDLE
+                || status == STATUS_BUSY
+                || status == STATUS_UNAVAILABLE)) {
+            throw new IllegalArgumentException("status is invalid.");
+        }
+
+        return status;
+    }
+
+    /**
+     * Check if name is valid.
+     *
+     * @param name The name that might be valid
+     * @return The valid name
+     * @throws IllegalArgumentException if name is not valid.
+     */
+    private static @NonNull String checkName(String name) {
+        return Preconditions.checkStringNotEmpty(name, "name cannot be empty.");
+    }
+
     private PrinterInfo(Parcel parcel) {
-        mId = parcel.readParcelable(null);
-        mName = parcel.readString();
-        mStatus = parcel.readInt();
+        // mName can be null due to unchecked set in Builder.setName and status can be invalid
+        // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
+        mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
+        mName = checkName(parcel.readString());
+        mStatus = checkStatus(parcel.readInt());
         mDescription = parcel.readString();
         mCapabilities = parcel.readParcelable(null);
         mIconResourceId = parcel.readInt();
@@ -261,8 +288,8 @@
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + ((mId != null) ? mId.hashCode() : 0);
-        result = prime * result + ((mName != null) ? mName.hashCode() : 0);
+        result = prime * result + mId.hashCode();
+        result = prime * result + mName.hashCode();
         result = prime * result + mStatus;
         result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0);
         result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0);
@@ -282,15 +309,11 @@
      * @hide
      */
     public boolean equalsIgnoringStatus(PrinterInfo other) {
-        if (mId == null) {
-            if (other.mId != null) {
-                return false;
-            }
-        } else if (!mId.equals(other.mId)) {
+        if (!mId.equals(other.mId)) {
             return false;
         }
-        if (!TextUtils.equals(mName, other.mName)) {
-            return false;
+        if (!mName.equals(other.mName)) {
+           return false;
         }
         if (!TextUtils.equals(mDescription, other.mDescription)) {
             return false;
@@ -363,7 +386,15 @@
      * Builder for creating of a {@link PrinterInfo}.
      */
     public static final class Builder {
-        private final PrinterInfo mPrototype;
+        private @NonNull PrinterId mPrinterId;
+        private @NonNull String mName;
+        private @Status int mStatus;
+        private int mIconResourceId;
+        private boolean mHasCustomPrinterIcon;
+        private String mDescription;
+        private PendingIntent mInfoIntent;
+        private PrinterCapabilitiesInfo mCapabilities;
+        private int mCustomPrinterIconGen;
 
         /**
          * Constructor.
@@ -375,19 +406,9 @@
          * printer name is empty or the status is not a valid one.
          */
         public Builder(@NonNull PrinterId printerId, @NonNull String name, @Status int status) {
-            if (printerId == null) {
-                throw new IllegalArgumentException("printerId cannot be null.");
-            }
-            if (TextUtils.isEmpty(name)) {
-                throw new IllegalArgumentException("name cannot be empty.");
-            }
-            if (!isValidStatus(status)) {
-                throw new IllegalArgumentException("status is invalid.");
-            }
-            mPrototype = new PrinterInfo();
-            mPrototype.mId = printerId;
-            mPrototype.mName = name;
-            mPrototype.mStatus = status;
+            mPrinterId = checkPrinterId(printerId);
+            mName = checkName(name);
+            mStatus = checkStatus(status);
         }
 
         /**
@@ -396,8 +417,15 @@
          * @param other Other info from which to start building.
          */
         public Builder(@NonNull PrinterInfo other) {
-            mPrototype = new PrinterInfo();
-            mPrototype.copyFrom(other);
+            mPrinterId = other.mId;
+            mName = other.mName;
+            mStatus = other.mStatus;
+            mIconResourceId = other.mIconResourceId;
+            mHasCustomPrinterIcon = other.mHasCustomPrinterIcon;
+            mDescription = other.mDescription;
+            mInfoIntent = other.mInfoIntent;
+            mCapabilities = other.mCapabilities;
+            mCustomPrinterIconGen = other.mCustomPrinterIconGen;
         }
 
         /**
@@ -405,13 +433,12 @@
          *
          * @param status The status.
          * @return This builder.
-         *
          * @see PrinterInfo#STATUS_IDLE
          * @see PrinterInfo#STATUS_BUSY
          * @see PrinterInfo#STATUS_UNAVAILABLE
          */
         public @NonNull Builder setStatus(@Status int status) {
-            mPrototype.mStatus = status;
+            mStatus = checkStatus(status);
             return this;
         }
 
@@ -424,7 +451,8 @@
          * @see PrinterInfo.Builder#setHasCustomPrinterIcon
          */
         public @NonNull Builder setIconResourceId(@DrawableRes int iconResourceId) {
-            mPrototype.mIconResourceId = iconResourceId;
+            mIconResourceId = Preconditions.checkArgumentNonnegative(iconResourceId,
+                    "iconResourceId can't be negative");
             return this;
         }
 
@@ -442,7 +470,7 @@
          * @return This builder.
          */
         public @NonNull Builder setHasCustomPrinterIcon() {
-            mPrototype.mHasCustomPrinterIcon = true;
+            mHasCustomPrinterIcon = true;
             return this;
         }
 
@@ -454,7 +482,7 @@
          * @return This builder.
          */
         public @NonNull Builder setName(@NonNull String name) {
-            mPrototype.mName = name;
+            mName = checkName(name);
             return this;
         }
 
@@ -466,7 +494,7 @@
          * @return This builder.
          */
         public @NonNull Builder setDescription(@NonNull String description) {
-            mPrototype.mDescription = description;
+            mDescription = description;
             return this;
         }
 
@@ -478,7 +506,7 @@
          * @return This builder.
          */
         public @NonNull Builder setInfoIntent(@NonNull PendingIntent infoIntent) {
-            mPrototype.mInfoIntent = infoIntent;
+            mInfoIntent = infoIntent;
             return this;
         }
 
@@ -489,7 +517,7 @@
          * @return This builder.
          */
         public @NonNull Builder setCapabilities(@NonNull PrinterCapabilitiesInfo capabilities) {
-            mPrototype.mCapabilities = capabilities;
+            mCapabilities = capabilities;
             return this;
         }
 
@@ -499,13 +527,9 @@
          * @return A new {@link PrinterInfo}.
          */
         public @NonNull PrinterInfo build() {
-            return mPrototype;
-        }
-
-        private boolean isValidStatus(int status) {
-            return (status == STATUS_IDLE
-                    || status == STATUS_BUSY
-                    || status == STATUS_UNAVAILABLE);
+            return new PrinterInfo(mPrinterId, mName, mStatus, mIconResourceId,
+                    mHasCustomPrinterIcon, mDescription, mInfoIntent, mCapabilities,
+                    mCustomPrinterIconGen);
         }
 
         /**
@@ -517,7 +541,7 @@
          * @hide
          */
         public @NonNull Builder incCustomPrinterIconGen() {
-            mPrototype.mCustomPrinterIconGen++;
+            mCustomPrinterIconGen++;
             return this;
         }
     }
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index d0037b7..3104492 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -30,6 +30,8 @@
 import android.print.PrinterId;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -346,6 +348,7 @@
      */
     public final PrinterId generatePrinterId(String localId) {
         throwIfNotCalledOnMainThread();
+        localId = Preconditions.checkNotNull(localId, "localId cannot be null");
         return new PrinterId(new ComponentName(getPackageName(),
                 getClass().getName()), localId);
     }
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 03e0e11..2a9d4ae 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -104,12 +104,6 @@
          * <p>TYPE: String</p>
          */
         public static final String COLUMN_E164_NUMBER = "e164_number";
-
-        /** @hide */
-        public static final String COLUMN_INDEX_STRIPPED = "index_stripped";
-
-        /** @hide */
-        public static final String COLUMN_INDEX_E164 = "index_e164";
     }
 
     /** @hide */
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 10432b5..b547432 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -409,6 +409,20 @@
                 "directories_enterprise");
 
         /**
+         * Access file provided by remote directory. It allows both personal and work remote
+         * directory, but not local and invisible diretory.
+         *
+         * It's supported only by a few specific places for referring to contact pictures in the
+         * remote directory. Contact picture URIs, e.g.
+         * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}, may contain this kind of URI.
+         *
+         * @hide
+         */
+        public static final Uri ENTERPRISE_FILE_URI = Uri.withAppendedPath(AUTHORITY_URI,
+                "directory_file_enterprise");
+
+
+        /**
          * The MIME-type of {@link #CONTENT_URI} providing a directory of
          * contact directories.
          */
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index b2d9b934..8472f78 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -609,7 +609,7 @@
          * This download has successfully completed.
          * Warning: there might be other status values that indicate success
          * in the future.
-         * Use isSucccess() to capture the entire category.
+         * Use isStatusSuccess() to capture the entire category.
          */
         public static final int STATUS_SUCCESS = 200;
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
old mode 100644
new mode 100755
index 3e06ecf..3042aa9
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4429,6 +4429,13 @@
         public static final String HTTP_PROXY = Global.HTTP_PROXY;
 
         /**
+         * Package designated as always-on VPN provider.
+         *
+         * @hide
+         */
+        public static final String ALWAYS_ON_VPN_APP = "always_on_vpn_app";
+
+        /**
          * Whether applications can be installed for this user via the system's
          * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
          *
@@ -7409,6 +7416,9 @@
                 BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
         /** {@hide} */
         public static final String
+                BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_";
+        /** {@hide} */
+        public static final String
                 BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
         /** {@hide} */
         public static final String
@@ -7511,6 +7521,14 @@
         }
 
         /**
+         * Get the key that retrieves a bluetooth a2dp src's priority.
+         * @hide
+         */
+        public static final String getBluetoothA2dpSrcPriorityKey(String address) {
+            return BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+        }
+
+        /**
          * Get the key that retrieves a bluetooth Input Device's priority.
          * @hide
          */
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 88bd283..eff09d6 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -85,6 +85,13 @@
             "android.service.zen.automatic.configurationActivity";
 
     /**
+     * The name of the {@code meta-data} tag containing the maximum number of rule instances that
+     * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+     */
+    public static final String META_DATA_RULE_INSTANCE_LIMIT =
+            "android.service.zen.automatic.ruleInstanceLimit";
+
+    /**
      * A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
      */
     public static final String EXTRA_RULE_ID = "android.content.automatic.ruleId";
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d146e5e..cd19607 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -167,6 +167,7 @@
         final Rect mDispatchedOutsets = new Rect();
         final Rect mFinalSystemInsets = new Rect();
         final Rect mFinalStableInsets = new Rect();
+        final Rect mBackdropFrame = new Rect();
         final Configuration mConfiguration = new Configuration();
 
         final WindowManager.LayoutParams mLayout
@@ -675,8 +676,8 @@
                     final int relayoutResult = mSession.relayout(
                         mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                             View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
-                            mVisibleInsets, mStableInsets, mOutsets, mConfiguration,
-                            mSurfaceHolder.mSurface);
+                            mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
+                            mConfiguration, mSurfaceHolder.mSurface);
 
                     if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
                             + ", frame=" + mWinFrame);
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index d286231..117de774 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -16,30 +16,56 @@
 
 package android.text.style;
 
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Paint;
 import android.os.Parcel;
 import android.text.ParcelableSpan;
 import android.text.TextPaint;
 import android.text.TextUtils;
+import android.util.LocaleList;
+
 import java.util.Locale;
 
 /**
  * Changes the {@link Locale} of the text to which the span is attached.
  */
 public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan {
-    private final Locale mLocale;
+    @NonNull
+    private final LocaleList mLocales;
 
     /**
-     * Creates a LocaleSpan.
-     * @param locale The {@link Locale} of the text to which the span is
-     * attached.
+     * Creates a {@link LocaleSpan} from a well-formed {@link Locale}.  Note that only
+     * {@link Locale} objects that can be created by {@link Locale#forLanguageTag(String)} are
+     * supported.
+     *
+     * <p><b>Caveat:</b> Do not specify any {@link Locale} object that cannot be created by
+     * {@link Locale#forLanguageTag(String)}.  {@code new Locale(" a ", " b c", " d")} is an
+     * example of such a malformed {@link Locale} object.</p>
+     *
+     * @param locale The {@link Locale} of the text to which the span is attached.
+     *
+     * @see #LocaleSpan(LocaleList)
      */
-    public LocaleSpan(Locale locale) {
-        mLocale = locale;
+    public LocaleSpan(@Nullable Locale locale) {
+        mLocales = new LocaleList(locale);
     }
 
-    public LocaleSpan(Parcel src) {
-        mLocale = new Locale(src.readString(), src.readString(), src.readString());
+    /**
+     * Creates a {@link LocaleSpan} from {@link LocaleList}.
+     *
+     * @param locales The {@link LocaleList} of the text to which the span is attached.
+     * @throws NullPointerException if {@code locales} is null
+     */
+    public LocaleSpan(@NonNull LocaleList locales) {
+        Preconditions.checkNotNull(locales, "locales cannot be null");
+        mLocales = locales;
+    }
+
+    public LocaleSpan(Parcel source) {
+        mLocales = LocaleList.CREATOR.createFromParcel(source);
     }
 
     @Override
@@ -64,31 +90,40 @@
 
     /** @hide */
     public void writeToParcelInternal(Parcel dest, int flags) {
-        dest.writeString(mLocale.getLanguage());
-        dest.writeString(mLocale.getCountry());
-        dest.writeString(mLocale.getVariant());
+        mLocales.writeToParcel(dest, flags);
     }
 
     /**
-     * Returns the {@link Locale}.
+     * @return The {@link Locale} for this span.  If multiple locales are associated with this
+     * span, only the first locale is returned.  {@code null} if no {@link Locale} is specified.
      *
-     * @return The {@link Locale} for this span.
+     * @see LocaleList#getPrimary()
+     * @see #getLocales()
      */
+    @Nullable
     public Locale getLocale() {
-        return mLocale;
+        return mLocales.getPrimary();
+    }
+
+    /**
+     * @return The entire list of locales that are associated with this span.
+     */
+    @NonNull
+    public LocaleList getLocales() {
+        return mLocales;
     }
 
     @Override
     public void updateDrawState(TextPaint ds) {
-        apply(ds, mLocale);
+        apply(ds, mLocales);
     }
 
     @Override
     public void updateMeasureState(TextPaint paint) {
-        apply(paint, mLocale);
+        apply(paint, mLocales);
     }
 
-    private static void apply(Paint paint, Locale locale) {
-        paint.setTextLocale(locale);
+    private static void apply(@NonNull Paint paint, @NonNull LocaleList locales) {
+        paint.setTextLocales(locales);
     }
 }
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 6b449f9..1b00db2 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -16,6 +16,8 @@
 
 package android.text.style;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
@@ -84,7 +86,15 @@
 
     private int mFlags;
     private final String[] mSuggestions;
-    private final String mLocaleString;
+    /**
+     * Kept for compatibility for apps that rely on invalid locale strings e.g.
+     * {@code new Locale(" an ", " i n v a l i d ", "data")}, which cannot be handled by
+     * {@link #mLanguageTag}.
+     */
+    @NonNull
+    private final String mLocaleStringForCompatibility;
+    @NonNull
+    private final String mLanguageTag;
     private final String mNotificationTargetClassName;
     private final String mNotificationTargetPackageName;
     private final int mHashCode;
@@ -130,14 +140,18 @@
         final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
         mSuggestions = Arrays.copyOf(suggestions, N);
         mFlags = flags;
+        final Locale sourceLocale;
         if (locale != null) {
-            mLocaleString = locale.toString();
+            sourceLocale = locale;
         } else if (context != null) {
-            mLocaleString = context.getResources().getConfiguration().locale.toString();
+            // TODO: Consider to context.getResources().getResolvedLocale() instead.
+            sourceLocale = context.getResources().getConfiguration().locale;
         } else {
             Log.e("SuggestionSpan", "No locale or context specified in SuggestionSpan constructor");
-            mLocaleString = "";
+            sourceLocale = null;
         }
+        mLocaleStringForCompatibility = sourceLocale == null ? "" : sourceLocale.toString();
+        mLanguageTag = sourceLocale == null ? "" : sourceLocale.toLanguageTag();
 
         if (context != null) {
             mNotificationTargetPackageName = context.getPackageName();
@@ -150,7 +164,8 @@
         } else {
             mNotificationTargetClassName = "";
         }
-        mHashCode = hashCodeInternal(mSuggestions, mLocaleString, mNotificationTargetClassName);
+        mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility,
+                mNotificationTargetClassName);
 
         initStyle(context);
     }
@@ -194,7 +209,8 @@
     public SuggestionSpan(Parcel src) {
         mSuggestions = src.readStringArray();
         mFlags = src.readInt();
-        mLocaleString = src.readString();
+        mLocaleStringForCompatibility = src.readString();
+        mLanguageTag = src.readString();
         mNotificationTargetClassName = src.readString();
         mNotificationTargetPackageName = src.readString();
         mHashCode = src.readInt();
@@ -214,10 +230,29 @@
     }
 
     /**
-     * @return the locale of the suggestions
+     * @deprecated use {@link #getLocaleObject()} instead.
+     * @return the locale of the suggestions. An empty string is returned if no locale is specified.
      */
+    @NonNull
+    @Deprecated
     public String getLocale() {
-        return mLocaleString;
+        return mLocaleStringForCompatibility;
+    }
+
+    /**
+     * Returns a well-formed BCP 47 language tag representation of the suggestions, as a
+     * {@link Locale} object.
+     *
+     * <p><b>Caveat</b>: The returned object is guaranteed to be a  a well-formed BCP 47 language tag
+     * representation.  For example, this method can return an empty locale rather than returning a
+     * malformed data when this object is initialized with an malformed {@link Locale} object, e.g.
+     * {@code new Locale(" a ", " b c d ", " "}.</p>
+     *
+     * @return the locale of the suggestions. {@code null} is returned if no locale is specified.
+     */
+    @Nullable
+    public Locale getLocaleObject() {
+        return mLanguageTag.isEmpty() ? null : Locale.forLanguageTag(mLanguageTag);
     }
 
     /**
@@ -255,7 +290,8 @@
     public void writeToParcelInternal(Parcel dest, int flags) {
         dest.writeStringArray(mSuggestions);
         dest.writeInt(mFlags);
-        dest.writeString(mLocaleString);
+        dest.writeString(mLocaleStringForCompatibility);
+        dest.writeString(mLanguageTag);
         dest.writeString(mNotificationTargetClassName);
         dest.writeString(mNotificationTargetPackageName);
         dest.writeInt(mHashCode);
@@ -290,10 +326,10 @@
         return mHashCode;
     }
 
-    private static int hashCodeInternal(String[] suggestions, String locale,
-            String notificationTargetClassName) {
+    private static int hashCodeInternal(String[] suggestions, @NonNull String languageTag,
+            @NonNull String localeStringForCompatibility, String notificationTargetClassName) {
         return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
-                locale, notificationTargetClassName});
+                languageTag, localeStringForCompatibility, notificationTargetClassName});
     }
 
     public static final Parcelable.Creator<SuggestionSpan> CREATOR =
diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java
index b10f6a0..817541c 100644
--- a/core/java/android/transition/SidePropagation.java
+++ b/core/java/android/transition/SidePropagation.java
@@ -16,6 +16,7 @@
 package android.transition;
 
 import android.graphics.Rect;
+import android.transition.Slide.GravityFlag;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -45,7 +46,7 @@
      *             {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT},
      *             {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}.
      */
-    public void setSide(int side) {
+    public void setSide(@GravityFlag int side) {
         mSide = side;
     }
 
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index 9063b43..9af65e4 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -17,6 +17,7 @@
 
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -27,6 +28,9 @@
 import android.view.animation.DecelerateInterpolator;
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This transition tracks changes to the visibility of target views in the
  * start and end scenes and moves views in or out from one of the edges of the
@@ -42,7 +46,12 @@
     private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
     private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
     private CalculateSlide mSlideCalculator = sCalculateBottom;
-    private int mSlideEdge = Gravity.BOTTOM;
+    private @GravityFlag int mSlideEdge = Gravity.BOTTOM;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END})
+    public @interface GravityFlag {}
 
     private interface CalculateSlide {
 
@@ -176,7 +185,7 @@
      *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
      * @attr ref android.R.styleable#Slide_slideEdge
      */
-    public void setSlideEdge(int slideEdge) {
+    public void setSlideEdge(@GravityFlag int slideEdge) {
         switch (slideEdge) {
             case Gravity.LEFT:
                 mSlideCalculator = sCalculateLeft;
@@ -214,6 +223,7 @@
      *         {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
      * @attr ref android.R.styleable#Slide_slideEdge
      */
+    @GravityFlag
     public int getSlideEdge() {
         return mSlideEdge;
     }
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index e711812..eb95a02 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -16,17 +16,21 @@
 
 package android.transition;
 
-import com.android.internal.R;
-
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.Animator.AnimatorPauseListener;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This transition tracks changes to the visibility of target views in the
  * start and end scenes. Visibility is determined not just by the
@@ -46,6 +50,11 @@
     private static final String PROPNAME_PARENT = "android:visibility:parent";
     private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag=true, value={MODE_IN, MODE_OUT})
+    @interface VisibilityMode {}
+
     /**
      * Mode used in {@link #setMode(int)} to make the transition
      * operate on targets that are appearing. Maybe be combined with
@@ -99,7 +108,7 @@
      *             {@link #MODE_IN} and {@link #MODE_OUT}.
      * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode
      */
-    public void setMode(int mode) {
+    public void setMode(@VisibilityMode int mode) {
         if ((mode & ~(MODE_IN | MODE_OUT)) != 0) {
             throw new IllegalArgumentException("Only MODE_IN and MODE_OUT flags are allowed");
         }
@@ -113,6 +122,7 @@
      *         {@link #MODE_IN} and {@link #MODE_OUT}.
      * @attr ref android.R.styleable#VisibilityTransition_transitionVisibilityMode
      */
+    @VisibilityMode
     public int getMode() {
         return mMode;
     }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
new file mode 100644
index 0000000..81c8c45
--- /dev/null
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -0,0 +1,1033 @@
+/*
+ * 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.util.apk;
+
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.DigestException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * APK Signature Scheme v2 verifier.
+ *
+ * @hide for internal use only.
+ */
+public class ApkSignatureSchemeV2Verifier {
+
+    /**
+     * {@code .SF} file header section attribute indicating that the APK is signed not just with
+     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
+     * facilitates v2 signature stripping detection.
+     *
+     * <p>The attribute contains a comma-separated set of signature scheme IDs.
+     */
+    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
+    // TODO: Change the value when signing scheme finalized.
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 1234567890;
+
+    /**
+     * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static X509Certificate[][] verify(String apkFile)
+            throws SignatureNotFoundException, SecurityException, IOException {
+        try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
+            return verify(apk);
+        }
+    }
+
+    /**
+     * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+     * @throws IOException if an I/O error occurs while reading the APK file.
+     */
+    public static X509Certificate[][] verify(RandomAccessFile apk)
+            throws SignatureNotFoundException, SecurityException, IOException {
+
+        long fileSize = apk.length();
+        if (fileSize > Integer.MAX_VALUE) {
+            throw new IOException("File too large: " + apk.length() + " bytes");
+        }
+        MappedByteBuffer apkContents =
+                apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+        // Attempt to preload the contents into memory for faster overall verification (v2 and
+        // older) at the expense of somewhat increased latency for rejecting malformed APKs.
+        apkContents.load();
+        return verify(apkContents);
+    }
+
+    /**
+     * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
+     * associated with each signer.
+     *
+     * @param apkContents contents of the APK. The contents start at the current position and end
+     *        at the limit of the buffer.
+     *
+     * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
+     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
+     */
+    public static X509Certificate[][] verify(ByteBuffer apkContents)
+            throws SignatureNotFoundException, SecurityException {
+        // Avoid modifying byte order, position, limit, and mark of the original apkContents.
+        apkContents = apkContents.slice();
+
+        // ZipUtils and APK Signature Scheme v2 verifier expect little-endian byte order.
+        apkContents.order(ByteOrder.LITTLE_ENDIAN);
+
+        // Find the offset of ZIP End of Central Directory (EoCD)
+        int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(apkContents);
+        if (eocdOffset == -1) {
+            throw new SignatureNotFoundException(
+                    "Not an APK file: ZIP End of Central Directory record not found");
+        }
+        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apkContents, eocdOffset)) {
+            throw new SignatureNotFoundException("ZIP64 APK not supported");
+        }
+        ByteBuffer eocd = sliceFromTo(apkContents, eocdOffset, apkContents.capacity());
+
+        // Look up the offset of ZIP Central Directory.
+        long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
+        if (centralDirOffsetLong >= eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory offset out of range: " + centralDirOffsetLong
+                    + ". ZIP End of Central Directory offset: " + eocdOffset);
+        }
+        long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocd);
+        if (centralDirOffsetLong + centralDirSizeLong != eocdOffset) {
+            throw new SignatureNotFoundException(
+                    "ZIP Central Directory is not immediately followed by End of Central"
+                    + " Directory");
+        }
+        int centralDirOffset = (int) centralDirOffsetLong;
+
+        // Find the APK Signing Block.
+        int apkSigningBlockOffset = findApkSigningBlock(apkContents, centralDirOffset);
+        ByteBuffer apkSigningBlock =
+                sliceFromTo(apkContents, apkSigningBlockOffset, centralDirOffset);
+
+        // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
+        ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlock);
+
+        // Verify the contents of the APK outside of the APK Signing Block using the APK Signature
+        // Scheme v2 Block.
+        return verify(
+                apkContents,
+                apkSignatureSchemeV2Block,
+                apkSigningBlockOffset,
+                centralDirOffset,
+                eocdOffset);
+    }
+
+    /**
+     * Verifies the contents outside of the APK Signing Block using the provided APK Signature
+     * Scheme v2 Block.
+     */
+    private static X509Certificate[][] verify(
+            ByteBuffer apkContents,
+            ByteBuffer v2Block,
+            int apkSigningBlockOffset,
+            int centralDirOffset,
+            int eocdOffset) throws SecurityException {
+        int signerCount = 0;
+        Map<Integer, byte[]> contentDigests = new HashMap<>();
+        List<X509Certificate[]> signerCerts = new ArrayList<>();
+        CertificateFactory certFactory;
+        try {
+            certFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+        ByteBuffer signers;
+        try {
+            signers = getLengthPrefixedSlice(v2Block);
+        } catch (IOException e) {
+            throw new SecurityException("Failed to read list of signers", e);
+        }
+        while (signers.hasRemaining()) {
+            signerCount++;
+            try {
+                ByteBuffer signer = getLengthPrefixedSlice(signers);
+                X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
+                signerCerts.add(certs);
+            } catch (IOException | BufferUnderflowException | SecurityException e) {
+                throw new SecurityException(
+                        "Failed to parse/verify signer #" + signerCount + " block",
+                        e);
+            }
+        }
+
+        if (signerCount < 1) {
+            throw new SecurityException("No signers found");
+        }
+
+        if (contentDigests.isEmpty()) {
+            throw new SecurityException("No content digests found");
+        }
+
+        verifyIntegrity(
+                contentDigests,
+                apkContents,
+                apkSigningBlockOffset,
+                centralDirOffset,
+                eocdOffset);
+
+        return signerCerts.toArray(new X509Certificate[signerCerts.size()][]);
+    }
+
+    private static X509Certificate[] verifySigner(
+            ByteBuffer signerBlock,
+            Map<Integer, byte[]> contentDigests,
+            CertificateFactory certFactory) throws SecurityException, IOException {
+        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
+        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
+        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
+
+        int signatureCount = 0;
+        int bestSigAlgorithm = -1;
+        byte[] bestSigAlgorithmSignatureBytes = null;
+        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
+        while (signatures.hasRemaining()) {
+            signatureCount++;
+            try {
+                ByteBuffer signature = getLengthPrefixedSlice(signatures);
+                if (signature.remaining() < 8) {
+                    throw new SecurityException("Signature record too short");
+                }
+                int sigAlgorithm = signature.getInt();
+                signaturesSigAlgorithms.add(sigAlgorithm);
+                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
+                    continue;
+                }
+                if ((bestSigAlgorithm == -1)
+                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
+                    bestSigAlgorithm = sigAlgorithm;
+                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new SecurityException(
+                        "Failed to parse signature record #" + signatureCount,
+                        e);
+            }
+        }
+        if (bestSigAlgorithm == -1) {
+            if (signatureCount == 0) {
+                throw new SecurityException("No signatures found");
+            } else {
+                throw new SecurityException("No supported signatures found");
+            }
+        }
+
+        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
+        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
+                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
+        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
+        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
+        boolean sigVerified;
+        try {
+            PublicKey publicKey =
+                    KeyFactory.getInstance(keyAlgorithm)
+                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
+            sig.initVerify(publicKey);
+            if (jcaSignatureAlgorithmParams != null) {
+                sig.setParameter(jcaSignatureAlgorithmParams);
+            }
+            sig.update(signedData);
+            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
+                | InvalidAlgorithmParameterException | SignatureException e) {
+            throw new SecurityException(
+                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
+        }
+        if (!sigVerified) {
+            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
+        }
+
+        // Signature over signedData has verified.
+
+        byte[] contentDigest = null;
+        signedData.clear();
+        ByteBuffer digests = getLengthPrefixedSlice(signedData);
+        List<Integer> digestsSigAlgorithms = new ArrayList<>();
+        int digestCount = 0;
+        while (digests.hasRemaining()) {
+            digestCount++;
+            try {
+                ByteBuffer digest = getLengthPrefixedSlice(digests);
+                if (digest.remaining() < 8) {
+                    throw new IOException("Record too short");
+                }
+                int sigAlgorithm = digest.getInt();
+                digestsSigAlgorithms.add(sigAlgorithm);
+                if (sigAlgorithm == bestSigAlgorithm) {
+                    contentDigest = readLengthPrefixedByteArray(digest);
+                }
+            } catch (IOException | BufferUnderflowException e) {
+                throw new IOException("Failed to parse digest record #" + digestCount, e);
+            }
+        }
+
+        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
+            throw new SecurityException(
+                    "Signature algorithms don't match between digests and signatures records");
+        }
+        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
+        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
+        if ((previousSignerDigest != null)
+                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
+            throw new SecurityException(
+                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                    + " contents digest does not match the digest specified by a preceding signer");
+        }
+
+        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
+        List<X509Certificate> certs = new ArrayList<>();
+        int certificateCount = 0;
+        while (certificates.hasRemaining()) {
+            certificateCount++;
+            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
+            X509Certificate certificate;
+            try {
+                certificate = (X509Certificate)
+                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
+            } catch (CertificateException e) {
+                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
+            }
+            certificate = new VerbatimX509Certificate(certificate, encodedCert);
+            certs.add(certificate);
+        }
+
+        if (certs.isEmpty()) {
+            throw new SecurityException("No certificates listed");
+        }
+        X509Certificate mainCertificate = certs.get(0);
+        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
+        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
+            throw new SecurityException(
+                    "Public key mismatch between certificate and signature record");
+        }
+
+        return certs.toArray(new X509Certificate[certs.size()]);
+    }
+
+    private static void verifyIntegrity(
+            Map<Integer, byte[]> expectedDigests,
+            ByteBuffer apkContents,
+            int apkSigningBlockOffset,
+            int centralDirOffset,
+            int eocdOffset) throws SecurityException {
+
+        if (expectedDigests.isEmpty()) {
+            throw new SecurityException("No digests provided");
+        }
+
+        ByteBuffer beforeApkSigningBlock = sliceFromTo(apkContents, 0, apkSigningBlockOffset);
+        ByteBuffer centralDir = sliceFromTo(apkContents, centralDirOffset, eocdOffset);
+        // For the purposes of integrity verification, ZIP End of Central Directory's field Start of
+        // Central Directory must be considered to point to the offset of the APK Signing Block.
+        byte[] eocdBytes = new byte[apkContents.capacity() - eocdOffset];
+        apkContents.position(eocdOffset);
+        apkContents.get(eocdBytes);
+        ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);
+        eocd.order(apkContents.order());
+        ZipUtils.setZipEocdCentralDirectoryOffset(eocd, apkSigningBlockOffset);
+
+        int[] digestAlgorithms = new int[expectedDigests.size()];
+        int digestAlgorithmCount = 0;
+        for (int digestAlgorithm : expectedDigests.keySet()) {
+            digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
+            digestAlgorithmCount++;
+        }
+        Map<Integer, byte[]> actualDigests;
+        try {
+            actualDigests =
+                    computeContentDigests(
+                            digestAlgorithms,
+                            new ByteBuffer[] {beforeApkSigningBlock, centralDir, eocd});
+        } catch (DigestException e) {
+            throw new SecurityException("Failed to compute digest(s) of contents", e);
+        }
+        for (Map.Entry<Integer, byte[]> entry : expectedDigests.entrySet()) {
+            int digestAlgorithm = entry.getKey();
+            byte[] expectedDigest = entry.getValue();
+            byte[] actualDigest = actualDigests.get(digestAlgorithm);
+            if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
+                throw new SecurityException(
+                        getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
+                        + " digest of contents did not verify");
+            }
+        }
+    }
+
+    private static Map<Integer, byte[]> computeContentDigests(
+            int[] digestAlgorithms,
+            ByteBuffer[] contents) throws DigestException {
+        // For each digest algorithm the result is computed as follows:
+        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
+        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
+        //    No chunks are produced for empty (zero length) segments.
+        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
+        //    length in bytes (uint32 little-endian) and the chunk's contents.
+        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
+        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all
+        //    segments in-order.
+
+        int totalChunkCount = 0;
+        for (ByteBuffer input : contents) {
+            totalChunkCount += getChunkCount(input.remaining());
+        }
+
+        Map<Integer, byte[]> digestsOfChunks = new HashMap<>(totalChunkCount);
+        for (int digestAlgorithm : digestAlgorithms) {
+            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+            byte[] concatenationOfChunkCountAndChunkDigests =
+                    new byte[5 + totalChunkCount * digestOutputSizeBytes];
+            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
+            setUnsignedInt32LittleEndian(
+                    totalChunkCount,
+                    concatenationOfChunkCountAndChunkDigests,
+                    1);
+            digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
+        }
+
+        byte[] chunkContentPrefix = new byte[5];
+        chunkContentPrefix[0] = (byte) 0xa5;
+        int chunkIndex = 0;
+        for (ByteBuffer input : contents) {
+            while (input.hasRemaining()) {
+                int chunkSize = Math.min(input.remaining(), CHUNK_SIZE_BYTES);
+                ByteBuffer chunk = getByteBuffer(input, chunkSize);
+                for (int digestAlgorithm : digestAlgorithms) {
+                    String jcaAlgorithmName =
+                            getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+                    MessageDigest md;
+                    try {
+                        md = MessageDigest.getInstance(jcaAlgorithmName);
+                    } catch (NoSuchAlgorithmException e) {
+                        throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+                    }
+                    chunk.clear();
+                    setUnsignedInt32LittleEndian(chunk.remaining(), chunkContentPrefix, 1);
+                    md.update(chunkContentPrefix);
+                    md.update(chunk);
+                    byte[] concatenationOfChunkCountAndChunkDigests =
+                            digestsOfChunks.get(digestAlgorithm);
+                    int expectedDigestSizeBytes =
+                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
+                    int actualDigestSizeBytes = md.digest(concatenationOfChunkCountAndChunkDigests,
+                            5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes);
+                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {
+                        throw new RuntimeException(
+                                "Unexpected output size of " + md.getAlgorithm() + " digest: "
+                                        + actualDigestSizeBytes);
+                    }
+                }
+                chunkIndex++;
+            }
+        }
+
+        Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.length);
+        for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
+            int digestAlgorithm = entry.getKey();
+            byte[] input = entry.getValue();
+            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(jcaAlgorithmName);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(jcaAlgorithmName + " digest not supported", e);
+            }
+            byte[] output = md.digest(input);
+            result.put(digestAlgorithm, output);
+        }
+        return result;
+    }
+
+    private static final int getChunkCount(int inputSizeBytes) {
+        return (inputSizeBytes + CHUNK_SIZE_BYTES - 1) / CHUNK_SIZE_BYTES;
+    }
+
+    private static final int CHUNK_SIZE_BYTES = 1024 * 1024;
+
+    private static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
+    private static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
+    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
+    private static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
+    private static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
+    private static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
+    private static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
+    private static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;
+
+    private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
+    private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
+
+    private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA512:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
+        int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
+        int digestAlgorithm2 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm2);
+        return compareContentDigestAlgorithm(digestAlgorithm1, digestAlgorithm2);
+    }
+
+    private static int compareContentDigestAlgorithm(int digestAlgorithm1, int digestAlgorithm2) {
+        switch (digestAlgorithm1) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 0;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return -1;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                switch (digestAlgorithm2) {
+                    case CONTENT_DIGEST_CHUNKED_SHA256:
+                        return 1;
+                    case CONTENT_DIGEST_CHUNKED_SHA512:
+                        return 0;
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown digestAlgorithm2: " + digestAlgorithm2);
+                }
+            default:
+                throw new IllegalArgumentException("Unknown digestAlgorithm1: " + digestAlgorithm1);
+        }
+    }
+
+    private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA256:
+                return CONTENT_DIGEST_CHUNKED_SHA256;
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+            case SIGNATURE_DSA_WITH_SHA512:
+                return CONTENT_DIGEST_CHUNKED_SHA512;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return "SHA-256";
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return "SHA-512";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
+        switch (digestAlgorithm) {
+            case CONTENT_DIGEST_CHUNKED_SHA256:
+                return 256 / 8;
+            case CONTENT_DIGEST_CHUNKED_SHA512:
+                return 512 / 8;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown content digest algorthm: " + digestAlgorithm);
+        }
+    }
+
+    private static String getSignatureAlgorithmJcaKeyAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return "RSA";
+            case SIGNATURE_ECDSA_WITH_SHA256:
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return "EC";
+            case SIGNATURE_DSA_WITH_SHA256:
+            case SIGNATURE_DSA_WITH_SHA512:
+                return "DSA";
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    private static Pair<String, ? extends AlgorithmParameterSpec>
+            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
+        switch (sigAlgorithm) {
+            case SIGNATURE_RSA_PSS_WITH_SHA256:
+                return Pair.create(
+                        "SHA256withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
+            case SIGNATURE_RSA_PSS_WITH_SHA512:
+                return Pair.create(
+                        "SHA512withRSA/PSS",
+                        new PSSParameterSpec(
+                                "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
+                return Pair.create("SHA256withRSA", null);
+            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
+                return Pair.create("SHA512withRSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA256:
+                return Pair.create("SHA256withECDSA", null);
+            case SIGNATURE_ECDSA_WITH_SHA512:
+                return Pair.create("SHA512withECDSA", null);
+            case SIGNATURE_DSA_WITH_SHA256:
+                return Pair.create("SHA256withDSA", null);
+            case SIGNATURE_DSA_WITH_SHA512:
+                return Pair.create("SHA512withDSA", null);
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown signature algorithm: 0x"
+                                + Long.toHexString(sigAlgorithm & 0xffffffff));
+        }
+    }
+
+    /**
+     * Returns new byte buffer whose content is a shared subsequence of this buffer's content
+     * between the specified start (inclusive) and end (exclusive) positions. As opposed to
+     * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
+     * buffer's byte order.
+     */
+    private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException("start: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException("end < start: " + end + " < " + start);
+        }
+        int capacity = source.capacity();
+        if (end > source.capacity()) {
+            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
+        }
+        int originalLimit = source.limit();
+        int originalPosition = source.position();
+        try {
+            source.position(0);
+            source.limit(end);
+            source.position(start);
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            return result;
+        } finally {
+            source.position(0);
+            source.limit(originalLimit);
+            source.position(originalPosition);
+        }
+    }
+
+    /**
+     * Relative <em>get</em> method for reading {@code size} number of bytes from the current
+     * position of this buffer.
+     *
+     * <p>This method reads the next {@code size} bytes at this buffer's current position,
+     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
+     * {@code size}, byte order set to this buffer's byte order; and then increments the position by
+     * {@code size}.
+     */
+    private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
+            throws BufferUnderflowException {
+        if (size < 0) {
+            throw new IllegalArgumentException("size: " + size);
+        }
+        int originalLimit = source.limit();
+        int position = source.position();
+        int limit = position + size;
+        if ((limit < position) || (limit > originalLimit)) {
+            throw new BufferUnderflowException();
+        }
+        source.limit(limit);
+        try {
+            ByteBuffer result = source.slice();
+            result.order(source.order());
+            source.position(limit);
+            return result;
+        } finally {
+            source.limit(originalLimit);
+        }
+    }
+
+    private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
+        if (source.remaining() < 4) {
+            throw new IOException(
+                    "Remaining buffer too short to contain length of length-prefixed field."
+                            + " Remaining: " + source.remaining());
+        }
+        int len = source.getInt();
+        if (len < 0) {
+            throw new IllegalArgumentException("Negative length");
+        } else if (len > source.remaining()) {
+            throw new IOException("Length-prefixed field longer than remaining buffer."
+                    + " Field length: " + len + ", remaining: " + source.remaining());
+        }
+        return getByteBuffer(source, len);
+    }
+
+    private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
+        int len = buf.getInt();
+        if (len < 0) {
+            throw new IOException("Negative length");
+        } else if (len > buf.remaining()) {
+            throw new IOException("Underflow while reading length-prefixed value. Length: " + len
+                    + ", available: " + buf.remaining());
+        }
+        byte[] result = new byte[len];
+        buf.get(result);
+        return result;
+    }
+
+    private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
+        result[offset] = (byte) (value & 0xff);
+        result[offset + 1] = (byte) ((value >>> 8) & 0xff);
+        result[offset + 2] = (byte) ((value >>> 16) & 0xff);
+        result[offset + 3] = (byte) ((value >>> 24) & 0xff);
+    }
+
+    private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
+    private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
+    private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
+
+    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
+
+    private static int findApkSigningBlock(ByteBuffer apkContents, int centralDirOffset)
+            throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkContents);
+
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes payload
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+
+        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
+            throw new SignatureNotFoundException(
+                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
+                            + centralDirOffset);
+        }
+        // Check magic field present
+        if ((apkContents.getLong(centralDirOffset - 16) != APK_SIG_BLOCK_MAGIC_LO)
+                || (apkContents.getLong(centralDirOffset - 8) != APK_SIG_BLOCK_MAGIC_HI)) {
+            throw new SignatureNotFoundException(
+                    "No APK Signing Block before ZIP Central Directory");
+        }
+        // Read and compare size fields
+        long apkSigBlockSizeLong = apkContents.getLong(centralDirOffset - 24);
+        if ((apkSigBlockSizeLong < 24) || (apkSigBlockSizeLong > Integer.MAX_VALUE - 8)) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block size out of range: " + apkSigBlockSizeLong);
+        }
+        int apkSigBlockSizeFromFooter = (int) apkSigBlockSizeLong;
+        int totalSize = apkSigBlockSizeFromFooter + 8;
+        int apkSigBlockOffset = centralDirOffset - totalSize;
+        if (apkSigBlockOffset < 0) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
+        }
+        long apkSigBlockSizeFromHeader = apkContents.getLong(apkSigBlockOffset);
+        if (apkSigBlockSizeFromHeader != apkSigBlockSizeFromFooter) {
+            throw new SignatureNotFoundException(
+                    "APK Signing Block sizes in header and footer do not match: "
+                            + apkSigBlockSizeFromHeader + " vs " + apkSigBlockSizeFromFooter);
+        }
+        return apkSigBlockOffset;
+    }
+
+    private static ByteBuffer findApkSignatureSchemeV2Block(ByteBuffer apkSigningBlock)
+            throws SignatureNotFoundException {
+        checkByteOrderLittleEndian(apkSigningBlock);
+        // FORMAT:
+        // OFFSET       DATA TYPE  DESCRIPTION
+        // * @+0  bytes uint64:    size in bytes (excluding this field)
+        // * @+8  bytes pairs
+        // * @-24 bytes uint64:    size in bytes (same as the one above)
+        // * @-16 bytes uint128:   magic
+        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
+
+        int entryCount = 0;
+        while (pairs.hasRemaining()) {
+            entryCount++;
+            if (pairs.remaining() < 8) {
+                throw new SignatureNotFoundException(
+                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
+            }
+            long lenLong = pairs.getLong();
+            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount
+                                + " size out of range: " + lenLong);
+            }
+            int len = (int) lenLong;
+            int nextEntryPos = pairs.position() + len;
+            if (len > pairs.remaining()) {
+                throw new SignatureNotFoundException(
+                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
+                                + ", available: " + pairs.remaining());
+            }
+            int id = pairs.getInt();
+            if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
+                return getByteBuffer(pairs, len - 4);
+            }
+            pairs.position(nextEntryPos);
+        }
+
+        throw new SignatureNotFoundException(
+                "No APK Signature Scheme v2 block in APK Signing Block");
+    }
+
+    private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    public static class SignatureNotFoundException extends Exception {
+        public SignatureNotFoundException(String message) {
+            super(message);
+        }
+
+        public SignatureNotFoundException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    /**
+     * For legacy reasons we need to return exactly the original encoded certificate bytes, instead
+     * of letting the underlying implementation have a shot at re-encoding the data.
+     */
+    private static class VerbatimX509Certificate extends WrappedX509Certificate {
+        private byte[] encodedVerbatim;
+
+        public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) {
+            super(wrapped);
+            this.encodedVerbatim = encodedVerbatim;
+        }
+
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            return encodedVerbatim;
+        }
+    }
+
+    private static class WrappedX509Certificate extends X509Certificate {
+        private final X509Certificate wrapped;
+
+        public WrappedX509Certificate(X509Certificate wrapped) {
+            this.wrapped = wrapped;
+        }
+
+        @Override
+        public Set<String> getCriticalExtensionOIDs() {
+            return wrapped.getCriticalExtensionOIDs();
+        }
+
+        @Override
+        public byte[] getExtensionValue(String oid) {
+            return wrapped.getExtensionValue(oid);
+        }
+
+        @Override
+        public Set<String> getNonCriticalExtensionOIDs() {
+            return wrapped.getNonCriticalExtensionOIDs();
+        }
+
+        @Override
+        public boolean hasUnsupportedCriticalExtension() {
+            return wrapped.hasUnsupportedCriticalExtension();
+        }
+
+        @Override
+        public void checkValidity()
+                throws CertificateExpiredException, CertificateNotYetValidException {
+            wrapped.checkValidity();
+        }
+
+        @Override
+        public void checkValidity(Date date)
+                throws CertificateExpiredException, CertificateNotYetValidException {
+            wrapped.checkValidity(date);
+        }
+
+        @Override
+        public int getVersion() {
+            return wrapped.getVersion();
+        }
+
+        @Override
+        public BigInteger getSerialNumber() {
+            return wrapped.getSerialNumber();
+        }
+
+        @Override
+        public Principal getIssuerDN() {
+            return wrapped.getIssuerDN();
+        }
+
+        @Override
+        public Principal getSubjectDN() {
+            return wrapped.getSubjectDN();
+        }
+
+        @Override
+        public Date getNotBefore() {
+            return wrapped.getNotBefore();
+        }
+
+        @Override
+        public Date getNotAfter() {
+            return wrapped.getNotAfter();
+        }
+
+        @Override
+        public byte[] getTBSCertificate() throws CertificateEncodingException {
+            return wrapped.getTBSCertificate();
+        }
+
+        @Override
+        public byte[] getSignature() {
+            return wrapped.getSignature();
+        }
+
+        @Override
+        public String getSigAlgName() {
+            return wrapped.getSigAlgName();
+        }
+
+        @Override
+        public String getSigAlgOID() {
+            return wrapped.getSigAlgOID();
+        }
+
+        @Override
+        public byte[] getSigAlgParams() {
+            return wrapped.getSigAlgParams();
+        }
+
+        @Override
+        public boolean[] getIssuerUniqueID() {
+            return wrapped.getIssuerUniqueID();
+        }
+
+        @Override
+        public boolean[] getSubjectUniqueID() {
+            return wrapped.getSubjectUniqueID();
+        }
+
+        @Override
+        public boolean[] getKeyUsage() {
+            return wrapped.getKeyUsage();
+        }
+
+        @Override
+        public int getBasicConstraints() {
+            return wrapped.getBasicConstraints();
+        }
+
+        @Override
+        public byte[] getEncoded() throws CertificateEncodingException {
+            return wrapped.getEncoded();
+        }
+
+        @Override
+        public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+                InvalidKeyException, NoSuchProviderException, SignatureException {
+            wrapped.verify(key);
+        }
+
+        @Override
+        public void verify(PublicKey key, String sigProvider)
+                throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+                NoSuchProviderException, SignatureException {
+            wrapped.verify(key, sigProvider);
+        }
+
+        @Override
+        public String toString() {
+            return wrapped.toString();
+        }
+
+        @Override
+        public PublicKey getPublicKey() {
+            return wrapped.getPublicKey();
+        }
+    }
+}
diff --git a/core/java/android/util/apk/ZipUtils.java b/core/java/android/util/apk/ZipUtils.java
new file mode 100644
index 0000000..a383d5c
--- /dev/null
+++ b/core/java/android/util/apk/ZipUtils.java
@@ -0,0 +1,163 @@
+/*
+ * 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.util.apk;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Assorted ZIP format helpers.
+ *
+ * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances except that the byte
+ * order of these buffers is little-endian.
+ */
+abstract class ZipUtils {
+    private ZipUtils() {}
+
+    private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
+    private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
+    private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
+    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+    private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
+
+    private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
+    private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
+
+    private static final int UINT32_MAX_VALUE = 0xffff;
+
+    /**
+     * Returns the position at which ZIP End of Central Directory record starts in the provided
+     * buffer or {@code -1} if the record is not present.
+     *
+     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+     */
+    public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
+        assertByteOrderLittleEndian(zipContents);
+
+        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
+        // The record can be identified by its 4-byte signature/magic which is located at the very
+        // beginning of the record. A complication is that the record is variable-length because of
+        // the comment field.
+        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
+        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
+        // the candidate record's comment length is such that the remainder of the record takes up
+        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
+        // size of the comment field is 65535 bytes because the field is an unsigned 32-bit number.
+
+        int archiveSize = zipContents.capacity();
+        if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
+            System.out.println("File size smaller than EOCD min size");
+            return -1;
+        }
+        int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT32_MAX_VALUE);
+        int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
+        for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
+                expectedCommentLength++) {
+            int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
+            if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
+                int actualCommentLength =
+                        getUnsignedInt16(
+                                zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
+                if (actualCommentLength == expectedCommentLength) {
+                    return eocdStartPos;
+                }
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
+     * Locator.
+     *
+     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
+     */
+    public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
+            ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
+        assertByteOrderLittleEndian(zipContents);
+
+        // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
+        // Directory Record.
+
+        int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
+        if (locatorPosition < 0) {
+            return false;
+        }
+
+        return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
+    }
+
+    /**
+     * Returns the offset of the start of the ZIP Central Directory in the archive.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        return getUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
+    }
+
+    /**
+     * Sets the offset of the start of the ZIP Central Directory in the archive.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static void setZipEocdCentralDirectoryOffset(
+            ByteBuffer zipEndOfCentralDirectory, long offset) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        setUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
+                offset);
+    }
+
+    /**
+     * Returns the size (in bytes) of the ZIP Central Directory.
+     *
+     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
+     */
+    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
+        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
+        return getUnsignedInt32(
+                zipEndOfCentralDirectory,
+                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
+    }
+
+    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
+        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
+            throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
+        }
+    }
+
+    private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
+        return buffer.getShort(offset) & 0xffff;
+    }
+
+    private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
+        return buffer.getInt(offset) & 0xffffffffL;
+    }
+
+    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
+        if ((value < 0) || (value > 0xffffffffL)) {
+            throw new IllegalArgumentException("uint32 value of out range: " + value);
+        }
+        buffer.putInt(buffer.position() + offset, (int) value);
+    }
+}
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
index fd57806..302a08d 100644
--- a/core/java/android/util/jar/StrictJarFile.java
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -18,7 +18,6 @@
 package android.util.jar;
 
 import dalvik.system.CloseGuard;
-import java.io.ByteArrayInputStream;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -30,9 +29,7 @@
 import java.util.zip.Inflater;
 import java.util.zip.InflaterInputStream;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 import java.util.jar.JarFile;
-import java.util.jar.Manifest;
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
@@ -59,7 +56,13 @@
     private final CloseGuard guard = CloseGuard.get();
     private boolean closed;
 
-    public StrictJarFile(String fileName) throws IOException, SecurityException {
+    public StrictJarFile(String fileName)
+            throws IOException, SecurityException {
+        this(fileName, true);
+    }
+
+    public StrictJarFile(String fileName, boolean verify)
+            throws IOException, SecurityException {
         this.nativeHandle = nativeOpenJarFile(fileName);
         this.raf = new RandomAccessFile(fileName, "r");
 
@@ -67,17 +70,23 @@
             // Read the MANIFEST and signature files up front and try to
             // parse them. We never want to accept a JAR File with broken signatures
             // or manifests, so it's best to throw as early as possible.
-            HashMap<String, byte[]> metaEntries = getMetaEntries();
-            this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
-            this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
-            Set<String> files = manifest.getEntries().keySet();
-            for (String file : files) {
-                if (findEntry(file) == null) {
-                    throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+            if (verify) {
+                HashMap<String, byte[]> metaEntries = getMetaEntries();
+                this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+                this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+                Set<String> files = manifest.getEntries().keySet();
+                for (String file : files) {
+                    if (findEntry(file) == null) {
+                        throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+                    }
                 }
-            }
 
-            isSigned = verifier.readCertificates() && verifier.isSignedJar();
+                isSigned = verifier.readCertificates() && verifier.isSignedJar();
+            } else {
+                isSigned = false;
+                this.manifest = null;
+                this.verifier = null;
+            }
         } catch (IOException | SecurityException e) {
             nativeClose(this.nativeHandle);
             IoUtils.closeQuietly(this.raf);
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index ca2aec1..0546a5f 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -32,8 +32,12 @@
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
+import android.util.ArraySet;
+import android.util.apk.ApkSignatureSchemeV2Verifier;
 import libcore.io.Base64;
 import sun.security.jca.Providers;
 import sun.security.pkcs.PKCS7;
@@ -353,6 +357,43 @@
             return;
         }
 
+        // Check whether APK Signature Scheme v2 signature was stripped.
+        String apkSignatureSchemeIdList =
+                attributes.getValue(
+                        ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME);
+        if (apkSignatureSchemeIdList != null) {
+            // This field contains a comma-separated list of APK signature scheme IDs which were
+            // used to sign this APK. If an ID is known to us, it means signatures of that scheme
+            // were stripped from the APK because otherwise we wouldn't have fallen back to
+            // verifying the APK using the JAR signature scheme.
+            boolean v2SignatureGenerated = false;
+            StringTokenizer tokenizer = new StringTokenizer(apkSignatureSchemeIdList, ",");
+            while (tokenizer.hasMoreTokens()) {
+                String idText = tokenizer.nextToken().trim();
+                if (idText.isEmpty()) {
+                    continue;
+                }
+                int id;
+                try {
+                    id = Integer.parseInt(idText);
+                } catch (Exception ignored) {
+                    continue;
+                }
+                if (id == ApkSignatureSchemeV2Verifier.SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID) {
+                    // This APK was supposed to be signed with APK Signature Scheme v2 but no such
+                    // signature was found.
+                    v2SignatureGenerated = true;
+                    break;
+                }
+            }
+
+            if (v2SignatureGenerated) {
+                throw new SecurityException(signatureFile + " indicates " + jarName + " is signed"
+                        + " using APK Signature Scheme v2, but no such signature was found."
+                        + " Signature stripped?");
+            }
+        }
+
         // Do we actually have any signatures to look at?
         if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
             return;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index b3cd8c11..bea36c0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -81,6 +81,8 @@
      * so complex relayout of the window should not happen based on them.
      * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
      * treat as real display. Example of such area is a chin in some models of wearable devices.
+     * @param outBackdropFrame Rect which is used draw the resizing background during a resize
+     * operation.
      * @param outConfiguration New configuration of window, if it is now
      * becoming visible and the global configuration has changed since it
      * was last displayed.
@@ -93,7 +95,8 @@
             int requestedWidth, int requestedHeight, int viewVisibility,
             int flags, out Rect outFrame, out Rect outOverscanInsets,
             out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
-            out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
+            out Rect outOutsets, out Rect outBackdropFrame, out Configuration outConfig,
+            out Surface outSurface);
 
     /**
      *  Position a window relative to it's parent (attached) window without triggering
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f4fa980..0981e69 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -107,6 +107,7 @@
     final Rect mContentInsets = new Rect();
     final Rect mStableInsets = new Rect();
     final Rect mOutsets = new Rect();
+    final Rect mBackdropFrame = new Rect();
     final Configuration mConfiguration = new Configuration();
 
     static final int KEEP_SCREEN_ON_MSG = 1;
@@ -529,8 +530,8 @@
                             visible ? VISIBLE : GONE,
                             WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                             mWinFrame, mOverscanInsets, mContentInsets,
-                            mVisibleInsets, mStableInsets, mOutsets, mConfiguration,
-                            mNewSurface);
+                            mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
+                            mConfiguration, mNewSurface);
                     if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                         reportDrawNeeded = true;
                     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0316506..0b8018b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17259,8 +17259,10 @@
      */
     @CallSuper
     protected boolean verifyDrawable(Drawable who) {
-        return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who)
-                || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
+        // Avoid verifying the scroll bar drawable so that we don't end up in
+        // an invalidation loop. This effectively prevents the scroll bar
+        // drawable from triggering invalidations and scheduling runnables.
+        return who == mBackground || (mForegroundInfo != null && mForegroundInfo.mDrawable == who);
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a14f0dc..9c19bf1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -17,6 +17,10 @@
 package android.view;
 
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
 
 import android.Manifest;
 import android.animation.LayoutTransition;
@@ -141,6 +145,9 @@
      */
     static final int MAX_TRACKBALL_DELAY = 250;
 
+    private static final int RESIZE_MODE_FREEFORM = 0;
+    private static final int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
     static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
 
     static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
@@ -225,8 +232,10 @@
     boolean mIsAnimating;
 
     private boolean mDragResizing;
+    private int mResizeMode;
     private int mCanvasOffsetX;
     private int mCanvasOffsetY;
+    private boolean mActivityRelaunched;
 
     CompatibilityInfo.Translator mTranslator;
 
@@ -1337,6 +1346,17 @@
         host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
     }
 
+    private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
+        return lp.type == TYPE_STATUS_BAR_PANEL
+                || lp.type == TYPE_INPUT_METHOD
+                || lp.type == TYPE_VOLUME_OVERLAY;
+    }
+
+    private int dipToPx(int dip) {
+        final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+        return (int) (displayMetrics.density * dip + 0.5f);
+    }
+
     private void performTraversals() {
         // cache mView since it is used so much below...
         final View host = mView;
@@ -1391,18 +1411,16 @@
             mFullRedrawNeeded = true;
             mLayoutRequested = true;
 
-            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
-                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+            if (shouldUseDisplaySize(lp)) {
                 // NOTE -- system code, won't try to do compat mode.
                 Point size = new Point();
                 mDisplay.getRealSize(size);
                 desiredWindowWidth = size.x;
                 desiredWindowHeight = size.y;
             } else {
-                DisplayMetrics packageMetrics =
-                    mView.getContext().getResources().getDisplayMetrics();
-                desiredWindowWidth = packageMetrics.widthPixels;
-                desiredWindowHeight = packageMetrics.heightPixels;
+                Configuration config = mContext.getResources().getConfiguration();
+                desiredWindowWidth = dipToPx(config.screenWidthDp);
+                desiredWindowHeight = dipToPx(config.screenHeightDp);
             }
 
             // We used to use the following condition to choose 32 bits drawing caches:
@@ -1486,17 +1504,21 @@
                 if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                     insetsChanged = true;
                 }
-                if ((lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
-                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT)
-                        && (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
-                                || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
-                                || lp.type == WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)) {
+                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                     windowSizeMayChange = true;
-                    // NOTE -- system code, won't try to do compat mode.
-                    Point size = new Point();
-                    mDisplay.getRealSize(size);
-                    desiredWindowWidth = size.x;
-                    desiredWindowHeight = size.y;
+
+                    if (shouldUseDisplaySize(lp)) {
+                        // NOTE -- system code, won't try to do compat mode.
+                        Point size = new Point();
+                        mDisplay.getRealSize(size);
+                        desiredWindowWidth = size.x;
+                        desiredWindowHeight = size.y;
+                    } else {
+                        Configuration config = res.getConfiguration();
+                        desiredWindowWidth = dipToPx(config.screenWidthDp);
+                        desiredWindowHeight = dipToPx(config.screenHeightDp);
+                    }
                 }
             }
 
@@ -1576,12 +1598,17 @@
                         frame.width() < desiredWindowWidth && frame.width() != mWidth)
                 || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                         frame.height() < desiredWindowHeight && frame.height() != mHeight));
-        windowShouldResize |= mDragResizing;
+        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
 
         // If the backdrop frame doesn't equal to a frame, we are starting a resize operation, so
         // force it to be resized.
         windowShouldResize |= !mPendingBackDropFrame.equals(mWinFrame);
 
+        // If the activity was just relaunched, it might have unfrozen the task bounds (while
+        // relaunching), so we need to force a call into window manager to pick up the latest
+        // bounds.
+        windowShouldResize |= mActivityRelaunched;
+
         // Determine whether to compute insets.
         // If there are no inset listeners remaining then we may still need to compute
         // insets in case the old insets were non-empty and must be reset.
@@ -1770,11 +1797,17 @@
                     }
                 }
 
-                final boolean dragResizing = (relayoutResult
-                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0;
+                final boolean freeformResizing = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+                final boolean dockedResizing = (relayoutResult
+                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+                final boolean dragResizing = freeformResizing || dockedResizing;
                 if (mDragResizing != dragResizing) {
                     if (dragResizing) {
                         startDragResizing(mPendingBackDropFrame);
+                        mResizeMode = freeformResizing
+                                ? RESIZE_MODE_FREEFORM
+                                : RESIZE_MODE_DOCKED_DIVIDER;
                     } else {
                         // We shouldn't come here, but if we come we should end the resize.
                         endDragResizing();
@@ -1921,29 +1954,7 @@
             // in the attach info. We translate only the window frame since on window move
             // the window manager tells us only for the new frame but the insets are the
             // same and we do not want to translate them more than once.
-
-            // TODO: Well, we are checking whether the frame has changed similarly
-            // to how this is done for the insets. This is however incorrect since
-            // the insets and the frame are translated. For example, the old frame
-            // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
-            // reported frame is (2, 2 - 2, 2) which implies no change but this is not
-            // true since we are comparing a not translated value to a translated one.
-            // This scenario is rare but we may want to fix that.
-
-            final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left
-                    || mAttachInfo.mWindowTop != frame.top);
-            if (windowMoved) {
-                if (mTranslator != null) {
-                    mTranslator.translateRectInScreenToAppWinFrame(frame);
-                }
-                mAttachInfo.mWindowLeft = frame.left;
-                mAttachInfo.mWindowTop = frame.top;
-
-                // Update the light position for the new window offsets.
-                if (mAttachInfo.mHardwareRenderer != null) {
-                    mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
-                }
-            }
+            maybeHandleWindowMove(frame);
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -2058,6 +2069,7 @@
         mFirst = false;
         mWillDrawSoon = false;
         mNewSurfaceNeeded = false;
+        mActivityRelaunched = false;
         mViewVisibility = viewVisibility;
         mHadWindowFocus = hasWindowFocus;
 
@@ -2107,6 +2119,31 @@
         mIsInTraversal = false;
     }
 
+    private void maybeHandleWindowMove(Rect frame) {
+
+        // TODO: Well, we are checking whether the frame has changed similarly
+        // to how this is done for the insets. This is however incorrect since
+        // the insets and the frame are translated. For example, the old frame
+        // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
+        // reported frame is (2, 2 - 2, 2) which implies no change but this is not
+        // true since we are comparing a not translated value to a translated one.
+        // This scenario is rare but we may want to fix that.
+
+        final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
+                || mAttachInfo.mWindowTop != frame.top;
+        if (windowMoved) {
+            if (mTranslator != null) {
+                mTranslator.translateRectInScreenToAppWinFrame(frame);
+            }
+            mAttachInfo.mWindowLeft = frame.left;
+            mAttachInfo.mWindowTop = frame.top;
+
+            // Update the light position for the new window offsets.
+            if (mAttachInfo.mHardwareRenderer != null) {
+                mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
+            }
+        }
+    }
     private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
         Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
         try {
@@ -3368,10 +3405,19 @@
 
                     mPendingBackDropFrame.set(mWinFrame);
 
-                    if (mView != null) {
-                        forceLayout(mView);
+                    // Suppress layouts during resizing - a correct layout will happen when resizing
+                    // is done, and this just increases system load.
+                    boolean isDockedDivider = mWindowAttributes.type == TYPE_DOCK_DIVIDER;
+                    boolean suppress = (mDragResizing && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER)
+                            || isDockedDivider;
+                    if (!suppress) {
+                        if (mView != null) {
+                            forceLayout(mView);
+                        }
+                        requestLayout();
+                    } else {
+                        maybeHandleWindowMove(mWinFrame);
                     }
-                    requestLayout();
                 }
                 break;
             case MSG_WINDOW_FOCUS_CHANGED: {
@@ -5484,7 +5530,8 @@
                 (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                 viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                 mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
-                mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);
+                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,
+                mSurface);
         //Log.d(mTag, "<<<<<< BACK FROM relayout");
         if (restore) {
             params.restore();
@@ -7010,6 +7057,15 @@
     }
 
     /**
+     * Tells this instance that its corresponding activity has just relaunched. In this case, we
+     * need to force a relayout of the window to make sure we get the correct bounds from window
+     * manager.
+     */
+    public void reportActivityRelaunched() {
+        mActivityRelaunched = true;
+    }
+
+    /**
      * Class for managing the accessibility interaction connection
      * based on the global accessibility state.
      */
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index d89369b..dfe0cc7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2120,4 +2120,10 @@
      * @hide
      */
     public abstract void onMultiWindowModeChanged();
+
+    /**
+     * Called when the activity just relaunched.
+     * @hide
+     */
+    public abstract void reportActivityRelaunched();
 }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 8c68e92..1530b47 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,6 +30,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
+
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.FileDescriptor;
@@ -70,16 +71,23 @@
     public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
 
     /**
+     * The window is being resized by dragging on the docked divider. The client should render
+     * at (0, 0) and extend its background to the background frame passed into
+     * {@link IWindow#resized}.
+     */
+    public static final int RELAYOUT_RES_DRAG_RESIZING_DOCKED = 0x8;
+
+    /**
      * The window is being resized by dragging one of the window corners,
      * in this case the surface would be fullscreen-sized. The client should
      * render to the actual frame location (instead of (0,curScrollY)).
      */
-    public static final int RELAYOUT_RES_DRAG_RESIZING = 0x8;
+    public static final int RELAYOUT_RES_DRAG_RESIZING_FREEFORM = 0x10;
 
     /**
      * The window manager has changed the size of the surface from the last call.
      */
-    public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x10;
+    public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x20;
 
     /**
      * Flag for relayout: the client will be later giving
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index a78b56a..4a1142f 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1333,9 +1333,9 @@
      * Calculates the stable insets without running a layout.
      *
      * @param displayRotation the current display rotation
-     * @param outInsets the insets to return
      * @param displayWidth the current display width
      * @param displayHeight the current display height
+     * @param outInsets the insets to return
      */
     public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
             Rect outInsets);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index b6570cc..9e79057 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -58,5 +58,5 @@
     void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
             boolean touchExplorationEnabled);
 
-    IBinder getWindowToken(int windowId);
+    IBinder getWindowToken(int windowId, int userId);
 }
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 6e5e591..a10f792 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -195,7 +195,6 @@
     public boolean commitText(CharSequence text, int newCursorPosition) {
         if (DEBUG) Log.v(TAG, "commitText " + text);
         replaceText(text, newCursorPosition, false);
-        mIMM.notifyUserAction();
         sendCurrentText();
         return true;
     }
@@ -230,7 +229,7 @@
             b = tmp;
         }
 
-        // ignore the composing text.
+        // Ignore the composing text.
         int ca = getComposingSpanStart(content);
         int cb = getComposingSpanEnd(content);
         if (cb < ca) {
@@ -266,6 +265,167 @@
         return true;
     }
 
+    private static int INVALID_INDEX = -1;
+    private static int findIndexBackward(final CharSequence cs, final int from,
+            final int numCodePoints) {
+        int currentIndex = from;
+        boolean waitingHighSurrogate = false;
+        final int N = cs.length();
+        if (currentIndex < 0 || N < currentIndex) {
+            return INVALID_INDEX;  // The starting point is out of range.
+        }
+        if (numCodePoints < 0) {
+            return INVALID_INDEX;  // Basically this should not happen.
+        }
+        int remainingCodePoints = numCodePoints;
+        while (true) {
+            if (remainingCodePoints == 0) {
+                return currentIndex;  // Reached to the requested length in code points.
+            }
+
+            --currentIndex;
+            if (currentIndex < 0) {
+                if (waitingHighSurrogate) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                return 0;  // Reached to the beginning of the text w/o any invalid surrogate pair.
+            }
+            final char c = cs.charAt(currentIndex);
+            if (waitingHighSurrogate) {
+                if (!java.lang.Character.isHighSurrogate(c)) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                waitingHighSurrogate = false;
+                --remainingCodePoints;
+                continue;
+            }
+            if (!java.lang.Character.isSurrogate(c)) {
+                --remainingCodePoints;
+                continue;
+            }
+            if (java.lang.Character.isHighSurrogate(c)) {
+                return INVALID_INDEX;  // A invalid surrogate pair is found.
+            }
+            waitingHighSurrogate = true;
+        }
+    }
+
+    private static int findIndexForward(final CharSequence cs, final int from,
+            final int numCodePoints) {
+        int currentIndex = from;
+        boolean waitingLowSurrogate = false;
+        final int N = cs.length();
+        if (currentIndex < 0 || N < currentIndex) {
+            return INVALID_INDEX;  // The starting point is out of range.
+        }
+        if (numCodePoints < 0) {
+            return INVALID_INDEX;  // Basically this should not happen.
+        }
+        int remainingCodePoints = numCodePoints;
+
+        while (true) {
+            if (remainingCodePoints == 0) {
+                return currentIndex;  // Reached to the requested length in code points.
+            }
+
+            if (currentIndex >= N) {
+                if (waitingLowSurrogate) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                return N;  // Reached to the end of the text w/o any invalid surrogate pair.
+            }
+            final char c = cs.charAt(currentIndex);
+            if (waitingLowSurrogate) {
+                if (!java.lang.Character.isLowSurrogate(c)) {
+                    return INVALID_INDEX;  // An invalid surrogate pair is found.
+                }
+                --remainingCodePoints;
+                waitingLowSurrogate = false;
+                ++currentIndex;
+                continue;
+            }
+            if (!java.lang.Character.isSurrogate(c)) {
+                --remainingCodePoints;
+                ++currentIndex;
+                continue;
+            }
+            if (java.lang.Character.isLowSurrogate(c)) {
+                return INVALID_INDEX;  // A invalid surrogate pair is found.
+            }
+            waitingLowSurrogate = true;
+            ++currentIndex;
+        }
+    }
+
+    /**
+     * The default implementation performs the deletion around the current selection position of the
+     * editable text.
+     * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     */
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+                + " / " + afterLength);
+        final Editable content = getEditable();
+        if (content == null) return false;
+
+        beginBatchEdit();
+
+        int a = Selection.getSelectionStart(content);
+        int b = Selection.getSelectionEnd(content);
+
+        if (a > b) {
+            int tmp = a;
+            a = b;
+            b = tmp;
+        }
+
+        // Ignore the composing text.
+        int ca = getComposingSpanStart(content);
+        int cb = getComposingSpanEnd(content);
+        if (cb < ca) {
+            int tmp = ca;
+            ca = cb;
+            cb = tmp;
+        }
+        if (ca != -1 && cb != -1) {
+            if (ca < a) a = ca;
+            if (cb > b) b = cb;
+        }
+
+        if (a >= 0 && b >= 0) {
+            final int start = findIndexBackward(content, a, Math.max(beforeLength, 0));
+            if (start != INVALID_INDEX) {
+                final int end = findIndexForward(content, b, Math.max(afterLength, 0));
+                if (end != INVALID_INDEX) {
+                    final int numDeleteBefore = a - start;
+                    if (numDeleteBefore > 0) {
+                        content.delete(start, a);
+                    }
+                    final int numDeleteAfter = end - b;
+                    if (numDeleteAfter > 0) {
+                        content.delete(b - numDeleteBefore, end - numDeleteBefore);
+                    }
+                }
+            }
+            // NOTE: You may think we should return false here if start and/or end is INVALID_INDEX,
+            // but the truth is that IInputConnectionWrapper running in the middle of IPC calls
+            // always returns true to the IME without waiting for the completion of this method as
+            // IInputConnectionWrapper#isAtive() returns true.  This is actually why some methods
+            // including this method look like asynchronous calls from the IME.
+        }
+
+        endBatchEdit();
+
+        return true;
+    }
+
     /**
      * The default implementation removes the composing state from the
      * current editable text.  In addition, only if dummy mode, a key event is
@@ -450,7 +610,6 @@
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         if (DEBUG) Log.v(TAG, "setComposingText " + text);
         replaceText(text, newCursorPosition, true);
-        mIMM.notifyUserAction();
         return true;
     }
 
@@ -523,29 +682,17 @@
      * attached to the input connection's view.
      */
     public boolean sendKeyEvent(KeyEvent event) {
-        synchronized (mIMM.mH) {
-            ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;
-            if (viewRootImpl == null) {
-                if (mIMM.mServedView != null) {
-                    viewRootImpl = mIMM.mServedView.getViewRootImpl();
-                }
-            }
-            if (viewRootImpl != null) {
-                viewRootImpl.dispatchKeyFromIme(event);
-            }
-        }
-        mIMM.notifyUserAction();
+        mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
         return false;
     }
-    
+
     /**
      * Updates InputMethodManager with the current fullscreen mode.
      */
     public boolean reportFullscreenMode(boolean enabled) {
-        mIMM.setFullscreenMode(enabled);
         return true;
     }
-    
+
     private void sendCurrentText() {
         if (!mDummyMode) {
             return;
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index be7bc14..eb773e2 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -348,6 +348,33 @@
     public boolean deleteSurroundingText(int beforeLength, int afterLength);
 
     /**
+     * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
+     *
+     * <ul>
+     *     <li>The lengths are supplied in code points, not in Java chars or in glyphs.</>
+     *     <li>This method does nothing if there are one or more invalid surrogate pairs in the
+     *     requested range.</li>
+     * </ul>
+     *
+     * <p><strong>Editor authors:</strong> In addition to the requirement in
+     * {@link #deleteSurroundingText(int, int)}, make sure to do nothing when one ore more invalid
+     * surrogate pairs are found in the requested range.</p>
+     *
+     * @see #deleteSurroundingText(int, int)
+     *
+     * @param beforeLength The number of characters before the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the beginning of the
+     *        text and the cursor, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @param afterLength The number of characters after the cursor to be deleted, in code points.
+     *        If this is greater than the number of existing characters between the cursor and
+     *        the end of the text, then this method does not fail but deletes all the characters in
+     *        that range.
+     * @return true on success, false if the input connection is no longer valid.
+     */
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+
+    /**
      * Replace the currently composing text with the given text, and
      * set the new cursor position. Any composing text set previously
      * will be removed automatically.
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 231aa07..e5ae422 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -62,6 +62,10 @@
         return mTarget.getExtractedText(request, flags);
     }
 
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+    }
+
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         return mTarget.deleteSurroundingText(beforeLength, afterLength);
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5e07347..9647345 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -25,6 +25,8 @@
 import com.android.internal.view.InputBindResult;
 import com.android.internal.view.InputMethodClient;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.graphics.Rect;
@@ -527,7 +529,7 @@
             }
         }
     }
-    
+
     private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
         private final InputMethodManager mParentInputMethodManager;
         private boolean mActive;
@@ -549,13 +551,23 @@
         }
 
         @Override
+        protected void onUserAction() {
+            mParentInputMethodManager.notifyUserAction();
+        }
+
+        @Override
+        protected void onReportFullscreenMode(boolean enabled) {
+            mParentInputMethodManager.setFullscreenMode(enabled);
+        }
+
+        @Override
         public String toString() {
             return "ControlledInputConnectionWrapper{mActive=" + mActive
                     + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
                     + "}";
         }
     }
-    
+
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -1813,6 +1825,34 @@
         return DISPATCH_NOT_HANDLED;
     }
 
+    /**
+     * Provides the default implementation of {@link InputConnection#sendKeyEvent(KeyEvent)}, which
+     * is expected to dispatch an keyboard event sent from the IME to an appropriate event target
+     * depending on the given {@link View} and the current focus state.
+     *
+     * <p>CAUTION: This method is provided only for the situation where
+     * {@link InputConnection#sendKeyEvent(KeyEvent)} needs to be implemented without relying on
+     * {@link BaseInputConnection}. Do not use this API for anything else.</p>
+     *
+     * @param targetView the default target view. If {@code null} is specified, then this method
+     * tries to find a good event target based on the current focus state.
+     * @param event the key event to be dispatched.
+     */
+    public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
+            @NonNull KeyEvent event) {
+        synchronized (mH) {
+            ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
+            if (viewRootImpl == null) {
+                if (mServedView != null) {
+                    viewRootImpl = mServedView.getViewRootImpl();
+                }
+            }
+            if (viewRootImpl != null) {
+                viewRootImpl.dispatchKeyFromIme(event);
+            }
+        }
+    }
+
     // Must be called on the main looper
     void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
         final boolean handled;
diff --git a/core/java/android/webkit/TokenBindingService.java b/core/java/android/webkit/TokenBindingService.java
new file mode 100644
index 0000000..a6d7b4a
--- /dev/null
+++ b/core/java/android/webkit/TokenBindingService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+
+import java.security.KeyPair;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Enables the token binding procotol, and provides access to the keys. See
+ * https://tools.ietf.org/html/draft-ietf-tokbind-protocol-03
+ *
+ * All methods are required to be called on the UI thread where WebView is
+ * attached to the View hierarchy.
+ * @hide
+ */
+public abstract class TokenBindingService {
+
+    public static final String KEY_ALGORITHM_RSA2048_PKCS_1_5 = "RSA2048_PKCS_1.5";
+    public static final String KEY_ALGORITHM_RSA2048_PSS = "RSA2048PSS";
+    public static final String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
+
+    /**
+     * Returns the default TokenBinding service instance. At present there is
+     * only one token binding service instance for all WebView instances,
+     * however this restriction may be relaxed in the future.
+     *
+     * @return The default TokenBindingService instance.
+     */
+    public static TokenBindingService getInstance() {
+        return WebViewFactory.getProvider().getTokenBindingService();
+    }
+
+    /**
+     * Enables the token binding protocol. The token binding protocol
+     * has to be enabled before creating any WebViews.
+     *
+     * @throws IllegalStateException if a WebView was already created.
+     */
+    public abstract void enableTokenBinding();
+
+    /**
+     * Retrieves the key pair for a given origin from the internal
+     * TokenBinding key store asynchronously.
+     * Will create a key pair if one does not exist.
+     *
+     * @param origin The origin for the server.
+     * @param algorithm The algorithm for generating the token binding key.
+     * @param callback The callback that will be called when key is available.
+     *        Cannot be null.
+     */
+    public abstract void getKey(Uri origin,
+                                String algorithm,
+                                ValueCallback<KeyPair> callback);
+    /**
+     * Deletes specified key (for use when associated cookie is cleared).
+     *
+     * @param origin The origin of the server.
+     * @param callback The callback that will be called when key is deleted. The
+     *        callback parameter (Boolean) will indicate if operation is
+     *        successful or if failed. The callback can be null.
+     */
+    public abstract void deleteKey(Uri origin,
+                                   ValueCallback<Boolean> callback);
+
+     /**
+      * Deletes all the keys (for use when cookies are cleared).
+      *
+      * @param callback The callback that will be called when keys are deleted.
+      *        The callback parameter (Boolean) will indicate if operation is
+      *        successful or if failed. The callback can be null.
+      */
+    public abstract void deleteAllKeys(ValueCallback<Boolean> callback);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 9105394..02c911f 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -103,6 +103,15 @@
     CookieManager getCookieManager();
 
     /**
+     * Gets the TokenBindingService instance for this WebView implementation. The
+     * implementation must return the same instance on subsequent calls.
+     *
+     * @return the TokenBindingService instance
+     * @hide
+     */
+    TokenBindingService getTokenBindingService();
+
+    /**
      * 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 e1ce9fe..6241a4c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5803,6 +5803,11 @@
         }
 
         @Override
+        public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+            return getTarget().deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+        }
+
+        @Override
         public boolean setComposingText(CharSequence text, int newCursorPosition) {
             return getTarget().setComposingText(text, newCursorPosition);
         }
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 2aaa356..cde7604 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -16,8 +16,11 @@
 
 package android.widget;
 
+import android.annotation.AttrRes;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.StyleRes;
 import android.annotation.Widget;
 import android.content.Context;
@@ -37,9 +40,13 @@
 import java.util.TimeZone;
 
 /**
- * This class is a calendar widget for displaying and selecting dates. The range
- * of dates supported by this calendar is configurable. A user can select a date
- * by taping on it and can scroll and fling the calendar to a desired date.
+ * This class is a calendar widget for displaying and selecting dates. The
+ * range of dates supported by this calendar is configurable.
+ * <p>
+ * The exact appearance and interaction model of this widget may vary between
+ * OS versions and themes (e.g. Holo versus Material), but in general a user
+ * can select a date by tapping on it and can scroll or fling the calendar to a
+ * desired date.
  *
  * @attr ref android.R.styleable#CalendarView_showWeekNumber
  * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
@@ -77,22 +84,24 @@
          * @param month The month that was set [0-11].
          * @param dayOfMonth The day of the month that was set.
          */
-        public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth);
+        void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth);
     }
 
-    public CalendarView(Context context) {
+    public CalendarView(@NonNull Context context) {
         this(context, null);
     }
 
-    public CalendarView(Context context, AttributeSet attrs) {
+    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, R.attr.calendarViewStyle);
     }
 
-    public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
         final TypedArray a = context.obtainStyledAttributes(
@@ -322,7 +331,7 @@
      *
      * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
      */
-    public void setWeekDayTextAppearance(int resourceId) {
+    public void setWeekDayTextAppearance(@StyleRes int resourceId) {
         mDelegate.setWeekDayTextAppearance(resourceId);
     }
 
@@ -333,7 +342,7 @@
      *
      * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
      */
-    public int getWeekDayTextAppearance() {
+    public @StyleRes int getWeekDayTextAppearance() {
         return mDelegate.getWeekDayTextAppearance();
     }
 
@@ -344,7 +353,7 @@
      *
      * @attr ref android.R.styleable#CalendarView_dateTextAppearance
      */
-    public void setDateTextAppearance(int resourceId) {
+    public void setDateTextAppearance(@StyleRes int resourceId) {
         mDelegate.setDateTextAppearance(resourceId);
     }
 
@@ -355,7 +364,7 @@
      *
      * @attr ref android.R.styleable#CalendarView_dateTextAppearance
      */
-    public int getDateTextAppearance() {
+    public @StyleRes int getDateTextAppearance() {
         return mDelegate.getDateTextAppearance();
     }
 
@@ -552,36 +561,29 @@
         int getShownWeekCount();
 
         void setSelectedWeekBackgroundColor(@ColorInt int color);
-        @ColorInt
-        int getSelectedWeekBackgroundColor();
+        @ColorInt int getSelectedWeekBackgroundColor();
 
         void setFocusedMonthDateColor(@ColorInt int color);
-        @ColorInt
-        int getFocusedMonthDateColor();
+        @ColorInt int getFocusedMonthDateColor();
 
         void setUnfocusedMonthDateColor(@ColorInt int color);
-        @ColorInt
-        int getUnfocusedMonthDateColor();
+        @ColorInt int getUnfocusedMonthDateColor();
 
         void setWeekNumberColor(@ColorInt int color);
-        @ColorInt
-        int getWeekNumberColor();
+        @ColorInt int getWeekNumberColor();
 
         void setWeekSeparatorLineColor(@ColorInt int color);
-        @ColorInt
-        int getWeekSeparatorLineColor();
+        @ColorInt int getWeekSeparatorLineColor();
 
         void setSelectedDateVerticalBar(@DrawableRes int resourceId);
         void setSelectedDateVerticalBar(Drawable drawable);
         Drawable getSelectedDateVerticalBar();
 
         void setWeekDayTextAppearance(@StyleRes int resourceId);
-        @StyleRes
-        int getWeekDayTextAppearance();
+        @StyleRes int getWeekDayTextAppearance();
 
         void setDateTextAppearance(@StyleRes int resourceId);
-        @StyleRes
-        int getDateTextAppearance();
+        @StyleRes int getDateTextAppearance();
 
         void setMinDate(long minDate);
         long getMinDate();
diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java
index 2fb2101..02f7e7a 100644
--- a/core/java/android/widget/DropDownListView.java
+++ b/core/java/android/widget/DropDownListView.java
@@ -19,18 +19,10 @@
 
 import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.IntProperty;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-import android.widget.ListView;
-
 
 /**
  * Wrapper class for a ListView. This wrapper can hijack the focus to
@@ -41,26 +33,6 @@
  * @hide
  */
 public class DropDownListView extends ListView {
-    /** Duration in milliseconds of the drag-to-open click animation. */
-    private static final long CLICK_ANIM_DURATION = 150;
-
-    /** Target alpha value for drag-to-open click animation. */
-    private static final int CLICK_ANIM_ALPHA = 0x80;
-
-    /** Wrapper around Drawable's <code>alpha</code> property. */
-    private static final IntProperty<Drawable> DRAWABLE_ALPHA =
-            new IntProperty<Drawable>("alpha") {
-                @Override
-                public void setValue(Drawable object, int value) {
-                    object.setAlpha(value);
-                }
-
-                @Override
-                public Integer get(Drawable object) {
-                    return object.getAlpha();
-                }
-            };
-
     /*
      * WARNING: This is a workaround for a touch mode issue.
      *
@@ -99,9 +71,6 @@
     /** Whether to force drawing of the pressed state selector. */
     private boolean mDrawsInPressedState;
 
-    /** Current drag-to-open click animation, if any. */
-    private Animator mClickAnimation;
-
     /** Helper for drag-to-open auto scrolling. */
     private AbsListViewAutoScroller mScrollHelper;
 
@@ -110,7 +79,7 @@
      *
      * @param context this view's context
      */
-    public DropDownListView(Context context, boolean hijackFocus) {
+    public DropDownListView(@NonNull Context context, boolean hijackFocus) {
         this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle);
     }
 
@@ -119,7 +88,7 @@
      *
      * @param context this view's context
      */
-    public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) {
+    public DropDownListView(@NonNull Context context, boolean hijackFocus, int defStyleAttr) {
         super(context, null, defStyleAttr);
         mHijackFocus = hijackFocus;
         // TODO: Add an API to control this
@@ -132,7 +101,7 @@
     }
 
     @Override
-    public boolean onHoverEvent(MotionEvent ev) {
+    public boolean onHoverEvent(@NonNull MotionEvent ev) {
         // Allow the super class to handle hover state management first.
         final boolean handled = super.onHoverEvent(ev);
 
@@ -169,7 +138,7 @@
      * @param activePointerId id of the pointer that activated forwarding
      * @return whether the event was handled
      */
-    public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+    public boolean onForwardedEvent(@NonNull MotionEvent event, int activePointerId) {
         boolean handledEvent = true;
         boolean clearPressedItem = false;
 
@@ -201,7 +170,8 @@
                 handledEvent = true;
 
                 if (actionMasked == MotionEvent.ACTION_UP) {
-                    clickPressedItem(child, position);
+                    final long id = getItemIdAtPosition(position);
+                    performItemClick(child, position, id);
                 }
                 break;
         }
@@ -234,30 +204,6 @@
         this.mListSelectionHidden = listSelectionHidden;
     }
 
-    /**
-     * Starts an alpha animation on the selector. When the animation ends,
-     * the list performs a click on the item.
-     */
-    private void clickPressedItem(final View child, final int position) {
-        final long id = getItemIdAtPosition(position);
-        final Animator anim = ObjectAnimator.ofInt(
-                mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
-        anim.setDuration(CLICK_ANIM_DURATION);
-        anim.setInterpolator(new AccelerateDecelerateInterpolator());
-        anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-            public void onAnimationEnd(Animator animation) {
-                performItemClick(child, position, id);
-            }
-        });
-        anim.start();
-
-        if (mClickAnimation != null) {
-            mClickAnimation.cancel();
-        }
-        mClickAnimation = anim;
-    }
-
     private void clearPressedItem() {
         mDrawsInPressedState = false;
         setPressed(false);
@@ -267,14 +213,9 @@
         if (motionView != null) {
             motionView.setPressed(false);
         }
-
-        if (mClickAnimation != null) {
-            mClickAnimation.cancel();
-            mClickAnimation = null;
-        }
     }
 
-    private void setPressedItem(View child, int position, float x, float y) {
+    private void setPressedItem(@NonNull View child, int position, float x, float y) {
         mDrawsInPressedState = true;
 
         // Ordering is essential. First, update the container's pressed state.
@@ -311,11 +252,6 @@
         // Refresh the drawable state to reflect the new pressed state,
         // which will also update the selector state.
         refreshDrawableState();
-
-        if (mClickAnimation != null) {
-            mClickAnimation.cancel();
-            mClickAnimation = null;
-        }
     }
 
     @Override
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 8e711b0..06daf61 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -1946,6 +1946,9 @@
     public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
         mActionMenuPresenterCallback = pcb;
         mMenuBuilderCallback = mcb;
+        if (mMenuView != null) {
+            mMenuView.setMenuCallbacks(pcb, mcb);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
new file mode 100644
index 0000000..df9cf43
--- /dev/null
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -0,0 +1,134 @@
+/*
+ * 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.app;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.R;
+
+/**
+ * Activity to confirm with the user that it is ok to create a new user, as requested by
+ * an app. It has to do some checks to decide what kind of prompt the user should be shown.
+ * Particularly, it needs to check if the account requested already exists on another user.
+ */
+public class ConfirmUserCreationActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
+
+    private static final String TAG = "CreateUser";
+
+    private String mUserName;
+    private String mAccountName;
+    private String mAccountType;
+    private PersistableBundle mAccountOptions;
+    private boolean mCanProceed;
+    private UserManager mUserManager;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        mUserName = intent.getStringExtra(UserManager.EXTRA_USER_NAME);
+        mAccountName = intent.getStringExtra(UserManager.EXTRA_USER_ACCOUNT_NAME);
+        mAccountType = intent.getStringExtra(UserManager.EXTRA_USER_ACCOUNT_TYPE);
+        mAccountOptions = (PersistableBundle)
+                intent.getParcelableExtra(UserManager.EXTRA_USER_ACCOUNT_OPTIONS);
+
+        mUserManager = getSystemService(UserManager.class);
+
+        String message = checkUserCreationRequirements();
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mMessage = message;
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mPositiveButtonListener = this;
+
+        // Show the negative button if the user actually has a choice
+        if (mCanProceed) {
+            ap.mNegativeButtonText = getString(android.R.string.cancel);
+            ap.mNegativeButtonListener = this;
+        }
+        setupAlert();
+    }
+
+    private String checkUserCreationRequirements() {
+        final String callingPackage = getCallingPackage();
+        if (callingPackage == null) {
+            throw new SecurityException(
+                    "User Creation intent must be launched with startActivityForResult");
+        }
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = getPackageManager().getApplicationInfo(callingPackage, 0);
+        } catch (NameNotFoundException nnfe) {
+            throw new SecurityException(
+                    "Cannot find the calling package");
+        }
+        final String message;
+        // Check the user restrictions
+        boolean cantCreateUser = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+        // Check the system state and user count
+        boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers();
+        // Check the account existence
+        final Account account = new Account(mAccountName, mAccountType);
+        boolean accountExists = mAccountName != null && mAccountType != null
+                && (AccountManager.get(this).someUserHasAccount(account)
+                    | mUserManager.someUserHasSeedAccount(mAccountName, mAccountType));
+        mCanProceed = true;
+        final String appName = appInfo.loadLabel(getPackageManager()).toString();
+        if (cantCreateUser) {
+            message = getString(R.string.user_creation_cannot_add, appName);
+            mCanProceed = false;
+        } else if (cantCreateAnyMoreUsers) {
+            message = getString(R.string.user_creation_cannot_add_any_more, appName);
+            mCanProceed = false;
+        } else if (accountExists) {
+            message = getString(R.string.user_creation_account_exists, appName, mAccountName);
+        } else {
+            message = getString(R.string.user_creation_adding, appName, mAccountName);
+        }
+        return message;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        setResult(RESULT_CANCELED);
+        if (which == BUTTON_POSITIVE && mCanProceed) {
+            Log.i(TAG, "Ok, creating user");
+            UserInfo user = mUserManager.createUser(mUserName, 0);
+            if (user == null) {
+                Log.e(TAG, "Couldn't create user");
+                finish();
+                return;
+            }
+            mUserManager.setSeedAccountData(user.id, mAccountName, mAccountType, mAccountOptions);
+            setResult(RESULT_OK);
+        }
+        finish();
+    }
+}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index ce4fc06..b0b25d3 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -63,6 +63,22 @@
     public static final int PROFILE_CHALLENGE = 271;
     public static final int QS_BATTERY_DETAIL = 272;
 
+    /**
+     * Logged when the user goes into the overview history.
+     */
+    public static final int OVERVIEW_HISTORY = 273;
+
+    /**
+     * Logged when the user pages through overview.
+     */
+    public static final int ACTION_OVERVIEW_PAGE = 274;
+
+    /**
+     * Logged when the user launches a task from overview.
+     */
+    public static final int ACTION_OVERVIEW_SELECT = 275;
+
+
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
             throw new IllegalArgumentException("Must define metric category");
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index fdf5f84..59a1e4a 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -209,12 +209,8 @@
     }
 
     private void addMiddleTarget(boolean isHorizontalDivision) {
-        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
-        int end = isHorizontalDivision
-                ? mDisplayHeight - mInsets.bottom
-                : mDisplayWidth - mInsets.right;
-        mTargets.add(new SnapTarget(start + (end - start) / 2 - mDividerSize / 2,
-                SnapTarget.FLAG_NONE));
+        mTargets.add(new SnapTarget(DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                mInsets, mDisplayWidth, mDisplayHeight, mDividerSize), SnapTarget.FLAG_NONE));
     }
 
     public SnapTarget getMiddleTarget() {
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
index 25a060e..f7f5f15 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -19,6 +19,11 @@
 import android.graphics.Rect;
 import android.view.WindowManager;
 
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
 /**
  * Utility functions for docked stack divider used by both window manager and System UI.
  *
@@ -43,17 +48,17 @@
                 outRect.top = position + dividerSize;
                 break;
         }
-        if (outRect.left > outRect.right) {
-            outRect.left = outRect.right;
+        if (outRect.left >= outRect.right) {
+            outRect.left = outRect.right - 1;
         }
-        if (outRect.top > outRect.bottom) {
-            outRect.top = outRect.bottom;
+        if (outRect.top >= outRect.bottom) {
+            outRect.top = outRect.bottom - 1;
         }
-        if (outRect.right < outRect.left) {
-            outRect.right = outRect.left;
+        if (outRect.right <= outRect.left) {
+            outRect.right = outRect.left + 1;
         }
-        if (outRect.bottom < outRect.top) {
-            outRect.bottom = outRect.top;
+        if (outRect.bottom <= outRect.top) {
+            outRect.bottom = outRect.top + 1;
         }
     }
 
@@ -71,4 +76,45 @@
                 return 0;
         }
     }
+
+    public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets,
+            int displayWidth, int displayHeight, int dividerSize) {
+        int start = isHorizontalDivision ? insets.top : insets.left;
+        int end = isHorizontalDivision
+                ? displayHeight - insets.bottom
+                : displayWidth - insets.right;
+        return start + (end - start) / 2 - dividerSize / 2;
+    }
+
+    public static int getDockSideFromCreatedMode(boolean dockOnTopOrLeft,
+            boolean isHorizontalDivision) {
+        if (dockOnTopOrLeft) {
+            if (isHorizontalDivision) {
+                return DOCKED_TOP;
+            } else {
+                return DOCKED_LEFT;
+            }
+        } else {
+            if (isHorizontalDivision) {
+                return DOCKED_BOTTOM;
+            } else {
+                return DOCKED_RIGHT;
+            }
+        }
+    }
+
+    public static int invertDockSide(int dockSide) {
+        switch (dockSide) {
+            case WindowManager.DOCKED_LEFT:
+                return WindowManager.DOCKED_RIGHT;
+            case WindowManager.DOCKED_TOP:
+                return WindowManager.DOCKED_BOTTOM;
+            case WindowManager.DOCKED_RIGHT:
+                return WindowManager.DOCKED_LEFT;
+            case WindowManager.DOCKED_BOTTOM:
+                return WindowManager.DOCKED_TOP;
+            default:
+                return WindowManager.DOCKED_INVALID;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b4c4ef5..4670cca 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -684,6 +684,13 @@
         }
     }
 
+    @Override
+    public void reportActivityRelaunched() {
+        if (mDecor != null && mDecor.getViewRootImpl() != null) {
+            mDecor.getViewRootImpl().reportActivityRelaunched();
+        }
+    }
+
     private static void clearMenuViews(PanelFeatureState st) {
         // This can be called on config changes, so we should make sure
         // the views will be reconstructed based on the new orientation, etc.
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5127408..381e71f 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.util;
 
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.text.TextUtils;
+
 import java.util.Collection;
 
 /**
@@ -31,6 +35,22 @@
     }
 
     /**
+     * Ensures that an string reference passed as a parameter to the calling
+     * method is not empty.
+     *
+     * @param string an string reference
+     * @return the string reference that was validated
+     * @throws IllegalArgumentException if {@code string} is empty
+     */
+    public static @NonNull String checkStringNotEmpty(final String string,
+            final Object errorMessage) {
+        if (TextUtils.isEmpty(string)) {
+            throw new IllegalArgumentException(String.valueOf(errorMessage));
+        }
+        return string;
+    }
+
+    /**
      * Ensures that an object reference passed as a parameter to the calling
      * method is not null.
      *
@@ -38,7 +58,7 @@
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
      */
-    public static <T> T checkNotNull(final T reference) {
+    public static @NonNull <T> T checkNotNull(final T reference) {
         if (reference == null) {
             throw new NullPointerException();
         }
@@ -55,7 +75,7 @@
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
      */
-    public static <T> T checkNotNull(final T reference, final Object errorMessage) {
+    public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
         if (reference == null) {
             throw new NullPointerException(String.valueOf(errorMessage));
         }
@@ -95,7 +115,8 @@
      * @return the validated numeric value
      * @throws IllegalArgumentException if {@code value} was negative
      */
-    public static int checkArgumentNonnegative(final int value, final String errorMessage) {
+    public static @IntRange(from = 0) int checkArgumentNonnegative(final int value,
+            final String errorMessage) {
         if (value < 0) {
             throw new IllegalArgumentException(errorMessage);
         }
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 85ec29c..3be15f3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -30,7 +30,7 @@
 
 import java.lang.ref.WeakReference;
 
-public class IInputConnectionWrapper extends IInputContext.Stub {
+public abstract class IInputConnectionWrapper extends IInputContext.Stub {
     static final String TAG = "IInputConnectionWrapper";
 
     private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
@@ -49,6 +49,7 @@
     private static final int DO_FINISH_COMPOSING_TEXT = 65;
     private static final int DO_SEND_KEY_EVENT = 70;
     private static final int DO_DELETE_SURROUNDING_TEXT = 80;
+    private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81;
     private static final int DO_BEGIN_BATCH_EDIT = 90;
     private static final int DO_END_BATCH_EDIT = 95;
     private static final int DO_REPORT_FULLSCREEN_MODE = 100;
@@ -80,15 +81,25 @@
     }
     
     public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
-        mInputConnection = new WeakReference<InputConnection>(conn);
+        mInputConnection = new WeakReference<>(conn);
         mMainLooper = mainLooper;
         mH = new MyHandler(mMainLooper);
     }
 
-    public boolean isActive() {
-        return true;
-    }
-    
+    abstract protected boolean isActive();
+
+    /**
+     * Called when the user took some actions that should be taken into consideration to update the
+     * LRU list for input method rotation.
+     */
+    abstract protected void onUserAction();
+
+    /**
+     * Called when the input method started or stopped full-screen mode.
+     *
+     */
+    abstract protected void onReportFullscreenMode(boolean enabled);
+
     public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
         dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
     }
@@ -155,9 +166,14 @@
         dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
     }
 
-    public void deleteSurroundingText(int leftLength, int rightLength) {
+    public void deleteSurroundingText(int beforeLength, int afterLength) {
         dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
-            leftLength, rightLength));
+                beforeLength, afterLength));
+    }
+
+    public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
+                beforeLength, afterLength));
     }
 
     public void beginBatchEdit() {
@@ -284,6 +300,7 @@
                     return;
                 }
                 ic.commitText((CharSequence)msg.obj, msg.arg1);
+                onUserAction();
                 return;
             }
             case DO_SET_SELECTION: {
@@ -338,6 +355,7 @@
                     return;
                 }
                 ic.setComposingText((CharSequence)msg.obj, msg.arg1);
+                onUserAction();
                 return;
             }
             case DO_SET_COMPOSING_REGION: {
@@ -369,6 +387,7 @@
                     return;
                 }
                 ic.sendKeyEvent((KeyEvent)msg.obj);
+                onUserAction();
                 return;
             }
             case DO_CLEAR_META_KEY_STATES: {
@@ -389,6 +408,15 @@
                 ic.deleteSurroundingText(msg.arg1, msg.arg2);
                 return;
             }
+            case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: {
+                InputConnection ic = mInputConnection.get();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
+                    return;
+                }
+                ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2);
+                return;
+            }
             case DO_BEGIN_BATCH_EDIT: {
                 InputConnection ic = mInputConnection.get();
                 if (ic == null || !isActive()) {
@@ -413,7 +441,9 @@
                     Log.w(TAG, "reportFullscreenMode on inexistent InputConnection");
                     return;
                 }
-                ic.reportFullscreenMode(msg.arg1 == 1);
+                final boolean enabled = msg.arg1 == 1;
+                ic.reportFullscreenMode(enabled);
+                onReportFullscreenMode(enabled);
                 return;
             }
             case DO_PERFORM_PRIVATE_COMMAND: {
@@ -454,7 +484,7 @@
     Message obtainMessageII(int what, int arg1, int arg2) {
         return mH.obtainMessage(what, arg1, arg2);
     }
-    
+
     Message obtainMessageO(int what, Object arg1) {
         return mH.obtainMessage(what, 0, 0, arg1);
     }
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index fd2b513..f7ec420 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -38,8 +38,9 @@
     
     void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
             IInputContextCallback callback);
-    
-    void deleteSurroundingText(int leftLength, int rightLength);
+
+    void deleteSurroundingText(int beforeLength, int afterLength);
+    void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
 
     void setComposingText(CharSequence text, int newCursorPosition);
 
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 7dc927f..94790c1 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -400,7 +400,7 @@
             return false;
         }
     }
-    
+
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         try {
             mIInputContext.deleteSurroundingText(beforeLength, afterLength);
@@ -410,6 +410,15 @@
         }
     }
 
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        try {
+            mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     public boolean reportFullscreenMode(boolean enabled) {
         try {
             mIInputContext.reportFullscreenMode(enabled);
diff --git a/core/java/com/android/server/backup/PermissionBackupHelper.java b/core/java/com/android/server/backup/PermissionBackupHelper.java
new file mode 100644
index 0000000..ff0e63d
--- /dev/null
+++ b/core/java/com/android/server/backup/PermissionBackupHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.app.AppGlobals;
+import android.app.backup.BlobBackupHelper;
+import android.content.pm.IPackageManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class PermissionBackupHelper extends BlobBackupHelper {
+    private static final String TAG = "PermissionBackup";
+    private static final boolean DEBUG = false;
+
+    // current schema of the backup state blob
+    private static final int STATE_VERSION = 1;
+
+    // key under which the permission-grant state blob is committed to backup
+    private static final String KEY_PERMISSIONS = "permissions";
+
+    public PermissionBackupHelper() {
+        super(STATE_VERSION, KEY_PERMISSIONS);
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        IPackageManager pm = AppGlobals.getPackageManager();
+        if (DEBUG) {
+            Slog.d(TAG, "Handling backup of " + key);
+        }
+        try {
+            switch (key) {
+                case KEY_PERMISSIONS:
+                    return pm.getPermissionGrantBackup(UserHandle.USER_SYSTEM);
+
+                default:
+                    Slog.w(TAG, "Unexpected backup key " + key);
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Unable to store payload " + key);
+        }
+        return null;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        IPackageManager pm = AppGlobals.getPackageManager();
+        if (DEBUG) {
+            Slog.d(TAG, "Handling restore of " + key);
+        }
+        try {
+            switch (key) {
+                case KEY_PERMISSIONS:
+                    pm.restorePermissionGrants(payload, UserHandle.USER_SYSTEM);
+                    break;
+
+                default:
+                    Slog.w(TAG, "Unexpected restore key " + key);
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Unable to restore key " + key);
+        }
+    }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 234815f..3f76e13 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -46,6 +46,7 @@
     private static final String SYNC_SETTINGS_HELPER = "account_sync_settings";
     private static final String PREFERRED_HELPER = "preferred_activities";
     private static final String NOTIFICATION_HELPER = "notifications";
+    private static final String PERMISSION_HELPER = "permissions";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -94,6 +95,7 @@
         addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
         addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
         addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+        addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
 
         super.onBackup(oldState, data, newState);
     }
@@ -128,6 +130,7 @@
         addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
         addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
         addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+        addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
 
         try {
             super.onRestore(data, appVersionCode, newState);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 63f193d..40af22a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1257,6 +1257,7 @@
     REG_JNI(register_android_os_Binder),
     REG_JNI(register_android_os_Parcel),
     REG_JNI(register_android_nio_utils),
+    REG_JNI(register_android_graphics_Canvas),
     REG_JNI(register_android_graphics_Graphics),
     REG_JNI(register_android_view_DisplayEventReceiver),
     REG_JNI(register_android_view_RenderNode),
@@ -1289,7 +1290,6 @@
     REG_JNI(register_android_graphics_BitmapRegionDecoder),
     REG_JNI(register_android_graphics_Camera),
     REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
-    REG_JNI(register_android_graphics_Canvas),
     REG_JNI(register_android_graphics_CanvasProperty),
     REG_JNI(register_android_graphics_ColorFilter),
     REG_JNI(register_android_graphics_DrawFilter),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a805b6d..80ccb61 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -774,11 +774,14 @@
     return ret;
 }
 
-static void Bitmap_destructor(JNIEnv* env, jobject, jlong bitmapHandle) {
-    LocalScopedBitmap bitmap(bitmapHandle);
+static void Bitmap_destruct(Bitmap* bitmap) {
     bitmap->detachFromJava();
 }
 
+static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
+}
+
 static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
     LocalScopedBitmap bitmap(bitmapHandle);
     bitmap->freePixels();
@@ -1357,7 +1360,7 @@
         (void*)Bitmap_copy },
     {   "nativeCopyAshmem",         "(J)Landroid/graphics/Bitmap;",
         (void*)Bitmap_copyAshmem },
-    {   "nativeDestructor",         "(J)V", (void*)Bitmap_destructor },
+    {   "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
     {   "nativeRecycle",            "(J)Z", (void*)Bitmap_recycle },
     {   "nativeReconfigure",        "(JIIIIZ)V", (void*)Bitmap_reconfigure },
     {   "nativeCompress",           "(JIILjava/io/OutputStream;[B)Z",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 0a25a0a..98f8ce3 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -76,9 +76,12 @@
         AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT
     };
 
-    static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        delete obj;
+    static void deletePaint(Paint* paint) {
+        delete paint;
+    }
+
+    static jlong getNativeFinalizer(JNIEnv*, jobject) {
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(&deletePaint));
     }
 
     static jlong init(JNIEnv* env, jobject) {
@@ -863,7 +866,7 @@
 }; // namespace PaintGlue
 
 static const JNINativeMethod methods[] = {
-    {"nFinalizer", "(J)V", (void*) PaintGlue::finalizer},
+    {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer},
     {"nInit","()J", (void*) PaintGlue::init},
     {"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
 
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index fda0ffa..2507e4dd 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -60,6 +60,27 @@
     // as all the data needed is contained within the newly created LocalMatrixShader.
     SkASSERT(shaderHandle);
     SkAutoTUnref<SkShader> currentShader(reinterpret_cast<SkShader*>(shaderHandle));
+
+    // Attempt to peel off an existing proxy shader and get the proxy's matrix. If
+    // the proxy existed and it's matrix equals the desired matrix then just return
+    // the proxy, otherwise replace it with a new proxy containing the desired matrix.
+    //
+    // refAsALocalMatrixShader(): if the shader contains a proxy then it unwraps the proxy
+    //                            returning both the underlying shader and the proxy's matrix.
+    // newWithLocalMatrix(): will return a proxy shader that wraps the provided shader and
+    //                       concats the provided local matrix with the shader's matrix.
+    //
+    // WARNING: This proxy replacement only behaves like a setter because the Java
+    //          API enforces that all local matrices are set using this call and
+    //          not passed to the constructor of the Shader.
+    SkMatrix proxyMatrix;
+    SkAutoTUnref<SkShader> baseShader(currentShader->refAsALocalMatrixShader(&proxyMatrix));
+    if (baseShader.get()) {
+        if (proxyMatrix == *matrix) {
+            return reinterpret_cast<jlong>(currentShader.detach());
+        }
+        return reinterpret_cast<jlong>(baseShader->newWithLocalMatrix(*matrix));
+    }
     return reinterpret_cast<jlong>(currentShader->newWithLocalMatrix(*matrix));
 }
 
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index e4e73a4..34877e0 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -37,8 +37,12 @@
     return reinterpret_cast<Canvas*>(canvasHandle);
 }
 
-static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) {
-    delete get_canvas(canvasHandle);
+static void delete_canvas(Canvas* canvas) {
+    delete canvas;
+}
+
+static jlong getNativeFinalizer(JNIEnv* env, jobject clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&delete_canvas));
 }
 
 // Native wrapper constructor used by Canvas(Bitmap)
@@ -710,7 +714,7 @@
 }; // namespace CanvasJNI
 
 static const JNINativeMethod gMethods[] = {
-    {"finalizer", "(J)V", (void*) CanvasJNI::finalizer},
+    {"getNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
     {"initRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
     {"native_setBitmap", "!(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
     {"native_isOpaque","!(J)Z", (void*) CanvasJNI::isOpaque},
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 42f3fb0..61f185e 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -656,18 +656,59 @@
 }
 
 // ----------------------------------------------------------------------------
-static jint android_media_AudioTrack_get_native_frame_count(JNIEnv *env,  jobject thiz) {
+static jint android_media_AudioTrack_get_buffer_size_frames(JNIEnv *env,  jobject thiz) {
     sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
-            "Unable to retrieve AudioTrack pointer for frameCount()");
+            "Unable to retrieve AudioTrack pointer for getBufferSizeInFrames()");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    ssize_t result = lpTrack->getBufferSizeInFrames();
+    if (result < 0) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Internal error detected in getBufferSizeInFrames() = " + result);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    return (jint)result;
+}
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_set_buffer_size_frames(JNIEnv *env,
+        jobject thiz, jint bufferSizeInFrames) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for setBufferSizeInFrames()");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    // Value will be coerced into the valid range.
+    // But internal values are unsigned, size_t, so we need to clip
+    // against zero here where it is signed.
+    if (bufferSizeInFrames < 0) {
+        bufferSizeInFrames = 0;
+    }
+    ssize_t result = lpTrack->setBufferSizeInFrames(bufferSizeInFrames);
+    if (result < 0) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Internal error detected in setBufferSizeInFrames() = " + result);
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    return (jint)result;
+}
+
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_buffer_capacity_frames(JNIEnv *env,  jobject thiz) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getBufferCapacityInFrames()");
         return (jint)AUDIO_JAVA_ERROR;
     }
 
     return lpTrack->frameCount();
 }
 
-
 // ----------------------------------------------------------------------------
 static jint android_media_AudioTrack_set_playback_rate(JNIEnv *env,  jobject thiz,
         jint sampleRateInHz) {
@@ -857,6 +898,17 @@
     return (jint)lpTrack->latency();
 }
 
+// ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_underrun_count(JNIEnv *env,  jobject thiz) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getUnderrunCount()");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    return (jint)lpTrack->getUnderrunCount();
+}
 
 // ----------------------------------------------------------------------------
 static jint android_media_AudioTrack_get_timestamp(JNIEnv *env,  jobject thiz, jlongArray jTimestamp) {
@@ -1073,8 +1125,12 @@
     {"native_write_short",   "([SIIIZ)I",(void *)android_media_AudioTrack_writeArray<jshortArray>},
     {"native_write_float",   "([FIIIZ)I",(void *)android_media_AudioTrack_writeArray<jfloatArray>},
     {"native_setVolume",     "(FF)V",    (void *)android_media_AudioTrack_set_volume},
-    {"native_get_native_frame_count",
-                             "()I",      (void *)android_media_AudioTrack_get_native_frame_count},
+    {"native_get_buffer_size_frames",
+                             "()I",      (void *)android_media_AudioTrack_get_buffer_size_frames},
+    {"native_set_buffer_size_frames",
+                             "(I)I",     (void *)android_media_AudioTrack_set_buffer_size_frames},
+    {"native_get_buffer_capacity_frames",
+                             "()I",      (void *)android_media_AudioTrack_get_buffer_capacity_frames},
     {"native_set_playback_rate",
                              "(I)I",     (void *)android_media_AudioTrack_set_playback_rate},
     {"native_get_playback_rate",
@@ -1094,6 +1150,7 @@
     {"native_set_position",  "(I)I",     (void *)android_media_AudioTrack_set_position},
     {"native_get_position",  "()I",      (void *)android_media_AudioTrack_get_position},
     {"native_get_latency",   "()I",      (void *)android_media_AudioTrack_get_latency},
+    {"native_get_underrun_count", "()I",      (void *)android_media_AudioTrack_get_underrun_count},
     {"native_get_timestamp", "([J)I",    (void *)android_media_AudioTrack_get_timestamp},
     {"native_set_loop",      "(III)I",   (void *)android_media_AudioTrack_set_loop},
     {"native_reload_static", "()I",      (void *)android_media_AudioTrack_reload},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea3c60c..a277568 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -224,6 +224,8 @@
     <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTABLE" />
     <protected-broadcast android:name="android.intent.action.MEDIA_EJECT" />
 
+    <protected-broadcast android:name="android.intent.action.PICTURE_IN_PICTURE_BUTTON" />
+
     <protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL" />
     <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
     <!-- @deprecated.  Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. -->
@@ -2990,6 +2992,18 @@
             </intent-filter>
         </activity>
 
+        <!-- Activity to prompt user if it's ok to create a new user sandbox for a
+             specified account. -->
+        <activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
+                android:excludeFromRecents="true"
+                android:process=":ui"
+                android:theme="@style/Theme.Material.DayNight.Dialog.Alert">
+            <intent-filter android:priority="1000">
+                <action android:name="android.os.action.CREATE_USER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <receiver android:name="com.android.server.BootReceiver"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3435474..0f98cfb 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -23,4 +23,12 @@
 
     <!-- Flags enabling default window features. See Window.java -->
     <bool name="config_defaultWindowFeatureOptionsPanel">false</bool>
+
+    <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
+    <string translatable="false" name="config_defaultPictureInPictureBounds">"1420 100 1820 325"</string>
+
+    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
+         is located in center. -->
+    <string translatable="false" name="config_centeredPictureInPictureBounds">"600 331 1320 749"</string>
+
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2acb91d..53a733d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -413,7 +413,10 @@
     <!-- Boolean indicating whether or not wifi firmware debugging is enabled -->
     <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">true</bool>
 
-    <!-- Integer specifying the basic Quality Network Selection parameters -->
+    <!-- Boolean indicating whether or not wifi should turn off when emergency call is made -->
+    <bool translatable="false" name="config_wifi_turn_off_during_emergency_call">false</bool>
+
+    <!-- Integer specifying the basic autojoin parameters -->
     <integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_threshold">-65</integer>
     <integer translatable="false" name="config_wifi_framework_5GHz_preference_boost_factor">40</integer>
     <integer translatable="false" name="config_wifi_framework_current_association_hysteresis_high">16</integer>
@@ -978,6 +981,7 @@
             0 - Nothing
             1 - Recent apps view in SystemUI
             2 - Launch assist intent
+            3 - Start picture-in-picture (PIP) or launch PIP UI
          This needs to match the constants in
          policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
     -->
@@ -2358,6 +2362,9 @@
          (range of 18 - 21 kHz). -->
     <bool name="config_supportSpeakerNearUltrasound">true</bool>
 
+    <!-- Whether the Unprocessed audio source supports the required frequency range and level -->
+    <bool name="config_supportAudioSourceUnprocessed">false</bool>
+
     <!-- Flag indicating device support for EAP SIM, AKA, AKA' -->
     <bool name="config_eap_sim_based_auth_supported">true</bool>
 
@@ -2415,6 +2422,10 @@
     <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
     <string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string>
 
+    <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
+         is located in center. -->
+    <string translatable="false" name="config_centeredPictureInPictureBounds">"0 0 300 300"</string>
+
     <!-- Controls the snap mode for the docked stack divider
              0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
              1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
@@ -2425,4 +2436,10 @@
     <!-- List of comma separated package names for which we the system will not show crash, ANR,
          etc. dialogs. -->
     <string translatable="false" name="config_appsNotReportingCrashes"></string>
+
+    <!-- Inactivity threshold (in milliseconds) used in JobScheduler. JobScheduler will consider
+         the device to be "idle" after being inactive for this long. -->
+    <integer name="config_jobSchedulerInactivityIdleThreshold">4260000</integer>
+    <!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
+    <integer name="config_jobSchedulerIdleWindowSlop">300000</integer>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 96285ab..4ad36f5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4148,4 +4148,13 @@
 
     <string name="importance_from_topic">You set the importance of these notifications.</string>
     <string name="importance_from_person">This is important because of the people involved.</string>
+
+    <!-- Message to user that app trying to create user is not allowed to due to restrictions. [CHAR LIMIT=none] -->
+    <string name="user_creation_cannot_add"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user, but is currently prohibited.</string>
+    <!-- Message to user that app trying to create user is not allowed to due to user limit being reached. [CHAR LIMIT=none] -->
+    <string name="user_creation_cannot_add_any_more"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user, but the user limit has been reached.</string>
+    <!-- Message to user that app trying to create user for an account that already exists. [CHAR LIMIT=none] -->
+    <string name="user_creation_account_exists"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user, but the account <b><xliff:g id="account" example="foobar">%2$s</xliff:g></b> already exists on this device. Proceed anyway?</string>
+    <!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] -->
+    <string name="user_creation_adding"><b><xliff:g id="app" example="Gmail">%1$s</xliff:g></b> is trying to add a new user for the account <b><xliff:g id="account" example="foobar">%2$s</xliff:g></b>. Proceed?</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 55f704c..b4cb664 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -300,10 +300,13 @@
   <java-symbol type="bool" name="config_wifi_enable_5GHz_preference" />
   <java-symbol type="bool" name="config_wifi_revert_country_code_on_cellular_loss" />
   <java-symbol type="bool" name="config_wifi_enable_wifi_firmware_debugging" />
+  <java-symbol type="bool" name="config_wifi_turn_off_during_emergency_call" />
   <java-symbol type="bool" name="config_supportMicNearUltrasound" />
   <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
+  <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" />
   <java-symbol type="bool" name="config_freeformWindowManagement" />
   <java-symbol type="string" name="config_defaultPictureInPictureBounds" />
+  <java-symbol type="string" name="config_centeredPictureInPictureBounds" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
@@ -935,6 +938,10 @@
   <java-symbol type="string" name="time_picker_increment_minute_button" />
   <java-symbol type="string" name="time_picker_increment_set_pm_button" />
   <java-symbol type="string" name="upload_file" />
+  <java-symbol type="string" name="user_creation_cannot_add" />
+  <java-symbol type="string" name="user_creation_cannot_add_any_more" />
+  <java-symbol type="string" name="user_creation_account_exists" />
+  <java-symbol type="string" name="user_creation_adding" />
   <java-symbol type="string" name="user_switched" />
   <java-symbol type="string" name="user_switching_message" />
   <java-symbol type="string" name="user_logging_out_message" />
@@ -2266,6 +2273,9 @@
 
   <java-symbol type="integer" name="config_defaultNightMode" />
 
+  <java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
+  <java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
+
   <java-symbol type="style" name="Animation.ImmersiveModeConfirmation" />
 
   <java-symbol type="integer" name="config_screen_magnification_multi_tap_adjustment" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index b5b1a25..169ef0b 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -25,14 +25,14 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 
-import dalvik.system.VMRuntime;
-
 import java.io.OutputStream;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 
+import libcore.util.NativeAllocationRegistry;
+
 public final class Bitmap implements Parcelable {
     private static final String TAG = "Bitmap";
 
@@ -44,6 +44,10 @@
      */
     public static final int DENSITY_NONE = 0;
 
+    // Estimated size of the Bitmap native allocation, not including
+    // pixel data.
+    private static final long NATIVE_ALLOCATION_SIZE = 32;
+
     /**
      * Backing buffer for the Bitmap.
      */
@@ -51,7 +55,6 @@
 
     // Convenience for JNI access
     private final long mNativePtr;
-    private final BitmapFinalizer mFinalizer;
 
     private final boolean mIsMutable;
 
@@ -125,9 +128,13 @@
         }
 
         mNativePtr = nativeBitmap;
-        mFinalizer = new BitmapFinalizer(nativeBitmap);
-        int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
-        mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
+        long nativeSize = NATIVE_ALLOCATION_SIZE;
+        if (buffer == null) {
+            nativeSize += getByteCount();
+        }
+        NativeAllocationRegistry registry = new NativeAllocationRegistry(
+            nativeGetNativeFinalizer(), nativeSize);
+        registry.registerNativeAllocation(this, nativeBitmap);
     }
 
     /**
@@ -253,7 +260,7 @@
             throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
         }
 
-        nativeReconfigure(mFinalizer.mNativeBitmap, width, height, config.nativeInt,
+        nativeReconfigure(mNativePtr, width, height, config.nativeInt,
                 mBuffer.length, mRequestPremultiplied);
         mWidth = width;
         mHeight = height;
@@ -330,8 +337,8 @@
      * there are no more references to this bitmap.
      */
     public void recycle() {
-        if (!mRecycled && mFinalizer.mNativeBitmap != 0) {
-            if (nativeRecycle(mFinalizer.mNativeBitmap)) {
+        if (!mRecycled && mNativePtr != 0) {
+            if (nativeRecycle(mNativePtr)) {
                 // return value indicates whether native pixel object was actually recycled.
                 // false indicates that it is still in use at the native level and these
                 // objects should not be collected now. They will be collected later when the
@@ -364,7 +371,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called getGenerationId() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return nativeGenerationId(mFinalizer.mNativeBitmap);
+        return nativeGenerationId(mNativePtr);
     }
 
     /**
@@ -520,7 +527,7 @@
             throw new RuntimeException("Buffer not large enough for pixels");
         }
 
-        nativeCopyPixelsToBuffer(mFinalizer.mNativeBitmap, dst);
+        nativeCopyPixelsToBuffer(mNativePtr, dst);
 
         // now update the buffer's position
         int position = dst.position();
@@ -560,7 +567,7 @@
             throw new RuntimeException("Buffer not large enough for pixels");
         }
 
-        nativeCopyPixelsFromBuffer(mFinalizer.mNativeBitmap, src);
+        nativeCopyPixelsFromBuffer(mNativePtr, src);
 
         // now update the buffer's position
         int position = src.position();
@@ -582,7 +589,7 @@
      */
     public Bitmap copy(Config config, boolean isMutable) {
         checkRecycled("Can't copy a recycled bitmap");
-        Bitmap b = nativeCopy(mFinalizer.mNativeBitmap, config.nativeInt, isMutable);
+        Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable);
         if (b != null) {
             b.setPremultiplied(mRequestPremultiplied);
             b.mDensity = mDensity;
@@ -598,7 +605,7 @@
      */
     public Bitmap createAshmemBitmap() {
         checkRecycled("Can't copy a recycled bitmap");
-        Bitmap b = nativeCopyAshmem(mFinalizer.mNativeBitmap);
+        Bitmap b = nativeCopyAshmem(mNativePtr);
         if (b != null) {
             b.setPremultiplied(mRequestPremultiplied);
             b.mDensity = mDensity;
@@ -859,7 +866,7 @@
         }
         bm.setHasAlpha(hasAlpha);
         if (config == Config.ARGB_8888 && !hasAlpha) {
-            nativeErase(bm.mFinalizer.mNativeBitmap, 0xff000000);
+            nativeErase(bm.mNativePtr, 0xff000000);
         }
         // No need to initialize the bitmap to zeroes with other configs;
         // it is backed by a VM byte array which is by definition preinitialized
@@ -1049,7 +1056,7 @@
             throw new IllegalArgumentException("quality must be 0..100");
         }
         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
-        boolean result = nativeCompress(mFinalizer.mNativeBitmap, format.nativeInt,
+        boolean result = nativeCompress(mNativePtr, format.nativeInt,
                 quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
         Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
         return result;
@@ -1093,7 +1100,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called isPremultiplied() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return nativeIsPremultiplied(mFinalizer.mNativeBitmap);
+        return nativeIsPremultiplied(mNativePtr);
     }
 
     /**
@@ -1119,7 +1126,7 @@
     public final void setPremultiplied(boolean premultiplied) {
         checkRecycled("setPremultiplied called on a recycled bitmap");
         mRequestPremultiplied = premultiplied;
-        nativeSetPremultiplied(mFinalizer.mNativeBitmap, premultiplied);
+        nativeSetPremultiplied(mNativePtr, premultiplied);
     }
 
     /** Returns the bitmap's width */
@@ -1220,7 +1227,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return nativeRowBytes(mFinalizer.mNativeBitmap);
+        return nativeRowBytes(mNativePtr);
     }
 
     /**
@@ -1266,7 +1273,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return Config.nativeToConfig(nativeConfig(mFinalizer.mNativeBitmap));
+        return Config.nativeToConfig(nativeConfig(mNativePtr));
     }
 
     /** Returns true if the bitmap's config supports per-pixel alpha, and
@@ -1281,7 +1288,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return nativeHasAlpha(mFinalizer.mNativeBitmap);
+        return nativeHasAlpha(mNativePtr);
     }
 
     /**
@@ -1296,7 +1303,7 @@
      */
     public void setHasAlpha(boolean hasAlpha) {
         checkRecycled("setHasAlpha called on a recycled bitmap");
-        nativeSetHasAlpha(mFinalizer.mNativeBitmap, hasAlpha, mRequestPremultiplied);
+        nativeSetHasAlpha(mNativePtr, hasAlpha, mRequestPremultiplied);
     }
 
     /**
@@ -1320,7 +1327,7 @@
         if (mRecycled) {
             Log.w(TAG, "Called hasMipMap() on a recycle()'d bitmap! This is undefined behavior!");
         }
-        return nativeHasMipMap(mFinalizer.mNativeBitmap);
+        return nativeHasMipMap(mNativePtr);
     }
 
     /**
@@ -1345,7 +1352,7 @@
      */
     public final void setHasMipMap(boolean hasMipMap) {
         checkRecycled("setHasMipMap called on a recycled bitmap");
-        nativeSetHasMipMap(mFinalizer.mNativeBitmap, hasMipMap);
+        nativeSetHasMipMap(mNativePtr, hasMipMap);
     }
 
     /**
@@ -1358,7 +1365,7 @@
         if (!isMutable()) {
             throw new IllegalStateException("cannot erase immutable bitmaps");
         }
-        nativeErase(mFinalizer.mNativeBitmap, c);
+        nativeErase(mNativePtr, c);
     }
 
     /**
@@ -1375,7 +1382,7 @@
     public int getPixel(int x, int y) {
         checkRecycled("Can't call getPixel() on a recycled bitmap");
         checkPixelAccess(x, y);
-        return nativeGetPixel(mFinalizer.mNativeBitmap, x, y);
+        return nativeGetPixel(mNativePtr, x, y);
     }
 
     /**
@@ -1408,7 +1415,7 @@
             return; // nothing to do
         }
         checkPixelsAccess(x, y, width, height, offset, stride, pixels);
-        nativeGetPixels(mFinalizer.mNativeBitmap, pixels, offset, stride,
+        nativeGetPixels(mNativePtr, pixels, offset, stride,
                         x, y, width, height);
     }
 
@@ -1489,7 +1496,7 @@
             throw new IllegalStateException();
         }
         checkPixelAccess(x, y);
-        nativeSetPixel(mFinalizer.mNativeBitmap, x, y, color);
+        nativeSetPixel(mNativePtr, x, y, color);
     }
 
     /**
@@ -1525,7 +1532,7 @@
             return; // nothing to do
         }
         checkPixelsAccess(x, y, width, height, offset, stride, pixels);
-        nativeSetPixels(mFinalizer.mNativeBitmap, pixels, offset, stride,
+        nativeSetPixels(mNativePtr, pixels, offset, stride,
                         x, y, width, height);
     }
 
@@ -1563,7 +1570,7 @@
      */
     public void writeToParcel(Parcel p, int flags) {
         checkRecycled("Can't parcel a recycled bitmap");
-        if (!nativeWriteToParcel(mFinalizer.mNativeBitmap, mIsMutable, mDensity, p)) {
+        if (!nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)) {
             throw new RuntimeException("native writeToParcel failed");
         }
     }
@@ -1609,7 +1616,7 @@
     public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
         checkRecycled("Can't extractAlpha on a recycled bitmap");
         long nativePaint = paint != null ? paint.getNativeInstance() : 0;
-        Bitmap bm = nativeExtractAlpha(mFinalizer.mNativeBitmap, nativePaint, offsetXY);
+        Bitmap bm = nativeExtractAlpha(mNativePtr, nativePaint, offsetXY);
         if (bm == null) {
             throw new RuntimeException("Failed to extractAlpha on Bitmap");
         }
@@ -1629,7 +1636,7 @@
         if (other.isRecycled()) {
             throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
         }
-        return nativeSameAs(mFinalizer.mNativeBitmap, other.mFinalizer.mNativeBitmap);
+        return nativeSameAs(mNativePtr, other.mNativePtr);
     }
 
     /**
@@ -1660,41 +1667,6 @@
         return nativeRefPixelRef(mNativePtr);
     }
 
-    private static class BitmapFinalizer {
-        private long mNativeBitmap;
-
-        // Native memory allocated for the duration of the Bitmap,
-        // if pixel data allocated into native memory, instead of java byte[]
-        private int mNativeAllocationByteCount;
-
-        BitmapFinalizer(long nativeBitmap) {
-            mNativeBitmap = nativeBitmap;
-        }
-
-        public void setNativeAllocationByteCount(int nativeByteCount) {
-            if (mNativeAllocationByteCount != 0) {
-                VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
-            }
-            mNativeAllocationByteCount = nativeByteCount;
-            if (mNativeAllocationByteCount != 0) {
-                VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
-            }
-        }
-
-        @Override
-        public void finalize() {
-            try {
-                super.finalize();
-            } catch (Throwable t) {
-                // Ignore
-            } finally {
-                setNativeAllocationByteCount(0);
-                nativeDestructor(mNativeBitmap);
-                mNativeBitmap = 0;
-            }
-        }
-    }
-
     //////////// native methods
 
     private static native Bitmap nativeCreate(int[] colors, int offset,
@@ -1703,7 +1675,7 @@
     private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
                                             boolean isMutable);
     private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
-    private static native void nativeDestructor(long nativeBitmap);
+    private static native long nativeGetNativeFinalizer();
     private static native boolean nativeRecycle(long nativeBitmap);
     private static native void nativeReconfigure(long nativeBitmap, int width, int height,
                                                  int config, int allocSize,
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 1cc5346..d4f745d 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -31,6 +31,8 @@
 
 import javax.microedition.khronos.opengles.GL;
 
+import libcore.util.NativeAllocationRegistry;
+
 /**
  * The Canvas class holds the "draw" calls. To draw something, you need
  * 4 basic components: A Bitmap to hold the pixels, a Canvas to host
@@ -50,7 +52,7 @@
 
     /**
      * Should only be assigned in constructors (or setBitmap if software canvas),
-     * freed in finalizer.
+     * freed by NativeAllocation.
      * @hide
      */
     protected long mNativeCanvasWrapper;
@@ -85,32 +87,15 @@
     // (see SkCanvas.cpp, SkDraw.cpp)
     private static final int MAXMIMUM_BITMAP_SIZE = 32766;
 
+    // The approximate size of the native allocation associated with
+    // a Canvas object.
+    private static final long NATIVE_ALLOCATION_SIZE = 525;
+
+    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+        getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+
     // This field is used to finalize the native Canvas properly
-    private final CanvasFinalizer mFinalizer;
-
-    private static final class CanvasFinalizer {
-        private long mNativeCanvasWrapper;
-
-        public CanvasFinalizer(long nativeCanvas) {
-            mNativeCanvasWrapper = nativeCanvas;
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                dispose();
-            } finally {
-                super.finalize();
-            }
-        }
-
-        public void dispose() {
-            if (mNativeCanvasWrapper != 0) {
-                finalizer(mNativeCanvasWrapper);
-                mNativeCanvasWrapper = 0;
-            }
-        }
-    }
+    private Runnable mFinalizer;
 
     /**
      * Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
@@ -122,7 +107,7 @@
         if (!isHardwareAccelerated()) {
             // 0 means no native bitmap
             mNativeCanvasWrapper = initRaster(null);
-            mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+            mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
         } else {
             mFinalizer = null;
         }
@@ -143,7 +128,7 @@
         }
         throwIfCannotDraw(bitmap);
         mNativeCanvasWrapper = initRaster(bitmap);
-        mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+        mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
         mBitmap = bitmap;
         mDensity = bitmap.mDensity;
     }
@@ -154,7 +139,7 @@
             throw new IllegalStateException();
         }
         mNativeCanvasWrapper = nativeCanvas;
-        mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+        mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
         mDensity = Bitmap.getDefaultDensity();
     }
 
@@ -1980,7 +1965,11 @@
      * @hide
      */
     public void release() {
-        mFinalizer.dispose();
+        mNativeCanvasWrapper = 0;
+        if (mFinalizer != null) {
+            mFinalizer.run();
+            mFinalizer = null;
+        }
     }
 
     /**
@@ -2148,5 +2137,5 @@
                                                      float hOffset,
                                                      float vOffset,
                                                      int flags, long nativePaint, long nativeTypeface);
-    private static native void finalizer(long nativeCanvas);
+    private static native long getNativeFinalizer();
 }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 90522f7..dfb8bb8 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -30,6 +30,8 @@
 import java.util.HashMap;
 import java.util.Locale;
 
+import libcore.util.NativeAllocationRegistry;
+
 /**
  * The Paint class holds the style and color information about how to draw
  * geometries, text and bitmaps.
@@ -39,6 +41,12 @@
     private long mNativePaint;
     private long mNativeShader = 0;
 
+    // The approximate size of a native paint object.
+    private static final long NATIVE_PAINT_SIZE = 98;
+
+    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+        nGetNativeFinalizer(), NATIVE_PAINT_SIZE);
+
     /**
      * @hide
      */
@@ -444,6 +452,7 @@
      */
     public Paint(int flags) {
         mNativePaint = nInit();
+        sRegistry.registerNativeAllocation(this, mNativePaint);
         setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
         // TODO: Turning off hinting has undesirable side effects, we need to
         //       revisit hinting once we add support for subpixel positioning
@@ -462,12 +471,12 @@
      */
     public Paint(Paint paint) {
         mNativePaint = nInitWithPaint(paint.getNativeInstance());
+        sRegistry.registerNativeAllocation(this, mNativePaint);
         setClassVariablesFrom(paint);
     }
 
     /** Restores the paint to its default settings. */
     public void reset() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nReset(mNativePaint);
         setFlags(HIDDEN_DEFAULT_PAINT_FLAGS);
 
@@ -502,8 +511,6 @@
      * methods on this.
      */
     public void set(Paint src) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
-        if (src.mNativePaint == 0) throw new NullPointerException("Source is already finalized!");
         if (this != src) {
             // copy over the native settings
             nSet(mNativePaint, src.mNativePaint);
@@ -554,7 +561,6 @@
      * @hide
      */
     public long getNativeInstance() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
         if (newNativeShader != mNativeShader) {
             mNativeShader = newNativeShader;
@@ -592,7 +598,6 @@
      * @return the paint's flags (see enums ending in _Flag for bit masks)
      */
     public int getFlags() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetFlags(mNativePaint);
     }
 
@@ -604,7 +609,6 @@
      * @param flags The new flag bits for the paint
      */
     public void setFlags(int flags) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetFlags(mNativePaint, flags);
     }
 
@@ -615,7 +619,6 @@
      * {@link #HINTING_OFF} or {@link #HINTING_ON}.
      */
     public int getHinting() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetHinting(mNativePaint);
     }
 
@@ -626,7 +629,6 @@
      * {@link #HINTING_OFF} or {@link #HINTING_ON}.
      */
     public void setHinting(int mode) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetHinting(mNativePaint, mode);
     }
 
@@ -653,7 +655,6 @@
      * @param aa true to set the antialias bit in the flags, false to clear it
      */
     public void setAntiAlias(boolean aa) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetAntiAlias(mNativePaint, aa);
     }
 
@@ -684,7 +685,6 @@
      * @param dither true to set the dithering bit in flags, false to clear it
      */
     public void setDither(boolean dither) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetDither(mNativePaint, dither);
     }
 
@@ -706,7 +706,6 @@
      *                   false to clear it.
      */
     public void setLinearText(boolean linearText) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetLinearText(mNativePaint, linearText);
     }
 
@@ -728,7 +727,6 @@
      *                     flags, false to clear it.
      */
     public void setSubpixelText(boolean subpixelText) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetSubpixelText(mNativePaint, subpixelText);
     }
 
@@ -750,7 +748,6 @@
      *                      flags, false to clear it.
      */
     public void setUnderlineText(boolean underlineText) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetUnderlineText(mNativePaint, underlineText);
     }
 
@@ -772,7 +769,6 @@
      *                       flags, false to clear it.
      */
     public void setStrikeThruText(boolean strikeThruText) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStrikeThruText(mNativePaint, strikeThruText);
     }
 
@@ -794,7 +790,6 @@
      *                     flags, false to clear it.
      */
     public void setFakeBoldText(boolean fakeBoldText) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetFakeBoldText(mNativePaint, fakeBoldText);
     }
 
@@ -822,7 +817,6 @@
      *               flags, false to clear it.
      */
     public void setFilterBitmap(boolean filter) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetFilterBitmap(mNativePaint, filter);
     }
 
@@ -836,7 +830,6 @@
      * @return the paint's style setting (Fill, Stroke, StrokeAndFill)
      */
     public Style getStyle() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return sStyleArray[nGetStyle(mNativePaint)];
     }
 
@@ -848,7 +841,6 @@
      * @param style The new style to set in the paint
      */
     public void setStyle(Style style) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStyle(mNativePaint, style.nativeInt);
     }
 
@@ -862,7 +854,6 @@
      */
     @ColorInt
     public int getColor() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetColor(mNativePaint);
     }
 
@@ -877,7 +868,6 @@
      * @param color The new color (including alpha) to set in the paint.
      */
     public void setColor(@ColorInt int color) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetColor(mNativePaint, color);
     }
 
@@ -891,7 +881,6 @@
      * @return the alpha component of the paint's color.
      */
     public int getAlpha() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetAlpha(mNativePaint);
     }
 
@@ -905,7 +894,6 @@
      * @param a set the alpha component [0..255] of the paint's color.
      */
     public void setAlpha(int a) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetAlpha(mNativePaint, a);
     }
 
@@ -933,7 +921,6 @@
      *         Stroke or StrokeAndFill.
      */
     public float getStrokeWidth() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetStrokeWidth(mNativePaint);
     }
 
@@ -948,7 +935,6 @@
      *              style is Stroke or StrokeAndFill.
      */
     public void setStrokeWidth(float width) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStrokeWidth(mNativePaint, width);
     }
 
@@ -962,7 +948,6 @@
      *         Stroke or StrokeAndFill.
      */
     public float getStrokeMiter() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetStrokeMiter(mNativePaint);
     }
 
@@ -976,7 +961,6 @@
      *              style is Stroke or StrokeAndFill.
      */
     public void setStrokeMiter(float miter) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStrokeMiter(mNativePaint, miter);
     }
 
@@ -990,7 +974,6 @@
      *         style is Stroke or StrokeAndFill.
      */
     public Cap getStrokeCap() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return sCapArray[nGetStrokeCap(mNativePaint)];
     }
 
@@ -1001,7 +984,6 @@
      *            style is Stroke or StrokeAndFill.
      */
     public void setStrokeCap(Cap cap) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStrokeCap(mNativePaint, cap.nativeInt);
     }
 
@@ -1011,7 +993,6 @@
      * @return the paint's Join.
      */
     public Join getStrokeJoin() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return sJoinArray[nGetStrokeJoin(mNativePaint)];
     }
 
@@ -1022,7 +1003,6 @@
      *             Stroke or StrokeAndFill.
      */
     public void setStrokeJoin(Join join) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetStrokeJoin(mNativePaint, join.nativeInt);
     }
 
@@ -1038,7 +1018,6 @@
      *                 drawn with a hairline (width == 0)
      */
     public boolean getFillPath(Path src, Path dst) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetFillPath(mNativePaint, src.ni(), dst.ni());
     }
 
@@ -1061,6 +1040,11 @@
      * @return       shader
      */
     public Shader setShader(Shader shader) {
+        // If mShader changes, cached value of native shader aren't valid, since
+        // old shader's pointer may be reused by another shader allocation later
+        if (mShader != shader) {
+            mNativeShader = -1;
+        }
         // Defer setting the shader natively until getNativeInstance() is called
         mShader = shader;
         return shader;
@@ -1082,7 +1066,6 @@
      * @return       filter
      */
     public ColorFilter setColorFilter(ColorFilter filter) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long filterNative = 0;
         if (filter != null)
             filterNative = filter.native_instance;
@@ -1110,7 +1093,6 @@
      * @return         xfermode
      */
     public Xfermode setXfermode(Xfermode xfermode) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long xfermodeNative = 0;
         if (xfermode != null)
             xfermodeNative = xfermode.native_instance;
@@ -1138,7 +1120,6 @@
      * @return       effect
      */
     public PathEffect setPathEffect(PathEffect effect) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long effectNative = 0;
         if (effect != null) {
             effectNative = effect.native_instance;
@@ -1168,7 +1149,6 @@
      * @return           maskfilter
      */
     public MaskFilter setMaskFilter(MaskFilter maskfilter) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long maskfilterNative = 0;
         if (maskfilter != null) {
             maskfilterNative = maskfilter.native_instance;
@@ -1200,7 +1180,6 @@
      * @return         typeface
      */
     public Typeface setTypeface(Typeface typeface) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long typefaceNative = 0;
         if (typeface != null) {
             typefaceNative = typeface.native_instance;
@@ -1239,7 +1218,6 @@
      */
     @Deprecated
     public Rasterizer setRasterizer(Rasterizer rasterizer) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         long rasterizerNative = 0;
         if (rasterizer != null) {
             rasterizerNative = rasterizer.native_instance;
@@ -1262,7 +1240,6 @@
      * opaque, or the alpha from the shadow color if not.
      */
     public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
       nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
     }
 
@@ -1280,7 +1257,6 @@
      * @hide
      */
     public boolean hasShadowLayer() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nHasShadowLayer(mNativePaint);
     }
 
@@ -1293,7 +1269,6 @@
      * @return the paint's Align value for drawing text.
      */
     public Align getTextAlign() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return sAlignArray[nGetTextAlign(mNativePaint)];
     }
 
@@ -1306,7 +1281,6 @@
      * @param align set the paint's Align value for drawing text.
      */
     public void setTextAlign(Align align) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetTextAlign(mNativePaint, align.nativeInt);
     }
 
@@ -1340,7 +1314,6 @@
      * @param locale the paint's locale value for drawing text, must not be null.
      */
     public void setTextLocale(@NonNull Locale locale) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (locale == null) {
             throw new IllegalArgumentException("locale cannot be null");
         }
@@ -1379,7 +1352,6 @@
      * @param locales the paint's locale list for drawing text, must not be null or empty.
      */
     public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (locales == null || locales.isEmpty()) {
             throw new IllegalArgumentException("locales cannot be null or empty");
         }
@@ -1408,7 +1380,6 @@
      * @return true if elegant metrics are enabled for text drawing.
      */
     public boolean isElegantTextHeight() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nIsElegantTextHeight(mNativePaint);
     }
 
@@ -1422,7 +1393,6 @@
      * @param elegant set the paint's elegant metrics flag for drawing text.
      */
     public void setElegantTextHeight(boolean elegant) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetElegantTextHeight(mNativePaint, elegant);
     }
 
@@ -1434,7 +1404,6 @@
      * @return the paint's text size.
      */
     public float getTextSize() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetTextSize(mNativePaint);
     }
 
@@ -1446,7 +1415,6 @@
      * @param textSize set the paint's text size.
      */
     public void setTextSize(float textSize) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetTextSize(mNativePaint, textSize);
     }
 
@@ -1459,7 +1427,6 @@
      * @return the paint's scale factor in X for drawing/measuring text
      */
     public float getTextScaleX() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetTextScaleX(mNativePaint);
     }
 
@@ -1473,7 +1440,6 @@
      * @param scaleX set the paint's scale in X for drawing/measuring text.
      */
     public void setTextScaleX(float scaleX) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetTextScaleX(mNativePaint, scaleX);
     }
 
@@ -1486,7 +1452,6 @@
      * @return         the paint's skew factor in X for drawing text.
      */
     public float getTextSkewX() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetTextSkewX(mNativePaint);
     }
 
@@ -1499,7 +1464,6 @@
      * @param skewX set the paint's skew factor in X for drawing text.
      */
     public void setTextSkewX(float skewX) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetTextSkewX(mNativePaint, skewX);
     }
 
@@ -1512,7 +1476,6 @@
      * @return         the paint's letter-spacing for drawing text.
      */
     public float getLetterSpacing() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetLetterSpacing(mNativePaint);
     }
 
@@ -1524,7 +1487,6 @@
      * @param letterSpacing set the paint's letter-spacing for drawing text.
      */
     public void setLetterSpacing(float letterSpacing) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetLetterSpacing(mNativePaint, letterSpacing);
     }
 
@@ -1546,7 +1508,6 @@
      * @param settings the font feature settings string to use, may be null.
      */
     public void setFontFeatureSettings(String settings) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (settings != null && settings.equals("")) {
             settings = null;
         }
@@ -1566,7 +1527,6 @@
      * @hide
      */
     public int getHyphenEdit() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetHyphenEdit(mNativePaint);
     }
 
@@ -1579,7 +1539,6 @@
      * @hide
      */
     public void setHyphenEdit(int hyphen) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         nSetHyphenEdit(mNativePaint, hyphen);
     }
 
@@ -1591,7 +1550,6 @@
      *         current typeface and text size.
      */
     public float ascent() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nAscent(mNativePaint, mNativeTypeface);
     }
 
@@ -1605,7 +1563,6 @@
      *         the current typeface and text size.
      */
     public float descent() {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nDescent(mNativePaint, mNativeTypeface);
     }
 
@@ -1652,7 +1609,6 @@
      * @return the font's recommended interline spacing.
      */
     public float getFontMetrics(FontMetrics metrics) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetFontMetrics(mNativePaint, mNativeTypeface, metrics);
     }
 
@@ -1698,7 +1654,6 @@
      * @return the font's interline spacing.
      */
     public int getFontMetricsInt(FontMetricsInt fmi) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nGetFontMetricsInt(mNativePaint, mNativeTypeface, fmi);
     }
 
@@ -1731,7 +1686,6 @@
      * @return      The width of the text
      */
     public float measureText(char[] text, int index, int count) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1764,7 +1718,6 @@
      * @return      The width of the text
      */
     public float measureText(String text, int start, int end) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1794,7 +1747,6 @@
      * @return      The width of the text
      */
     public float measureText(String text) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1855,7 +1807,6 @@
      */
     public int breakText(char[] text, int index, int count,
                                 float maxWidth, float[] measuredWidth) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1903,7 +1854,6 @@
     public int breakText(CharSequence text, int start, int end,
                          boolean measureForwards,
                          float maxWidth, float[] measuredWidth) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1952,7 +1902,6 @@
      */
     public int breakText(String text, boolean measureForwards,
                                 float maxWidth, float[] measuredWidth) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -1990,7 +1939,6 @@
      */
     public int getTextWidths(char[] text, int index, int count,
                              float[] widths) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2074,7 +2022,6 @@
      * @return       the number of code units in the specified text.
      */
     public int getTextWidths(String text, int start, int end, float[] widths) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2128,7 +2075,6 @@
             int contextIndex, int contextCount, boolean isRtl, float[] advances,
             int advancesIndex) {
 
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (chars == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2175,7 +2121,6 @@
     public float getTextRunAdvances(CharSequence text, int start, int end,
             int contextStart, int contextEnd, boolean isRtl, float[] advances,
             int advancesIndex) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2257,7 +2202,6 @@
      */
     public float getTextRunAdvances(String text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2322,7 +2266,6 @@
      */
     public int getTextRunCursor(char[] text, int contextStart, int contextLength,
             int dir, int offset, int cursorOpt) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         int contextEnd = contextStart + contextLength;
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
@@ -2410,7 +2353,6 @@
      */
     public int getTextRunCursor(String text, int contextStart, int contextEnd,
             int dir, int offset, int cursorOpt) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
                 | (text.length() - contextEnd) | cursorOpt) < 0)
@@ -2437,7 +2379,6 @@
      */
     public void getTextPath(char[] text, int index, int count,
                             float x, float y, Path path) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
@@ -2460,7 +2401,6 @@
      */
     public void getTextPath(String text, int start, int end,
                             float x, float y, Path path) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
@@ -2479,7 +2419,6 @@
      *               allocated by the caller.
      */
     public void getTextBounds(String text, int start, int end, Rect bounds) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
@@ -2500,7 +2439,6 @@
      *               allocated by the caller.
      */
     public void getTextBounds(char[] text, int index, int count, Rect bounds) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
@@ -2528,7 +2466,6 @@
      * @return true if the typeface has a glyph for the string
      */
     public boolean hasGlyph(String string) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         return nHasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
     }
 
@@ -2570,7 +2507,6 @@
      */
     public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd,
             boolean isRtl, int offset) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2601,7 +2537,6 @@
      */
     public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, int offset) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2652,7 +2587,6 @@
      */
     public int getOffsetForAdvance(char[] text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, float advance) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2680,7 +2614,6 @@
      */
     public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart,
             int contextEnd, boolean isRtl, float advance) {
-        if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
@@ -2698,18 +2631,6 @@
         return result;
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mNativePaint != 0) {
-                nFinalizer(mNativePaint);
-                mNativePaint = 0;
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
     private static native long nInit();
     private static native long nInitWithPaint(long paint);
     private static native void nReset(long paintPtr);
@@ -2765,7 +2686,7 @@
                                 String text, int start, int end, int bidiFlags, Rect bounds);
     private static native void nGetCharArrayBounds(long nativePaint, long typefacePtr,
                                 char[] text, int index, int count, int bidiFlags, Rect bounds);
-    private static native void nFinalizer(long nativePaint);
+    private static native long nGetNativeFinalizer();
 
     private static native void nSetShadowLayer(long paintPtr,
             float radius, float dx, float dy, int color);
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index 78424e3..c0dfe77 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -204,11 +204,13 @@
 
     /**
      * Sets the X position around which the drawable is rotated.
+     * <p>
+     * If the X pivot is relative (as specified by
+     * {@link #setPivotXRelative(boolean)}), then the position represents a
+     * fraction of the drawable width. Otherwise, the position represents an
+     * absolute value in pixels.
      *
-     * @param pivotX X position around which to rotate. If the X pivot is
-     *            relative, the position represents a fraction of the drawable
-     *            width. Otherwise, the position represents an absolute value in
-     *            pixels.
+     * @param pivotX X position around which to rotate
      * @see #setPivotXRelative(boolean)
      * @attr ref android.R.styleable#RotateDrawable_pivotX
      */
@@ -254,11 +256,13 @@
 
     /**
      * Sets the Y position around which the drawable is rotated.
+     * <p>
+     * If the Y pivot is relative (as specified by
+     * {@link #setPivotYRelative(boolean)}), then the position represents a
+     * fraction of the drawable height. Otherwise, the position represents an
+     * absolute value in pixels.
      *
-     * @param pivotY Y position around which to rotate. If the Y pivot is
-     *            relative, the position represents a fraction of the drawable
-     *            height. Otherwise, the position represents an absolute value
-     *            in pixels.
+     * @param pivotY Y position around which to rotate
      * @see #getPivotY()
      * @attr ref android.R.styleable#RotateDrawable_pivotY
      */
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index bbfc022..fa7c8aa 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -53,6 +53,7 @@
     FrameInfoVisualizer.cpp \
     GammaFontRenderer.cpp \
     GlopBuilder.cpp \
+    GpuMemoryTracker.cpp \
     GradientCache.cpp \
     Image.cpp \
     Interpolator.cpp \
@@ -225,11 +226,14 @@
     $(hwui_test_common_src_files) \
     tests/unit/CanvasStateTests.cpp \
     tests/unit/ClipAreaTests.cpp \
+    tests/unit/CrashHandlerInjector.cpp \
     tests/unit/DamageAccumulatorTests.cpp \
     tests/unit/DeviceInfoTests.cpp \
     tests/unit/FatVectorTests.cpp \
+    tests/unit/GpuMemoryTrackerTests.cpp \
     tests/unit/LayerUpdateQueueTests.cpp \
     tests/unit/LinearAllocatorTests.cpp \
+    tests/unit/LeakCheckTests.cpp \
     tests/unit/VectorDrawableTests.cpp \
     tests/unit/OffscreenBufferPoolTests.cpp \
     tests/unit/StringUtilsTests.cpp
@@ -297,9 +301,9 @@
     tests/microbench/PathParserBench.cpp \
     tests/microbench/ShadowBench.cpp
 
-# ifeq (true, $(HWUI_NEW_OPS))
-#     LOCAL_SRC_FILES += \
-#         tests/microbench/FrameBuilderBench.cpp
-# endif
+ifeq (true, $(HWUI_NEW_OPS))
+    LOCAL_SRC_FILES += \
+        tests/microbench/FrameBuilderBench.cpp
+endif
 
 include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index 41411a9..6afff1b 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -39,40 +39,22 @@
         if (!mTexture) {
             Caches& caches = Caches::getInstance();
             mTexture = new Texture(caches);
-            mTexture->width = buffer->getWidth();
-            mTexture->height = buffer->getHeight();
+            mTexture->wrap(mImage->getTexture(),
+                    buffer->getWidth(), buffer->getHeight(), GL_RGBA);
             createEntries(caches, map, count);
         }
     } else {
         ALOGW("Could not create atlas image");
-        delete mImage;
-        mImage = nullptr;
+        terminate();
     }
-
-    updateTextureId();
 }
 
 void AssetAtlas::terminate() {
-    if (mImage) {
-        delete mImage;
-        mImage = nullptr;
-        updateTextureId();
-    }
-}
-
-
-void AssetAtlas::updateTextureId() {
-    mTexture->id = mImage ? mImage->getTexture() : 0;
-    if (mTexture->id) {
-        // Texture ID changed, force-set to defaults to sync the wrapper & GL
-        // state objects
-        mTexture->setWrap(GL_CLAMP_TO_EDGE, false, true);
-        mTexture->setFilter(GL_NEAREST, false, true);
-    }
-    for (size_t i = 0; i < mEntries.size(); i++) {
-        AssetAtlas::Entry* entry = mEntries.valueAt(i);
-        entry->texture->id = mTexture->id;
-    }
+    delete mImage;
+    mImage = nullptr;
+    delete mTexture;
+    mTexture = nullptr;
+    mEntries.clear();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -80,13 +62,13 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
-    ssize_t index = mEntries.indexOfKey(pixelRef);
-    return index >= 0 ? mEntries.valueAt(index) : nullptr;
+    auto result = mEntries.find(pixelRef);
+    return result != mEntries.end() ? result->second.get() : nullptr;
 }
 
 Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
-    ssize_t index = mEntries.indexOfKey(pixelRef);
-    return index >= 0 ? mEntries.valueAt(index)->texture : nullptr;
+    auto result = mEntries.find(pixelRef);
+    return result != mEntries.end() ? result->second->texture : nullptr;
 }
 
 /**
@@ -94,7 +76,8 @@
  * instead of applying the changes to the virtual textures.
  */
 struct DelegateTexture: public Texture {
-    DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { }
+    DelegateTexture(Caches& caches, Texture* delegate)
+            : Texture(caches), mDelegate(delegate) { }
 
     virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
             bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
@@ -111,8 +94,8 @@
 }; // struct DelegateTexture
 
 void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
-    const float width = float(mTexture->width);
-    const float height = float(mTexture->height);
+    const float width = float(mTexture->width());
+    const float height = float(mTexture->height());
 
     for (int i = 0; i < count; ) {
         SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]);
@@ -133,13 +116,13 @@
 
         Texture* texture = new DelegateTexture(caches, mTexture);
         texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType());
-        texture->width = pixelRef->info().width();
-        texture->height = pixelRef->info().height();
+        texture->wrap(mTexture->id(), pixelRef->info().width(),
+                pixelRef->info().height(), mTexture->format());
 
-        Entry* entry = new Entry(pixelRef, texture, mapper, *this);
+        std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this));
         texture->uvMapper = &entry->uvMapper;
 
-        mEntries.add(entry->pixelRef, entry);
+        mEntries.emplace(entry->pixelRef, std::move(entry));
     }
 }
 
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
index a037725..75400ff 100644
--- a/libs/hwui/AssetAtlas.h
+++ b/libs/hwui/AssetAtlas.h
@@ -17,19 +17,17 @@
 #ifndef ANDROID_HWUI_ASSET_ATLAS_H
 #define ANDROID_HWUI_ASSET_ATLAS_H
 
-#include <GLES2/gl2.h>
-
-#include <ui/GraphicBuffer.h>
-
-#include <utils/KeyedVector.h>
-
-#include <cutils/compiler.h>
-
-#include <SkBitmap.h>
-
 #include "Texture.h"
 #include "UvMapper.h"
 
+#include <cutils/compiler.h>
+#include <GLES2/gl2.h>
+#include <ui/GraphicBuffer.h>
+#include <SkBitmap.h>
+
+#include <memory>
+#include <unordered_map>
+
 namespace android {
 namespace uirenderer {
 
@@ -71,6 +69,10 @@
             return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey;
         }
 
+        ~Entry() {
+            delete texture;
+        }
+
     private:
         /**
          * The pixel ref that generated this atlas entry.
@@ -90,10 +92,6 @@
                 , atlas(atlas) {
         }
 
-        ~Entry() {
-            delete texture;
-        }
-
         friend class AssetAtlas;
     };
 
@@ -127,7 +125,7 @@
      * Can return 0 if the atlas is not initialized.
      */
     uint32_t getWidth() const {
-        return mTexture ? mTexture->width : 0;
+        return mTexture ? mTexture->width() : 0;
     }
 
     /**
@@ -135,7 +133,7 @@
      * Can return 0 if the atlas is not initialized.
      */
     uint32_t getHeight() const {
-        return mTexture ? mTexture->height : 0;
+        return mTexture ? mTexture->height() : 0;
     }
 
     /**
@@ -143,7 +141,7 @@
      * Can return 0 if the atlas is not initialized.
      */
     GLuint getTexture() const {
-        return mTexture ? mTexture->id : 0;
+        return mTexture ? mTexture->id() : 0;
     }
 
     /**
@@ -160,7 +158,6 @@
 
 private:
     void createEntries(Caches& caches, int64_t* map, int count);
-    void updateTextureId();
 
     Texture* mTexture;
     Image* mImage;
@@ -168,7 +165,7 @@
     const bool mBlendKey;
     const bool mOpaqueKey;
 
-    KeyedVector<const SkPixelRef*, Entry*> mEntries;
+    std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries;
 }; // class AssetAtlas
 
 }; // namespace uirenderer
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 5b34f6b..7ecc743 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -217,7 +217,7 @@
             .setMeshTexturedUnitQuad(nullptr)
             .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
-            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
             .build();
     renderer.renderGlop(state, glop);
 }
@@ -337,7 +337,7 @@
 
 static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state,
         PathTexture& texture, const RecordedOp& op) {
-    Rect dest(texture.width, texture.height);
+    Rect dest(texture.width(), texture.height());
     dest.translate(texture.left - texture.offset,
             texture.top - texture.offset);
     Glop glop;
@@ -399,7 +399,7 @@
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
-            .setModelViewMapUnitToRectSnap(Rect(texture->width, texture->height))
+            .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
             .build();
     renderer.renderGlop(state, glop);
 }
@@ -483,10 +483,10 @@
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
-    Rect uv(std::max(0.0f, op.src.left / texture->width),
-            std::max(0.0f, op.src.top / texture->height),
-            std::min(1.0f, op.src.right / texture->width),
-            std::min(1.0f, op.src.bottom / texture->height));
+    Rect uv(std::max(0.0f, op.src.left / texture->width()),
+            std::max(0.0f, op.src.top / texture->height()),
+            std::min(1.0f, op.src.right / texture->width()),
+            std::min(1.0f, op.src.bottom / texture->height()));
 
     const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
@@ -784,6 +784,7 @@
                 .build();
         renderer.renderGlop(state, glop);
     }
+    renderer.renderState().layerPool().putOrDelete(*op.layerHandle);
 }
 
 } // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 42fb66f..4fbff0d 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -48,7 +48,7 @@
 
     // attach the texture to the FBO
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            offscreenBuffer->texture.id, 0);
+            offscreenBuffer->texture.id(), 0);
     LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
@@ -84,7 +84,7 @@
             area.getWidth(), area.getHeight());
     if (!area.isEmpty()) {
         mCaches.textureState().activateTexture(0);
-        mCaches.textureState().bindTexture(buffer->texture.id);
+        mCaches.textureState().bindTexture(buffer->texture.id());
 
         glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
                 area.left, mRenderTarget.viewportHeight - area.bottom,
@@ -272,7 +272,7 @@
                     OffscreenBuffer* layer = mRenderTarget.offscreenBuffer;
                     mRenderTarget.stencil = mCaches.renderBufferCache.get(
                             Stencil::getLayerStencilFormat(),
-                            layer->texture.width, layer->texture.height);
+                            layer->texture.width(), layer->texture.height());
                     // stencil is bound + allocated - associate it with current FBO
                     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                             GL_RENDERBUFFER, mRenderTarget.stencil->getName());
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index e857f6b..10c4698 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -45,9 +45,15 @@
      * Position agnostic shadow lighting info. Used with all shadow ops in scene.
      */
     struct LightInfo {
-        float lightRadius = 0;
-        uint8_t ambientShadowAlpha = 0;
-        uint8_t spotShadowAlpha = 0;
+        LightInfo() : LightInfo(0, 0, 0) {}
+        LightInfo(float lightRadius, uint8_t ambientShadowAlpha,
+                uint8_t spotShadowAlpha)
+                : lightRadius(lightRadius)
+                , ambientShadowAlpha(ambientShadowAlpha)
+                , spotShadowAlpha(spotShadowAlpha) {}
+        float lightRadius;
+        uint8_t ambientShadowAlpha;
+        uint8_t spotShadowAlpha;
     };
 
     BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
new file mode 100644
index 0000000..4fb5701
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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 "utils/StringUtils.h"
+#include "Texture.h"
+
+#include <cutils/compiler.h>
+#include <GpuMemoryTracker.h>
+#include <utils/Trace.h>
+#include <array>
+#include <sstream>
+#include <unordered_set>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+pthread_t gGpuThread = 0;
+
+#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
+
+const char* TYPE_NAMES[] = {
+        "Texture",
+        "OffscreenBuffer",
+        "Layer",
+};
+
+struct TypeStats {
+    int totalSize = 0;
+    int count = 0;
+};
+
+static std::array<TypeStats, NUM_TYPES> gObjectStats;
+static std::unordered_set<GpuMemoryTracker*> gObjectSet;
+
+void GpuMemoryTracker::notifySizeChanged(int newSize) {
+    int delta = newSize - mSize;
+    mSize = newSize;
+    gObjectStats[static_cast<int>(mType)].totalSize += delta;
+}
+
+void GpuMemoryTracker::startTrackingObject() {
+    auto result = gObjectSet.insert(this);
+    LOG_ALWAYS_FATAL_IF(!result.second,
+            "startTrackingObject() on %p failed, already being tracked!", this);
+    gObjectStats[static_cast<int>(mType)].count++;
+}
+
+void GpuMemoryTracker::stopTrackingObject() {
+    size_t removed = gObjectSet.erase(this);
+    LOG_ALWAYS_FATAL_IF(removed != 1,
+            "stopTrackingObject removed %zd, is %p not being tracked?",
+            removed, this);
+    gObjectStats[static_cast<int>(mType)].count--;
+}
+
+void GpuMemoryTracker::onGLContextCreated() {
+    LOG_ALWAYS_FATAL_IF(gGpuThread != 0, "We already have a GL thread? "
+            "current = %lu, gl thread = %lu", pthread_self(), gGpuThread);
+    gGpuThread = pthread_self();
+}
+
+void GpuMemoryTracker::onGLContextDestroyed() {
+    gGpuThread = 0;
+    if (CC_UNLIKELY(gObjectSet.size() > 0)) {
+        std::stringstream os;
+        dump(os);
+        ALOGE("%s", os.str().c_str());
+        LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
+    }
+}
+
+void GpuMemoryTracker::dump() {
+    std::stringstream strout;
+    dump(strout);
+    ALOGD("%s", strout.str().c_str());
+}
+
+void GpuMemoryTracker::dump(std::ostream& stream) {
+    for (int type = 0; type < NUM_TYPES; type++) {
+        const TypeStats& stats = gObjectStats[type];
+        stream << TYPE_NAMES[type];
+        stream << " is using " << SizePrinter{stats.totalSize};
+        stream << ", count = " << stats.count;
+        stream << std::endl;
+    }
+}
+
+int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
+    return gObjectStats[static_cast<int>(type)].count;
+}
+
+int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
+    return gObjectStats[static_cast<int>(type)].totalSize;
+}
+
+void GpuMemoryTracker::onFrameCompleted() {
+    if (ATRACE_ENABLED()) {
+        char buf[128];
+        for (int type = 0; type < NUM_TYPES; type++) {
+            snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
+            const TypeStats& stats = gObjectStats[type];
+            ATRACE_INT(buf, stats.totalSize);
+            snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
+            ATRACE_INT(buf, stats.count);
+        }
+    }
+
+    std::vector<const Texture*> freeList;
+    for (const auto& obj : gObjectSet) {
+        if (obj->objectType() == GpuObjectType::Texture) {
+            const Texture* texture = static_cast<Texture*>(obj);
+            if (texture->cleanup) {
+                ALOGE("Leaked texture marked for cleanup! id=%u, size %ux%u",
+                        texture->id(), texture->width(), texture->height());
+                freeList.push_back(texture);
+            }
+        }
+    }
+    for (auto& texture : freeList) {
+        const_cast<Texture*>(texture)->deleteTexture();
+        delete texture;
+    }
+}
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
new file mode 100644
index 0000000..851aeae
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <cutils/log.h>
+#include <pthread.h>
+#include <ostream>
+
+namespace android {
+namespace uirenderer {
+
+extern pthread_t gGpuThread;
+
+#define ASSERT_GPU_THREAD() LOG_ALWAYS_FATAL_IF( \
+        !pthread_equal(gGpuThread, pthread_self()), \
+        "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
+        "!= gpu thread %lu", this, static_cast<int>(mType), mSize, \
+        pthread_self(), gGpuThread)
+
+enum class GpuObjectType {
+    Texture = 0,
+    OffscreenBuffer,
+    Layer,
+
+    TypeCount,
+};
+
+class GpuMemoryTracker {
+public:
+    GpuObjectType objectType() { return mType; }
+    int objectSize() { return mSize; }
+
+    static void onGLContextCreated();
+    static void onGLContextDestroyed();
+    static void dump();
+    static void dump(std::ostream& stream);
+    static int getInstanceCount(GpuObjectType type);
+    static int getTotalSize(GpuObjectType type);
+    static void onFrameCompleted();
+
+protected:
+    GpuMemoryTracker(GpuObjectType type) : mType(type) {
+        ASSERT_GPU_THREAD();
+        startTrackingObject();
+    }
+
+    ~GpuMemoryTracker() {
+        notifySizeChanged(0);
+        stopTrackingObject();
+    }
+
+    void notifySizeChanged(int newSize);
+
+private:
+    void startTrackingObject();
+    void stopTrackingObject();
+
+    int mSize = 0;
+    GpuObjectType mType;
+};
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 8c46450..522aa96 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -110,9 +110,7 @@
 
 void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) {
     if (texture) {
-        const uint32_t size = texture->width * texture->height * bytesPerPixel();
-        mSize -= size;
-
+        mSize -= texture->objectSize();
         texture->deleteTexture();
         delete texture;
     }
@@ -167,18 +165,16 @@
     getGradientInfo(colors, count, info);
 
     Texture* texture = new Texture(Caches::getInstance());
-    texture->width = info.width;
-    texture->height = 2;
     texture->blend = info.hasAlpha;
     texture->generation = 1;
 
     // Asume the cache is always big enough
-    const uint32_t size = texture->width * texture->height * bytesPerPixel();
+    const uint32_t size = info.width * 2 * bytesPerPixel();
     while (getSize() + size > mMaxSize) {
         mCache.removeOldest();
     }
 
-    generateTexture(colors, positions, texture);
+    generateTexture(colors, positions, info.width, 2, texture);
 
     mSize += size;
     mCache.put(gradient, texture);
@@ -231,10 +227,10 @@
     dst += 4 * sizeof(float);
 }
 
-void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) {
-    const uint32_t width = texture->width;
+void GradientCache::generateTexture(uint32_t* colors, float* positions,
+        const uint32_t width, const uint32_t height, Texture* texture) {
     const GLsizei rowBytes = width * bytesPerPixel();
-    uint8_t pixels[rowBytes * texture->height];
+    uint8_t pixels[rowBytes * height];
 
     static ChannelSplitter gSplitters[] = {
             &android::uirenderer::GradientCache::splitToBytes,
@@ -277,17 +273,13 @@
 
     memcpy(pixels + rowBytes, pixels, rowBytes);
 
-    glGenTextures(1, &texture->id);
-    Caches::getInstance().textureState().bindTexture(texture->id);
     glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
     if (mUseFloatTexture) {
         // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0,
-                GL_RGBA, GL_FLOAT, pixels);
+        texture->upload(width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, pixels);
     } else {
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+        texture->upload(width, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
     }
 
     texture->setFilter(GL_LINEAR);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 7534c5d..b762ca7 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -143,7 +143,8 @@
     Texture* addLinearGradient(GradientCacheEntry& gradient,
             uint32_t* colors, float* positions, int count);
 
-    void generateTexture(uint32_t* colors, float* positions, Texture* texture);
+    void generateTexture(uint32_t* colors, float* positions,
+            const uint32_t width, const uint32_t height, Texture* texture);
 
     struct GradientInfo {
         uint32_t width;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 0fe20ad..8369266 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -36,7 +36,8 @@
 namespace uirenderer {
 
 Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight)
-        : state(State::Uncached)
+        : GpuMemoryTracker(GpuObjectType::Layer)
+        , state(State::Uncached)
         , caches(Caches::getInstance())
         , renderState(renderState)
         , texture(caches)
@@ -45,8 +46,8 @@
     // preserves the old inc/dec ref locations. This should be changed...
     incStrong(nullptr);
     renderTarget = GL_TEXTURE_2D;
-    texture.width = layerWidth;
-    texture.height = layerHeight;
+    texture.mWidth = layerWidth;
+    texture.mHeight = layerHeight;
     renderState.registerLayer(this);
 }
 
@@ -54,10 +55,9 @@
     renderState.unregisterLayer(this);
     SkSafeUnref(colorFilter);
 
-    if (stencil || fbo || texture.id) {
-        renderState.requireGLContext();
+    if (stencil || fbo || texture.mId) {
         removeFbo();
-        deleteTexture();
+        texture.deleteTexture();
     }
 
     delete[] mesh;
@@ -65,7 +65,7 @@
 
 void Layer::onGlContextLost() {
     removeFbo();
-    deleteTexture();
+    texture.deleteTexture();
 }
 
 uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
@@ -179,8 +179,8 @@
 }
 
 void Layer::bindTexture() const {
-    if (texture.id) {
-        caches.textureState().bindTexture(renderTarget, texture.id);
+    if (texture.mId) {
+        caches.textureState().bindTexture(renderTarget, texture.mId);
     }
 }
 
@@ -191,28 +191,22 @@
 }
 
 void Layer::generateTexture() {
-    if (!texture.id) {
-        glGenTextures(1, &texture.id);
-    }
-}
-
-void Layer::deleteTexture() {
-    if (texture.id) {
-        texture.deleteTexture();
-        texture.id = 0;
+    if (!texture.mId) {
+        glGenTextures(1, &texture.mId);
     }
 }
 
 void Layer::clearTexture() {
-    caches.textureState().unbindTexture(texture.id);
-    texture.id = 0;
+    caches.textureState().unbindTexture(texture.mId);
+    texture.mId = 0;
 }
 
 void Layer::allocateTexture() {
 #if DEBUG_LAYERS
     ALOGD("  Allocate layer: %dx%d", getWidth(), getHeight());
 #endif
-    if (texture.id) {
+    if (texture.mId) {
+        texture.updateSize(getWidth(), getHeight(), GL_RGBA);
         glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
         glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index e90f055..e00ae66 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -24,6 +24,7 @@
 #include <memory>
 
 #include <GLES2/gl2.h>
+#include <GpuMemoryTracker.h>
 
 #include <ui/Region.h>
 
@@ -54,7 +55,7 @@
 /**
  * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
-class Layer : public VirtualLightRefBase {
+class Layer : public VirtualLightRefBase, GpuMemoryTracker {
 public:
     enum class Type {
         Texture,
@@ -94,8 +95,8 @@
         regionRect.set(bounds.leftTop().x, bounds.leftTop().y,
                bounds.rightBottom().x, bounds.rightBottom().y);
 
-        const float texX = 1.0f / float(texture.width);
-        const float texY = 1.0f / float(texture.height);
+        const float texX = 1.0f / float(texture.mWidth);
+        const float texY = 1.0f / float(texture.mHeight);
         const float height = layer.getHeight();
         texCoords.set(
                regionRect.left * texX, (height - regionRect.top) * texY,
@@ -112,11 +113,11 @@
     void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom);
 
     inline uint32_t getWidth() const {
-        return texture.width;
+        return texture.mWidth;
     }
 
     inline uint32_t getHeight() const {
-        return texture.height;
+        return texture.mHeight;
     }
 
     /**
@@ -131,8 +132,7 @@
     bool resize(const uint32_t width, const uint32_t height);
 
     void setSize(uint32_t width, uint32_t height) {
-        texture.width = width;
-        texture.height = height;
+        texture.updateSize(width, height, texture.format());
     }
 
     ANDROID_API void setPaint(const SkPaint* paint);
@@ -201,7 +201,7 @@
     }
 
     inline GLuint getTextureId() const {
-        return texture.id;
+        return texture.id();
     }
 
     inline Texture& getTexture() {
@@ -263,7 +263,6 @@
     void bindTexture() const;
     void generateTexture();
     void allocateTexture();
-    void deleteTexture();
 
     /**
      * When the caller frees the texture itself, the caller
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index b117754..f5681ce 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -112,7 +112,6 @@
         layer->bindTexture();
         layer->setFilter(GL_NEAREST);
         layer->setWrap(GL_CLAMP_TO_EDGE, false);
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
 #if DEBUG_LAYERS
         dump();
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 92b758d..0cd763d 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <GpuMemoryTracker.h>
 #include "OpenGLRenderer.h"
 
 #include "DeferredDisplayList.h"
@@ -200,6 +201,7 @@
 
 #if DEBUG_MEMORY_USAGE
         mCaches.dumpMemoryUsage();
+        GPUMemoryTracker::dump();
 #else
         if (Properties::debugLevel & kDebugMemory) {
             mCaches.dumpMemoryUsage();
@@ -1497,7 +1499,7 @@
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
-            .setModelViewMapUnitToRectSnap(Rect(texture->width, texture->height))
+            .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
             .build();
     renderGlop(glop);
 }
@@ -1601,10 +1603,10 @@
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
-    Rect uv(std::max(0.0f, src.left / texture->width),
-            std::max(0.0f, src.top / texture->height),
-            std::min(1.0f, src.right / texture->width),
-            std::min(1.0f, src.bottom / texture->height));
+    Rect uv(std::max(0.0f, src.left / texture->width()),
+            std::max(0.0f, src.top / texture->height()),
+            std::min(1.0f, src.right / texture->width()),
+            std::min(1.0f, src.bottom / texture->height()));
 
     const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
@@ -1977,7 +1979,7 @@
             .setMeshTexturedUnitQuad(nullptr)
             .setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
-            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
             .build();
     renderGlop(glop);
 }
@@ -2316,7 +2318,7 @@
 
 void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y,
         const SkPaint* paint) {
-    if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) {
+    if (quickRejectSetupScissor(x, y, x + texture->width(), y + texture->height())) {
         return;
     }
 
@@ -2326,7 +2328,7 @@
             .setMeshTexturedUnitQuad(nullptr)
             .setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha)
             .setTransform(*currentSnapshot(),  TransformFlags::None)
-            .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height))
+            .setModelViewMapUnitToRect(Rect(x, y, x + texture->width(), y + texture->height()))
             .build();
     renderGlop(glop);
 }
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 06ea55a..bfabc1d 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -185,7 +185,7 @@
 
 void PathCache::removeTexture(PathTexture* texture) {
     if (texture) {
-        const uint32_t size = texture->width * texture->height;
+        const uint32_t size = texture->width() * texture->height();
 
         // If there is a pending task we must wait for it to return
         // before attempting our cleanup
@@ -209,9 +209,7 @@
             ALOGD("Shape deleted, size = %d", size);
         }
 
-        if (texture->id) {
-            Caches::getInstance().textureState().deleteTexture(texture->id);
-        }
+        texture->deleteTexture();
         delete texture;
     }
 }
@@ -248,8 +246,7 @@
     drawPath(path, paint, bitmap, left, top, offset, width, height);
 
     PathTexture* texture = new PathTexture(Caches::getInstance(),
-            left, top, offset, width, height,
-            path->getGenerationID());
+            left, top, offset, path->getGenerationID());
     generateTexture(entry, &bitmap, texture);
 
     return texture;
@@ -262,7 +259,7 @@
     // Note here that we upload to a texture even if it's bigger than mMaxSize.
     // Such an entry in mCache will only be temporary, since it will be evicted
     // immediately on trim, or on any other Path entering the cache.
-    uint32_t size = texture->width * texture->height;
+    uint32_t size = texture->width() * texture->height();
     mSize += size;
     PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
             texture->id, size, mSize);
@@ -280,24 +277,8 @@
 
 void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
     ATRACE_NAME("Upload Path Texture");
-    SkAutoLockPixels alp(bitmap);
-    if (!bitmap.readyToDraw()) {
-        ALOGE("Cannot generate texture from bitmap");
-        return;
-    }
-
-    glGenTextures(1, &texture->id);
-
-    Caches::getInstance().textureState().bindTexture(texture->id);
-    // Textures are Alpha8
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
-    texture->blend = true;
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
-            GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
-
+    texture->upload(bitmap);
     texture->setFilter(GL_LINEAR);
-    texture->setWrap(GL_CLAMP_TO_EDGE);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -320,16 +301,12 @@
     texture->left = left;
     texture->top = top;
     texture->offset = offset;
-    texture->width = width;
-    texture->height = height;
 
     if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
         SkBitmap* bitmap = new SkBitmap();
         drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height);
         t->setResult(bitmap);
     } else {
-        texture->width = 0;
-        texture->height = 0;
         t->setResult(nullptr);
     }
 }
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 302e9f8..18f380f 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -61,13 +61,11 @@
  */
 struct PathTexture: public Texture {
     PathTexture(Caches& caches, float left, float top,
-            float offset, int width, int height, int generation)
+            float offset, int generation)
             : Texture(caches)
             , left(left)
             , top(top)
             , offset(offset) {
-        this->width = width;
-        this->height = height;
         this->generation = generation;
     }
     PathTexture(Caches& caches, int generation)
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 83652c6..6f4a683 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -57,7 +57,7 @@
 }
 
 static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) {
-    caches->textureState().bindTexture(texture->id);
+    caches->textureState().bindTexture(texture->id());
     texture->setWrapST(wrapS, wrapT);
 }
 
@@ -219,8 +219,8 @@
 
     outData->bitmapSampler = (*textureUnit)++;
 
-    const float width = outData->bitmapTexture->width;
-    const float height = outData->bitmapTexture->height;
+    const float width = outData->bitmapTexture->width();
+    const float height = outData->bitmapTexture->height();
 
     description->hasBitmap = true;
     if (!caches.extensions().hasNPot()
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 51f1652..f1e28b7 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -187,13 +187,10 @@
         texture = new ShadowTexture(caches);
         texture->left = shadow.penX;
         texture->top = shadow.penY;
-        texture->width = shadow.width;
-        texture->height = shadow.height;
         texture->generation = 0;
         texture->blend = true;
 
         const uint32_t size = shadow.width * shadow.height;
-        texture->bitmapSize = size;
 
         // Don't even try to cache a bitmap that's bigger than the cache
         if (size < mMaxSize) {
@@ -202,15 +199,11 @@
             }
         }
 
-        glGenTextures(1, &texture->id);
-
-        caches.textureState().bindTexture(texture->id);
         // Textures are Alpha8
         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+        texture->upload(GL_ALPHA, shadow.width, shadow.height,
                 GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
-
         texture->setFilter(GL_LINEAR);
         texture->setWrap(GL_CLAMP_TO_EDGE);
 
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 5195b45..8a6b28d 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -14,14 +14,29 @@
  * limitations under the License.
  */
 
-#include <utils/Log.h>
-
 #include "Caches.h"
 #include "Texture.h"
+#include "utils/TraceUtils.h"
+
+#include <utils/Log.h>
+
+#include <SkCanvas.h>
 
 namespace android {
 namespace uirenderer {
 
+static int bytesPerPixel(GLint glFormat) {
+    switch (glFormat) {
+    case GL_ALPHA:
+        return 1;
+    case GL_RGB:
+        return 3;
+    case GL_RGBA:
+    default:
+        return 4;
+    }
+}
+
 void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
         GLenum renderTarget) {
 
@@ -32,7 +47,7 @@
         mWrapT = wrapT;
 
         if (bindTexture) {
-            mCaches.textureState().bindTexture(renderTarget, id);
+            mCaches.textureState().bindTexture(renderTarget, mId);
         }
 
         glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
@@ -50,7 +65,7 @@
         mMagFilter = mag;
 
         if (bindTexture) {
-            mCaches.textureState().bindTexture(renderTarget, id);
+            mCaches.textureState().bindTexture(renderTarget, mId);
         }
 
         if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR;
@@ -60,8 +75,189 @@
     }
 }
 
-void Texture::deleteTexture() const {
-    mCaches.textureState().deleteTexture(id);
+void Texture::deleteTexture() {
+    mCaches.textureState().deleteTexture(mId);
+    mId = 0;
+}
+
+bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) {
+    if (mWidth == width && mHeight == height && mFormat == format) {
+        return false;
+    }
+    mWidth = width;
+    mHeight = height;
+    mFormat = format;
+    notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat));
+    return true;
+}
+
+void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
+        GLenum format, GLenum type, const void* pixels) {
+    bool needsAlloc = updateSize(width, height, internalformat);
+    if (!needsAlloc && !pixels) {
+        return;
+    }
+    mCaches.textureState().activateTexture(0);
+    if (!mId) {
+        glGenTextures(1, &mId);
+        needsAlloc = true;
+    }
+    mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
+    if (needsAlloc) {
+        glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+                format, type, pixels);
+    } else {
+        glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+                format, type, pixels);
+    }
+}
+
+static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
+        GLsizei width, GLsizei height, const GLvoid * data) {
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
+    const bool useStride = stride != width
+            && Caches::getInstance().extensions().hasUnpackRowLength();
+    if ((stride == width) || useStride) {
+        if (useStride) {
+            glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
+        }
+
+        if (resize) {
+            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+        } else {
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+        }
+
+        if (useStride) {
+            glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        }
+    } else {
+        //  With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
+        //  if the stride doesn't match the width
+
+        GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
+        if (!temp) return;
+
+        uint8_t * pDst = (uint8_t *)temp;
+        uint8_t * pSrc = (uint8_t *)data;
+        for (GLsizei i = 0; i < height; i++) {
+            memcpy(pDst, pSrc, width * bpp);
+            pDst += width * bpp;
+            pSrc += stride * bpp;
+        }
+
+        if (resize) {
+            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+        } else {
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
+        }
+
+        free(temp);
+    }
+}
+
+static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
+        bool resize, GLenum format, GLenum type) {
+    uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
+            bitmap.width(), bitmap.height(), bitmap.getPixels());
+}
+
+static void colorTypeToGlFormatAndType(SkColorType colorType,
+        GLint* outFormat, GLint* outType) {
+    switch (colorType) {
+    case kAlpha_8_SkColorType:
+        *outFormat = GL_ALPHA;
+        *outType = GL_UNSIGNED_BYTE;
+        break;
+    case kRGB_565_SkColorType:
+        *outFormat = GL_RGB;
+        *outType = GL_UNSIGNED_SHORT_5_6_5;
+        break;
+    // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
+    case kARGB_4444_SkColorType:
+    case kIndex_8_SkColorType:
+    case kN32_SkColorType:
+        *outFormat = GL_RGBA;
+        *outType = GL_UNSIGNED_BYTE;
+        break;
+    default:
+        LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
+        break;
+    }
+}
+
+void Texture::upload(const SkBitmap& bitmap) {
+    SkAutoLockPixels alp(bitmap);
+
+    if (!bitmap.readyToDraw()) {
+        ALOGE("Cannot generate texture from bitmap");
+        return;
+    }
+
+    ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height());
+
+    // We could also enable mipmapping if both bitmap dimensions are powers
+    // of 2 but we'd have to deal with size changes. Let's keep this simple
+    const bool canMipMap = mCaches.extensions().hasNPot();
+
+    // If the texture had mipmap enabled but not anymore,
+    // force a glTexImage2D to discard the mipmap levels
+    bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap();
+
+    if (!mId) {
+        glGenTextures(1, &mId);
+        needsAlloc = true;
+    }
+
+    GLint format, type;
+    colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type);
+
+    if (updateSize(bitmap.width(), bitmap.height(), format)) {
+        needsAlloc = true;
+    }
+
+    blend = !bitmap.isOpaque();
+    mCaches.textureState().bindTexture(mId);
+
+    if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType
+            || bitmap.colorType() == kIndex_8_SkColorType)) {
+        SkBitmap rgbaBitmap;
+        rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight,
+                bitmap.alphaType()));
+        rgbaBitmap.eraseColor(0);
+
+        SkCanvas canvas(rgbaBitmap);
+        canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
+
+        uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type);
+    } else {
+        uploadSkBitmapToTexture(bitmap, needsAlloc, format, type);
+    }
+
+    if (canMipMap) {
+        mipMap = bitmap.hasHardwareMipMap();
+        if (mipMap) {
+            glGenerateMipmap(GL_TEXTURE_2D);
+        }
+    }
+
+    if (mFirstFilter) {
+        setFilter(GL_NEAREST);
+    }
+
+    if (mFirstWrap) {
+        setWrap(GL_CLAMP_TO_EDGE);
+    }
+}
+
+void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) {
+    mId = id;
+    mWidth = width;
+    mHeight = height;
+    mFormat = format;
+    // We're wrapping an existing texture, so don't double count this memory
+    notifySizeChanged(0);
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 1c544b9..4e8e6dc 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -17,20 +17,27 @@
 #ifndef ANDROID_HWUI_TEXTURE_H
 #define ANDROID_HWUI_TEXTURE_H
 
+#include "GpuMemoryTracker.h"
+
 #include <GLES2/gl2.h>
+#include <SkBitmap.h>
 
 namespace android {
 namespace uirenderer {
 
 class Caches;
 class UvMapper;
+class Layer;
 
 /**
  * Represents an OpenGL texture.
  */
-class Texture {
+class Texture : public GpuMemoryTracker {
 public:
-    Texture(Caches& caches) : mCaches(caches) { }
+    Texture(Caches& caches)
+        : GpuMemoryTracker(GpuObjectType::Texture)
+        , mCaches(caches)
+    { }
 
     virtual ~Texture() { }
 
@@ -53,12 +60,55 @@
     /**
      * Convenience method to call glDeleteTextures() on this texture's id.
      */
-    void deleteTexture() const;
+    void deleteTexture();
 
     /**
-     * Name of the texture.
+     * Sets the width, height, and format of the texture along with allocating
+     * the texture ID. Does nothing if the width, height, and format are already
+     * the requested values.
+     *
+     * The image data is undefined after calling this.
      */
-    GLuint id = 0;
+    void resize(uint32_t width, uint32_t height, GLint format) {
+        upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+    }
+
+    /**
+     * Updates this Texture with the contents of the provided SkBitmap,
+     * also setting the appropriate width, height, and format. It is not necessary
+     * to call resize() prior to this.
+     *
+     * Note this does not set the generation from the SkBitmap.
+     */
+    void upload(const SkBitmap& source);
+
+    /**
+     * Basically glTexImage2D/glTexSubImage2D.
+     */
+    void upload(GLint internalformat, uint32_t width, uint32_t height,
+            GLenum format, GLenum type, const void* pixels);
+
+    /**
+     * Wraps an existing texture.
+     */
+    void wrap(GLuint id, uint32_t width, uint32_t height, GLint format);
+
+    GLuint id() const {
+        return mId;
+    }
+
+    uint32_t width() const {
+        return mWidth;
+    }
+
+    uint32_t height() const {
+        return mHeight;
+    }
+
+    GLint format() const {
+        return mFormat;
+    }
+
     /**
      * Generation of the backing bitmap,
      */
@@ -68,14 +118,6 @@
      */
     bool blend = false;
     /**
-     * Width of the backing bitmap.
-     */
-    uint32_t width = 0;
-    /**
-     * Height of the backing bitmap.
-     */
-    uint32_t height = 0;
-    /**
      * Indicates whether this texture should be cleaned up after use.
      */
     bool cleanup = false;
@@ -100,6 +142,19 @@
     void* isInUse = nullptr;
 
 private:
+    // TODO: Temporarily grant private access to Layer, remove once
+    // Layer can be de-tangled from being a dual-purpose render target
+    // and external texture wrapper
+    friend class Layer;
+
+    // Returns true if the size changed, false if it was the same
+    bool updateSize(uint32_t width, uint32_t height, GLint format);
+
+    GLuint mId = 0;
+    uint32_t mWidth = 0;
+    uint32_t mHeight = 0;
+    GLint mFormat = 0;
+
     /**
      * Last wrap modes set on this texture.
      */
@@ -120,7 +175,7 @@
 
 class AutoTexture {
 public:
-    AutoTexture(const Texture* texture)
+    AutoTexture(Texture* texture)
             : texture(texture) {}
     ~AutoTexture() {
         if (texture && texture->cleanup) {
@@ -129,7 +184,7 @@
         }
     }
 
-    const Texture *const texture;
+    Texture* const texture;
 }; // class AutoTexture
 
 }; // namespace uirenderer
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 21901cf..31bfa3a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -166,7 +166,8 @@
         if (canCache) {
             texture = new Texture(Caches::getInstance());
             texture->bitmapSize = size;
-            Caches::getInstance().textureState().generateTexture(bitmap, texture, false);
+            texture->generation = bitmap->getGenerationID();
+            texture->upload(*bitmap);
 
             mSize += size;
             TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
@@ -179,7 +180,8 @@
     } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
         // Texture was in the cache but is dirty, re-upload
         // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
-        Caches::getInstance().textureState().generateTexture(bitmap, texture, true);
+        texture->upload(*bitmap);
+        texture->generation = bitmap->getGenerationID();
     }
 
     return texture;
@@ -204,7 +206,8 @@
         const uint32_t size = bitmap->rowBytes() * bitmap->height();
         texture = new Texture(Caches::getInstance());
         texture->bitmapSize = size;
-        Caches::getInstance().textureState().generateTexture(bitmap, texture, false);
+        texture->upload(*bitmap);
+        texture->generation = bitmap->getGenerationID();
         texture->cleanup = true;
     }
 
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 191c8a8..463450c 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -25,6 +25,7 @@
 #include "Debug.h"
 
 #include <vector>
+#include <unordered_map>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index d2685da..8ba4761 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -111,11 +111,11 @@
 
 CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
         : mTexture(Caches::getInstance())
+        , mWidth(width)
+        , mHeight(height)
         , mFormat(format)
         , mMaxQuadCount(maxQuadCount)
         , mCaches(Caches::getInstance()) {
-    mTexture.width = width;
-    mTexture.height = height;
     mTexture.blend = true;
 
     mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
@@ -160,10 +160,7 @@
         delete mPixelBuffer;
         mPixelBuffer = nullptr;
     }
-    if (mTexture.id) {
-        mCaches.textureState().deleteTexture(mTexture.id);
-        mTexture.id = 0;
-    }
+    mTexture.deleteTexture();
     mDirty = false;
     mCurrentQuad = 0;
 }
@@ -183,22 +180,9 @@
         mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
     }
 
-    if (!mTexture.id) {
-        glGenTextures(1, &mTexture.id);
-
-        mCaches.textureState().bindTexture(mTexture.id);
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-        // Initialize texture dimensions
-        glTexImage2D(GL_TEXTURE_2D, 0, mFormat, getWidth(), getHeight(), 0,
-                mFormat, GL_UNSIGNED_BYTE, nullptr);
-
-        const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
-
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-    }
+    mTexture.resize(mWidth, mHeight, mFormat);
+    mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
+    mTexture.setWrap(GL_CLAMP_TO_EDGE);
 }
 
 bool CacheTexture::upload() {
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 6dabc76..5510666 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -92,11 +92,11 @@
     bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
 
     inline uint16_t getWidth() const {
-        return mTexture.width;
+        return mWidth;
     }
 
     inline uint16_t getHeight() const {
-        return mTexture.height;
+        return mHeight;
     }
 
     inline GLenum getFormat() const {
@@ -122,7 +122,7 @@
 
     GLuint getTextureId() {
         allocatePixelBuffer();
-        return mTexture.id;
+        return mTexture.id();
     }
 
     inline bool isDirty() const {
@@ -183,6 +183,7 @@
 
     PixelBuffer* mPixelBuffer = nullptr;
     Texture mTexture;
+    uint32_t mWidth, mHeight;
     GLenum mFormat;
     bool mLinearFiltering = false;
     bool mDirty = false;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 227b640..98c94df 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -34,29 +34,22 @@
 
 OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
         uint32_t viewportWidth, uint32_t viewportHeight)
-        : renderState(renderState)
+        : GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
+        , renderState(renderState)
         , viewportWidth(viewportWidth)
         , viewportHeight(viewportHeight)
         , texture(caches) {
-    texture.width = computeIdealDimension(viewportWidth);
-    texture.height = computeIdealDimension(viewportHeight);
+    uint32_t width = computeIdealDimension(viewportWidth);
+    uint32_t height = computeIdealDimension(viewportHeight);
+    texture.resize(width, height, GL_RGBA);
     texture.blend = true;
-
-    caches.textureState().activateTexture(0);
-    glGenTextures(1, &texture.id);
-    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
-    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+    texture.setWrap(GL_CLAMP_TO_EDGE);
     // not setting filter on texture, since it's set when drawing, based on transform
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
 }
 
 Rect OffscreenBuffer::getTextureCoordinates() {
-    const float texX = 1.0f / float(texture.width);
-    const float texY = 1.0f / float(texture.height);
+    const float texX = 1.0f / static_cast<float>(texture.width());
+    const float texY = 1.0f / static_cast<float>(texture.height());
     return Rect(0, viewportHeight * texY, viewportWidth * texX, 0);
 }
 
@@ -69,8 +62,8 @@
     size_t count;
     const android::Rect* rects = safeRegion.getArray(&count);
 
-    const float texX = 1.0f / float(texture.width);
-    const float texY = 1.0f / float(texture.height);
+    const float texX = 1.0f / float(texture.width());
+    const float texY = 1.0f / float(texture.height());
 
     FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
     TextureVertex* mesh = &meshVector[0];
@@ -157,8 +150,8 @@
 OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
         const uint32_t width, const uint32_t height) {
     RenderState& renderState = layer->renderState;
-    if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
-            && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+    if (layer->texture.width() == OffscreenBuffer::computeIdealDimension(width)
+            && layer->texture.height() == OffscreenBuffer::computeIdealDimension(height)) {
         // resize in place
         layer->viewportWidth = width;
         layer->viewportHeight = height;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index 2d8d529..94155ef 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -17,10 +17,10 @@
 #ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
 #define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
 
+#include <GpuMemoryTracker.h>
 #include "Caches.h"
 #include "Texture.h"
 #include "utils/Macros.h"
-
 #include <ui/Region.h>
 
 #include <set>
@@ -40,7 +40,7 @@
  * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
  * the purpose of improving reuse.
  */
-class OffscreenBuffer {
+class OffscreenBuffer : GpuMemoryTracker {
 public:
     OffscreenBuffer(RenderState& renderState, Caches& caches,
             uint32_t viewportWidth, uint32_t viewportHeight);
@@ -58,7 +58,7 @@
 
     static uint32_t computeIdealDimension(uint32_t dimension);
 
-    uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+    uint32_t getSizeInBytes() { return texture.objectSize(); }
 
     RenderState& renderState;
 
@@ -124,8 +124,8 @@
 
         Entry(OffscreenBuffer* layer)
                 : layer(layer)
-                , width(layer->texture.width)
-                , height(layer->texture.height) {
+                , width(layer->texture.width())
+                , height(layer->texture.height()) {
         }
 
         static int compare(const Entry& lhs, const Entry& rhs);
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 4fa8200..b6dba02 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <GpuMemoryTracker.h>
 #include "renderstate/RenderState.h"
 
 #include "renderthread/CanvasContext.h"
 #include "renderthread/EglManager.h"
 #include "utils/GLUtils.h"
-
 #include <algorithm>
 
 namespace android {
@@ -40,6 +40,8 @@
 void RenderState::onGLContextCreated() {
     LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
             "State object lifecycle not managed correctly");
+    GpuMemoryTracker::onGLContextCreated();
+
     mBlend = new Blend();
     mMeshState = new MeshState();
     mScissor = new Scissor();
@@ -106,6 +108,8 @@
     mScissor = nullptr;
     delete mStencil;
     mStencil = nullptr;
+
+    GpuMemoryTracker::onGLContextDestroyed();
 }
 
 void RenderState::flush(Caches::FlushMode mode) {
@@ -205,17 +209,6 @@
     }
 }
 
-void RenderState::requireGLContext() {
-    assertOnGLThread();
-    LOG_ALWAYS_FATAL_IF(!mRenderThread.eglManager().hasEglContext(),
-            "No GL context!");
-}
-
-void RenderState::assertOnGLThread() {
-    pthread_t curr = pthread_self();
-    LOG_ALWAYS_FATAL_IF(!pthread_equal(mThreadId, curr), "Wrong thread!");
-}
-
 class DecStrongTask : public renderthread::RenderTask {
 public:
     DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
@@ -231,7 +224,11 @@
 };
 
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
-    mRenderThread.queue(new DecStrongTask(object));
+    if (pthread_equal(mThreadId, pthread_self())) {
+        object->decStrong(nullptr);
+    } else {
+        mRenderThread.queue(new DecStrongTask(object));
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -310,7 +307,7 @@
             texture.texture->setFilter(texture.filter, true, false, texture.target);
         }
 
-        mCaches->textureState().bindTexture(texture.target, texture.texture->id);
+        mCaches->textureState().bindTexture(texture.target, texture.texture->id());
         meshState().enableTexCoordsVertexArray();
         meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride);
 
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index dcd5ea6..e5d3e79 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -86,8 +86,6 @@
         mRegisteredContexts.erase(context);
     }
 
-    void requireGLContext();
-
     // TODO: This system is a little clunky feeling, this could use some
     // more thinking...
     void postDecStrong(VirtualLightRefBase* object);
@@ -107,7 +105,6 @@
 private:
     void interruptForFunctorInvoke();
     void resumeFromFunctorInvoke();
-    void assertOnGLThread();
 
     RenderState(renderthread::RenderThread& thread);
     ~RenderState();
diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp
index 1f50f71..26ebdee 100644
--- a/libs/hwui/renderstate/TextureState.cpp
+++ b/libs/hwui/renderstate/TextureState.cpp
@@ -34,134 +34,6 @@
     GL_TEXTURE3
 };
 
-static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
-        GLsizei width, GLsizei height, const GLvoid * data) {
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
-    const bool useStride = stride != width
-            && Caches::getInstance().extensions().hasUnpackRowLength();
-    if ((stride == width) || useStride) {
-        if (useStride) {
-            glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
-        }
-
-        if (resize) {
-            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
-        } else {
-            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
-        }
-
-        if (useStride) {
-            glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-        }
-    } else {
-        //  With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
-        //  if the stride doesn't match the width
-
-        GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
-        if (!temp) return;
-
-        uint8_t * pDst = (uint8_t *)temp;
-        uint8_t * pSrc = (uint8_t *)data;
-        for (GLsizei i = 0; i < height; i++) {
-            memcpy(pDst, pSrc, width * bpp);
-            pDst += width * bpp;
-            pSrc += stride * bpp;
-        }
-
-        if (resize) {
-            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
-        } else {
-            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
-        }
-
-        free(temp);
-    }
-}
-
-static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
-        bool resize, GLenum format, GLenum type) {
-    uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
-            bitmap.width(), bitmap.height(), bitmap.getPixels());
-}
-
-void TextureState::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) {
-    SkAutoLockPixels alp(*bitmap);
-
-    if (!bitmap->readyToDraw()) {
-        ALOGE("Cannot generate texture from bitmap");
-        return;
-    }
-
-    ATRACE_FORMAT("Upload %ux%u Texture", bitmap->width(), bitmap->height());
-
-    // We could also enable mipmapping if both bitmap dimensions are powers
-    // of 2 but we'd have to deal with size changes. Let's keep this simple
-    const bool canMipMap = Caches::getInstance().extensions().hasNPot();
-
-    // If the texture had mipmap enabled but not anymore,
-    // force a glTexImage2D to discard the mipmap levels
-    const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
-            bitmap->height() != int(texture->height) ||
-            (regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap());
-
-    if (!regenerate) {
-        glGenTextures(1, &texture->id);
-    }
-
-    texture->generation = bitmap->getGenerationID();
-    texture->width = bitmap->width();
-    texture->height = bitmap->height();
-
-    bindTexture(texture->id);
-
-    switch (bitmap->colorType()) {
-    case kAlpha_8_SkColorType:
-        uploadSkBitmapToTexture(*bitmap, resize, GL_ALPHA, GL_UNSIGNED_BYTE);
-        texture->blend = true;
-        break;
-    case kRGB_565_SkColorType:
-        uploadSkBitmapToTexture(*bitmap, resize, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
-        texture->blend = false;
-        break;
-    case kN32_SkColorType:
-        uploadSkBitmapToTexture(*bitmap, resize, GL_RGBA, GL_UNSIGNED_BYTE);
-        // Do this after calling getPixels() to make sure Skia's deferred
-        // decoding happened
-        texture->blend = !bitmap->isOpaque();
-        break;
-    case kARGB_4444_SkColorType:
-    case kIndex_8_SkColorType: {
-        SkBitmap rgbaBitmap;
-        rgbaBitmap.allocPixels(SkImageInfo::MakeN32(texture->width, texture->height,
-                bitmap->alphaType()));
-        rgbaBitmap.eraseColor(0);
-
-        SkCanvas canvas(rgbaBitmap);
-        canvas.drawBitmap(*bitmap, 0.0f, 0.0f, nullptr);
-
-        uploadSkBitmapToTexture(rgbaBitmap, resize, GL_RGBA, GL_UNSIGNED_BYTE);
-        texture->blend = !bitmap->isOpaque();
-        break;
-    }
-    default:
-        ALOGW("Unsupported bitmap colorType: %d", bitmap->colorType());
-        break;
-    }
-
-    if (canMipMap) {
-        texture->mipMap = bitmap->hasHardwareMipMap();
-        if (texture->mipMap) {
-            glGenerateMipmap(GL_TEXTURE_2D);
-        }
-    }
-
-    if (!regenerate) {
-        texture->setFilter(GL_NEAREST);
-        texture->setWrap(GL_CLAMP_TO_EDGE);
-    }
-}
-
 TextureState::TextureState()
         : mTextureUnit(0) {
     glActiveTexture(kTextureUnits[0]);
diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h
index 3a2b85a..ec94d7e 100644
--- a/libs/hwui/renderstate/TextureState.h
+++ b/libs/hwui/renderstate/TextureState.h
@@ -76,13 +76,6 @@
      */
     void unbindTexture(GLuint texture);
 
-    /**
-     * Generates the texture from a bitmap into the specified texture structure.
-     *
-     * @param regenerate If true, the bitmap data is reuploaded into the texture, but
-     *        no new texture is generated.
-     */
-    void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate);
 private:
     // total number of texture units available for use
     static const int kTextureUnitsCount = 4;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 24d43df..dd48a83 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <GpuMemoryTracker.h>
 #include "CanvasContext.h"
 
 #include "AnimationContext.h"
@@ -497,6 +498,8 @@
 
     mJankTracker.addFrame(*mCurrentFrameInfo);
     mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+
+    GpuMemoryTracker::onFrameCompleted();
 }
 
 // Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 43282c9..72c7e4e 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -450,6 +450,11 @@
     } else {
         fprintf(file, "\nNo caches instance.\n");
     }
+#if HWUI_NEW_OPS
+    fprintf(file, "\nPipeline=FrameBuilder\n");
+#else
+    fprintf(file, "\nPipeline=OpenGLRenderer\n");
+#endif
     fflush(file);
     return nullptr;
 }
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 624f3bd..d56693c 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,10 @@
 #include "DeferredLayerUpdater.h"
 #include "LayerRenderer.h"
 
+#include <unistd.h>
+#include <signal.h>
+#include <setjmp.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -121,5 +125,41 @@
     canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint);
 }
 
+static void defaultCrashHandler() {
+    fprintf(stderr, "RenderThread crashed!");
+}
+
+static jmp_buf gErrJmpBuff;
+static std::function<void()> gCrashHandler = defaultCrashHandler;
+
+static void signalHandler(int sig) {
+    longjmp(gErrJmpBuff, 1);
+}
+
+void TestUtils::setRenderThreadCrashHandler(std::function<void()> crashHandler) {
+    gCrashHandler = crashHandler;
+}
+
+void TestUtils::TestTask::run() {
+    struct sigaction act;
+    memset(&act, 0, sizeof(act));
+    act.sa_handler = signalHandler;
+
+    if (setjmp(gErrJmpBuff)) {
+        gCrashHandler();
+        return;
+    }
+
+    sigaction(SIGABRT, &act, nullptr);
+
+
+    // RenderState only valid once RenderThread is running, so queried here
+    RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
+
+    renderState.onGLContextCreated();
+    rtCallback(renderthread::RenderThread::getInstance());
+    renderState.onGLContextDestroyed();
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index edde31e..ae08142 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -171,21 +171,23 @@
         syncHierarchyPropertiesAndDisplayListImpl(node.get());
     }
 
+    static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+        TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+        std::vector<sp<RenderNode>> vec;
+        vec.emplace_back(node);
+        return vec;
+    }
+
     typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
 
+    static void setRenderThreadCrashHandler(std::function<void()> crashHandler);
+
     class TestTask : public renderthread::RenderTask {
     public:
         TestTask(RtCallback rtCallback)
                 : rtCallback(rtCallback) {}
         virtual ~TestTask() {}
-        virtual void run() override {
-            // RenderState only valid once RenderThread is running, so queried here
-            RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
-
-            renderState.onGLContextCreated();
-            rtCallback(renderthread::RenderThread::getInstance());
-            renderState.onGLContextDestroyed();
-        };
+        virtual void run() override;
         RtCallback rtCallback;
     };
 
@@ -197,6 +199,10 @@
         renderthread::RenderThread::getInstance().queueAndWait(&task);
     }
 
+    static bool isRenderThreadRunning() {
+        return renderthread::RenderThread::hasInstance();
+    }
+
     static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
 
     static void drawTextToCanvas(TestCanvas* canvas, const char* text,
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 1616a95..02a3950 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -25,6 +25,7 @@
 #include <unistd.h>
 #include <unordered_map>
 #include <vector>
+#include <pthread.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
diff --git a/libs/hwui/tests/unit/CrashHandlerInjector.cpp b/libs/hwui/tests/unit/CrashHandlerInjector.cpp
new file mode 100644
index 0000000..685c264
--- /dev/null
+++ b/libs/hwui/tests/unit/CrashHandlerInjector.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+
+using namespace android::uirenderer;
+
+static void gunitCrashHandler() {
+    FAIL() << "RenderThread fatal exception!";
+}
+
+static void hookError() {
+    TestUtils::setRenderThreadCrashHandler(gunitCrashHandler);
+}
+
+class HookErrorInit {
+public:
+    HookErrorInit() { hookError(); }
+};
+
+static HookErrorInit sInit;
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index bded50a..b51bd2f 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -32,13 +32,6 @@
 const LayerUpdateQueue sEmptyLayerUpdateQueue;
 const Vector3 sLightCenter = {100, 100, 100};
 
-static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector<sp<RenderNode>> vec;
-    vec.emplace_back(node);
-    return vec;
-}
-
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
  * virtual methods.
@@ -139,7 +132,7 @@
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
@@ -165,7 +158,7 @@
         canvas.drawPoint(50, 50, strokedPaint);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SimpleStrokeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
@@ -180,7 +173,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
 
     FailRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
@@ -215,7 +208,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SimpleBatchingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -256,7 +249,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     ClippedMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -284,7 +277,7 @@
         TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     TextMergingTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
@@ -315,7 +308,7 @@
         }
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     TextStrikethroughTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -349,7 +342,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     TextureLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex());
@@ -394,7 +387,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(parent), sLightCenter);
+            TestUtils::createSyncedNodeList(parent), sLightCenter);
     RenderNodeTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -418,7 +411,7 @@
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, createSyncedNodeList(node), sLightCenter);
+            200, 200, TestUtils::createSyncedNodeList(node), sLightCenter);
     ClippedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
 }
@@ -460,7 +453,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SaveLayerSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -532,7 +525,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SaveLayerNestedTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
@@ -552,7 +545,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
@@ -590,12 +583,12 @@
 
     auto node = TestUtils::createNode(0, 0, 200, 200,
             [](RenderProperties& props, RecordingCanvas& canvas) {
-        canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kMatrixClip_SaveFlag);
+        canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
         canvas.drawRect(0, 0, 200, 200, SkPaint());
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SaveLayerUnclippedSimpleTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -649,7 +642,7 @@
         canvas.restoreToCount(restoreTo);
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SaveLayerUnclippedMergedClearsTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex())
@@ -711,7 +704,7 @@
         canvas.restore();
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     SaveLayerUnclippedComplexTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(12, renderer.getIndex());
@@ -762,7 +755,7 @@
     OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
     *layerHandle = &layer;
 
-    auto syncedNodeList = createSyncedNodeList(node);
+    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
 
     // only enqueue partial damage
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
@@ -863,7 +856,7 @@
     OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
     *(parent->getLayerHandle()) = &parentLayer;
 
-    auto syncedList = createSyncedNodeList(parent);
+    auto syncedList = TestUtils::createSyncedNodeList(parent);
 
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
@@ -919,7 +912,7 @@
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            createSyncedNodeList(parent), sLightCenter);
+            TestUtils::createSyncedNodeList(parent), sLightCenter);
     ZReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
@@ -1002,7 +995,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
-            createSyncedNodeList(parent), sLightCenter);
+            TestUtils::createSyncedNodeList(parent), sLightCenter);
     ProjectionReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(3, renderer.getIndex());
@@ -1045,7 +1038,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(parent), sLightCenter);
+            TestUtils::createSyncedNodeList(parent), sLightCenter);
     ShadowTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
@@ -1086,7 +1079,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+            TestUtils::createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
     ShadowSaveLayerTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(5, renderer.getIndex());
@@ -1132,7 +1125,7 @@
     layer.setWindowTransform(windowTransform);
     *layerHandle = &layer;
 
-    auto syncedList = createSyncedNodeList(parent);
+    auto syncedList = TestUtils::createSyncedNodeList(parent);
     LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
     FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
@@ -1165,7 +1158,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
-            createSyncedNodeList(parent), sLightCenter);
+            TestUtils::createSyncedNodeList(parent), sLightCenter);
     ShadowLayeringTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(4, renderer.getIndex());
@@ -1193,7 +1186,7 @@
     });
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
-            createSyncedNodeList(node), sLightCenter);
+            TestUtils::createSyncedNodeList(node), sLightCenter);
     PropertyTestRenderer renderer(opValidateCallback);
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
@@ -1332,7 +1325,7 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 10000, 10000, paint);
     });
-    auto nodes = createSyncedNodeList(node); // sync before querying height
+    auto nodes = TestUtils::createSyncedNodeList(node); // sync before querying height
 
     FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter);
     SaveLayerAlphaClipTestRenderer renderer(outObservedData);
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
new file mode 100644
index 0000000..aa1dcb2
--- /dev/null
+++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
@@ -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.
+ */
+
+
+#include <gtest/gtest.h>
+#include <GpuMemoryTracker.h>
+
+#include "renderthread/EglManager.h"
+#include "renderthread/RenderThread.h"
+#include "tests/common/TestUtils.h"
+
+#include <utils/StrongPointer.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+class TestGPUObject : public GpuMemoryTracker {
+public:
+    TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
+
+    void changeSize(int newSize) {
+        notifySizeChanged(newSize);
+    }
+};
+
+// Other tests may have created a renderthread and EGL context.
+// This will destroy the EGLContext on RenderThread if it exists so that the
+// current thread can spoof being a GPU thread
+static void destroyEglContext() {
+    if (TestUtils::isRenderThreadRunning()) {
+        TestUtils::runOnRenderThread([](RenderThread& thread) {
+            thread.eglManager().destroy();
+        });
+    }
+}
+
+TEST(GpuMemoryTracker, sizeCheck) {
+    destroyEglContext();
+
+    GpuMemoryTracker::onGLContextCreated();
+    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+    {
+        TestGPUObject myObj;
+        ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+        myObj.changeSize(500);
+        ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+        myObj.changeSize(1000);
+        ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+        myObj.changeSize(300);
+        ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+    }
+    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+    GpuMemoryTracker::onGLContextDestroyed();
+}
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
new file mode 100644
index 0000000..41e44fc
--- /dev/null
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "BakedOpRenderer.h"
+#include "BakedOpDispatcher.h"
+#include "FrameBuilder.h"
+#include "LayerUpdateQueue.h"
+#include "RecordingCanvas.h"
+#include "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const Vector3 sLightCenter = {100, 100, 100};
+
+RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
+    auto node = TestUtils::createNode(0, 0, 200, 200,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
+        canvas.drawRect(0, 0, 200, 200, SkPaint());
+        canvas.restore();
+    });
+    BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128};
+    RenderState& renderState = renderThread.renderState();
+    Caches& caches = Caches::getInstance();
+
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            TestUtils::createSyncedNodeList(node), sLightCenter);
+    BakedOpRenderer renderer(caches, renderState, true, lightInfo);
+    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+}
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
index e96e9ba..2fd8795 100644
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -36,8 +36,8 @@
         EXPECT_EQ(49u, layer.viewportWidth);
         EXPECT_EQ(149u, layer.viewportHeight);
 
-        EXPECT_EQ(64u, layer.texture.width);
-        EXPECT_EQ(192u, layer.texture.height);
+        EXPECT_EQ(64u, layer.texture.width());
+        EXPECT_EQ(192u, layer.texture.height());
 
         EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
     });
@@ -100,8 +100,8 @@
         ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
         EXPECT_EQ(60u, layer->viewportWidth);
         EXPECT_EQ(55u, layer->viewportHeight);
-        EXPECT_EQ(64u, layer->texture.width);
-        EXPECT_EQ(64u, layer->texture.height);
+        EXPECT_EQ(64u, layer->texture.width());
+        EXPECT_EQ(64u, layer->texture.height());
 
         // resized to use different object in pool
         auto layer2 = pool.get(thread.renderState(), 128u, 128u);
@@ -110,12 +110,14 @@
         ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
         EXPECT_EQ(120u, layer2->viewportWidth);
         EXPECT_EQ(125u, layer2->viewportHeight);
-        EXPECT_EQ(128u, layer2->texture.width);
-        EXPECT_EQ(128u, layer2->texture.height);
+        EXPECT_EQ(128u, layer2->texture.width());
+        EXPECT_EQ(128u, layer2->texture.height());
 
         // original allocation now only thing in pool
         EXPECT_EQ(1u, pool.getCount());
         EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
+
+        pool.putOrDelete(layer2);
     });
 }
 
diff --git a/libs/hwui/tests/unit/StringUtilsTests.cpp b/libs/hwui/tests/unit/StringUtilsTests.cpp
index 6b2e265..b60e96c 100644
--- a/libs/hwui/tests/unit/StringUtilsTests.cpp
+++ b/libs/hwui/tests/unit/StringUtilsTests.cpp
@@ -36,3 +36,18 @@
     EXPECT_TRUE(collection.has("GL_ext1"));
     EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
 }
+
+TEST(StringUtils, sizePrinter) {
+    std::stringstream os;
+    os << SizePrinter{500};
+    EXPECT_EQ("500.00B", os.str());
+    os.str("");
+    os << SizePrinter{46080};
+    EXPECT_EQ("45.00KiB", os.str());
+    os.str("");
+    os << SizePrinter{5 * 1024 * 1024 + 520 * 1024};
+    EXPECT_EQ("5.51MiB", os.str());
+    os.str("");
+    os << SizePrinter{2147483647};
+    EXPECT_EQ("2048.00MiB", os.str());
+}
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
index 055869f..05a3d59 100644
--- a/libs/hwui/utils/StringUtils.h
+++ b/libs/hwui/utils/StringUtils.h
@@ -18,6 +18,8 @@
 
 #include <string>
 #include <unordered_set>
+#include <ostream>
+#include <iomanip>
 
 namespace android {
 namespace uirenderer {
@@ -34,6 +36,21 @@
     static unordered_string_set split(const char* spacedList);
 };
 
+struct SizePrinter {
+    int bytes;
+    friend std::ostream& operator<<(std::ostream& stream, const SizePrinter& d) {
+        static const char* SUFFIXES[] = {"B", "KiB", "MiB"};
+        size_t suffix = 0;
+        double temp = d.bytes;
+        while (temp > 1000 && suffix < 2) {
+            temp /= 1024.0;
+            suffix++;
+        }
+        stream << std::fixed << std::setprecision(2) << temp << SUFFIXES[suffix];
+        return stream;
+    }
+};
+
 } /* namespace uirenderer */
 } /* namespace android */
 
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index 05b4a72..dcc4946 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -18,6 +18,7 @@
 #include "AnimationContext.h"
 #include "DisplayListCanvas.h"
 #include "IContextFactory.h"
+#include "RecordingCanvas.h"
 #include "RenderNode.h"
 #include "SkTypes.h"
 #include "gui/BufferQueue.h"
@@ -88,9 +89,11 @@
         mProxy->setup(mSize.width(), mSize.height(), 800.0f,
                              255 * 0.075f, 255 * 0.15f);
         mProxy->setLightCenter(lightVector);
-        mCanvas.reset(new
-            android::uirenderer::DisplayListCanvas(mSize.width(),
-                                                   mSize.height()));
+#if HWUI_NEW_OPS
+        mCanvas.reset(new android::uirenderer::RecordingCanvas(mSize.width(), mSize.height()));
+#else
+        mCanvas.reset(new android::uirenderer::DisplayListCanvas(mSize.width(), mSize.height()));
+#endif
     }
 
     SkCanvas* prepareToDraw() {
@@ -168,7 +171,11 @@
 
     std::unique_ptr<android::uirenderer::RenderNode> mRootNode;
     std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy;
+#if HWUI_NEW_OPS
+    std::unique_ptr<android::uirenderer::RecordingCanvas> mCanvas;
+#else
     std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas;
+#endif
     android::sp<android::IGraphicBufferProducer> mProducer;
     android::sp<android::IGraphicBufferConsumer> mConsumer;
     android::sp<android::CpuConsumer> mCpuConsumer;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index e92f294..606447b5 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -223,10 +223,19 @@
     @SystemApi
     public final static int FLAG_BYPASS_MUTE = 0x1 << 7;
 
+    /**
+     * @hide
+     * Flag requesting a low latency path.
+     * When using this flag, the sample rate must match the native sample rate
+     * of the device. Effects processing is also unavailable.
+     */
+    public final static int FLAG_LOW_LATENCY = 0x1 << 8;
+
     private final static int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO |
             FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY |
-            FLAG_BYPASS_MUTE;
-    private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED | FLAG_HW_AV_SYNC;
+            FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY;
+    private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
+            FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
 
     private int mUsage = USAGE_UNKNOWN;
     private int mContentType = CONTENT_TYPE_UNKNOWN;
@@ -520,8 +529,9 @@
          * instance with {@link AudioRecord#AudioRecord(AudioAttributes, AudioFormat, int)}.
          * @param preset one of {@link MediaRecorder.AudioSource#DEFAULT},
          *     {@link MediaRecorder.AudioSource#MIC}, {@link MediaRecorder.AudioSource#CAMCORDER},
-         *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} or
-         *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
+         *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION},
+         *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION} or
+         *     {@link MediaRecorder.AudioSource#UNPROCESSED}
          * @return the same Builder instance.
          */
         @SystemApi
@@ -532,6 +542,7 @@
                 case MediaRecorder.AudioSource.CAMCORDER:
                 case MediaRecorder.AudioSource.VOICE_RECOGNITION:
                 case MediaRecorder.AudioSource.VOICE_COMMUNICATION:
+                case MediaRecorder.AudioSource.UNPROCESSED:
                     mSource = preset;
                     break;
                 default:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ea1690f..5ad6b08 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3305,12 +3305,19 @@
             "android.media.property.SUPPORT_SPEAKER_NEAR_ULTRASOUND";
 
     /**
+     * Used as a key for {@link #getProperty} to determine if the unprocessed audio source is
+     * available and supported with the expected frequency range and level response.
+     */
+    public static final String PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED =
+            "android.media.property.SUPPORT_AUDIO_SOURCE_UNPROCESSED";
+    /**
      * Returns the value of the property with the specified key.
      * @param key One of the strings corresponding to a property key: either
      *            {@link #PROPERTY_OUTPUT_SAMPLE_RATE},
      *            {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER},
-     *            {@link #PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND}, or
-     *            {@link #PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND}.
+     *            {@link #PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND},
+     *            {@link #PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND}, or
+     *            {@link #PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED}.
      * @return A string representing the associated value for that property key,
      *         or null if there is no value for that key.
      */
@@ -3329,6 +3336,9 @@
         } else if (PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND.equals(key)) {
             return String.valueOf(getContext().getResources().getBoolean(
                     com.android.internal.R.bool.config_supportSpeakerNearUltrasound));
+        } else if (PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED.equals(key)) {
+            return String.valueOf(getContext().getResources().getBoolean(
+                    com.android.internal.R.bool.config_supportAudioSourceUnprocessed));
         } else {
             // null or unknown key
             return null;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 974b62e..7c21893 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -51,7 +51,7 @@
  * been read yet. Data should be read from the audio hardware in chunks of sizes inferior to
  * the total recording buffer size.
  */
-public class AudioRecord
+public class AudioRecord implements AudioRouting
 {
     //---------------------------------------------------------
     // Constants
@@ -392,6 +392,20 @@
     }
 
     /**
+     * A constructor which explicitly connects a Native (C++) AudioRecord. For use by
+     * the AudioRecordRoutingProxy subclass.
+     * @param nativeRecordInJavaObj A C/C++ pointer to a native AudioRecord
+     * (associated with an OpenSL ES recorder).
+     */
+    /*package*/ AudioRecord(long nativeRecordInJavaObj) {
+        mNativeRecorderInJavaObj = nativeRecordInJavaObj;
+
+        // other initialization here...
+
+        mState = STATE_INITIALIZED;
+    }
+
+    /**
      * Builder class for {@link AudioRecord} objects.
      * Use this class to configure and create an <code>AudioRecord</code> instance. By setting the
      * recording source and audio format parameters, you indicate which of
@@ -1221,23 +1235,6 @@
         return native_set_marker_pos(markerInFrames);
     }
 
-
-    //--------------------------------------------------------------------------
-    // (Re)Routing Info
-    //--------------------
-    /**
-     * Defines the interface by which applications can receive notifications of routing
-     * changes for the associated {@link AudioRecord}.
-     */
-    public interface OnRoutingChangedListener {
-        /**
-         * Called when the routing of an AudioRecord changes from either and explicit or
-         * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-from
-         * device.
-         */
-        public void onRoutingChanged(AudioRecord audioRecord);
-    }
-
     /**
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioRecord.
      * Note: The query is only valid if the AudioRecord is currently recording. If it is not,
@@ -1258,6 +1255,89 @@
         return null;
     }
 
+    /*
+     * Call BEFORE adding a routing callback handler.
+     */
+    private void testEnableNativeRoutingCallbacks() {
+        if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+            native_enableDeviceCallback();
+        }
+    }
+
+    /*
+     * Call AFTER removing a routing callback handler.
+     */
+    private void testDisableNativeRoutingCallbacks() {
+        if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+            native_disableDeviceCallback();
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // >= "N" (Re)Routing Info
+    //--------------------
+    /**
+     * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+     * {@link AudioRecord#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
+     *      android.os.Handler)}
+     * by an app to receive (re)routing notifications.
+     */
+    private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
+    mNewRoutingChangeListeners =
+        new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+
+    /**
+     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of
+     * routing changes on this AudioRecord.
+     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+     * notifications of rerouting events.
+     * @param handler  Specifies 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 addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+            android.os.Handler handler) {
+        if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
+            synchronized (mNewRoutingChangeListeners) {
+                testEnableNativeRoutingCallbacks();
+                mNewRoutingChangeListeners.put(
+                    listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
+                            handler != null ? handler : new Handler(mInitializationLooper)));
+            }
+        }
+    }
+
+    /**
+     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+    * to receive rerouting notifications.
+    * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+    * to remove.
+    */
+    public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
+        synchronized (mNewRoutingChangeListeners) {
+            if (mNewRoutingChangeListeners.containsKey(listener)) {
+                mNewRoutingChangeListeners.remove(listener);
+                testDisableNativeRoutingCallbacks();
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // Marshmallow (Re)Routing Info
+    //--------------------
+    /**
+     * Defines the interface by which applications can receive notifications of routing
+     * changes for the associated {@link AudioRecord}.
+     */
+    public interface OnRoutingChangedListener {
+        /**
+         * Called when the routing of an AudioRecord changes from either and explicit or
+         * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-from
+         * device.
+         */
+        public void onRoutingChanged(AudioRecord audioRecord);
+    }
+
     /**
      * The list of AudioRecord.OnRoutingChangedListener interface added (with
      * {@link AudioRecord#addOnRoutingChangedListener(OnRoutingChangedListener,android.os.Handler)}
@@ -1276,13 +1356,12 @@
      * the callback. If <code>null</code>, the {@link Handler} associated with the main
      * {@link Looper} will be used.
      */
+    @Deprecated
     public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
             android.os.Handler handler) {
         if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
             synchronized (mRoutingChangeListeners) {
-                if (mRoutingChangeListeners.size() == 0) {
-                    native_enableDeviceCallback();
-                }
+                testEnableNativeRoutingCallbacks();
                 mRoutingChangeListeners.put(
                     listener, new NativeRoutingEventHandlerDelegate(this, listener,
                             handler != null ? handler : new Handler(mInitializationLooper)));
@@ -1291,22 +1370,73 @@
     }
 
     /**
-     * Removes an {@link OnRoutingChangedListener} which has been previously added
+      * Removes an {@link OnRoutingChangedListener} which has been previously added
      * to receive rerouting notifications.
      * @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
      */
+    @Deprecated
     public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
         synchronized (mRoutingChangeListeners) {
             if (mRoutingChangeListeners.containsKey(listener)) {
                 mRoutingChangeListeners.remove(listener);
-                if (mRoutingChangeListeners.size() == 0) {
-                    native_disableDeviceCallback();
-                }
+                testDisableNativeRoutingCallbacks();
             }
         }
     }
 
     /**
+     * >= "N" Routing
+     * Helper class to handle the forwarding of native events to the appropriate listener
+     * (potentially) handled in a different thread
+     */
+    private class NativeNewRoutingEventHandlerDelegate {
+        private final Handler mHandler;
+
+        NativeNewRoutingEventHandlerDelegate(final AudioRecord record,
+                                   final AudioRouting.OnRoutingChangedListener listener,
+                                   Handler handler) {
+            // find the looper for our new event handler
+            Looper looper;
+            if (handler != null) {
+                looper = handler.getLooper();
+            } else {
+                // no given handler, use the looper the AudioRecord was created in
+                looper = mInitializationLooper;
+            }
+
+            // construct the event handler with this looper
+            if (looper != null) {
+                // implement the event handler delegate
+                mHandler = new Handler(looper) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        if (record == null) {
+                            return;
+                        }
+                        switch(msg.what) {
+                        case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
+                            if (listener != null) {
+                                listener.onRoutingChanged(record);
+                            }
+                            break;
+                        default:
+                            loge("Unknown native event type: " + msg.what);
+                            break;
+                        }
+                    }
+                };
+            } else {
+                mHandler = null;
+            }
+        }
+
+        Handler getHandler() {
+            return mHandler;
+        }
+    }
+
+    /**
+     * Marshmallow Routing
      * Helper class to handle the forwarding of native events to the appropriate listener
      * (potentially) handled in a different thread
      */
@@ -1355,21 +1485,34 @@
             return mHandler;
         }
     }
+
     /**
      * Sends device list change notification to all listeners.
      */
     private void broadcastRoutingChange() {
+        AudioManager.resetAudioPortGeneration();
+        // Marshmallow Routing
         Collection<NativeRoutingEventHandlerDelegate> values;
         synchronized (mRoutingChangeListeners) {
             values = mRoutingChangeListeners.values();
         }
-        AudioManager.resetAudioPortGeneration();
         for(NativeRoutingEventHandlerDelegate delegate : values) {
             Handler handler = delegate.getHandler();
             if (handler != null) {
                 handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
             }
         }
+        // >= "N" Routing
+        Collection<NativeNewRoutingEventHandlerDelegate> newValues;
+        synchronized (mNewRoutingChangeListeners) {
+            newValues = mNewRoutingChangeListeners.values();
+        }
+        for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
+            Handler handler = delegate.getHandler();
+            if (handler != null) {
+                handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+            }
+        }
     }
 
     /**
diff --git a/media/java/android/media/AudioRecordRoutingProxy.java b/media/java/android/media/AudioRecordRoutingProxy.java
new file mode 100644
index 0000000..b0c19e4
--- /dev/null
+++ b/media/java/android/media/AudioRecordRoutingProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * An AudioRecord connected to a native (C/C++) which allows access only to routing methods.
+ */
+class AudioRecordRoutingProxy extends AudioRecord {
+    /**
+     * A constructor which explicitly connects a Native (C++) AudioRecord. For use by
+     * the AudioRecordRoutingProxy subclass.
+     * @param nativeRecordInJavaObj A C/C++ pointer to a native AudioRecord
+     * (associated with an OpenSL ES recorder).
+     */
+    public AudioRecordRoutingProxy(long nativeRecordInJavaObj) {
+        super(nativeRecordInJavaObj);
+    }
+}
diff --git a/media/java/android/media/AudioRouting.java b/media/java/android/media/AudioRouting.java
new file mode 100644
index 0000000..2161cf3
--- /dev/null
+++ b/media/java/android/media/AudioRouting.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 android.media;
+
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * AudioRouting defines an interface for controlling routing and routing notifications in
+ * AudioTrack and AudioRecord objects.
+ */
+public interface AudioRouting {
+    /**
+     * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+     * the output/input to/from.
+     * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+     *  If deviceInfo is null, default routing is restored.
+     * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+     * does not correspond to a valid audio device.
+     */
+    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
+
+    /**
+     * Returns the selected output/input specified by {@link #setPreferredDevice}. Note that this
+     * is not guaranteed to correspond to the actual device being used for playback/recording.
+     */
+    public AudioDeviceInfo getPreferredDevice();
+
+    /**
+     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+     * changes on this AudioTrack/AudioRecord.
+     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+     * notifications of rerouting events.
+     * @param handler  Specifies 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 addOnRoutingListener(OnRoutingChangedListener listener,
+            Handler handler);
+
+    /**
+     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+     * to receive rerouting notifications.
+     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+     * to remove.
+     */
+    public void removeOnRoutingListener(OnRoutingChangedListener listener);
+
+    /**
+     * Defines the interface by which applications can receive notifications of routing
+     * changes for the associated {@link AudioRouting}.
+     */
+    public interface OnRoutingChangedListener {
+        public void onRoutingChanged(AudioRouting router);
+    }
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index a810ff1..4319840 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -42,7 +42,6 @@
 
 import com.android.internal.app.IAppOpsService;
 
-
 /**
  * The AudioTrack class manages and plays a single audio resource for Java applications.
  * It allows streaming of PCM audio buffers to the audio sink for playback. This is
@@ -78,7 +77,7 @@
  *
  * AudioTrack is not final and thus permits subclasses, but such use is not recommended.
  */
-public class AudioTrack
+public class AudioTrack implements AudioRouting
 {
     //---------------------------------------------------------
     // Constants
@@ -318,10 +317,11 @@
     // Used exclusively by native code
     //--------------------
     /**
+     * @hide
      * Accessed by native methods: provides access to C++ AudioTrack object.
      */
     @SuppressWarnings("unused")
-    private long mNativeTrackInJavaObj;
+    protected long mNativeTrackInJavaObj;
     /**
      * Accessed by native methods: provides access to the JNI data (i.e. resources used by
      * the native AudioTrack object, but not stored in it).
@@ -524,6 +524,31 @@
     }
 
     /**
+     * A constructor which explicitly connects a Native (C++) AudioTrack. For use by
+     * the AudioTrackRoutingProxy subclass.
+     * @param nativeTrackInJavaObj a C/C++ pointer to a native AudioTrack
+     * (associated with an OpenSL ES player).
+     */
+    /*package*/ AudioTrack(long nativeTrackInJavaObj) {
+        mNativeTrackInJavaObj = nativeTrackInJavaObj;
+
+        // "final"s
+        mAttributes = null;
+        mAppOps = null;
+
+        // remember which looper is associated with the AudioTrack instantiation
+        Looper looper;
+        if ((looper = Looper.myLooper()) == null) {
+            looper = Looper.getMainLooper();
+        }
+        mInitializationLooper = looper;
+
+        // other initialization...
+
+        mState = STATE_INITIALIZED;
+    }
+
+    /**
      * Builder class for {@link AudioTrack} objects.
      * Use this class to configure and create an <code>AudioTrack</code> instance. By setting audio
      * attributes and audio format parameters, you indicate which of those vary from the default
@@ -1027,8 +1052,68 @@
         }
     }
 
+// TODO Change getBufferCapacityInFrames() reference below to
+// {@link #getBufferCapacityInFrames()} after @hide is removed.
+// TODO Change setBufferSizeInFrames(int) reference below to
+// {@link #setBufferSizeInFrames(int)} after @hide is removed.
     /**
-     *  Returns the frame count of the native <code>AudioTrack</code> buffer.
+     *  Returns the effective size of the <code>AudioTrack</code> buffer
+     * that the application writes to.
+     *  <p> This will be less than or equal to the result of
+     * getBufferCapacityInFrames().
+     * It will be equal if setBufferSizeInFrames(int) has never been called.
+     *  <p> If the track is subsequently routed to a different output sink, the buffer
+     *  size and capacity may enlarge to accommodate.
+     *  <p> If the <code>AudioTrack</code> encoding indicates compressed data,
+     *  e.g. {@link AudioFormat#ENCODING_AC3}, then the frame count returned is
+     *  the size of the native <code>AudioTrack</code> buffer in bytes.
+     *  <p> See also {@link AudioManager#getProperty(String)} for key
+     *  {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}.
+     *  @return current size in frames of the <code>AudioTrack</code> buffer.
+     *  @throws IllegalStateException
+     */
+    public int getBufferSizeInFrames() {
+        return native_get_buffer_size_frames();
+    }
+
+// TODO Change getBufferCapacityInFrames() reference below to
+// {@link #getBufferCapacityInFrames()} after @hide is removed.
+    /**
+     * Limits the effective size of the <code>AudioTrack</code> buffer
+     * that the application writes to.
+     * <p> A write to this AudioTrack will not fill the buffer beyond this limit.
+     * If a blocking write is used then the write will block until the the data
+     * can fit within this limit.
+     * <p>Changing this limit modifies the latency associated with
+     * the buffer for this track. A smaller size will give lower latency
+     * but there may be more glitches due to buffer underruns.
+     *  <p>The actual size used may not be equal to this requested size.
+     * It will be limited to a valid range with a maximum of
+     * getBufferCapacityInFrames().
+     * It may also be adjusted slightly for internal reasons.
+     * If bufferSizeInFrames is less than zero then {@link #ERROR_BAD_VALUE}
+     * will be returned.
+     * <p>This method is only supported for PCM audio.
+     * It is not supported for compressed audio tracks.
+     *
+     * @param bufferSizeInFrames requested buffer size
+     * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE},
+     *    {@link #ERROR_INVALID_OPERATION}
+     *  @throws IllegalStateException
+     * @hide
+     */
+    public int setBufferSizeInFrames(int bufferSizeInFrames) {
+        if (mDataLoadMode == MODE_STATIC || mState == STATE_UNINITIALIZED) {
+            return ERROR_INVALID_OPERATION;
+        }
+        if (bufferSizeInFrames < 0) {
+            return ERROR_BAD_VALUE;
+        }
+        return native_set_buffer_size_frames(bufferSizeInFrames);
+    }
+
+    /**
+     *  Returns the maximum size of the native <code>AudioTrack</code> buffer.
      *  <p> If the track's creation mode is {@link #MODE_STATIC},
      *  it is equal to the specified bufferSizeInBytes on construction, converted to frame units.
      *  A static track's native frame count will not change.
@@ -1043,11 +1128,12 @@
      *  the size of the native <code>AudioTrack</code> buffer in bytes.
      *  <p> See also {@link AudioManager#getProperty(String)} for key
      *  {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}.
-     *  @return current size in frames of the <code>AudioTrack</code> buffer.
+     *  @return maximum size in frames of the <code>AudioTrack</code> buffer.
      *  @throws IllegalStateException
+     * @hide
      */
-    public int getBufferSizeInFrames() {
-        return native_get_native_frame_count();
+    public int getBufferCapacityInFrames() {
+        return native_get_buffer_capacity_frames();
     }
 
     /**
@@ -1058,7 +1144,7 @@
      */
     @Deprecated
     protected int getNativeFrameCount() {
-        return native_get_native_frame_count();
+        return native_get_buffer_capacity_frames();
     }
 
     /**
@@ -1106,6 +1192,25 @@
     }
 
     /**
+     * Returns the number of underrun occurrences in the application-level write buffer
+     * since the AudioTrack was created.
+     * An underrun occurs if the application does not write audio
+     * data quickly enough, causing the buffer to underflow
+     * and a potential audio glitch or pop.
+     * Underruns are less likely when buffer sizes are large.
+     * <p> Though the "int" type is signed 32-bits, the value should be reinterpreted
+     * as if it is unsigned 32-bits.
+     * That is, the next position after 0x7FFFFFFF is (int) 0x80000000.
+     * This is a continuously advancing counter. It can wrap around to zero
+     * if there are too many underruns. If there were, for example, 68 underruns per
+     * second then the counter would wrap in 2 years.
+     * @hide
+     */
+    public int getUnderrunCount() {
+        return native_get_underrun_count();
+    }
+
+    /**
      *  Returns the output sample rate in Hz for the specified stream type.
      */
     static public int getNativeOutputSampleRate(int streamType) {
@@ -2247,22 +2352,6 @@
         }
     }
 
-    //--------------------------------------------------------------------------
-    // (Re)Routing Info
-    //--------------------
-    /**
-     * Defines the interface by which applications can receive notifications of routing
-     * changes for the associated {@link AudioTrack}.
-     */
-    public interface OnRoutingChangedListener {
-        /**
-         * Called when the routing of an AudioTrack changes from either and explicit or
-         * policy rerouting.  Use {@link #getRoutedDevice()} to retrieve the newly routed-to
-         * device.
-         */
-        public void onRoutingChanged(AudioTrack audioTrack);
-    }
-
     /**
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack.
      * Note: The query is only valid if the AudioTrack is currently playing. If it is not,
@@ -2283,6 +2372,89 @@
         return null;
     }
 
+    /*
+     * Call BEFORE adding a routing callback handler.
+     */
+    private void testEnableNativeRoutingCallbacks() {
+        if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+            native_enableDeviceCallback();
+        }
+    }
+
+    /*
+     * Call AFTER removing a routing callback handler.
+     */
+    private void testDisableNativeRoutingCallbacks() {
+        if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+            native_disableDeviceCallback();
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // >= "N" (Re)Routing Info
+    //--------------------
+    /**
+     * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+     * {@link AudioTrack#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
+     *          android.os.Handler)}
+     * by an app to receive (re)routing notifications.
+     */
+   private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
+    mNewRoutingChangeListeners =
+        new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+
+   /**
+    * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+    * changes on this AudioTrack.
+    * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+    * notifications of rerouting events.
+    * @param handler  Specifies 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 addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+            Handler handler) {
+        if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
+            synchronized (mNewRoutingChangeListeners) {
+                testEnableNativeRoutingCallbacks();
+                mNewRoutingChangeListeners.put(
+                    listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
+                            handler != null ? handler : new Handler(mInitializationLooper)));
+            }
+        }
+    }
+
+    /**
+     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+     * to receive rerouting notifications.
+     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+     * to remove.
+     */
+    public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
+        if (mNewRoutingChangeListeners.containsKey(listener)) {
+            mNewRoutingChangeListeners.remove(listener);
+        }
+        testDisableNativeRoutingCallbacks();
+    }
+
+    //--------------------------------------------------------------------------
+    // Marshmallow (Re)Routing Info
+    //--------------------
+    /**
+     * Defines the interface by which applications can receive notifications of routing
+     * changes for the associated {@link AudioTrack}.
+     */
+    @Deprecated
+    public interface OnRoutingChangedListener {
+        /**
+         * Called when the routing of an AudioTrack changes from either and explicit or
+         * policy rerouting.  Use {@link #getRoutedDevice()} to retrieve the newly routed-to
+         * device.
+         */
+        @Deprecated
+        public void onRoutingChanged(AudioTrack audioTrack);
+    }
+
     /**
      * The list of AudioTrack.OnRoutingChangedListener interfaces added (with
      * {@link AudioTrack#addOnRoutingChangedListener(OnRoutingChangedListener, android.os.Handler)}
@@ -2301,13 +2473,12 @@
      * the callback. If <code>null</code>, the {@link Handler} associated with the main
      * {@link Looper} will be used.
      */
+    @Deprecated
     public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
             android.os.Handler handler) {
         if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
             synchronized (mRoutingChangeListeners) {
-                if (mRoutingChangeListeners.size() == 0) {
-                    native_enableDeviceCallback();
-                }
+                testEnableNativeRoutingCallbacks();
                 mRoutingChangeListeners.put(
                     listener, new NativeRoutingEventHandlerDelegate(this, listener,
                             handler != null ? handler : new Handler(mInitializationLooper)));
@@ -2320,14 +2491,13 @@
      * to receive rerouting notifications.
      * @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
      */
+    @Deprecated
     public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
         synchronized (mRoutingChangeListeners) {
             if (mRoutingChangeListeners.containsKey(listener)) {
                 mRoutingChangeListeners.remove(listener);
             }
-            if (mRoutingChangeListeners.size() == 0) {
-                native_disableDeviceCallback();
-            }
+            testDisableNativeRoutingCallbacks();
         }
     }
 
@@ -2335,17 +2505,30 @@
      * Sends device list change notification to all listeners.
      */
     private void broadcastRoutingChange() {
+        AudioManager.resetAudioPortGeneration();
+
+        // Marshmallow Routing
         Collection<NativeRoutingEventHandlerDelegate> values;
         synchronized (mRoutingChangeListeners) {
             values = mRoutingChangeListeners.values();
         }
-        AudioManager.resetAudioPortGeneration();
         for(NativeRoutingEventHandlerDelegate delegate : values) {
             Handler handler = delegate.getHandler();
             if (handler != null) {
                 handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
             }
         }
+        // >= "N" Routing
+        Collection<NativeNewRoutingEventHandlerDelegate> newValues;
+        synchronized (mNewRoutingChangeListeners) {
+            newValues = mNewRoutingChangeListeners.values();
+        }
+        for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
+            Handler handler = delegate.getHandler();
+            if (handler != null) {
+                handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+            }
+        }
     }
 
     //---------------------------------------------------------
@@ -2428,6 +2611,7 @@
     }
 
     /**
+     * Marshmallow Routing API.
      * Helper class to handle the forwarding of native events to the appropriate listener
      * (potentially) handled in a different thread
      */
@@ -2477,6 +2661,57 @@
         }
     }
 
+    /**
+     * Marshmallow Routing API.
+     * Helper class to handle the forwarding of native events to the appropriate listener
+     * (potentially) handled in a different thread
+     */
+    private class NativeNewRoutingEventHandlerDelegate {
+        private final Handler mHandler;
+
+        NativeNewRoutingEventHandlerDelegate(final AudioTrack track,
+                                   final AudioRouting.OnRoutingChangedListener listener,
+                                   Handler handler) {
+            // find the looper for our new event handler
+            Looper looper;
+            if (handler != null) {
+                looper = handler.getLooper();
+            } else {
+                // no given handler, use the looper the AudioTrack was created in
+                looper = mInitializationLooper;
+            }
+
+            // construct the event handler with this looper
+            if (looper != null) {
+                // implement the event handler delegate
+                mHandler = new Handler(looper) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        if (track == null) {
+                            return;
+                        }
+                        switch(msg.what) {
+                        case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
+                            if (listener != null) {
+                                listener.onRoutingChanged(track);
+                            }
+                            break;
+                        default:
+                            loge("Unknown native event type: " + msg.what);
+                            break;
+                        }
+                    }
+                };
+            } else {
+                mHandler = null;
+            }
+        }
+
+        Handler getHandler() {
+            return mHandler;
+        }
+    }
+
     //---------------------------------------------------------
     // Java methods called from the native side
     //--------------------
@@ -2545,7 +2780,9 @@
 
     private native final int native_reload_static();
 
-    private native final int native_get_native_frame_count();
+    private native final int native_get_buffer_size_frames();
+    private native final int native_set_buffer_size_frames(int bufferSizeInFrames);
+    private native final int native_get_buffer_capacity_frames();
 
     private native final void native_setVolume(float leftVolume, float rightVolume);
 
@@ -2566,6 +2803,8 @@
 
     private native final int native_get_latency();
 
+    private native final int native_get_underrun_count();
+
     // longArray must be a non-null array of length >= 2
     // [0] is assigned the frame position
     // [1] is assigned the time in CLOCK_MONOTONIC nanoseconds
diff --git a/media/java/android/media/AudioTrackRoutingProxy.java b/media/java/android/media/AudioTrackRoutingProxy.java
new file mode 100644
index 0000000..9b97ae9
--- /dev/null
+++ b/media/java/android/media/AudioTrackRoutingProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * An AudioTrack connected to a native (C/C++) which allows access only to routing methods.
+ */
+class AudioTrackRoutingProxy extends AudioTrack {
+    /**
+     * A constructor which explicitly connects a Native (C++) AudioTrack. For use by
+     * the AudioTrackRoutingProxy subclass.
+     * @param nativeTrackInJavaObj a C/C++ pointer to a native AudioTrack
+     * (associated with an OpenSL ES player).
+     */
+    public AudioTrackRoutingProxy(long nativeTrackInJavaObj) {
+        super(nativeTrackInJavaObj);
+    }
+}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 8ac86b0..504c6d0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -251,6 +251,10 @@
          */
         public static final int REMOTE_SUBMIX = 8;
 
+        /** Microphone audio source tuned for unprocessed (raw) sound if available, behaves like
+         *  {@link #DEFAULT} otherwise. */
+        public static final int UNPROCESSED = 9;
+
         /**
          * Audio source for capturing broadcast radio tuner output.
          * @hide
@@ -405,7 +409,7 @@
      * @see android.media.MediaRecorder.AudioSource
      */
     public static final int getAudioSourceMax() {
-        return AudioSource.REMOTE_SUBMIX;
+        return AudioSource.UNPROCESSED;
     }
 
     /**
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 96c616b..dfe024a 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -37,6 +38,7 @@
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.sax.Element;
 import android.sax.ElementListener;
 import android.sax.RootElement;
@@ -328,8 +330,6 @@
     // used when scanning the image database so we know whether we have to prune
     // old thumbnail files
     private int mOriginalCount;
-    /** Whether the database had any entries in it before the scan started */
-    private boolean mWasEmptyPriorToScan = false;
     /** Whether the scanner has set a default sound for the ringer ringtone. */
     private boolean mDefaultRingtoneSet;
     /** Whether the scanner has set a default sound for the notification ringtone. */
@@ -562,12 +562,29 @@
                 FileEntry entry = beginFile(path, mimeType, lastModified,
                         fileSize, isDirectory, noMedia);
 
+                if (entry == null) {
+                    return null;
+                }
+
                 // if this file was just inserted via mtp, set the rowid to zero
                 // (even though it already exists in the database), to trigger
                 // the correct code path for updating its entry
                 if (mMtpObjectHandle != 0) {
                     entry.mRowId = 0;
                 }
+
+                if (entry.mPath != null &&
+                        ((!mDefaultNotificationSet &&
+                                doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
+                        || (!mDefaultRingtoneSet &&
+                                doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
+                        || (!mDefaultAlarmSet &&
+                                doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
+                    Log.w(TAG, "forcing rescan of " + entry.mPath +
+                            "since ringtone setting didn't finish");
+                    scanAlways = true;
+                }
+
                 // rescan for metadata if file was modified since last scan
                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
                     if (noMedia) {
@@ -947,6 +964,26 @@
             }
             Uri result = null;
             boolean needToSetSettings = false;
+            // Setting a flag in order not to use bulk insert for the file related with
+            // notifications, ringtones, and alarms, because the rowId of the inserted file is
+            // needed.
+            if (notifications && !mDefaultNotificationSet) {
+                if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
+                        doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
+                    needToSetSettings = true;
+                }
+            } else if (ringtones && !mDefaultRingtoneSet) {
+                if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
+                        doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
+                    needToSetSettings = true;
+                }
+            } else if (alarms && !mDefaultAlarmSet) {
+                if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
+                        doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
+                    needToSetSettings = true;
+                }
+            }
+
             if (rowId == 0) {
                 if (mMtpObjectHandle != 0) {
                     values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
@@ -958,28 +995,6 @@
                     }
                     values.put(Files.FileColumns.FORMAT, format);
                 }
-                // Setting a flag in order not to use bulk insert for the file related with
-                // notifications, ringtones, and alarms, because the rowId of the inserted file is
-                // needed.
-                if (mWasEmptyPriorToScan) {
-                    if (notifications && !mDefaultNotificationSet) {
-                        if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
-                                doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
-                            needToSetSettings = true;
-                        }
-                    } else if (ringtones && !mDefaultRingtoneSet) {
-                        if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
-                                doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
-                            needToSetSettings = true;
-                        }
-                    } else if (alarms && !mDefaultAlarmSet) {
-                        if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
-                                doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
-                            needToSetSettings = true;
-                        }
-                    }
-                }
-
                 // New file, insert it.
                 // Directories need to be inserted before the files they contain, so they
                 // get priority when bulk inserting.
@@ -1049,14 +1064,18 @@
 
         private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
 
-            String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
-                    settingName);
+            if(wasSettingAlreadySet(settingName)) {
+                return;
+            }
 
+            ContentResolver cr = mContext.getContentResolver();
+            String existingSettingValue = Settings.System.getString(cr, settingName);
             if (TextUtils.isEmpty(existingSettingValue)) {
                 // Set the setting to the given URI
-                Settings.System.putString(mContext.getContentResolver(), settingName,
+                Settings.System.putString(cr, settingName,
                         ContentUris.withAppendedId(uri, rowId).toString());
             }
+            Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
         }
 
         private int getFileTypeFromDrm(String path) {
@@ -1083,6 +1102,20 @@
 
     }; // end of anonymous MediaScannerClient instance
 
+    private String settingSetIndicatorName(String base) {
+        return base + "_set";
+    }
+
+    private boolean wasSettingAlreadySet(String name) {
+        ContentResolver cr = mContext.getContentResolver();
+        String indicatorName = settingSetIndicatorName(name);
+        try {
+            return Settings.System.getInt(cr, indicatorName) != 0;
+        } catch (SettingNotFoundException e) {
+            return false;
+        }
+    }
+
     private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
         Cursor c = null;
         String where = null;
@@ -1100,6 +1133,10 @@
             selectionArgs = new String[] { "" };
         }
 
+        mDefaultRingtoneSet = wasSettingAlreadySet(Settings.System.RINGTONE);
+        mDefaultNotificationSet = wasSettingAlreadySet(Settings.System.NOTIFICATION_SOUND);
+        mDefaultAlarmSet = wasSettingAlreadySet(Settings.System.ALARM_ALERT);
+
         // Tell the provider to not delete the file.
         // If the file is truly gone the delete is unnecessary, and we want to avoid
         // accidentally deleting files that are really there (this may happen if the
@@ -1117,7 +1154,6 @@
                 // with CursorWindow positioning.
                 long lastId = Long.MIN_VALUE;
                 Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
-                mWasEmptyPriorToScan = true;
 
                 while (true) {
                     selectionArgs[0] = "" + lastId;
@@ -1136,7 +1172,6 @@
                     if (num == 0) {
                         break;
                     }
-                    mWasEmptyPriorToScan = false;
                     while (c.moveToNext()) {
                         long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                         String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
@@ -1284,7 +1319,7 @@
         }
     }
 
-    private void postscan(String[] directories) throws RemoteException {
+    private void postscan(final String[] directories) throws RemoteException {
 
         // handle playlists last, after we know what media files are on the storage.
         if (mProcessPlaylists) {
diff --git a/packages/DocumentsUI/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_home.xml
index 0a258ac..696ee05 100644
--- a/packages/DocumentsUI/res/drawable/ic_root_home.xml
+++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml
@@ -1,15 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
-        android:fillColor="#000000"
-        android:pathData="M20 6h-8l-2-2H4c-1.1 0-1.99 .9 -1.99 2L2 18c0 1.1 .9 2 2 2h16c1.1 0 2-.9
-2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4
-8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z" />
-    <path
-        android:pathData="M0 0h24v24H0z" />
+        android:fillColor="#FF000000"
+        android:pathData="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
 </vector>
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
new file mode 100644
index 0000000..baa7a2e
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.documentsui.model.RootInfo;
+
+@LargeTest
+public class SearchViewUiTest extends InstrumentationTestCase {
+
+    private static final int TIMEOUT = 5000;
+    private static final String TAG = "SearchViewUiTest";
+    private static final String TARGET_PKG = "com.android.documentsui";
+    private static final String LAUNCHER_PKG = "com.android.launcher";
+
+    private UiBot mBot;
+    private UiDevice mDevice;
+    private Context mContext;
+    private ContentResolver mResolver;
+    private DocumentsProviderHelper mDocsHelper;
+    private ContentProviderClient mClient;
+    private RootInfo mRoot_0;
+    private RootInfo mRoot_1;
+
+    private UiObject mSearchView;
+    private UiObject mSearchTextField;
+    private UiObject mDocsList;
+    private UiObject mMessageTextView;
+    private UiObject mSearchIcon;
+
+    public void setUp() throws Exception {
+        // Initialize UiDevice instance.
+        Instrumentation instrumentation = getInstrumentation();
+
+        mDevice = UiDevice.getInstance(instrumentation);
+
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+
+        // Start from the home screen.
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+        // NOTE: Must be the "target" context, else security checks in content provider will fail.
+        mContext = instrumentation.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
+
+        // Launch app.
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+        // Wait for the app to appear.
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
+        mDevice.waitForIdle();
+
+        mBot = new UiBot(mDevice, TIMEOUT);
+
+        resetStorage(); // Just incase a test failed and tearDown didn't happen.
+
+        initUiObjects();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mDevice.pressBack();
+        resetStorage();
+        mClient.release();
+    }
+
+    private void resetStorage() throws RemoteException {
+        mClient.call("clear", null, null);
+        // TODO: Would be nice to have an event to wait on here.
+        mDevice.waitForIdle();
+    }
+
+    private void initTestFiles() throws RemoteException {
+        mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
+        mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
+
+        mDocsHelper.createDocument(mRoot_0, "text/plain", "file10.log");
+        mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
+        mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+
+        mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
+        mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+    }
+
+    private void initUiObjects() {
+        mSearchView = mBot.findSearchView();
+        mSearchTextField = mBot.findSearchViewTextField();
+        mDocsList = mBot.findDocumentsList();
+        mMessageTextView = mBot.findMessageTextView();
+        mSearchIcon = mBot.findSearchViewIcon();
+    }
+
+    public void testSearchViewExpandsOnClick() throws Exception {
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+
+        mSearchView.click();
+
+        assertTrue(mSearchTextField.exists());
+        assertTrue(mSearchTextField.isFocused());
+        assertFalse(mSearchIcon.exists());
+    }
+
+    public void testSearchViewCollapsesOnBack() throws Exception {
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+
+        mSearchView.click();
+
+        mDevice.pressBack();
+
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+    }
+
+    public void testSearchViewClearsTextOnBack() throws Exception {
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+
+        String query = "file2";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        assertSearchTextField(true, query);
+
+        mDevice.pressBack();
+
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+    }
+
+    public void testSearchFound() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        assertDefaultTestDir0();
+
+        String query = "file1";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        assertTrue(mDocsList.exists());
+        assertSearchTextField(true, query);
+
+        mDevice.pressEnter();
+
+        assertTrue(mDocsList.exists());
+        assertEquals(2, mDocsList.getChildCount());
+        mBot.assertHasDocuments("file1.png", "file10.log");
+        assertSearchTextField(false, query);
+    }
+
+    public void testSearchFoundClearsOnBack() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        assertDefaultTestDir0();
+
+        String query = "file1";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        mDevice.pressEnter();
+        mDevice.pressBack();
+
+        assertDefaultTestDir0();
+    }
+
+    public void testSearchNoResults() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        assertDefaultTestDir0();
+
+        String query = "chocolate";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        mDevice.pressEnter();
+
+        assertFalse(mDocsList.exists());
+        assertTrue(mMessageTextView.exists());
+        assertEquals(mContext.getString(R.string.empty), mMessageTextView.getText());
+        assertSearchTextField(false, query);
+    }
+
+    public void testSearchNoResultsClearsOnBack() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        assertDefaultTestDir0();
+
+        String query = "chocolate";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        mDevice.pressEnter();
+        mDevice.pressBack();
+
+        assertDefaultTestDir0();
+    }
+
+    public void testSearchFoundClearsDirectoryChange() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot(ROOT_0_ID);
+
+        assertDefaultTestDir0();
+
+        String query = "file1";
+        mSearchView.click();
+        mSearchTextField.setText(query);
+
+        mDevice.pressEnter();
+
+        mBot.openRoot(ROOT_1_ID);
+
+        // This assert is failing right now - fix will come with SearchManager refactoring
+        // assertDefaultTestDir1();
+        //
+        // mBot.openRoot(ROOT_0_ID);
+        //
+        // assertDefaultTestDir0();
+    }
+
+    private void assertDefaultTestDir0() throws UiObjectNotFoundException {
+        assertTrue(mSearchIcon.exists());
+        assertTrue(mDocsList.exists());
+        assertFalse(mSearchTextField.exists());
+        assertEquals(3, mDocsList.getChildCount());
+        mBot.assertHasDocuments("file2.csv", "file1.png", "file10.log");
+    }
+
+    private void assertDefaultTestDir1() throws UiObjectNotFoundException {
+        assertTrue(mSearchIcon.exists());
+        assertFalse(mSearchTextField.exists());
+        assertTrue(mDocsList.exists());
+        assertEquals(2, mDocsList.getChildCount());
+        mBot.assertHasDocuments("anotherFile0.log", "poodles.txt");
+    }
+
+    private void assertSearchTextField(boolean isFocused, String query)
+            throws UiObjectNotFoundException {
+        assertFalse(mSearchIcon.exists());
+        assertTrue(mSearchTextField.exists());
+        assertEquals(isFocused, mSearchTextField.isFocused());
+        assertEquals(query, mSearchTextField.getText());
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 50f4628..fb6445b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -135,7 +135,8 @@
             final RootInfo info = entry.getValue();
             final RowBuilder row = result.newRow();
             row.add(Root.COLUMN_ROOT_ID, id);
-            row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
+            row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD
+                    | Root.FLAG_SUPPORTS_SEARCH);
             row.add(Root.COLUMN_TITLE, id);
             row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
             row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
@@ -270,6 +271,29 @@
     }
 
     @Override
+    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+            throws FileNotFoundException {
+
+        StubDocument parentDocument = mRoots.get(rootId).document;
+        if (parentDocument == null || parentDocument.file.isFile()) {
+            throw new FileNotFoundException();
+        }
+
+        final MatrixCursor result = new MatrixCursor(
+                projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+
+        for (File file : parentDocument.file.listFiles()) {
+            if (file.getName().toLowerCase().contains(query)) {
+                StubDocument document = mStorage.get(getDocumentIdForFile(file));
+                if (document != null) {
+                    includeDocument(result, document);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
     public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
             throws FileNotFoundException {
         final StubDocument document = mStorage.get(docId);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 68cdf12..c4def8f 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -187,4 +187,41 @@
         mDevice.wait(Until.findObject(selector), mTimeout);
         return mDevice.findObject(selector);
     }
+
+    private UiObject findObject(String resourceId) {
+        final UiSelector object = new UiSelector().resourceId(resourceId);
+        return mDevice.findObject(object);
+    }
+
+    private UiObject findObject(String parentResourceId, String childResourceId) {
+        final UiSelector selector = new UiSelector()
+                .resourceId(parentResourceId)
+                .childSelector(new UiSelector().resourceId(childResourceId));
+        return mDevice.findObject(selector);
+    }
+
+    UiObject findDocumentsList() {
+        return findObject(
+                "com.android.documentsui:id/container_directory",
+                "com.android.documentsui:id/list");
+    }
+
+    UiObject findSearchView() {
+        return findObject("com.android.documentsui:id/menu_search");
+    }
+
+    UiObject findSearchViewTextField() {
+        return findObject("com.android.documentsui:id/menu_search", "android:id/search_src_text");
+    }
+
+    UiObject findSearchViewIcon() {
+        return findObject("com.android.documentsui:id/menu_search", "android:id/search_button");
+    }
+
+    UiObject findMessageTextView() {
+        return findObject(
+                "com.android.documentsui:id/container_directory",
+                "com.android.documentsui:id/message");
+    }
+
 }
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index f9fb85c..cde28fc 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -34,27 +34,33 @@
 
 namespace {
 
-// Maximum number of bytes to write in one request.
+// The numbers came from sdcard.c.
+// Maximum number of bytes to write/read in one request/one reply.
 constexpr size_t MAX_WRITE = 256 * 1024;
+constexpr size_t MAX_READ = 128 * 1024;
+
 constexpr size_t NUM_MAX_HANDLES = 1024;
 
 // Largest possible request.
 // The request size is bounded by the maximum size of a FUSE_WRITE request
 // because it has the largest possible data payload.
 constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
-        sizeof(struct fuse_write_in) + MAX_WRITE;
+        sizeof(struct fuse_write_in) + (MAX_WRITE > MAX_READ ? MAX_WRITE : MAX_READ);
 
 static jclass app_fuse_class;
 static jmethodID app_fuse_get_file_size;
 static jmethodID app_fuse_get_object_bytes;
 
+// NOTE:
+// FuseRequest and FuseResponse shares the same buffer to save memory usage, so the handlers must
+// not access input buffer after writing data to output buffer.
 struct FuseRequest {
     char buffer[MAX_REQUEST_SIZE];
     FuseRequest() {}
     const struct fuse_in_header& header() const {
         return *(const struct fuse_in_header*) buffer;
     }
-    const void* data() const {
+    void* data() {
         return (buffer + sizeof(struct fuse_in_header));
     }
     size_t data_length() const {
@@ -62,6 +68,26 @@
     }
 };
 
+template<typename T>
+class FuseResponse {
+   size_t size_;
+   T* const buffer_;
+public:
+   FuseResponse(void* buffer) : size_(0), buffer_(static_cast<T*>(buffer)) {}
+
+   void prepare_buffer(size_t size = sizeof(T)) {
+       memset(buffer_, 0, size);
+       size_ = size;
+   }
+
+   void set_size(size_t size) {
+       size_ = size;
+   }
+
+   size_t size() const { return size_; }
+   T* data() const { return buffer_; }
+};
+
 class ScopedFd {
     int mFd;
 
@@ -76,7 +102,7 @@
 };
 
 /**
- * The class is used to access AppFuse class in Java from fuse handlers.
+ * Fuse implementation consists of handlers parsing FUSE commands.
  */
 class AppFuse {
     JNIEnv* env_;
@@ -90,9 +116,9 @@
     AppFuse(JNIEnv* env, jobject self) :
         env_(env), self_(self), handle_counter_(0) {}
 
-    bool handle_fuse_request(int fd, const FuseRequest& req) {
-        ALOGV("Request op=%d", req.header().opcode);
-        switch (req.header().opcode) {
+    bool handle_fuse_request(int fd, FuseRequest* req) {
+        ALOGV("Request op=%d", req->header().opcode);
+        switch (req->header().opcode) {
             // TODO: Handle more operations that are enough to provide seekable
             // FD.
             case FUSE_LOOKUP:
@@ -110,20 +136,20 @@
                 invoke_handler(fd, req, &AppFuse::handle_fuse_open);
                 return true;
             case FUSE_READ:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192);
+                invoke_handler(fd, req, &AppFuse::handle_fuse_read);
                 return true;
             case FUSE_RELEASE:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0);
+                invoke_handler(fd, req, &AppFuse::handle_fuse_release);
                 return true;
             case FUSE_FLUSH:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0);
+                invoke_handler(fd, req, &AppFuse::handle_fuse_flush);
                 return true;
             default: {
                 ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
-                      req.header().opcode,
-                      req.header().unique,
-                      req.header().nodeid);
-                fuse_reply(fd, req.header().unique, -ENOSYS, NULL, 0);
+                      req->header().opcode,
+                      req->header().unique,
+                      req->header().nodeid);
+                fuse_reply(fd, req->header().unique, -ENOSYS, NULL, 0);
                 return true;
             }
         }
@@ -132,8 +158,7 @@
 private:
     int handle_fuse_lookup(const fuse_in_header& header,
                            const char* name,
-                           fuse_entry_out* out,
-                           uint32_t* /*unused*/) {
+                           FuseResponse<fuse_entry_out>* out) {
         if (header.nodeid != 1) {
             return -ENOENT;
         }
@@ -148,19 +173,19 @@
             return -ENOENT;
         }
 
-        out->nodeid = n;
-        out->attr_valid = 10;
-        out->entry_valid = 10;
-        out->attr.ino = n;
-        out->attr.mode = S_IFREG | 0777;
-        out->attr.size = size;
+        out->prepare_buffer();
+        out->data()->nodeid = n;
+        out->data()->attr_valid = 10;
+        out->data()->entry_valid = 10;
+        out->data()->attr.ino = n;
+        out->data()->attr.mode = S_IFREG | 0777;
+        out->data()->attr.size = size;
         return 0;
     }
 
     int handle_fuse_init(const fuse_in_header&,
                          const fuse_init_in* in,
-                         fuse_init_out* out,
-                         uint32_t* reply_size) {
+                         FuseResponse<fuse_init_out>* out) {
         // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out
         // defined (fuse version 7.6). The structure is the same from 7.6 through
         // 7.22. Beginning with 7.23, the structure increased in size and added
@@ -172,50 +197,58 @@
             return -1;
         }
 
+        // Before writing |out|, we need to copy data from |in|.
+        const uint32_t minor = in->minor;
+        const uint32_t max_readahead = in->max_readahead;
+
         // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
-        out->minor = std::min(in->minor, 15u);
+        size_t response_size = sizeof(fuse_init_out);
 #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
         // FUSE_KERNEL_VERSION >= 23.
 
         // If the kernel only works on minor revs older than or equal to 22,
         // then use the older structure size since this code only uses the 7.22
         // version of the structure.
-        if (in->minor <= 22) {
-            *reply_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
+        if (minor <= 22) {
+            response_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
         }
-#else
-        // Don't drop this line to prevent an 'unused' compile error.
-        *reply_size = sizeof(fuse_init_out);
 #endif
-
-        out->major = FUSE_KERNEL_VERSION;
-        out->max_readahead = in->max_readahead;
-        out->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
-        out->max_background = 32;
-        out->congestion_threshold = 32;
-        out->max_write = MAX_WRITE;
+        out->prepare_buffer(response_size);
+        out->data()->major = FUSE_KERNEL_VERSION;
+        out->data()->minor = std::min(minor, 15u);
+        out->data()->max_readahead = max_readahead;
+        out->data()->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
+        out->data()->max_background = 32;
+        out->data()->congestion_threshold = 32;
+        out->data()->max_write = MAX_WRITE;
 
         return 0;
     }
 
     int handle_fuse_getattr(const fuse_in_header& header,
                             const fuse_getattr_in* /* in */,
-                            fuse_attr_out* out,
-                            uint32_t* /*unused*/) {
-        if (header.nodeid != 1) {
-            return -ENOENT;
+                            FuseResponse<fuse_attr_out>* out) {
+        out->prepare_buffer();
+        out->data()->attr_valid = 10;
+        out->data()->attr.ino = header.nodeid;
+        if (header.nodeid == 1) {
+            out->data()->attr.mode = S_IFDIR | 0777;
+            out->data()->attr.size = 0;
+        } else {
+            int64_t size = get_file_size(header.nodeid);
+            if (size < 0) {
+                return -ENOENT;
+            }
+            out->data()->attr.mode = S_IFREG | 0777;
+            out->data()->attr.size = size;
         }
-        out->attr_valid = 1000 * 60 * 10;
-        out->attr.ino = header.nodeid;
-        out->attr.mode = S_IFDIR | 0777;
-        out->attr.size = 0;
+
         return 0;
     }
 
     int handle_fuse_open(const fuse_in_header& header,
                          const fuse_open_in* /* in */,
-                         fuse_open_out* out,
-                         uint32_t* /*unused*/) {
+                         FuseResponse<fuse_open_out>* out) {
         if (handles_.size() >= NUM_MAX_HANDLES) {
             // Too many open files.
             return -EMFILE;
@@ -224,68 +257,66 @@
         do {
            handle = handle_counter_++;
         } while (handles_.count(handle) != 0);
-
         handles_.insert(std::make_pair(handle, header.nodeid));
-        out->fh = handle;
+
+        out->prepare_buffer();
+        out->data()->fh = handle;
         return 0;
     }
 
     int handle_fuse_read(const fuse_in_header& /* header */,
                          const fuse_read_in* in,
-                         void* out,
-                         uint32_t* reply_size) {
+                         FuseResponse<void>* out) {
+        if (in->size > MAX_READ) {
+            return -EINVAL;
+        }
         const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
         if (it == handles_.end()) {
             return -EBADF;
         }
-        const int64_t result = get_object_bytes(
-                it->second,
-                in->offset,
-                in->size,
-                out);
+        uint64_t offset = in->offset;
+        uint32_t size = in->size;
+
+        // Overwrite the size after writing data.
+        out->prepare_buffer(0);
+        const int64_t result = get_object_bytes(it->second, offset, size, out->data());
         if (result < 0) {
             return -EIO;
         }
-        *reply_size = static_cast<size_t>(result);
+        out->set_size(result);
         return 0;
     }
 
     int handle_fuse_release(const fuse_in_header& /* header */,
                             const fuse_release_in* in,
-                            void* /* out */,
-                            uint32_t* /* reply_size */) {
+                            FuseResponse<void>* /* out */) {
         handles_.erase(in->fh);
         return 0;
     }
 
     int handle_fuse_flush(const fuse_in_header& /* header */,
                           const void* /* in */,
-                          void* /* out */,
-                          uint32_t* /* reply_size */) {
+                          FuseResponse<void>* /* out */) {
         return 0;
     }
 
     template <typename T, typename S>
     void invoke_handler(int fd,
-                        const FuseRequest& request,
+                        FuseRequest* request,
                         int (AppFuse::*handler)(const fuse_in_header&,
                                                 const T*,
-                                                S*,
-                                                uint32_t*),
-                        uint32_t reply_size = sizeof(S)) {
-        char reply_data[reply_size];
-        memset(reply_data, 0, reply_size);
+                                                FuseResponse<S>*)) {
+        FuseResponse<S> response(request->data());
         const int reply_code = (this->*handler)(
-                request.header(),
-                static_cast<const T*>(request.data()),
-                reinterpret_cast<S*>(reply_data),
-                &reply_size);
+                request->header(),
+                static_cast<const T*>(request->data()),
+                &response);
         fuse_reply(
                 fd,
-                request.header().unique,
+                request->header().unique,
                 reply_code,
-                reply_data,
-                reply_size);
+                request->data(),
+                response.size());
     }
 
     int64_t get_file_size(int inode) {
@@ -378,7 +409,7 @@
             continue;
         }
 
-        if (!appfuse.handle_fuse_request(fd, request)) {
+        if (!appfuse.handle_fuse_request(fd, &request)) {
             return JNI_TRUE;
         }
     }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 9c1cf64..811adda 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -1964,7 +1964,7 @@
                                 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE)
                                 || (mPrinter.getCapabilities() == null
                                 && printer.getCapabilities() != null);
-                mPrinter.copyFrom(printer);
+                mPrinter = printer;
             }
 
             if (available) {
@@ -2394,7 +2394,7 @@
 
             mPrinterAvailabilityDetector.updatePrinter(newPrinterState);
 
-            oldPrinterState.copyFrom(newPrinterState);
+            mCurrentPrinter = newPrinterState;
 
             if ((isActive && gotCapab) || (becameActive && hasCapab)) {
                 if (hasCapab && capabChanged) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
index f6caaa9..2706e25 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -190,18 +190,27 @@
      * Send the intent to trigger the {@link android.settings.ShowAdminSupportDetailsDialog}.
      */
     public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
-        Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
+        final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
         int adminUserId = UserHandle.myUserId();
+        if (admin.userId != UserHandle.USER_NULL) {
+            adminUserId = admin.userId;
+        }
+        context.startActivityAsUser(intent, new UserHandle(adminUserId));
+    }
+
+    public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+        final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
         if (admin != null) {
             if (admin.component != null) {
                 intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin.component);
             }
+            int adminUserId = UserHandle.myUserId();
             if (admin.userId != UserHandle.USER_NULL) {
                 adminUserId = admin.userId;
             }
             intent.putExtra(Intent.EXTRA_USER_ID, adminUserId);
         }
-        context.startActivityAsUser(intent, new UserHandle(adminUserId));
+        return intent;
     }
 
     public static void setTextViewPadlock(Context context,
@@ -224,6 +233,34 @@
             this.userId = userId;
         }
 
+        @Override
+        public boolean equals(Object object) {
+            if (object == this) return true;
+            if (!(object instanceof EnforcedAdmin)) return false;
+            EnforcedAdmin other = (EnforcedAdmin) object;
+            if (userId != other.userId) {
+                return false;
+            }
+            if ((component == null && other == null) ||
+                    (component != null && component.equals(other))) {
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "EnforcedAdmin{component=" + component + ",userId=" + userId + "}";
+        }
+
+        public void copyTo(EnforcedAdmin other) {
+            if (other == null) {
+                other = new EnforcedAdmin();
+            }
+            other.component = component;
+            other.userId = userId;
+        }
+
         public EnforcedAdmin() {}
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 569017a..13a46d0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -79,6 +79,15 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (enabled && isDisabledByAdmin()) {
+            mHelper.setDisabledByAdmin(null);
+            return;
+        }
+        super.setEnabled(enabled);
+    }
+
     public void setDisabledByAdmin(EnforcedAdmin admin) {
         if (mHelper.setDisabledByAdmin(admin)) {
             notifyChanged();
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index f041504..06aba96 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -45,7 +45,7 @@
     private EnforcedAdmin mEnforcedAdmin;
     private String mAttrUserRestriction = null;
 
-    RestrictedPreferenceHelper(Context context, Preference preference,
+    public RestrictedPreferenceHelper(Context context, Preference preference,
             AttributeSet attrs) {
         mContext = context;
         mPreference = preference;
@@ -54,21 +54,21 @@
         mRestrictedPadlockPadding = mContext.getResources().getDimensionPixelSize(
                 R.dimen.restricted_lock_icon_padding);
 
-        mAttrUserRestriction = attrs.getAttributeValue(
-                R.styleable.RestrictedPreference_userRestriction);
-        final TypedArray attributes = context.obtainStyledAttributes(attrs,
-                R.styleable.RestrictedPreference);
-        final TypedValue userRestriction =
-                attributes.peekValue(R.styleable.RestrictedPreference_userRestriction);
-        CharSequence data = null;
-        if (userRestriction != null && userRestriction.type == TypedValue.TYPE_STRING) {
-            if (userRestriction.resourceId != 0) {
-                data = context.getText(userRestriction.resourceId);
-            } else {
-                data = userRestriction.string;
+        if (attrs != null) {
+            final TypedArray attributes = context.obtainStyledAttributes(attrs,
+                    R.styleable.RestrictedPreference);
+            final TypedValue userRestriction =
+                    attributes.peekValue(R.styleable.RestrictedPreference_userRestriction);
+            CharSequence data = null;
+            if (userRestriction != null && userRestriction.type == TypedValue.TYPE_STRING) {
+                if (userRestriction.resourceId != 0) {
+                    data = context.getText(userRestriction.resourceId);
+                } else {
+                    data = userRestriction.string;
+                }
             }
+            mAttrUserRestriction = data == null ? null : data.toString();
         }
-        mAttrUserRestriction = data == null ? null : data.toString();
     }
 
     /**
@@ -100,7 +100,7 @@
     /**
      * Disable / enable if we have been passed the restriction in the xml.
      */
-    protected void onAttachedToHierarchy() {
+    public void onAttachedToHierarchy() {
         if (mAttrUserRestriction != null) {
             checkRestrictionAndSetDisabled(mAttrUserRestriction, UserHandle.myUserId());
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 308477b0..84e2bff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -79,6 +79,15 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (enabled && isDisabledByAdmin()) {
+            mHelper.setDisabledByAdmin(null);
+            return;
+        }
+        super.setEnabled(enabled);
+    }
+
     public void setDisabledByAdmin(EnforcedAdmin admin) {
         if (mHelper.setDisabledByAdmin(admin)) {
             notifyChanged();
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 72df96d..fa2226d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -2,7 +2,11 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
+import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -138,4 +142,33 @@
 
         return statusString;
     }
+
+    /**
+     * Determine whether a package is a "system package", in which case certain things (like
+     * disabling notifications or disabling the package altogether) should be disallowed.
+     */
+    public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
+        if (sSystemSignature == null) {
+            sSystemSignature = new Signature[]{ getSystemSignature(pm) };
+        }
+        return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
+    }
+
+    private static Signature[] sSystemSignature;
+
+    private static Signature getFirstSignature(PackageInfo pkg) {
+        if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
+            return pkg.signatures[0];
+        }
+        return null;
+    }
+
+    private static Signature getSystemSignature(PackageManager pm) {
+        try {
+            final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
+            return getFirstSignature(sys);
+        } catch (NameNotFoundException e) {
+        }
+        return null;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
new file mode 100755
index 0000000..77f2e19
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+final class A2dpSinkProfile implements LocalBluetoothProfile {
+    private static final String TAG = "A2dpSinkProfile";
+    private static boolean V = true;
+
+    private BluetoothA2dpSink mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final ParcelUuid[] SRC_UUIDS = {
+        BluetoothUuid.AudioSource,
+        BluetoothUuid.AdvAudioDist,
+    };
+
+    static final String NAME = "A2DPSink";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 5;
+
+    // These callbacks run on the main thread.
+    private final class A2dpSinkServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) Log.d(TAG,"Bluetooth service connected");
+            mService = (BluetoothA2dpSink) proxy;
+            // We just bound to the service, so refresh the UI for any connected A2DP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "A2dpSinkProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(A2dpSinkProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady=true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) Log.d(TAG,"Bluetooth service disconnected");
+            mIsProfileReady=false;
+        }
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    A2dpSinkProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new A2dpSinkServiceListener(),
+                BluetoothProfile.A2DP_SINK);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) return new ArrayList<BluetoothDevice>(0);
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (mService == null) return false;
+        List<BluetoothDevice> srcs = getConnectedDevices();
+        if (srcs != null) {
+            for (BluetoothDevice src : srcs) {
+                if (src.equals(device)) {
+                    // Connect to same device, Ignore it
+                    Log.d(TAG,"Ignoring Connect");
+                    return true;
+                }
+            }
+            for (BluetoothDevice src : srcs) {
+                mService.disconnect(src);
+            }
+        }
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (mService == null) return false;
+        // Downgrade priority as user is disconnecting the headset.
+        if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
+            mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        }
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) return false;
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) return;
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    boolean isA2dpPlaying() {
+        if (mService == null) return false;
+        List<BluetoothDevice> srcs = mService.getConnectedDevices();
+        if (!srcs.isEmpty()) {
+            if (mService.isA2dpPlaying(srcs.get(0))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        // we need to have same string in UI for even SINK Media Audio.
+        return R.string.bluetooth_profile_a2dp;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_a2dp_profile_summary_connected;
+
+            default:
+                return Utils.getConnectionStateSummary(state);
+        }
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_headphones_a2dp;
+    }
+
+    protected void finalize() {
+        if (V) Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP_SINK,
+                                                                       mService);
+                mService = null;
+            }catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up A2DP proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index d994841..7ee53a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -851,7 +851,8 @@
 
                 case BluetoothProfile.STATE_DISCONNECTED:
                     if (profile.isProfileReady()) {
-                        if (profile instanceof A2dpProfile) {
+                        if ((profile instanceof A2dpProfile)||
+                            (profile instanceof A2dpSinkProfile)){
                             a2dpNotConnected = true;
                         } else if (profile instanceof HeadsetProfile) {
                             headsetNotConnected = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
old mode 100644
new mode 100755
index f935f31..9c5abf3
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -162,6 +162,10 @@
                 if (a2dp != null && a2dp.isA2dpPlaying()) {
                     return;
                 }
+                A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
+                if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
+                    return;
+                }
             }
 
             if (mAdapter.startDiscovery()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
old mode 100644
new mode 100755
index 8f5e1f1..b05e34c
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -17,6 +17,7 @@
 package com.android.settingslib.bluetooth;
 
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothMap;
@@ -73,6 +74,7 @@
     private final BluetoothEventManager mEventManager;
 
     private A2dpProfile mA2dpProfile;
+    private A2dpSinkProfile mA2dpSinkProfile;
     private HeadsetProfile mHeadsetProfile;
     private MapProfile mMapProfile;
     private final HidProfile mHidProfile;
@@ -136,10 +138,10 @@
      * @param uuids
      */
     void updateLocalProfiles(ParcelUuid[] uuids) {
-        // A2DP
+        // A2DP SRC
         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
             if (mA2dpProfile == null) {
-                if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
+                if(DEBUG) Log.d(TAG, "Adding local A2DP SRC profile");
                 mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
                 addProfile(mA2dpProfile, A2dpProfile.NAME,
                         BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
@@ -148,6 +150,17 @@
             Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
         }
 
+        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
+            if (mA2dpSinkProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local A2DP Sink profile");
+                mA2dpSinkProfile = new A2dpSinkProfile(mContext, mLocalAdapter, mDeviceManager, this);
+                addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME,
+                        BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mA2dpSinkProfile != null) {
+            Log.w(TAG, "Warning: A2DP Sink profile was previously added but the UUID is now missing.");
+        }
+
         // Headset / Handsfree
         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
@@ -288,6 +301,10 @@
         if (profile != null) {
             return profile.isProfileReady();
         }
+        profile = mA2dpSinkProfile;
+        if (profile != null) {
+            return profile.isProfileReady();
+        }
         return false;
     }
 
@@ -295,6 +312,13 @@
         return mA2dpProfile;
     }
 
+    A2dpSinkProfile getA2dpSinkProfile() {
+        if ((mA2dpSinkProfile != null)&&(mA2dpSinkProfile.isProfileReady()))
+        return mA2dpSinkProfile;
+        else
+            return null;
+    }
+
     public HeadsetProfile getHeadsetProfile() {
         return mHeadsetProfile;
     }
@@ -345,6 +369,12 @@
             removedProfiles.remove(mA2dpProfile);
         }
 
+        if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) &&
+                mA2dpSinkProfile != null) {
+                profiles.add(mA2dpSinkProfile);
+                removedProfiles.remove(mA2dpSinkProfile);
+            }
+
         if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
             mOppProfile != null) {
             profiles.add(mOppProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index f7b291b..2ee4b12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -234,10 +234,13 @@
     }
 
     public boolean matches(WifiConfiguration config) {
-        if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint())
+        if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
             return config.FQDN.equals(mConfig.providerFriendlyName);
-        else
-            return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config);
+        } else {
+            return ssid.equals(removeDoubleQuotes(config.SSID))
+                    && security == getSecurity(config)
+                    && (mConfig == null || mConfig.shared == config.shared);
+        }
     }
 
     public WifiConfiguration getConfig() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6201fd6..8d57b88 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -146,6 +146,9 @@
     <!-- Access battery information -->
     <uses-permission android:name="android.permission.BATTERY_STATS" />
 
+    <!-- DevicePolicyManager get user restrictions -->
+    <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
@@ -306,6 +309,27 @@
             android:launchMode="singleTop"
             android:excludeFromRecents="true" />
 
+        <!-- started from PipUI -->
+        <activity
+            android:name="com.android.systemui.tv.pip.PipMenuActivity"
+            android:exported="true"
+            android:theme="@style/PipTheme"
+            android:launchMode="singleTop"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|layoutDirection"
+            android:resizeable="true"
+            android:supportsPictureInPicture="true"
+            androidprv:alwaysFocusable="true"
+            android:excludeFromRecents="true" />
+        <activity
+            android:name="com.android.systemui.tv.pip.PipOverlayActivity"
+            android:exported="true"
+            android:theme="@style/PipTheme"
+            android:launchMode="singleTop"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|layoutDirection"
+            android:resizeable="true"
+            android:supportsPictureInPicture="true"
+            android:excludeFromRecents="true" />
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index bc18221..2ea8c9c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,6 +13,7 @@
 -keep class com.android.systemui.statusbar.car.CarStatusBar
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
+-keep class com.android.systemui.SystemUIFactory
 
 -keepclassmembers class ** {
     public void onBusEvent(**);
diff --git a/packages/SystemUI/res/color/qs_tile_text.xml b/packages/SystemUI/res/color/qs_tile_text.xml
new file mode 100644
index 0000000..90e0bce
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_tile_text.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/qs_tile_disabled_color" />
+    <item android:color="#B3FFFFFF" /> <!-- 70% white -->
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_user_detail_name.xml b/packages/SystemUI/res/color/qs_user_detail_name.xml
index 57f7e65..35c7a4f 100644
--- a/packages/SystemUI/res/color/qs_user_detail_name.xml
+++ b/packages/SystemUI/res/color/qs_user_detail_name.xml
@@ -18,5 +18,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_activated="true" android:color="@color/current_user_border_color" />
+    <item android:state_enabled="false" android:color="@color/qs_tile_disabled_color" />
     <item android:color="#66ffffff" /> <!-- 40% white -->
 </selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/car_ic_arrow.xml b/packages/SystemUI/res/drawable/car_ic_arrow.xml
new file mode 100644
index 0000000..9d292cc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_arrow.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
+    <path
+        android:pathData="M0 0h48v48H0z"
+        android:fillColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/car_navigation_button.xml b/packages/SystemUI/res/layout/car_navigation_button.xml
new file mode 100644
index 0000000..87c8f04
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_navigation_button.xml
@@ -0,0 +1,42 @@
+<?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.
+*/
+-->
+
+<com.android.systemui.statusbar.car.CarNavigationButton
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="match_parent"
+        android:layout_width="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center">
+    <com.android.keyguard.AlphaOptimizedImageButton
+            android:id="@+id/car_nav_button_icon"
+            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:layout_centerInParent="true"
+            android:animateLayoutChanges="true">
+    </com.android.keyguard.AlphaOptimizedImageButton>
+
+    <com.android.keyguard.AlphaOptimizedImageButton
+            android:id="@+id/car_nav_button_more_icon"
+            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_toRightOf="@+id/car_nav_button_icon"
+            android:animateLayoutChanges="true">
+    </com.android.keyguard.AlphaOptimizedImageButton>
+</com.android.systemui.statusbar.car.CarNavigationButton>
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
new file mode 100644
index 0000000..c5b4e84
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -0,0 +1,35 @@
+<?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.keyguard.AlphaOptimizedLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_icon_area_inner"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <com.android.systemui.statusbar.StatusBarIconView
+        android:id="@+id/moreIcon"
+        android:layout_width="@dimen/status_bar_icon_size"
+        android:layout_height="match_parent"
+        android:src="@drawable/stat_notify_more"
+        android:visibility="gone" />
+    <com.android.systemui.statusbar.phone.IconMerger
+        android:id="@+id/notificationIcons"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"/>
+</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index 6facb71..ccdddf7 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -60,6 +60,7 @@
         android:clickable="true"
         android:focusable="true"
         android:scaleType="center"
+        android:contentDescription="@*android:string/media_route_controller_disconnect"
         android:src="@drawable/ic_qs_cancel" />
 
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
new file mode 100644
index 0000000..603ebbf
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+     <TextView android:id="@+id/tile_label"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:textColor="@color/qs_tile_text"
+             android:gravity="center_horizontal"
+             android:minLines="2"
+             android:padding="0dp"
+             android:fontFamily="sans-serif-condensed"
+             android:textStyle="normal"
+             android:textSize="@dimen/qs_tile_text_size"
+             android:clickable="false" />
+     <ImageView android:id="@+id/restricted_padlock"
+             android:layout_width="@dimen/qs_tile_text_size"
+             android:layout_height="@dimen/qs_tile_text_size"
+             android:src="@drawable/ic_settings_lock_outline"
+             android:layout_marginLeft="@dimen/restricted_padlock_pading"
+             android:baselineAlignBottom="true"
+             android:scaleType="centerInside"
+             android:visibility="gone" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index af22f03..a22c360 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -40,12 +40,25 @@
             systemui:framePadding="6dp"
             systemui:activeFrameColor="@color/current_user_border_color"/>
 
-    <TextView
-            android:id="@+id/user_name"
+    <LinearLayout
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="@dimen/qs_detail_item_secondary_text_size"
-            android:textColor="@color/qs_user_detail_name"
-            android:gravity="center_horizontal" />
+            android:layout_height="wrap_content">
+        <TextView
+                android:id="@+id/user_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="@dimen/qs_detail_item_secondary_text_size"
+                android:textColor="@color/qs_user_detail_name"
+                android:gravity="center_horizontal" />
+        <ImageView
+                android:id="@+id/restricted_padlock"
+                android:layout_width="@dimen/qs_detail_item_secondary_text_size"
+                android:layout_height="@dimen/qs_detail_item_secondary_text_size"
+                android:src="@drawable/ic_settings_lock_outline"
+                android:layout_marginLeft="@dimen/restricted_padlock_pading"
+                android:baselineAlignBottom="true"
+                android:scaleType="centerInside"
+                android:visibility="gone" />
+    </LinearLayout>
 
 </com.android.systemui.qs.tiles.UserDetailItemView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 5b44c17..c813818 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -37,7 +37,8 @@
             android:translationZ="4dp"
             android:contentDescription="@string/recents_lock_to_app_button_label"
             android:background="@drawable/recents_lock_to_task_button_bg"
-            android:visibility="invisible">
+            android:visibility="invisible"
+            android:alpha="0">
             <ImageView
                 android:layout_width="@dimen/recents_lock_to_app_icon_size"
                 android:layout_height="@dimen/recents_lock_to_app_icon_size"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 04f18c5..07ac39a 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+     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.
@@ -63,4 +64,12 @@
         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" />
 </com.android.systemui.recents.views.TaskViewHeader>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index aaa5a09..bea9f78 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -47,34 +47,14 @@
         android:orientation="horizontal"
         >
 
+        <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
+             PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/notification_icon_area"
             android:layout_width="0dip"
             android:layout_height="match_parent"
             android:layout_weight="1"
-            android:orientation="horizontal"
-            >
-            <!-- The alpha of this area is both controlled from PhoneStatusBarTransitions and
-                 PhoneStatusBar (DISABLE_NOTIFICATION_ICONS), so we need two views here. -->
-            <com.android.keyguard.AlphaOptimizedLinearLayout
-                android:id="@+id/notification_icon_area_inner"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                >
-                <com.android.systemui.statusbar.StatusBarIconView android:id="@+id/moreIcon"
-                    android:layout_width="@dimen/status_bar_icon_size"
-                    android:layout_height="match_parent"
-                    android:src="@drawable/stat_notify_more"
-                    android:visibility="gone"
-                    />
-                <com.android.systemui.statusbar.phone.IconMerger android:id="@+id/notificationIcons"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_alignParentStart="true"
-                    android:gravity="center_vertical"
-                    android:orientation="horizontal"/>
-            </com.android.keyguard.AlphaOptimizedLinearLayout>
-        </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+            android:orientation="horizontal" />
 
         <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
new file mode 100644
index 0000000..a638d175
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -0,0 +1,61 @@
+<?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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:layout_alignParentEnd="true"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingStart="10dp"
+        android:paddingEnd="10dp"
+        android:background="#88FFFFFF">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_gravity="center_vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+
+            <Button android:id="@+id/full"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/pip_fullscreen"
+                android:textSize="10sp"
+                android:focusable="true" />
+
+            <Button android:id="@+id/exit"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/pip_exit"
+                android:textSize="10sp"
+                android:focusable="true" />
+
+            <Button android:id="@+id/cancel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/pip_cancel"
+                android:textSize="10sp"
+                android:focusable="true" />
+        </LinearLayout>
+    </FrameLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml
new file mode 100644
index 0000000..e8691b5
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/guide_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:padding="3dp"
+        android:textSize="13sp"
+        android:textColor="#111111"
+        android:background="#99EEEEEE"
+        android:text="@string/pip_hold_home" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/arrays_car.xml b/packages/SystemUI/res/values/arrays_car.xml
index 230479d..8c760fc 100644
--- a/packages/SystemUI/res/values/arrays_car.xml
+++ b/packages/SystemUI/res/values/arrays_car.xml
@@ -22,7 +22,9 @@
          isn't a longpress action associated with a shortcut item, put in an empty item to make
          sure everything lines up.
     -->
-    <array name="car_shortcut_icons" />
-    <array name="car_shortcut_intent_uris" />
-    <array name="car_shortcut_longpress_intent_uris" />
+    <array name="car_facet_icons" />
+    <array name="car_facet_intent_uris" />
+    <array name="car_facet_longpress_intent_uris" />
+    <array name="car_facet_package_filters"/>
+    <array name="car_facet_category_filters"/>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 61c71dd..a80a5de 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -36,7 +36,6 @@
     <color name="system_warning_color">#fff4511e</color><!-- deep orange 600 -->
     <color name="qs_text">#FFFFFFFF</color>
     <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
-    <color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
     <color name="qs_subhead">#99FFFFFF</color><!-- 60% white -->
     <color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200 -->
     <color name="qs_detail_button">#FFB0BEC5</color><!-- 100% blue grey 200 -->
@@ -48,6 +47,7 @@
     <color name="data_usage_graph_warning">#FFFFFFFF</color>
     <color name="status_bar_clock_color">#FFFFFFFF</color>
     <color name="qs_user_detail_icon_muted">#FFFFFFFF</color> <!-- not so muted after all -->
+    <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
 
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff686868</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 955af82..e98ec82 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -177,6 +177,9 @@
     <!-- The animation duration for scrolling the stack to a particular item. -->
     <integer name="recents_animate_task_stack_scroll_duration">200</integer>
 
+    <!-- The animation duration for scrolling the stack to a particular item. -->
+    <integer name="recents_auto_advance_duration">2000</integer>
+
     <!-- The animation duration for entering and exiting the history. -->
     <integer name="recents_history_transition_duration">250</integer>
 
@@ -279,5 +282,8 @@
     <!-- Whether to show the full screen user switcher. -->
     <bool name="config_enableFullscreenUserSwitcher">false</bool>
 
+    <!-- SystemUIFactory component -->
+    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
+
 </resources>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 097c352..3fb5f18 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -167,6 +167,8 @@
     <dimen name="segmented_button_spacing">0dp</dimen>
     <dimen name="borderless_button_radius">2dp</dimen>
 
+    <dimen name="restricted_padlock_pading">4dp</dimen>
+
     <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
     <dimen name="qs_peek_height">0dp</dimen>
 
@@ -298,7 +300,7 @@
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
     <dimen name="notifications_top_padding">4dp</dimen>
-    
+
     <!-- Minimum distance the user has to drag down to go to the full shade. -->
     <dimen name="keyguard_drag_down_min_distance">100dp</dimen>
 
@@ -509,7 +511,7 @@
 
     <!-- The maximum width of the navigation bar ripples. -->
     <dimen name="key_button_ripple_max_width">95dp</dimen>
-    
+
     <!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card
          and the shadow. -->
     <dimen name="fake_shadow_inset">1dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0e4f98f..de49677 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3,16 +3,16 @@
 /**
  * Copyright (c) 2009, The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); 
- * you may not use this file except in compliance with the License. 
- * You may obtain a copy of the License at 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0 
+ *     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 
+ * 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.
  */
 -->
@@ -1173,6 +1173,11 @@
     <!-- Description for the toggle for fast-toggling recents via the recents button. DO NOT TRANSLATE -->
     <string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string>
 
+    <!-- Toggles the fast-toggling indicator. DO NOT TRANSLATE -->
+    <string name="overview_fast_toggle_indicator">Enable fast toggle indicator</string>
+    <!-- Description for the fast-toggling indicator. DO NOT TRANSLATE -->
+    <string name="overview_fast_toggle_indicator_desc">Show an indicator for the launch timeout</string>
+
     <!-- Toggle to set the initial scroll state to be paging or stack. DO NOT TRANSLATE -->
     <string name="overview_initial_state_paging">Initialize to paging</string>
     <!-- Description for the toggle to set the initial scroll state to be paging or stack. DO NOT TRANSLATE -->
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
new file mode 100644
index 0000000..d432a62
--- /dev/null
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Button to close PIP on PIP UI -->
+    <string name="pip_exit">Close PIP</string>
+    <!-- Button to move PIP screen to the fullscreen on PIP UI -->
+    <string name="pip_fullscreen">Full screen</string>
+    <!-- Button to play the current media on PIP UI -->
+    <string name="pip_play">Play</string>
+    <!-- Button to pause the current media on PIP UI -->
+    <string name="pip_pause">Pause</string>
+    <!-- Button to close PIP overlay menu on PIP UI -->
+    <string name="pip_cancel">Cancel</string>
+    <!-- Overlay text on PIP -->
+    <string name="pip_hold_home">Hold HOME to control PIP</string>
+</resources>
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
new file mode 100644
index 0000000..3f0caab
--- /dev/null
+++ b/packages/SystemUI/res/values/styles_tv.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="PipTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">false</item>
+     </style>
+</resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 90cd394..11ef735d 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -84,6 +84,12 @@
             android:title="@string/overview_fast_toggle_via_button"
             android:summary="@string/overview_fast_toggle_via_button_desc" />
 
+        <com.android.systemui.tuner.TunerSwitch
+            android:key="overview_fast_toggle_indicator"
+            android:title="@string/overview_fast_toggle_indicator"
+            android:summary="@string/overview_fast_toggle_indicator_desc"
+            android:dependency="overview_fast_toggle_via_button" />
+
     </PreferenceScreen>
 
     <SwitchPreference
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 870e0af..fabc7b7 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -86,6 +86,7 @@
     final private int[] mTmpPos = new int[2];
     private int mFalsingThreshold;
     private boolean mTouchAboveFalsingThreshold;
+    private boolean mDisableHwLayers;
 
     public SwipeHelper(int swipeDirection, Callback callback, Context context) {
         mCallback = callback;
@@ -115,6 +116,10 @@
         mPagingTouchSlop = pagingTouchSlop;
     }
 
+    public void setDisableHardwareLayers(boolean disableHwLayers) {
+        mDisableHwLayers = disableHwLayers;
+    }
+
     private float getPos(MotionEvent ev) {
         return mSwipeDirection == X ? ev.getX() : ev.getY();
     }
@@ -147,7 +152,7 @@
         }
     }
 
-    private float getSize(View v) {
+    protected float getSize(View v) {
         return mSwipeDirection == X ? v.getMeasuredWidth() :
                 v.getMeasuredHeight();
     }
@@ -178,10 +183,12 @@
         if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
             if (FADE_OUT_DURING_SWIPE && dismissable) {
                 float alpha = swipeProgress;
-                if (alpha != 0f && alpha != 1f) {
-                    animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                } else {
-                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                if (!mDisableHwLayers) {
+                    if (alpha != 0f && alpha != 1f) {
+                        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                    } else {
+                        animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                    }
                 }
                 animView.setAlpha(getSwipeProgressForOffset(animView));
             }
@@ -345,7 +352,9 @@
             duration = fixedDuration;
         }
 
-        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        if (!mDisableHwLayers) {
+            animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        }
         ObjectAnimator anim = createTranslationAnimation(animView, newPos);
         if (useAccelerateInterpolator) {
             anim.setInterpolator(mFastOutLinearInInterpolator);
@@ -362,7 +371,9 @@
                 if (endAction != null) {
                     endAction.run();
                 }
-                animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                if (!mDisableHwLayers) {
+                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
             }
         });
         anim.addUpdateListener(new AnimatorUpdateListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 19e299254..472648a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,6 +54,7 @@
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
             com.android.systemui.keyboard.KeyboardUI.class,
+            com.android.systemui.tv.pip.PipUI.class
     };
 
     /**
@@ -80,6 +81,8 @@
         // the theme set there.
         setTheme(R.style.systemui_theme);
 
+        SystemUIFactory.createFromConfig(this);
+
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
             filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
new file mode 100644
index 0000000..681b39e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+
+/**
+ * Class factory to provide customizable SystemUI components.
+ */
+public class SystemUIFactory {
+    private static final String TAG = "SystemUIFactory";
+
+    static SystemUIFactory mFactory;
+
+    public static SystemUIFactory getInstance() {
+        return mFactory;
+    }
+
+    public static void createFromConfig(Context context) {
+        final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
+        if (clsName == null || clsName.length() == 0) {
+            throw new RuntimeException("No SystemUIFactory component configured");
+        }
+
+        try {
+            Class<?> cls = null;
+            cls = context.getClassLoader().loadClass(clsName);
+            mFactory = (SystemUIFactory) cls.newInstance();
+        } catch (Throwable t) {
+            Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
+            throw new RuntimeException(t);
+        }
+    }
+
+    public SystemUIFactory() {}
+
+    public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
+            ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
+        return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
+    }
+
+    public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
+            LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
+            ViewGroup container) {
+        return new KeyguardBouncer(context, callback, lockPatternUtils, windowManager, container);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ed29a8f..9a00b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -69,6 +69,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.FingerprintUnlockController;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -591,8 +592,9 @@
         updateInputRestrictedLocked();
         mTrustManager.reportKeyguardShowingChanged();
 
-        mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext,
-                mViewMediatorCallback, mLockPatternUtils);
+        mStatusBarKeyguardViewManager =
+                SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
+                        mViewMediatorCallback, mLockPatternUtils);
         final ContentResolver cr = mContext.getContentResolver();
 
         mDeviceInteractive = mPM.isInteractive();
@@ -1736,10 +1738,15 @@
     public void onActivityDrawn() {
         mHandler.sendEmptyMessage(ON_ACTIVITY_DRAWN);
     }
+
     public ViewMediatorCallback getViewMediatorCallback() {
         return mViewMediatorCallback;
     }
 
+    public LockPatternUtils getLockPatternUtils() {
+        return mLockPatternUtils;
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.print("  mSystemReady: "); pw.println(mSystemReady);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index 01eb5f9..7651ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -81,7 +81,11 @@
                 }
             }
         }
-
+        if (state.disabledByPolicy) {
+            iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
+        } else {
+            iv.clearColorFilter();
+        }
     }
 
     protected int getIconMeasureMode() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 2d9d105..de7c02d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -23,10 +23,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
+
+import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.qs.QSTile.State;
 import com.android.systemui.qs.external.TileServices;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -47,6 +50,8 @@
 import java.util.Collection;
 import java.util.Objects;
 
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
 /**
  * Base quick-settings tile, extend this to create a new tile.
  *
@@ -256,6 +261,18 @@
         mCallbacks.clear();
     }
 
+    protected void checkIfRestrictionEnforced(State state, String userRestriction) {
+        EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+                userRestriction, UserHandle.myUserId());
+        if (admin != null) {
+            state.disabledByPolicy = true;
+            state.enforcedAdmin = admin;
+        } else {
+            state.disabledByPolicy = false;
+            state.enforcedAdmin = null;
+        }
+    }
+
     protected final class H extends Handler {
         private static final int ADD_CALLBACK = 1;
         private static final int CLICK = 2;
@@ -282,8 +299,14 @@
                     handleAddCallback((QSTile.Callback)msg.obj);
                 } else if (msg.what == CLICK) {
                     name = "handleClick";
-                    mAnnounceNextStateChange = true;
-                    handleClick();
+                    if (mState.disabledByPolicy) {
+                        Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+                                mContext, mState.enforcedAdmin);
+                        mHost.startActivityDismissingKeyguard(intent);
+                    } else {
+                        mAnnounceNextStateChange = true;
+                        handleClick();
+                    }
                 } else if (msg.what == SECONDARY_CLICK) {
                     name = "handleSecondaryClick";
                     handleSecondaryClick();
@@ -437,6 +460,8 @@
         public CharSequence contentDescription;
         public CharSequence dualLabelContentDescription;
         public boolean autoMirrorDrawable = true;
+        public boolean disabledByPolicy;
+        public EnforcedAdmin enforcedAdmin;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
@@ -446,12 +471,16 @@
                     || !Objects.equals(other.contentDescription, contentDescription)
                     || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
                     || !Objects.equals(other.dualLabelContentDescription,
-                    dualLabelContentDescription);
+                    dualLabelContentDescription)
+                    || !Objects.equals(other.disabledByPolicy, disabledByPolicy)
+                    || !Objects.equals(other.enforcedAdmin, enforcedAdmin);
             other.icon = icon;
             other.label = label;
             other.contentDescription = contentDescription;
             other.dualLabelContentDescription = dualLabelContentDescription;
             other.autoMirrorDrawable = autoMirrorDrawable;
+            other.disabledByPolicy = disabledByPolicy;
+            enforcedAdmin.copyTo(other.enforcedAdmin);
             return changed;
         }
 
@@ -467,6 +496,8 @@
             sb.append(",contentDescription=").append(contentDescription);
             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
             sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
+            sb.append(",disabledByPolicy=").append(disabledByPolicy);
+            sb.append(",enforcedAdmin=").append(enforcedAdmin);
             return sb.append(']');
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 41ac4d9..664ca39 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -19,32 +19,33 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Typeface;
 import android.util.MathUtils;
-import android.util.TypedValue;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.TextView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 
 /** View that represents a standard quick settings tile. **/
 public class QSTileView extends QSTileBaseView {
-    private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
-            Typeface.NORMAL);
-
     protected final Context mContext;
+    private QSIconView mIconView;
     private final int mTileSpacingPx;
     private int mTilePaddingTopPx;
 
     private TextView mLabel;
+    private ImageView mPadLock;
 
     public QSTileView(Context context, QSIconView icon) {
         super(context, icon);
 
         mContext = context;
+        mIconView = icon;
         final Resources res = context.getResources();
         mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing);
+
         setClipChildren(false);
 
         setClickable(true);
@@ -76,16 +77,10 @@
 
     private void createLabel() {
         final Resources res = mContext.getResources();
-        mLabel = new TextView(mContext);
-        mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
-        mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
-        mLabel.setMinLines(2);
-        mLabel.setPadding(0, 0, 0, 0);
-        mLabel.setTypeface(CONDENSED);
-        mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
-        mLabel.setClickable(false);
-        addView(mLabel);
+        View view = LayoutInflater.from(mContext).inflate(R.layout.qs_tile_label, null);
+        mLabel = (TextView) view.findViewById(R.id.tile_label);
+        mPadLock = (ImageView) view.findViewById(R.id.restricted_padlock);
+        addView(view);
     }
 
     public void init(OnClickListener clickPrimary, OnLongClickListener longClick) {
@@ -96,5 +91,7 @@
     protected void handleStateChanged(QSTile.State state) {
         super.handleStateChanged(state);
         mLabel.setText(state.label);
+        mLabel.setEnabled(!state.disabledByPolicy);
+        mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 4d9b266..39eda6b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.view.LayoutInflater;
@@ -99,14 +100,6 @@
 
     @Override
     public void handleClick() {
-        if (mController.isVolumeRestricted()) {
-            // Collapse the panels, so the user can see the toast.
-            mHost.collapsePanels();
-            SysUIToast.makeText(mContext, mContext.getString(
-                    com.android.internal.R.string.error_message_change_not_allowed),
-                    Toast.LENGTH_LONG).show();
-            return;
-        }
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         if (mState.value) {
             mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
@@ -123,6 +116,8 @@
         final boolean newValue = zen != Global.ZEN_MODE_OFF;
         final boolean valueChanged = state.value != newValue;
         state.value = newValue;
+        state.disabledByPolicy = mController.isVolumeRestricted();
+        checkIfRestrictionEnforced(state, UserManager.DISALLOW_ADJUST_VOLUME);
         switch (zen) {
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 21c5c96..167c611 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -43,6 +43,7 @@
     private TextView mName;
     private Typeface mRegularTypeface;
     private Typeface mActivatedTypeface;
+    private View mRestrictedPadlock;
 
     public UserDetailItemView(Context context) {
         this(context, null);
@@ -59,6 +60,7 @@
     public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.UserDetailItemView, defStyleAttr, defStyleRes);
         final int N = a.getIndexCount();
@@ -95,6 +97,12 @@
         mAvatar.setDrawable(picture);
     }
 
+    public void setDisabledByAdmin(boolean disabled) {
+        mRestrictedPadlock.setVisibility(disabled ? View.VISIBLE : View.GONE);
+        mName.setEnabled(!disabled);
+        mAvatar.setDisabled(disabled);
+    }
+
     @Override
     protected void onFinishInflate() {
         mAvatar = (UserAvatarView) findViewById(R.id.user_picture);
@@ -106,6 +114,7 @@
             mActivatedTypeface = mName.getTypeface();
         }
         updateTypeface();
+        mRestrictedPadlock = findViewById(R.id.restricted_padlock);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index d4f54b6..b44ef0b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -16,17 +16,18 @@
 
 package com.android.systemui.qs.tiles;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.R;
-import com.android.systemui.qs.PseudoGridView;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-
 import android.content.Context;
+import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.R;
+import com.android.systemui.qs.PseudoGridView;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
 /**
  * Quick settings detail view for user switching.
  */
@@ -55,11 +56,13 @@
     public static class Adapter extends UserSwitcherController.BaseUserAdapter
             implements OnClickListener {
 
-        private Context mContext;
+        private final Context mContext;
+        private final UserSwitcherController mController;
 
         public Adapter(Context context, UserSwitcherController controller) {
             super(controller);
             mContext = context;
+            mController = controller;
         }
 
         @Override
@@ -77,6 +80,7 @@
                 v.bind(name, item.picture);
             }
             v.setActivated(item.isCurrent);
+            v.setDisabledByAdmin(item.isDisabledByAdmin);
             v.setTag(item);
             return v;
         }
@@ -85,8 +89,14 @@
         public void onClick(View view) {
             UserSwitcherController.UserRecord tag =
                     (UserSwitcherController.UserRecord) view.getTag();
-            MetricsLogger.action(mContext, MetricsLogger.QS_SWITCH_USER);
-            switchTo(tag);
+            if (tag.isDisabledByAdmin) {
+                final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+                        mContext, tag.enforcedAdmin);
+                mController.startActivity(intent);
+            } else {
+                MetricsLogger.action(mContext, MetricsLogger.QS_SWITCH_USER);
+                switchTo(tag);
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index e4d8067..db55f28 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -113,16 +113,12 @@
     private FinishRecentsRunnable mFinishLaunchHomeRunnable;
 
     // The trigger to automatically launch the current task
-    private DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
-        @Override
-        public void run() {
-            dismissRecentsToFocusedTask();
-        }
-    });
+    private int mFocusTimerDuration;
+    private DozeTrigger mIterateTrigger;
 
     /**
      * A common Runnable to finish Recents by launching Home with an animation depending on the
-     * last activity launch state.  Generally we always launch home when we exit Recents rather than
+     * last activity launch state. Generally we always launch home when we exit Recents rather than
      * just finishing the activity since we don't know what is behind Recents in the task stack.
      */
     class FinishRecentsRunnable implements Runnable {
@@ -196,13 +192,13 @@
         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) {
-            ArrayList<Task> tasks = stack.getStackTasks();
-            int taskCount = tasks.size();
             for (int j = 0; j < taskCount; j++) {
                 Task t = tasks.get(j);
                 if (t.key.id == launchState.launchedToTaskId) {
@@ -214,12 +210,12 @@
         }
 
         // Animate the SystemUI scrims into view
-        boolean hasStatusBarScrim = stack.getStackTaskCount() > 0;
+        boolean hasStatusBarScrim = taskCount > 0;
         boolean animateStatusBarScrim = launchState.launchedFromHome;
-        boolean hasNavBarScrim = (stack.getStackTaskCount() > 0) && !config.hasTransposedNavBar;
+        boolean hasNavBarScrim = (taskCount > 0) && !config.hasTransposedNavBar;
         boolean animateNavBarScrim = true;
-        mScrimViews.prepareEnterRecentsAnimation(hasStatusBarScrim, animateStatusBarScrim, hasNavBarScrim,
-                animateNavBarScrim);
+        mScrimViews.prepareEnterRecentsAnimation(hasStatusBarScrim, animateStatusBarScrim,
+                hasNavBarScrim, animateNavBarScrim);
 
         // Keep track of whether we launched from the nav bar button or via alt-tab
         if (launchState.launchedWithAltTab) {
@@ -236,7 +232,6 @@
             MetricsLogger.count(this, "overview_source_home", 1);
         }
         // Keep track of the total stack task count
-        int taskCount = stack.getStackTaskCount();
         MetricsLogger.histogram(this, "overview_task_count", taskCount);
     }
 
@@ -357,6 +352,14 @@
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 
+        mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
+        mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
+            @Override
+            public void run() {
+                dismissRecentsToFocusedTask();
+            }
+        });
+
         // Create the home intent runnable
         Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
         homeIntent.addCategory(Intent.CATEGORY_HOME);
@@ -543,7 +546,8 @@
                     if (backward) {
                         EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                     } else {
-                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
+                        EventBus.getDefault().send(
+                                new FocusNextTaskViewEvent(false /* showTimerIndicator */));
                     }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
 
@@ -555,7 +559,8 @@
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_UP: {
-                EventBus.getDefault().send(new FocusNextTaskViewEvent());
+                EventBus.getDefault().send(
+                        new FocusNextTaskViewEvent(false /* showTimerIndicator */));
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_DOWN: {
@@ -564,12 +569,14 @@
             }
             case KeyEvent.KEYCODE_DEL:
             case KeyEvent.KEYCODE_FORWARD_DEL: {
-                EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
+                if (event.getRepeatCount() <= 0) {
+                    EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
 
-                // Keep track of deletions by keyboard
-                MetricsLogger.histogram(this, "overview_task_dismissed_source",
-                        Constants.Metrics.DismissSourceKeyboard);
-                return true;
+                    // Keep track of deletions by keyboard
+                    MetricsLogger.histogram(this, "overview_task_dismissed_source",
+                            Constants.Metrics.DismissSourceKeyboard);
+                    return true;
+                }
             }
             default:
                 break;
@@ -579,7 +586,10 @@
 
     @Override
     public void onUserInteraction() {
-        EventBus.getDefault().send(new UserInteractionEvent());
+        // TODO: Prevent creating so many events here
+        final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        EventBus.getDefault().send(new UserInteractionEvent(debugFlags.isFastToggleRecentsEnabled()
+                && debugFlags.isFastToggleIndicatorEnabled()));
     }
 
     @Override
@@ -603,11 +613,14 @@
 
     public final void onBusEvent(IterateRecentsEvent event) {
         if (!dismissHistory()) {
+            final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+
             // Focus the next task
-            EventBus.getDefault().send(new FocusNextTaskViewEvent());
+            EventBus.getDefault().send(
+                    new FocusNextTaskViewEvent(debugFlags.isFastToggleRecentsEnabled()
+                            && debugFlags.isFastToggleIndicatorEnabled()));
 
             // Start dozing after the recents button is clicked
-            RecentsDebugFlags debugFlags = Recents.getDebugFlags();
             if (debugFlags.isFastToggleRecentsEnabled()) {
                 if (!mIterateTrigger.isDozing()) {
                     mIterateTrigger.startDozing();
@@ -615,6 +628,8 @@
                     mIterateTrigger.poke();
                 }
             }
+
+            MetricsLogger.action(this, MetricsLogger.ACTION_OVERVIEW_PAGE);
         }
     }
 
@@ -701,7 +716,7 @@
         intent.setComponent(intent.resolveActivity(getPackageManager()));
         TaskStackBuilder.create(this)
                 .addNextIntentWithParentStack(intent).startActivities(null,
-                new UserHandle(event.task.key.userId));
+                        new UserHandle(event.task.key.userId));
 
         // Keep track of app-info invocations
         MetricsLogger.count(this, "overview_app_info", 1);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 67135d5..61780f8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -139,7 +139,7 @@
         } else {
             // In portrait, the search bar appears on the top (which already has the inset)
             int top = searchBarBounds.isEmpty() ? topInset : 0;
-            taskStackBounds.set(windowBounds.left, searchBarBounds.bottom + top,
+            taskStackBounds.set(windowBounds.left, windowBounds.top + searchBarBounds.bottom + top,
                     windowBounds.right - rightInset, windowBounds.bottom);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index c323522..3151fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -27,6 +27,7 @@
 public class RecentsDebugFlags implements TunerService.Tunable {
 
     private static final String KEY_FAST_TOGGLE = "overview_fast_toggle_via_button";
+    private static final String KEY_FAST_TOGGLE_INDICATOR = "overview_fast_toggle_indicator";
     private static final String KEY_INITIAL_STATE_PAGING = "overview_initial_state_paging";
 
     public static class Static {
@@ -49,6 +50,7 @@
     }
 
     private boolean mFastToggleRecents;
+    private boolean mFastToggleIndicator;
     private boolean mInitialStatePaging;
 
     /**
@@ -58,7 +60,8 @@
     public RecentsDebugFlags(Context context) {
         // Register all our flags, this will also call onTuningChanged() for each key, which will
         // initialize the current state of each flag
-        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_INITIAL_STATE_PAGING);
+        TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_FAST_TOGGLE_INDICATOR,
+                KEY_INITIAL_STATE_PAGING);
     }
 
     /**
@@ -69,6 +72,13 @@
     }
 
     /**
+     * @return whether we are enabling the fast toggle indicator.
+     */
+    public boolean isFastToggleIndicatorEnabled() {
+        return mFastToggleIndicator;
+    }
+
+    /**
      * @return whether the initial stack state is paging.
      */
     public boolean isInitialStatePaging() {
@@ -82,6 +92,10 @@
                 mFastToggleRecents = (newValue != null) &&
                         (Integer.parseInt(newValue) != 0);
                 break;
+            case KEY_FAST_TOGGLE_INDICATOR:
+                mFastToggleIndicator = (newValue != null) &&
+                        (Integer.parseInt(newValue) != 0);
+                break;
             case KEY_INITIAL_STATE_PAGING:
                 mInitialStatePaging = (newValue != null) &&
                         (Integer.parseInt(newValue) != 0);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 60a2ee5..7c25d24 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -372,7 +372,7 @@
             sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
             loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
             TaskStack stack = sInstanceLoadPlan.getTaskStack();
-            if (stack.getStackTaskCount() > 0) {
+            if (stack.getTaskCount() > 0) {
                 // We try and draw the thumbnail transition bitmap in parallel before
                 // toggle/show recents is called
                 preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
@@ -403,7 +403,7 @@
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return;
+        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
 
         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
         // Return early if there is no running task
@@ -455,7 +455,7 @@
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getStackTaskCount() == 0) return;
+        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
 
         ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
         // Return early if there is no running task (can't determine affiliated tasks in this case)
@@ -845,7 +845,7 @@
             return;
         }
 
-        boolean hasRecentTasks = stack.getStackTaskCount() > 0;
+        boolean hasRecentTasks = stack.getTaskCount() > 0;
         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
 
         if (useThumbnailTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index b0c8ff3..212c7f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -290,6 +290,28 @@
     }
 
     /**
+     * An event that can be reusable, only used for situations where we want to reduce memory
+     * allocations when events are sent frequently (ie. on scroll).
+     */
+    public static class ReusableEvent extends Event {
+
+        private int mDispatchCount;
+
+        protected ReusableEvent() {}
+
+        @Override
+        void onPostDispatch() {
+            super.onPostDispatch();
+            mDispatchCount++;
+        }
+
+        @Override
+        protected Object clone() throws CloneNotSupportedException {
+            throw new CloneNotSupportedException();
+        }
+    }
+
+    /**
      * An inter-process event super class that allows us to track user state across subscriber
      * invocations.
      */
@@ -770,8 +792,11 @@
         event.onPreDispatch();
 
         // We need to clone the list in case a subscriber unregisters itself during traversal
+        // TODO: Investigate whether we can skip the object creation here
         eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone();
-        for (final EventHandler eventHandler : eventHandlers) {
+        int eventHandlerCount = eventHandlers.size();
+        for (int i = 0; i < eventHandlerCount; i++) {
+            final EventHandler eventHandler = eventHandlers.get(i);
             if (eventHandler.subscriber.getReference() != null) {
                 if (event.requiresPost) {
                     mHandler.post(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
index cb5011a..ad9feb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
@@ -16,16 +16,21 @@
 
 package com.android.systemui.recents.events.ui;
 
+import android.util.MutableInt;
 import com.android.systemui.recents.events.EventBus;
 
 /**
  * This is sent whenever a new scroll gesture happens on a stack view.
  */
-public class StackViewScrolledEvent extends EventBus.Event {
+public class StackViewScrolledEvent extends EventBus.ReusableEvent {
 
-    public final int yMovement;
+    public final MutableInt yMovement;
 
-    public StackViewScrolledEvent(int yMovement) {
-        this.yMovement = yMovement;
+    public StackViewScrolledEvent() {
+        yMovement = new MutableInt(0);
+    }
+
+    public void updateY(int y) {
+        yMovement.value = y;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
index 6e6cd84..5a132c2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
@@ -22,5 +22,10 @@
  * This is sent whenever the user interacts with the activity.
  */
 public class UserInteractionEvent extends EventBus.Event {
-    // Simple event
+
+    public final boolean showTimerIndicator;
+
+    public UserInteractionEvent(boolean showTimerIndicator) {
+        this.showTimerIndicator = showTimerIndicator;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
index 21321f2..b85ddac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
@@ -25,6 +25,7 @@
  */
 public class DragDropTargetChangedEvent extends EventBus.Event {
 
+    // The task that is currently being dragged
     public final Task task;
     public final DropTarget dropTarget;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
index 171ab5e..def4ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -22,5 +22,10 @@
  * Focuses the next task view in the stack.
  */
 public class FocusNextTaskViewEvent extends EventBus.Event {
-    // Simple event
+
+    public final boolean showTimerIndicator;
+
+    public FocusNextTaskViewEvent(boolean showTimerIndicator) {
+        this.showTimerIndicator = showTimerIndicator;
+    }
 }
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 f0fa1da..80597bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -26,6 +26,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
@@ -129,6 +130,9 @@
             SystemServicesProxy ssp = Recents.getSystemServices();
             ssp.startActivityFromRecents(v.getContext(), task.key.id, task.title,
                     ActivityOptions.makeBasic());
+
+            MetricsLogger.action(v.getContext(), MetricsLogger.ACTION_OVERVIEW_SELECT,
+                    task.key.getComponent().toString());
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index a2f5159..39bb6ca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -28,6 +28,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
+import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
@@ -99,6 +100,8 @@
         });
         mAdapter.updateTasks(getContext(), stack);
         mIsVisible = true;
+
+        MetricsLogger.visible(mRecyclerView.getContext(), MetricsLogger.OVERVIEW_HISTORY);
     }
 
     /**
@@ -129,6 +132,8 @@
             setVisibility(View.INVISIBLE);
         }
         mIsVisible = false;
+
+        MetricsLogger.hidden(mRecyclerView.getContext(), MetricsLogger.OVERVIEW_HISTORY);
     }
 
     /**
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 f9e825a..01de60c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -307,11 +307,12 @@
     }
 
     /** Docks a task to the side of the screen and starts it. */
-    public void startTaskInDockedMode(int taskId, int createMode) {
+    public void startTaskInDockedMode(Context context, int taskId, int createMode) {
         if (mIam == null) return;
 
         try {
-            final ActivityOptions options = ActivityOptions.makeBasic();
+            // TODO: Determine what animation we want for the incoming task
+            final ActivityOptions options = ActivityOptions.makeCustomAnimation(context, 0, 0);
             options.setDockCreateMode(createMode);
             options.setLaunchStackId(DOCKED_STACK_ID);
             mIam.startActivityFromRecents(taskId, options.toBundle());
@@ -366,7 +367,7 @@
     }
 
     /**
-     * @return whether there are any docked tasks.
+     * @return whether there are any docked tasks for the current user.
      */
     public boolean hasDockedTask() {
         if (mIam == null) return false;
@@ -374,6 +375,9 @@
         ActivityManager.StackInfo stackInfo = null;
         try {
             stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+            if (stackInfo != null && stackInfo.userId != getCurrentUser()) {
+                return false;
+            }
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -917,6 +921,18 @@
         }
     }
 
+    /**
+     * Calculates the size of the dock divider in the current orientation.
+     */
+    public int getDockedDividerSize(Context context) {
+        Resources res = context.getResources();
+        int dividerWindowWidth = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        int dividerInsets = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_insets);
+        return dividerWindowWidth - 2 * dividerInsets;
+    }
+
     public void requestKeyboardShortcuts(Context context, KeyboardShortcutsReceiver receiver) {
         mWm.requestAppKeyboardShortcuts(receiver);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 2bf2ccb..33f116b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -18,13 +18,43 @@
 
 import android.animation.Animator;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewParent;
 
 /* Common code */
 public class Utilities {
 
+    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
+            new IntProperty<Drawable>("drawableAlpha") {
+                @Override
+                public void setValue(Drawable object, int alpha) {
+                    object.setAlpha(alpha);
+                }
+
+                @Override
+                public Integer get(Drawable object) {
+                    return object.getAlpha();
+                }
+            };
+
+    public static final Property<Drawable, Rect> DRAWABLE_RECT =
+            new Property<Drawable, Rect>(Rect.class, "drawableBounds") {
+                @Override
+                public void set(Drawable object, Rect bounds) {
+                    object.setBounds(bounds);
+                }
+
+                @Override
+                public Rect get(Drawable object) {
+                    return object.getBounds();
+                }
+            };
+
     /**
      * @return the first parent walking up the view hierarchy that has the given class type.
      *
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 d8dfce5..822ad77 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,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
+
 import com.android.systemui.Prefs;
+import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -120,6 +122,8 @@
             preloadRawTasks(isTopTaskHome);
         }
 
+        String dismissDescFormat = mContext.getString(
+                R.string.accessibility_recents_item_will_be_dismissed);
         long lastStackActiveTime = Prefs.getLong(mContext,
                 Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
         long newLastStackActiveTime = -1;
@@ -143,6 +147,7 @@
             // Load the title, icon, and color
             String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
             String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res);
+            String dismissDescription = String.format(dismissDescFormat, contentDescription);
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
                     : null;
@@ -151,8 +156,8 @@
 
             // Add the task to the stack
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
-                    thumbnail, title, contentDescription, activityColor, !isStackTask,
-                    t.bounds, t.taskDescription);
+                    thumbnail, title, contentDescription, dismissDescription, activityColor,
+                    !isStackTask, t.bounds, t.taskDescription);
 
             allTasks.add(task);
         }
@@ -219,7 +224,7 @@
     /** Returns whether there are any tasks in any stacks. */
     public boolean hasTasks() {
         if (mStack != null) {
-            return mStack.getStackTaskCount() > 0;
+            return mStack.getTaskCount() > 0;
         }
         return false;
     }
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 f7e2b9d..29e7077 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -54,6 +54,8 @@
         public long firstActiveTime;
         public long lastActiveTime;
 
+        private int mHashCode;
+
         public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
                 long lastActiveTime) {
             this.id = id;
@@ -62,6 +64,12 @@
             this.userId = userId;
             this.firstActiveTime = firstActiveTime;
             this.lastActiveTime = lastActiveTime;
+            updateHashCode();
+        }
+
+        public void setStackId(int stackId) {
+            this.stackId = stackId;
+            updateHashCode();
         }
 
         public ComponentName getComponent() {
@@ -79,7 +87,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(id, stackId, userId);
+            return mHashCode;
         }
 
         @Override
@@ -90,6 +98,10 @@
                     + "lat: " + lastActiveTime + ", "
                     + getComponent().getPackageName();
         }
+
+        private void updateHashCode() {
+            mHashCode = Objects.hash(id, stackId, userId);
+        }
     }
 
     public TaskKey key;
@@ -113,6 +125,7 @@
     public Bitmap thumbnail;
     public String title;
     public String contentDescription;
+    public String dismissDescription;
     public int colorPrimary;
     public boolean useLightOnPrimaryColor;
 
@@ -139,9 +152,9 @@
     }
 
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
-                Bitmap thumbnail, String title, String contentDescription, int colorPrimary,
-                boolean isHistorical, Rect bounds,
-                ActivityManager.TaskDescription taskDescription) {
+                Bitmap thumbnail, String title, String contentDescription,
+                String dismissDescription, int colorPrimary, boolean isHistorical,
+                Rect bounds, ActivityManager.TaskDescription taskDescription) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -151,6 +164,7 @@
         this.thumbnail = thumbnail;
         this.title = title;
         this.contentDescription = contentDescription;
+        this.dismissDescription = dismissDescription;
         this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
         this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
                 Color.WHITE) > 3f;
@@ -169,6 +183,7 @@
         this.thumbnail = o.thumbnail;
         this.title = o.title;
         this.contentDescription = o.contentDescription;
+        this.dismissDescription = o.dismissDescription;
         this.colorPrimary = o.colorPrimary;
         this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
         this.bounds = o.bounds;
@@ -201,7 +216,7 @@
      * Updates the stack id of this task.
      */
     public void setStackId(int stackId) {
-        key.stackId = stackId;
+        key.setStackId(stackId);
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
             mCallbacks.get(i).onTaskStackIdChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
index 288f07c..15f6b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
@@ -1,7 +1,8 @@
 package com.android.systemui.recents.model;
 
+import android.util.ArrayMap;
+
 import java.util.ArrayList;
-import java.util.HashMap;
 
 /** Represents a grouping of tasks witihin a stack. */
 public class TaskGrouping {
@@ -11,7 +12,7 @@
 
     Task.TaskKey mFrontMostTaskKey;
     ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
-    HashMap<Task.TaskKey, Integer> mTaskKeyIndices = new HashMap<Task.TaskKey, Integer>();
+    ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>();
 
     /** Creates a group with a specified affiliation. */
     public TaskGrouping(int affiliation) {
@@ -94,9 +95,10 @@
             return;
         }
 
+        int taskCount = mTaskKeys.size();
         mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
         mTaskKeyIndices.clear();
-        int taskCount = mTaskKeys.size();
+        mTaskKeyIndices.ensureCapacity(taskCount);
         for (int i = 0; i < taskCount; i++) {
             Task.TaskKey k = mTaskKeys.get(i);
             mTaskKeyIndices.put(k, i);
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 856200d..21d0bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,16 +16,27 @@
 
 package com.android.systemui.recents.model;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
+import android.view.animation.Interpolator;
+import com.android.internal.policy.DockedDividerUtils;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.NamedCounter;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -35,8 +46,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
 
@@ -44,6 +53,11 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
 
 
 /**
@@ -59,17 +73,14 @@
  */
 class FilteredTaskList {
 
-    private static final String TAG = "FilteredTaskList";
-    private static final boolean DEBUG = false;
-
     ArrayList<Task> mTasks = new ArrayList<>();
     ArrayList<Task> mFilteredTasks = new ArrayList<>();
-    HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<>();
+    ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
     TaskFilter mFilter;
 
     /** Sets the task filter, saving the current touch state */
     boolean setFilter(TaskFilter filter) {
-        ArrayList<Task> prevFilteredTasks = new ArrayList<Task>(mFilteredTasks);
+        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
         mFilter = filter;
         updateFilteredTasks();
         if (!prevFilteredTasks.equals(mFilteredTasks)) {
@@ -180,8 +191,9 @@
 
     /** Updates the mapping of tasks to indices. */
     private void updateFilteredTaskIndices() {
-        mTaskIndices.clear();
         int taskCount = mFilteredTasks.size();
+        mTaskIndices.clear();
+        mTaskIndices.ensureCapacity(taskCount);
         for (int i = 0; i < taskCount; i++) {
             Task t = mFilteredTasks.get(i);
             mTaskIndices.put(t.key, i);
@@ -229,30 +241,36 @@
     public static class DockState implements DropTarget {
 
         private static final int DOCK_AREA_ALPHA = 192;
-        public static final DockState NONE = new DockState(-1, 96, null, null);
-        public static final DockState LEFT = new DockState(
+        public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, null, null, null);
+        public static final DockState LEFT = new DockState(DOCKED_LEFT,
                 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA,
-                new RectF(0, 0, 0.15f, 1), new RectF(0, 0, 0.15f, 1));
-        public static final DockState TOP = new DockState(
+                new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
+                new RectF(0, 0, 0.5f, 1));
+        public static final DockState TOP = new DockState(DOCKED_TOP,
                 DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA,
-                new RectF(0, 0, 1, 0.15f), new RectF(0, 0, 1, 0.15f));
-        public static final DockState RIGHT = new DockState(
+                new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
+                new RectF(0, 0, 1, 0.5f));
+        public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
                 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA,
-                new RectF(0.85f, 0, 1, 1), new RectF(0.85f, 0, 1, 1));
-        public static final DockState BOTTOM = new DockState(
+                new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
+                new RectF(0.5f, 0, 1, 1));
+        public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
                 DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA,
-                new RectF(0, 0.85f, 1, 1), new RectF(0, 0.85f, 1, 1));
+                new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
+                new RectF(0, 0.5f, 1, 1));
 
         @Override
-        public boolean acceptsDrop(int x, int y, int width, int height) {
-            return touchAreaContainsPoint(width, height, x, y);
+        public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) {
+            return isCurrentTarget
+                    ? areaContainsPoint(expandedTouchDockArea, width, height, x, y)
+                    : areaContainsPoint(touchArea, width, height, x, y);
         }
 
         // Represents the view state of this dock state
         public class ViewState {
             public final int dockAreaAlpha;
             public final ColorDrawable dockAreaOverlay;
-            private ObjectAnimator dockAreaOverlayAnimator;
+            private AnimatorSet dockAreaOverlayAnimator;
 
             private ViewState(int alpha) {
                 dockAreaAlpha = alpha;
@@ -261,56 +279,130 @@
             }
 
             /**
-             * Creates a new alpha animation.
+             * Creates a new bounds and alpha animation.
              */
-            public void startAlphaAnimation(int alpha, int duration) {
+            public void startAnimation(Rect bounds, int alpha, int duration,
+                    Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
+                if (dockAreaOverlayAnimator != null) {
+                    dockAreaOverlayAnimator.cancel();
+                }
+
+                ArrayList<Animator> animators = new ArrayList<>();
                 if (dockAreaOverlay.getAlpha() != alpha) {
-                    if (dockAreaOverlayAnimator != null) {
-                        dockAreaOverlayAnimator.cancel();
+                    if (animateAlpha) {
+                        animators.add(ObjectAnimator.ofInt(dockAreaOverlay,
+                                Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), alpha));
+                    } else {
+                        dockAreaOverlay.setAlpha(alpha);
                     }
-                    dockAreaOverlayAnimator = ObjectAnimator.ofInt(dockAreaOverlay, "alpha", alpha);
+                }
+                if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
+                    if (animateBounds) {
+                        PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
+                                Utilities.DRAWABLE_RECT, new RectEvaluator(new Rect()),
+                                dockAreaOverlay.getBounds(), bounds);
+                        animators.add(ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop));
+                    } else {
+                        dockAreaOverlay.setBounds(bounds);
+                    }
+                }
+                if (!animators.isEmpty()) {
+                    dockAreaOverlayAnimator = new AnimatorSet();
+                    dockAreaOverlayAnimator.playTogether(animators);
                     dockAreaOverlayAnimator.setDuration(duration);
+                    dockAreaOverlayAnimator.setInterpolator(interpolator);
                     dockAreaOverlayAnimator.start();
                 }
             }
         }
 
+        public final int dockSide;
         public final int createMode;
         public final ViewState viewState;
-        private final RectF dockArea;
         private final RectF touchArea;
+        private final RectF dockArea;
+        private final RectF expandedTouchDockArea;
 
         /**
          * @param createMode used to pass to ActivityManager to dock the task
          * @param touchArea the area in which touch will initiate this dock state
          * @param dockArea the visible dock area
+         * @param expandedTouchDockArea the areain which touch will continue to dock after entering
+         *                              the initial touch area.  This is also the new dock area to
+         *                              draw.
          */
-        DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea) {
+        DockState(int dockSide, int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea,
+                  RectF expandedTouchDockArea) {
+            this.dockSide = dockSide;
             this.createMode = createMode;
             this.viewState = new ViewState(dockAreaAlpha);
             this.dockArea = dockArea;
             this.touchArea = touchArea;
+            this.expandedTouchDockArea = expandedTouchDockArea;
         }
 
         /**
-         * Returns whether {@param x} and {@param y} are contained in the touch area scaled to the
+         * Returns whether {@param x} and {@param y} are contained in the area scaled to the
          * given {@param width} and {@param height}.
          */
-        public boolean touchAreaContainsPoint(int width, int height, float x, float y) {
-            int left = (int) (touchArea.left * width);
-            int top = (int) (touchArea.top * height);
-            int right = (int) (touchArea.right * width);
-            int bottom = (int) (touchArea.bottom * height);
+        public boolean areaContainsPoint(RectF area, int width, int height, float x, float y) {
+            int left = (int) (area.left * width);
+            int top = (int) (area.top * height);
+            int right = (int) (area.right * width);
+            int bottom = (int) (area.bottom * height);
             return x >= left && y >= top && x <= right && y <= bottom;
         }
 
         /**
          * Returns the docked task bounds with the given {@param width} and {@param height}.
          */
-        public Rect getDockedBounds(int width, int height) {
+        public Rect getPreDockedBounds(int width, int height) {
             return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height),
                     (int) (dockArea.right * width), (int) (dockArea.bottom * height));
         }
+
+        /**
+         * Returns the expanded docked task bounds with the given {@param width} and
+         * {@param height}.
+         */
+        public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
+                Resources res) {
+            // Calculate the docked task bounds
+            boolean isHorizontalDivision =
+                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                    insets, width, height, dividerSize);
+            Rect newWindowBounds = new Rect();
+            DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
+                    width, height, dividerSize);
+            return newWindowBounds;
+        }
+
+        /**
+         * Returns the task stack bounds with the given {@param width} and
+         * {@param height}.
+         */
+        public Rect getDockedTaskStackBounds(int width, int height, int dividerSize, Rect insets,
+                Resources res) {
+            RecentsConfiguration config = Recents.getConfiguration();
+
+            // Calculate the inverse docked task bounds
+            boolean isHorizontalDivision =
+                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                    insets, width, height, dividerSize);
+            Rect newWindowBounds = new Rect();
+            DockedDividerUtils.calculateBoundsForPosition(position,
+                    DockedDividerUtils.invertDockSide(dockSide), newWindowBounds, width, height,
+                    dividerSize);
+
+            // Calculate the task stack bounds from the new window bounds
+            Rect searchBarSpaceBounds = new Rect();
+            Rect taskStackBounds = new Rect();
+            config.getTaskStackBounds(newWindowBounds, insets.top, insets.right,
+                    searchBarSpaceBounds, taskStackBounds);
+            return taskStackBounds;
+        }
     }
 
     // A comparator that sorts tasks by their last active time
@@ -344,7 +436,7 @@
     TaskStackCallbacks mCb;
 
     ArrayList<TaskGrouping> mGroups = new ArrayList<>();
-    HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<>();
+    ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
 
     public TaskStack() {
         // Ensure that we only show non-docked tasks
@@ -446,6 +538,7 @@
                 mCb.onHistoryTaskRemoved(this, t);
             }
         }
+        mRawTaskList.remove(t);
     }
 
     /**
@@ -456,8 +549,8 @@
      */
     public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
         // Compute a has set for each of the tasks
-        HashMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
-        HashMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+        ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+        ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
 
         ArrayList<Task> newTasks = new ArrayList<>();
 
@@ -588,16 +681,32 @@
     }
 
     /**
-     * Returns the number of tasks in the active stack.
+     * Returns the number of stack and freeform tasks.
      */
-    public int getStackTaskCount() {
+    public int getTaskCount() {
         return mStackTaskList.size();
     }
 
     /**
-     * Returns the number of freeform tasks in the active stack.
+     * Returns the number of stack tasks.
      */
-    public int getStackTaskFreeformCount() {
+    public int getStackTaskCount() {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int stackCount = 0;
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (!task.isFreeformTask()) {
+                stackCount++;
+            }
+        }
+        return stackCount;
+    }
+
+    /**
+     * Returns the number of freeform tasks.
+     */
+    public int getFreeformTaskCount() {
         ArrayList<Task> tasks = mStackTaskList.getTasks();
         int freeformCount = 0;
         int taskCount = tasks.size();
@@ -664,7 +773,7 @@
      */
     public void createAffiliatedGroupings(Context context) {
         if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) {
-            HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
+            ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
             // Sort all tasks by increasing firstActiveTime of the task
             ArrayList<Task> tasks = mStackTaskList.getTasks();
             Collections.sort(tasks, new Comparator<Task>() {
@@ -729,9 +838,10 @@
             mStackTaskList.set(tasks);
         } else {
             // Create the task groups
-            HashMap<Task.TaskKey, Task> tasksMap = new HashMap<>();
+            ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
             ArrayList<Task> tasks = mStackTaskList.getTasks();
             int taskCount = tasks.size();
+            tasksMap.ensureCapacity(taskCount);
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
                 TaskGrouping group;
@@ -773,12 +883,12 @@
      * Computes the components of tasks in this stack that have been removed as a result of a change
      * in the specified package.
      */
-    public HashSet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
         // Identify all the tasks that should be removed as a result of the package being removed.
         // Using a set to ensure that we callback once per unique component.
         SystemServicesProxy ssp = Recents.getSystemServices();
-        HashSet<ComponentName> existingComponents = new HashSet<>();
-        HashSet<ComponentName> removedComponents = new HashSet<>();
+        ArraySet<ComponentName> existingComponents = new ArraySet<>();
+        ArraySet<ComponentName> removedComponents = new ArraySet<>();
         ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
         for (Task.TaskKey t : taskKeys) {
             // Skip if this doesn't apply to the current user
@@ -816,8 +926,8 @@
     /**
      * Given a list of tasks, returns a map of each task's key to the task.
      */
-    private HashMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
-        HashMap<Task.TaskKey, Task> map = new HashMap<>();
+    private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+        ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
         int taskCount = tasks.size();
         for (int i = 0; i < taskCount; i++) {
             Task task = tasks.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java b/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java
index 8ae00a7..3ad368c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DropTarget.java
@@ -25,5 +25,5 @@
      * Returns whether this target can accept this drop.  The x,y are relative to the top level
      * RecentsView, and the width/height are of the RecentsView.
      */
-    boolean acceptsDrop(int x, int y, int width, int height);
+    boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 890713e..d3a1e91 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -17,13 +17,12 @@
 package com.android.systemui.recents.views;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.ArrayMap;
 import com.android.systemui.R;
 import com.android.systemui.recents.model.Task;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -32,7 +31,7 @@
 public class FreeformWorkspaceLayoutAlgorithm {
 
     // Optimization, allows for quick lookup of task -> rect
-    private HashMap<Task.TaskKey, RectF> mTaskRectMap = new HashMap<>();
+    private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
 
     private int mTaskPadding;
 
@@ -49,6 +48,7 @@
     public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
         Collections.reverse(freeformTasks);
         mTaskRectMap.clear();
+        mTaskRectMap.ensureCapacity(freeformTasks.size());
 
         int numFreeformTasks = stackLayout.mNumFreeformTasks;
         if (!freeformTasks.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index fdb0d32..b363ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -165,7 +165,7 @@
             int taskIndexFromFront = 0;
             int taskIndex = stack.indexOfStackTask(task);
             if (taskIndex > -1) {
-                taskIndexFromFront = stack.getStackTaskCount() - taskIndex - 1;
+                taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
             }
             EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
         } else {
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 e28e2b3..0d9a1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -63,6 +63,7 @@
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -76,8 +77,7 @@
  */
 public class RecentsView extends FrameLayout {
 
-    private static final String TAG = "RecentsView";
-    private static final boolean DEBUG = false;
+    private static final int DOCK_AREA_OVERLAY_TRANSITION_DURATION = 135;
 
     private final Handler mHandler;
 
@@ -89,15 +89,10 @@
     private boolean mAwaitingFirstLayout = true;
     private boolean mLastTaskLaunchedWasFreeform;
     private Rect mSystemInsets = new Rect();
+    private int mDividerSize;
 
     private RecentsTransitionHelper mTransitionHelper;
     private RecentsViewTouchHandler mTouchHandler;
-    private TaskStack.DockState[] mVisibleDockStates = {
-            TaskStack.DockState.LEFT,
-            TaskStack.DockState.TOP,
-            TaskStack.DockState.RIGHT,
-            TaskStack.DockState.BOTTOM,
-    };
 
     private final Interpolator mFastOutSlowInInterpolator;
     private final Interpolator mFastOutLinearInInterpolator;
@@ -118,12 +113,15 @@
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setWillNotDraw(false);
+
+        SystemServicesProxy ssp = Recents.getSystemServices();
         mHandler = new Handler();
         mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
         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);
+        mDividerSize = ssp.getDockedDividerSize(context);
         mTouchHandler = new RecentsViewTouchHandler(this);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
 
@@ -163,7 +161,7 @@
         }
 
         // Update the top level view's visibilities
-        if (stack.getStackTaskCount() > 0) {
+        if (stack.getTaskCount() > 0) {
             hideEmptyView();
         } else {
             showEmptyView();
@@ -431,8 +429,9 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
-            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        for (int i = visDockStates.size() - 1; i >= 0; i--) {
+            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
             if (d.getAlpha() > 0) {
                 d.draw(canvas);
             }
@@ -441,8 +440,9 @@
 
     @Override
     protected boolean verifyDrawable(Drawable who) {
-        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
-            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        for (int i = visDockStates.size() - 1; i >= 0; i--) {
+            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
             if (d == who) {
                 return true;
             }
@@ -470,52 +470,77 @@
 
     public final void onBusEvent(DragStartEvent event) {
         updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
-                TaskStack.DockState.NONE.viewState.dockAreaAlpha);
+                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
+                true /* animateAlpha */, false /* animateBounds */);
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
         if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
             updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
-                    TaskStack.DockState.NONE.viewState.dockAreaAlpha);
+                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
+                    true /* animateAlpha */, true /* animateBounds */);
         } else {
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
-            updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, -1);
+            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
+                    false /* isDefaultDockState */, -1, true /* animateAlpha */,
+                    true /* animateBounds */);
         }
     }
 
     public final void onBusEvent(final DragEndEvent event) {
-        // Animate the overlay alpha back to 0
-        updateVisibleDockRegions(null, -1);
-
         // Handle the case where we drop onto a dock region
         if (event.dropTarget instanceof TaskStack.DockState) {
-            TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+
+            // Hide the dock region
+            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1,
+                    false /* animateAlpha */, false /* animateBounds */);
+
             TaskStackLayoutAlgorithm stackLayout = mTaskStackView.getStackAlgorithm();
             TaskStackViewScroller stackScroller = mTaskStackView.getScroller();
             TaskViewTransform tmpTransform = new TaskViewTransform();
 
+            // We translated the view but we need to animate it back from the current layout-space
+            // rect to its final layout-space rect
+            int x = (int) event.taskView.getTranslationX();
+            int y = (int) event.taskView.getTranslationY();
+            Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
+                    event.taskView.getRight(), event.taskView.getBottom());
+            taskViewRect.offset(x, y);
+            event.taskView.setTranslationX(0);
+            event.taskView.setTranslationY(0);
+            event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
+                    taskViewRect.right, taskViewRect.bottom);
+
             // Remove the task view after it is docked
+            mTaskStackView.updateLayout(false /* boundScroll */);
             stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
                     null);
-            tmpTransform.scale = event.taskView.getScaleX();
-            tmpTransform.rect.offset(event.taskView.getTranslationX(),
-                    event.taskView.getTranslationY());
+            tmpTransform.alpha = 0;
+            tmpTransform.scale = 1f;
+            tmpTransform.rect.set(taskViewRect);
             mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
-                    new TaskViewAnimation(150, mFastOutLinearInInterpolator,
+                    new TaskViewAnimation(125, PhoneStatusBar.ALPHA_OUT,
                             new AnimatorListenerAdapter() {
                                 @Override
                                 public void onAnimationEnd(Animator animation) {
+                                    // Dock the task and launch it
+                                    SystemServicesProxy ssp = Recents.getSystemServices();
+                                    ssp.startTaskInDockedMode(getContext(), event.task.key.id,
+                                            dockState.createMode);
+                                    launchTask(event.task, null, INVALID_STACK_ID);
+
                                     mTaskStackView.getStack().removeTask(event.task);
                                 }
                             }));
 
-            // Dock the task and launch it
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
-            launchTask(event.task, null, INVALID_STACK_ID);
 
             MetricsLogger.action(mContext,
                     MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP);
+        } else {
+            // Animate the overlay alpha back to 0
+            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1,
+                    true /* animateAlpha */, false /* animateBounds */);
         }
     }
 
@@ -638,23 +663,34 @@
     /**
      * Updates the dock region to match the specified dock state.
      */
-    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) {
+    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
+            boolean isDefaultDockState, int overrideAlpha, boolean animateAlpha,
+            boolean animateBounds) {
         ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>();
         if (newDockStates != null) {
             Collections.addAll(newDockStatesSet, newDockStates);
         }
-        for (TaskStack.DockState dockState : mVisibleDockStates) {
+        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        for (int i = visDockStates.size() - 1; i >= 0; i--) {
+            TaskStack.DockState dockState = visDockStates.get(i);
             TaskStack.DockState.ViewState viewState = dockState.viewState;
             if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
                 // This is no longer visible, so hide it
-                viewState.startAlphaAnimation(0, 150);
+                viewState.startAnimation(null, 0, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
+                        PhoneStatusBar.ALPHA_OUT, animateAlpha, animateBounds);
             } else {
                 // This state is now visible, update the bounds and show it
                 int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
-                viewState.dockAreaOverlay.setBounds(
-                        dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight()));
-                viewState.dockAreaOverlay.setCallback(this);
-                viewState.startAlphaAnimation(alpha, 150);
+                Rect bounds = isDefaultDockState
+                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight())
+                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
+                        mDividerSize, mSystemInsets, getResources());
+                if (viewState.dockAreaOverlay.getCallback() != this) {
+                    viewState.dockAreaOverlay.setCallback(this);
+                    viewState.dockAreaOverlay.setBounds(bounds);
+                }
+                viewState.startAnimation(bounds, alpha, DOCK_AREA_OVERLAY_TRANSITION_DURATION,
+                        PhoneStatusBar.ALPHA_IN, animateAlpha, animateBounds);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 473334b..d8698ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -19,6 +19,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
@@ -64,13 +65,17 @@
 
     private Point mTaskViewOffset = new Point();
     private Point mDownPos = new Point();
-    private boolean mDragging;
+    private boolean mDragRequested;
+    private boolean mIsDragging;
+    private float mDragSlop;
 
     private DropTarget mLastDropTarget;
     private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+    private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
 
     public RecentsViewTouchHandler(RecentsView rv) {
         mRv = rv;
+        mDragSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
     }
 
     /**
@@ -93,16 +98,23 @@
         return dockStates;
     }
 
+    /**
+     * Returns the set of visible dock states for this current drag.
+     */
+    public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+        return mVisibleDockStates;
+    }
+
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         handleTouchEvent(ev);
-        return mDragging;
+        return mDragRequested;
     }
 
     /** Handles touch events once we have intercepted them */
     public boolean onTouchEvent(MotionEvent ev) {
         handleTouchEvent(ev);
-        return mDragging;
+        return mDragRequested;
     }
 
     /**** Events ****/
@@ -110,7 +122,9 @@
     public final void onBusEvent(DragStartEvent event) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         mRv.getParent().requestDisallowInterceptTouchEvent(true);
-        mDragging = true;
+        mDragRequested = true;
+        // We defer starting the actual drag handling until the user moves past the drag slop
+        mIsDragging = false;
         mDragTask = event.task;
         mTaskView = event.taskView;
         mDropTargets.clear();
@@ -124,11 +138,13 @@
         mTaskView.setTranslationX(x);
         mTaskView.setTranslationY(y);
 
-        if (!ssp.hasDockedTask()) {
+        mVisibleDockStates.clear();
+        if (!ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
             // Add the dock state drop targets (these take priority)
             TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
             for (TaskStack.DockState dockState : dockStates) {
                 registerDropTargetForCurrentDrag(dockState);
+                mVisibleDockStates.add(dockState);
             }
         }
 
@@ -137,7 +153,7 @@
     }
 
     public final void onBusEvent(DragEndEvent event) {
-        mDragging = false;
+        mDragRequested = false;
         mDragTask = null;
         mTaskView = null;
         mLastDropTarget = null;
@@ -153,25 +169,45 @@
                 mDownPos.set((int) ev.getX(), (int) ev.getY());
                 break;
             case MotionEvent.ACTION_MOVE: {
-                if (mDragging) {
-                    int width = mRv.getMeasuredWidth();
-                    int height = mRv.getMeasuredHeight();
-                    float evX = ev.getX();
-                    float evY = ev.getY();
-                    float x = evX - mTaskViewOffset.x;
-                    float y = evY - mTaskViewOffset.y;
+                float evX = ev.getX();
+                float evY = ev.getY();
+                float x = evX - mTaskViewOffset.x;
+                float y = evY - mTaskViewOffset.y;
 
-                    DropTarget currentDropTarget = null;
-                    for (DropTarget target : mDropTargets) {
-                        if (target.acceptsDrop((int) evX, (int) evY, width, height)) {
-                            currentDropTarget = target;
-                            break;
-                        }
+                if (mDragRequested) {
+                    if (!mIsDragging) {
+                        mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
                     }
-                    if (mLastDropTarget != currentDropTarget) {
-                        mLastDropTarget = currentDropTarget;
-                        EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
-                                currentDropTarget));
+                    if (mIsDragging) {
+                        int width = mRv.getMeasuredWidth();
+                        int height = mRv.getMeasuredHeight();
+
+                        DropTarget currentDropTarget = null;
+
+                        // Give priority to the current drop target to retain the touch handling
+                        if (mLastDropTarget != null) {
+                            if (mLastDropTarget.acceptsDrop((int) evX, (int) evY, width, height,
+                                    true /* isCurrentTarget */)) {
+                                currentDropTarget = mLastDropTarget;
+                            }
+                        }
+
+                        // Otherwise, find the next target to handle this event
+                        if (currentDropTarget == null) {
+                            for (DropTarget target : mDropTargets) {
+                                if (target.acceptsDrop((int) evX, (int) evY, width, height,
+                                        false /* isCurrentTarget */)) {
+                                    currentDropTarget = target;
+                                    break;
+                                }
+                            }
+                        }
+                        if (mLastDropTarget != currentDropTarget) {
+                            mLastDropTarget = currentDropTarget;
+                            EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
+                                    currentDropTarget));
+                        }
+
                     }
 
                     mTaskView.setTranslationX(x);
@@ -181,7 +217,7 @@
             }
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL: {
-                if (mDragging) {
+                if (mDragRequested) {
                     EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
                             mLastDropTarget));
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
deleted file mode 100644
index b7c1de3..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ /dev/null
@@ -1,403 +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.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-
-/**
- * This class facilitates swipe to dismiss. It defines an interface to be implemented by the
- * by the class hosting the views that need to swiped, and, using this interface, handles touch
- * events and translates / fades / animates the view as it is dismissed.
- */
-public class SwipeHelper {
-    static final String TAG = "SwipeHelper";
-    private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
-    private static final boolean CONSTRAIN_SWIPE = true;
-    private static final boolean FADE_OUT_DURING_SWIPE = true;
-    private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
-
-    public static final int X = 0;
-    public static final int Y = 1;
-
-    private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
-    private Interpolator mLinearOutSlowInInterpolator;
-
-    private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
-    private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
-    private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
-    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
-
-    public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
-                                                 // where fade starts
-    static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
-                                              // beyond which alpha->0
-    private float mMinAlpha = 0f;
-
-    private float mPagingTouchSlop;
-    Callback mCallback;
-    private int mSwipeDirection;
-    private VelocityTracker mVelocityTracker;
-
-    private float mInitialTouchPos;
-    private boolean mDragging;
-    private float mSnapBackTranslationX;
-
-    private View mCurrView;
-    private boolean mCanCurrViewBeDimissed;
-    private float mDensityScale;
-
-    public boolean mAllowSwipeTowardsStart = true;
-    public boolean mAllowSwipeTowardsEnd = true;
-    private boolean mRtl;
-
-    public SwipeHelper(Context context, int swipeDirection, Callback callback, float densityScale,
-            float pagingTouchSlop) {
-        mCallback = callback;
-        mSwipeDirection = swipeDirection;
-        mVelocityTracker = VelocityTracker.obtain();
-        mDensityScale = densityScale;
-        mPagingTouchSlop = pagingTouchSlop;
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.linear_out_slow_in);
-    }
-
-    public void setDensityScale(float densityScale) {
-        mDensityScale = densityScale;
-    }
-
-    public void setSnapBackTranslationX(float translationX) {
-        mSnapBackTranslationX = translationX;
-    }
-
-    public void setPagingTouchSlop(float pagingTouchSlop) {
-        mPagingTouchSlop = pagingTouchSlop;
-    }
-
-    public void cancelOngoingDrag() {
-        if (mDragging) {
-            if (mCurrView != null) {
-                mCallback.onDragCancelled(mCurrView);
-                setTranslation(mCurrView, 0);
-                mCallback.onSnapBackCompleted(mCurrView);
-                mCurrView = null;
-            }
-            mDragging = false;
-        }
-    }
-
-    public void resetTranslation(View v) {
-        setTranslation(v, 0);
-    }
-
-    private float getPos(MotionEvent ev) {
-        return mSwipeDirection == X ? ev.getX() : ev.getY();
-    }
-
-    private float getTranslation(View v) {
-        return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
-    }
-
-    private float getVelocity(VelocityTracker vt) {
-        return mSwipeDirection == X ? vt.getXVelocity() :
-                vt.getYVelocity();
-    }
-
-    private ObjectAnimator createTranslationAnimation(View v, float newPos) {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(v,
-                mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
-        return anim;
-    }
-
-    private float getPerpendicularVelocity(VelocityTracker vt) {
-        return mSwipeDirection == X ? vt.getYVelocity() :
-                vt.getXVelocity();
-    }
-
-    private void setTranslation(View v, float translate) {
-        if (mSwipeDirection == X) {
-            v.setTranslationX(translate);
-        } else {
-            v.setTranslationY(translate);
-        }
-    }
-
-    private float getSize(View v) {
-        final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
-        return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;
-    }
-
-    public void setMinAlpha(float minAlpha) {
-        mMinAlpha = minAlpha;
-    }
-
-    float getAlphaForOffset(View view) {
-        float viewSize = getSize(view);
-        final float fadeSize = ALPHA_FADE_END * viewSize;
-        float result = 1.0f;
-        float pos = getTranslation(view);
-        if (pos >= viewSize * ALPHA_FADE_START) {
-            result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
-        } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
-            result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
-        }
-        result = Math.min(result, 1.0f);
-        result = Math.max(result, 0f);
-        return Math.max(mMinAlpha, result);
-    }
-
-    /**
-     * Determines whether the given view has RTL layout.
-     */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    public static boolean isLayoutRtl(View view) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
-        } else {
-            return false;
-        }
-    }
-
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        final int action = ev.getAction();
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                mDragging = false;
-                mCurrView = mCallback.getChildAtPosition(ev);
-                mVelocityTracker.clear();
-                if (mCurrView != null) {
-                    mRtl = isLayoutRtl(mCurrView);
-                    mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
-                    mVelocityTracker.addMovement(ev);
-                    mInitialTouchPos = getPos(ev);
-                } else {
-                    mCanCurrViewBeDimissed = false;
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (mCurrView != null) {
-                    mVelocityTracker.addMovement(ev);
-                    float pos = getPos(ev);
-                    float delta = pos - mInitialTouchPos;
-                    if (Math.abs(delta) > mPagingTouchSlop) {
-                        mCallback.onBeginDrag(mCurrView);
-                        mDragging = true;
-                        mInitialTouchPos = pos - getTranslation(mCurrView);
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mDragging = false;
-                mCurrView = null;
-                break;
-        }
-        return mDragging;
-    }
-
-    /**
-     * @param view The view to be dismissed
-     * @param velocity The desired pixels/second speed at which the view should move
-     */
-    private void dismissChild(final View view, float velocity) {
-        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
-        float newPos;
-        if (velocity < 0
-                || (velocity == 0 && getTranslation(view) < 0)
-                // if we use the Menu to dismiss an item in landscape, animate up
-                || (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
-            newPos = -getSize(view);
-        } else {
-            newPos = getSize(view);
-        }
-        int duration = MAX_ESCAPE_ANIMATION_DURATION;
-        if (velocity != 0) {
-            duration = Math.min(duration,
-                                (int) (Math.abs(newPos - getTranslation(view)) *
-                                        1000f / Math.abs(velocity)));
-        } else {
-            duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
-        }
-
-        ValueAnimator anim = createTranslationAnimation(view, newPos);
-        anim.setInterpolator(sLinearInterpolator);
-        anim.setDuration(duration);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mCallback.onChildDismissed(view);
-                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
-                    view.setAlpha(1.f);
-                }
-            }
-        });
-        anim.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
-                    view.setAlpha(getAlphaForOffset(view));
-                }
-            }
-        });
-        anim.start();
-    }
-
-    private void snapChild(final View view, float velocity) {
-        final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
-        ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX);
-        int duration = SNAP_ANIM_LEN;
-        anim.setDuration(duration);
-        anim.setInterpolator(mLinearOutSlowInInterpolator);
-        anim.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
-                    view.setAlpha(getAlphaForOffset(view));
-                }
-                mCallback.onSwipeChanged(mCurrView, view.getTranslationX());
-            }
-        });
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
-                    view.setAlpha(1.0f);
-                }
-                mCallback.onSnapBackCompleted(view);
-            }
-        });
-        anim.start();
-    }
-
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (!mDragging) {
-            if (!onInterceptTouchEvent(ev)) {
-                return mCanCurrViewBeDimissed;
-            }
-        }
-
-        mVelocityTracker.addMovement(ev);
-        final int action = ev.getAction();
-        switch (action) {
-            case MotionEvent.ACTION_OUTSIDE:
-            case MotionEvent.ACTION_MOVE:
-                if (mCurrView != null) {
-                    float delta = getPos(ev) - mInitialTouchPos;
-                    setSwipeAmount(delta);
-                    mCallback.onSwipeChanged(mCurrView, delta);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                if (mCurrView != null) {
-                    endSwipe(mVelocityTracker);
-                }
-                break;
-        }
-        return true;
-    }
-
-    private void setSwipeAmount(float amount) {
-        // don't let items that can't be dismissed be dragged more than
-        // maxScrollDistance
-        if (CONSTRAIN_SWIPE
-                && (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {
-            float size = getSize(mCurrView);
-            float maxScrollDistance = 0.15f * size;
-            if (Math.abs(amount) >= size) {
-                amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
-            } else {
-                amount = maxScrollDistance * (float) Math.sin((amount/size)*(Math.PI/2));
-            }
-        }
-        setTranslation(mCurrView, amount);
-        if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
-            float alpha = getAlphaForOffset(mCurrView);
-            mCurrView.setAlpha(alpha);
-        }
-    }
-
-    private boolean isValidSwipeDirection(float amount) {
-        if (mSwipeDirection == X) {
-            if (mRtl) {
-                return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;
-            } else {
-                return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
-            }
-        }
-
-        // Vertical swipes are always valid.
-        return true;
-    }
-
-    private void endSwipe(VelocityTracker velocityTracker) {
-        velocityTracker.computeCurrentVelocity(1000 /* px/sec */);
-        float velocity = getVelocity(velocityTracker);
-        float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
-        float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
-        float translation = getTranslation(mCurrView);
-        // Decide whether to dismiss the current view
-        boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
-                Math.abs(translation) > 0.6 * getSize(mCurrView);
-        boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
-                (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
-                (velocity > 0) == (translation > 0);
-
-        boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
-                && isValidSwipeDirection(translation)
-                && (childSwipedFastEnough || childSwipedFarEnough);
-
-        if (dismissChild) {
-            // flingadingy
-            dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
-        } else {
-            // snappity
-            mCallback.onDragCancelled(mCurrView);
-            snapChild(mCurrView, velocity);
-        }
-    }
-
-    public interface Callback {
-        View getChildAtPosition(MotionEvent ev);
-
-        boolean canChildBeDismissed(View v);
-
-        void onBeginDrag(View v);
-
-        void onSwipeChanged(View v, float delta);
-
-        void onChildDismissed(View v);
-
-        void onSnapBackCompleted(View v);
-
-        void onDragCancelled(View v);
-    }
-}
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 80a35de..2930f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -97,7 +97,7 @@
         Task launchTargetTask = stack.getLaunchTarget();
 
         // Break early if there are no tasks
-        if (stack.getStackTaskCount() == 0) {
+        if (stack.getTaskCount() == 0) {
             return;
         }
 
@@ -159,7 +159,7 @@
         Task launchTargetTask = stack.getLaunchTarget();
 
         // Break early if there are no tasks
-        if (stack.getStackTaskCount() == 0) {
+        if (stack.getTaskCount() == 0) {
             return;
         }
 
@@ -229,7 +229,7 @@
         TaskStack stack = mStackView.getStack();
 
         // Break early if there are no tasks
-        if (stack.getStackTaskCount() == 0) {
+        if (stack.getTaskCount() == 0) {
             return;
         }
 
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 c2bb745..2fa99ce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -21,6 +21,8 @@
 import android.content.res.Resources;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.animation.AnimationUtils;
@@ -37,7 +39,6 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 
 /**
  * Used to describe a visible range that can be normalized to [0, 1].
@@ -137,9 +138,8 @@
         public static StackState getStackStateForStack(TaskStack stack) {
             SystemServicesProxy ssp = Recents.getSystemServices();
             boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
-            int taskCount = stack.getStackTaskCount();
-            int freeformCount = stack.getStackTaskFreeformCount();
-            int stackCount = taskCount - freeformCount;
+            int freeformCount = stack.getFreeformTaskCount();
+            int stackCount = stack.getStackTaskCount();
             if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
                 return SPLIT;
             } else if (hasFreeformWorkspaces && freeformCount > 0) {
@@ -270,7 +270,7 @@
     int mMaxTranslationZ;
 
     // Optimization, allows for quick lookup of task -> index
-    private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
+    private ArrayMap<Task.TaskKey, Integer> mTaskIndexMap = new ArrayMap<>();
 
     // The freeform workspace layout
     FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
@@ -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) {
+    void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
@@ -393,6 +393,9 @@
         ArrayList<Task> stackTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
             Task task = tasks.get(i);
+            if (ignoreTasksSet.contains(task)) {
+                continue;
+            }
             if (task.isFreeformTask()) {
                 freeformTasks.add(task);
             } else {
@@ -405,6 +408,7 @@
         // Put each of the tasks in the progress map at a fixed index (does not need to actually
         // map to a scroll position, just by index)
         int taskCount = stackTasks.size();
+        mTaskIndexMap.ensureCapacity(taskCount);
         for (int i = 0; i < taskCount; i++) {
             Task task = stackTasks.get(i);
             mTaskIndexMap.put(task.key, i);
@@ -645,7 +649,11 @@
             y += (mStackRect.top - mTaskRect.top);
             z = Math.max(mMinTranslationZ, Math.min(mMaxTranslationZ,
                     mMinTranslationZ + (p * (mMaxTranslationZ - mMinTranslationZ))));
-            relP = unfocusedP;
+            if (mNumStackTasks == 1) {
+                relP = 1f;
+            } else {
+                relP = Math.min(mMaxScrollP, unfocusedP);
+            }
         }
 
         // Fill out the transform
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 9568fac..713cfc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -28,8 +28,8 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.provider.Settings;
-import android.util.IntProperty;
-import android.util.Property;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -77,8 +77,7 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -105,19 +104,6 @@
     private static final int DRAG_SCALE_DURATION = 175;
     private static final float DRAG_SCALE_FACTOR = 1.05f;
 
-    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
-            new IntProperty<Drawable>("drawableAlpha") {
-                @Override
-                public void setValue(Drawable object, int alpha) {
-                    object.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(Drawable object) {
-                    return object.getAlpha();
-                }
-            };
-
     TaskStack mStack;
     TaskStackLayoutAlgorithm mLayoutAlgorithm;
     TaskStackViewScroller mStackScroller;
@@ -126,6 +112,7 @@
     GradientDrawable mFreeformWorkspaceBackground;
     ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
     ViewPool<TaskView, Task> mViewPool;
+    boolean mStartTimerIndicator;
 
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
@@ -135,6 +122,7 @@
     Task mFocusedTask;
 
     int mTaskCornerRadiusPx;
+    private int mDividerSize;
 
     boolean mTaskViewsClipDirty = true;
     boolean mAwaitingFirstLayout = true;
@@ -142,10 +130,14 @@
     boolean mTouchExplorationEnabled;
     boolean mScreenPinningEnabled;
 
-    Rect mTaskStackBounds = new Rect();
+    // The stable stack bounds are the full bounds that we were measured with from RecentsView
+    Rect mStableStackBounds = new Rect();
+    // The current stack bounds are dynamic and may change as the user drags and drops
+    Rect mStackBounds = new Rect();
     int[] mTmpVisibleRange = new int[2];
     Rect mTmpRect = new Rect();
-    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
+    ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
+    ArraySet<Task> mTmpTaskSet = new ArraySet<>();
     List<TaskView> mTmpTaskViews = new ArrayList<>();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
     LayoutInflater mInflater;
@@ -157,23 +149,35 @@
             new ValueAnimator.AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    mTaskViewsClipDirty = true;
-                    invalidate();
+                    if (!mTaskViewsClipDirty) {
+                        mTaskViewsClipDirty = true;
+                        invalidate();
+                    }
                 }
             };
 
     // The drop targets for a task drag
     private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
         @Override
-        public boolean acceptsDrop(int x, int y, int width, int height) {
-            return mLayoutAlgorithm.mFreeformRect.contains(x, y);
+        public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) {
+            // This drop target has a fixed bounds and should be checked last, so just fall through
+            // if it is the current target
+            if (!isCurrentTarget) {
+                return mLayoutAlgorithm.mFreeformRect.contains(x, y);
+            }
+            return false;
         }
     };
 
     private DropTarget mStackDropTarget = new DropTarget() {
         @Override
-        public boolean acceptsDrop(int x, int y, int width, int height) {
-            return mLayoutAlgorithm.mStackRect.contains(x, y);
+        public boolean acceptsDrop(int x, int y, int width, int height, boolean isCurrentTarget) {
+            // This drop target has a fixed bounds and should be checked last, so just fall through
+            // if it is the current target
+            if (!isCurrentTarget) {
+                return mLayoutAlgorithm.mStackRect.contains(x, y);
+            }
+            return false;
         }
     };
 
@@ -195,6 +199,7 @@
                 com.android.internal.R.interpolator.fast_out_slow_in);
         mTaskCornerRadiusPx = res.getDimensionPixelSize(
                 R.dimen.recents_task_view_rounded_corners_radius);
+        mDividerSize = ssp.getDockedDividerSize(context);
 
         int taskBarDismissDozeDelaySeconds = getResources().getInteger(
                 R.integer.recents_task_bar_dismiss_delay_seconds);
@@ -223,11 +228,8 @@
 
     @Override
     protected void onAttachedToWindow() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
-        mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
-                Settings.System.LOCK_TO_APP_ENABLED) != 0;
         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        readSystemFlags();
         super.onAttachedToWindow();
     }
 
@@ -333,6 +335,7 @@
         mUIDozeTrigger.resetTrigger();
         mStackScroller.reset();
         mLayoutAlgorithm.reset();
+        readSystemFlags();
         requestLayout();
     }
 
@@ -346,9 +349,8 @@
      * This call ignores freeform tasks.
      */
     private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
-                                       ArrayList<Task> tasks,
-                                       float stackScroll,
-                                       int[] visibleRangeOut) {
+            ArrayList<Task> tasks, float stackScroll,
+            int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) {
         int taskTransformCount = taskTransforms.size();
         int taskCount = tasks.size();
         int frontMostVisibleIndex = -1;
@@ -369,6 +371,10 @@
         TaskViewTransform frontTransform = null;
         for (int i = taskCount - 1; i >= 0; i--) {
             Task task = tasks.get(i);
+            if (ignoreTasksSet.contains(task)) {
+                continue;
+            }
+
             TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
                     taskTransforms.get(i), frontTransform);
 
@@ -409,28 +415,34 @@
      * they are initially picked up from the pool, when they will be placed in a suitable initial
      * position.
      */
-    private void bindTaskViewsWithStack() {
+    private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) {
         final float stackScroll = mStackScroller.getStackScroll();
         final int[] visibleStackRange = mTmpVisibleRange;
 
         // Get all the task transforms
         final ArrayList<Task> tasks = mStack.getStackTasks();
-        final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
-                stackScroll, visibleStackRange);
+        final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms,
+                tasks, stackScroll, visibleStackRange, ignoreTasksSet);
 
         // Return all the invisible children to the pool
-        mTmpTaskViewMap.clear();
         final List<TaskView> taskViews = getTaskViews();
         final int taskViewCount = taskViews.size();
         int lastFocusedTaskIndex = -1;
+        mTmpTaskViewMap.clear();
+        mTmpTaskViewMap.ensureCapacity(tasks.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);
 
+            // Skip ignored tasks
+            if (ignoreTasksSet.contains(task)) {
+                continue;
+            }
+
             if (task.isFreeformTask() ||
                     visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
-                mTmpTaskViewMap.put(task, tv);
+                mTmpTaskViewMap.put(task.key, tv);
             } else {
                 if (mTouchExplorationEnabled) {
                     lastFocusedTaskIndex = taskIndex;
@@ -442,16 +454,21 @@
 
         // Pick up all the newly visible children
         int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0;
-        for (int i = mStack.getStackTaskCount() - 1; i >= lastVisStackIndex; i--) {
+        for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) {
             final Task task = tasks.get(i);
             final TaskViewTransform transform = mCurrentTaskTransforms.get(i);
 
+            // Skip ignored tasks
+            if (ignoreTasksSet.contains(task)) {
+                continue;
+            }
+
             // Skip the invisible non-freeform stack tasks
             if (i > visibleStackRange[0] && !task.isFreeformTask()) {
                 continue;
             }
 
-            TaskView tv = mTmpTaskViewMap.get(task);
+            TaskView tv = mTmpTaskViewMap.get(task.key);
             if (tv == null) {
                 tv = mViewPool.pickUpViewFromPool(task, task);
                 if (task.isFreeformTask()) {
@@ -495,8 +512,16 @@
     /**
      * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its
      * current position as defined by the {@link TaskStackLayoutAlgorithm}.
+     *
+     * @param ignoreTasks the set of tasks to ignore in the relayout
      */
-    private void updateTaskViewsToLayout(TaskViewAnimation animation) {
+    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);
+
         // If we had a deferred animation, cancel that
         mDeferredTaskViewUpdateAnimation = null;
 
@@ -504,7 +529,7 @@
         cancelAllTaskViewAnimations();
 
         // Fetch the current set of TaskViews
-        bindTaskViewsWithStack();
+        bindTaskViewsWithStack(ignoreTasksSet);
 
         // Animate them to their final transforms with the given animation
         List<TaskView> taskViews = getTaskViews();
@@ -514,6 +539,10 @@
             final int taskIndex = mStack.indexOfStackTask(tv.getTask());
             final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
 
+            if (ignoreTasksSet.contains(tv.getTask())) {
+                continue;
+            }
+
             updateTaskViewToTransform(tv, transform, animation);
         }
     }
@@ -541,8 +570,7 @@
      */
     private void cancelAllTaskViewAnimations() {
         List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
+        for (int i = taskViews.size() - 1; i >= 0; i--) {
             final TaskView tv = taskViews.get(i);
             tv.cancelTransformAnimation();
         }
@@ -593,10 +621,20 @@
         mTaskViewsClipDirty = false;
     }
 
-    /** Updates the min and max virtual scroll bounds */
-    void updateLayout(boolean boundScrollToNewMinMax) {
+    /**
+     * Updates the min and max virtual scroll bounds.
+     *
+     * @param ignoreTasks 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);
+
         // Compute the min and max scroll values
-        mLayoutAlgorithm.update(mStack);
+        mLayoutAlgorithm.update(mStack, ignoreTasksSet);
 
         // Update the freeform workspace
         SystemServicesProxy ssp = Recents.getSystemServices();
@@ -623,24 +661,55 @@
      */
     private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
             final boolean requestViewFocus) {
+        return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, false);
+    }
+
+    /**
+     * Sets the focused task to the provided (bounded taskIndex).
+     *
+     * @return whether or not the stack will scroll as a part of this focus change
+     */
+    private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
+            final boolean requestViewFocus, final boolean showTimerIndicator) {
         // Find the next task to focus
-        int newFocusedTaskIndex = mStack.getStackTaskCount() > 0 ?
-                Math.max(0, Math.min(mStack.getStackTaskCount() - 1, taskIndex)) : -1;
+        int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
+                Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
         final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
                 mStack.getStackTasks().get(newFocusedTaskIndex) : null;
 
         // 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);
+                if (tv != null) {
+                    tv.getHeaderView().cancelFocusTimerIndicator();
+                }
+            }
         }
 
         boolean willScroll = false;
+
         mFocusedTask = newFocusedTask;
+
         if (newFocusedTask != null) {
+            // Start the timer indicator, if applicable
+            if (showTimerIndicator) {
+                final TaskView tv = getChildViewForTask(mFocusedTask);
+                if (tv != null) {
+                    tv.getHeaderView().startFocusTimerIndicator();
+                } else {
+                    // The view is null; set a flag for later
+                    mStartTimerIndicator = true;
+                }
+            }
+
             Runnable focusTaskRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    TaskView tv = getChildViewForTask(newFocusedTask);
+                    final TaskView tv = getChildViewForTask(newFocusedTask);
                     if (tv != null) {
                         tv.setFocusedState(true, requestViewFocus);
                     }
@@ -694,10 +763,28 @@
      * @param animated determines whether to actually draw the highlight along with the change in
      *                            focus.
      * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
-     *                               happens
+     *                               happens.
      */
     public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
                                        boolean cancelWindowAnimations) {
+        setRelativeFocusedTask(forward, stackTasksOnly, animated, false, false);
+    }
+
+    /**
+     * Sets the focused task relative to the currently focused task.
+     *
+     * @param forward whether to go to the next task in the stack (along the curve) or the previous
+     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
+     *                       if the currently focused task is not a stack task, will set the focus
+     *                       to the first visible stack task
+     * @param animated determines whether to actually draw the highlight along with the change in
+     *                            focus.
+     * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
+     *                               happens.
+     * @param showTimerIndicator determines whether or not to show an indicator for the task auto-advance.
+     */
+    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
+                                       boolean cancelWindowAnimations, boolean showTimerIndicator) {
         int newIndex = mStack.indexOfStackTask(mFocusedTask);
         if (mFocusedTask != null) {
             if (stackTasksOnly) {
@@ -721,7 +808,7 @@
             } else {
                 // No restrictions, lets just move to the new task (looping forward/backwards if
                 // necessary)
-                int taskCount = mStack.getStackTaskCount();
+                int taskCount = mStack.getTaskCount();
                 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount;
             }
         } else {
@@ -733,7 +820,7 @@
         }
         if (newIndex != -1) {
             boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */,
-                    true /* requestViewFocus */);
+                    true /* requestViewFocus */, showTimerIndicator);
             if (willScroll && cancelWindowAnimations) {
                 // As we iterate to the next/previous task, cancel any current/lagging window
                 // transition animations
@@ -774,7 +861,7 @@
             event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
             event.setContentDescription(frontMostTask.getTask().title);
         }
-        event.setItemCount(mStack.getStackTaskCount());
+        event.setItemCount(mStack.getTaskCount());
         event.setScrollY(mStackScroller.mScroller.getCurrY());
         event.setMaxScrollY(mStackScroller.progressToScrollRange(mLayoutAlgorithm.mMaxScrollP));
     }
@@ -790,7 +877,7 @@
             if (focusedTaskIndex > 0) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
             }
-            if (focusedTaskIndex < mStack.getStackTaskCount() - 1) {
+            if (focusedTaskIndex < mStack.getTaskCount() - 1) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
             }
         }
@@ -868,14 +955,18 @@
         }
     }
 
-    /** Computes the stack and task rects */
-    public void computeRects(Rect taskStackBounds) {
+    /**
+     * 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(false);
+        updateLayout(boundScroll, ignoreTasks);
     }
 
     /**
@@ -895,9 +986,19 @@
         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
     }
 
+    /**
+     * Updates the expected task stack bounds for this stack view.
+     */
     public void setTaskStackBounds(Rect taskStackBounds, Rect systemInsets) {
-        mTaskStackBounds.set(taskStackBounds);
+        // We can get spurious measure passes with the old bounds when docking, and since we are
+        // using the current stack bounds during drag and drop, don't overwrite them until we
+        // actually get new bounds
+        if (!taskStackBounds.equals(mStableStackBounds)) {
+            mStableStackBounds.set(taskStackBounds);
+            mStackBounds.set(taskStackBounds);
+        }
         mLayoutAlgorithm.setSystemInsets(systemInsets);
+        requestLayout();
     }
 
     /**
@@ -910,14 +1011,15 @@
         int height = MeasureSpec.getSize(heightMeasureSpec);
 
         // Compute our stack/task rects
-        computeRects(mTaskStackBounds);
+        computeRects(mStackBounds, false);
 
         // 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();
         }
-        bindTaskViewsWithStack();
+        mTmpTaskSet.clear();
+        bindTaskViewsWithStack(mTmpTaskSet);
 
         // Measure each of the TaskViews
         mTmpTaskViews.clear();
@@ -996,7 +1098,7 @@
         // until after the enter-animation
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
-        int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getStackTaskCount());
+        int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
         if (focusedTaskIndex != -1) {
             setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
                     false /* requestViewFocus */);
@@ -1011,14 +1113,9 @@
         }
     }
 
-    public boolean isTransformedTouchPointInView(float x, float y, TaskView tv) {
-        final float[] point = new float[2];
-        point[0] = x;
-        point[1] = y;
-        transformPointToViewLocal(point, tv);
-        x = point[0];
-        y = point[1];
-        return (0 <= x) && (x < tv.getWidth()) && (0 <= y) && (y < tv.getHeight());
+    public boolean isTouchPointInView(float x, float y, TaskView tv) {
+        return (tv.getLeft() <= x && x <= tv.getRight()) &&
+                (tv.getTop() <= y && y <= tv.getBottom());
     }
 
     @Override
@@ -1087,11 +1184,9 @@
 
             // 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 = null;
+            Task anchorTask = mStack.getStackFrontMostTask();
             float prevAnchorTaskScroll = 0;
-            boolean pullStackForward = stack.getStackTaskCount() > 0;
-            if (pullStackForward) {
-                anchorTask = mStack.getStackFrontMostTask();
+            if (anchorTask != null) {
                 prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
             }
 
@@ -1102,7 +1197,7 @@
                 // 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 (pullStackForward) {
+            } else if (anchorTask != null) {
                 // Otherwise, offset the scroll by the movement of the anchor task
                 float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
                 float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
@@ -1139,11 +1234,8 @@
         }
 
         // If there are no remaining tasks, then just close recents
-        if (mStack.getStackTaskCount() == 0) {
-            boolean shouldFinishActivity = (mStack.getStackTaskCount() == 0);
-            if (shouldFinishActivity) {
-                EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
-            }
+        if (mStack.getTaskCount() == 0) {
+            EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
         }
     }
 
@@ -1210,6 +1302,11 @@
         tv.setClipViewInStack(true);
         if (mFocusedTask == task) {
             tv.setFocusedState(true, false /* requestViewFocus */);
+            if (mStartTimerIndicator) {
+                // The timer indicator couldn't be started before, so start it now
+                tv.getHeaderView().startFocusTimerIndicator();
+                mStartTimerIndicator = false;
+            }
         }
 
         // Restore the action button visibility if it is the front most task view
@@ -1251,7 +1348,7 @@
 
     public final void onBusEvent(PackagesChangedEvent event) {
         // Compute which components need to be removed
-        HashSet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
+        ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
                 event.packageName, event.userId);
 
         // For other tasks, just remove them directly if they no longer exist
@@ -1318,7 +1415,8 @@
     }
 
     public final void onBusEvent(FocusNextTaskViewEvent event) {
-        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */);
+        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
+                event.showTimerIndicator);
     }
 
     public final void onBusEvent(FocusPreviousTaskViewEvent event) {
@@ -1328,6 +1426,9 @@
     public final void onBusEvent(UserInteractionEvent event) {
         // Poke the doze trigger on user interaction
         mUIDozeTrigger.poke();
+        if (event.showTimerIndicator && mFocusedTask != null) {
+            getChildViewForTask(mFocusedTask).getHeaderView().cancelFocusTimerIndicator();
+        }
     }
 
     public final void onBusEvent(RecentsVisibilityChangedEvent event) {
@@ -1348,6 +1449,7 @@
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
                 mTmpTransform, null);
         mTmpTransform.scale = finalScale;
+        mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
         updateTaskViewToTransform(event.taskView, mTmpTransform,
                 new TaskViewAnimation(DRAG_SCALE_DURATION, mFastOutSlowInInterpolator));
     }
@@ -1361,7 +1463,23 @@
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
-        // TODO: Animate the freeform workspace background etc.
+        if (event.dropTarget instanceof TaskStack.DockState) {
+            // Calculate the new task stack bounds that matches the window size that Recents will
+            // have after the drop
+            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 */);
+        } else {
+            // Restore the pre-drag task stack bounds
+            mStackBounds.set(mStableStackBounds);
+            computeRects(mStackBounds, true /* boundScroll */);
+            updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator),
+                    event.task /* ignoreTask */);
+        }
     }
 
     public final void onBusEvent(final DragEndEvent event) {
@@ -1420,7 +1538,7 @@
     }
 
     public final void onBusEvent(StackViewScrolledEvent event) {
-        mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement);
+        mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement.value);
     }
 
     public final void onBusEvent(IterateRecentsEvent event) {
@@ -1433,7 +1551,7 @@
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         mEnterAnimationComplete = true;
 
-        if (mStack.getStackTaskCount() > 0) {
+        if (mStack.getTaskCount() > 0) {
             // Start the task enter animations
             mAnimationHelper.startEnterAnimation(event.getAnimationTrigger());
 
@@ -1505,7 +1623,7 @@
 
         Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
         mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
-                DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
+                Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
         mFreeformWorkspaceBackgroundAnimator.setDuration(duration);
         mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator);
         mFreeformWorkspaceBackgroundAnimator.start();
@@ -1546,4 +1664,14 @@
     private boolean shouldShowHistoryButton() {
         return !mStack.getHistoricalTasks().isEmpty();
     }
+
+    /**
+     * Reads current system flags related to accessibility and screen pinning.
+     */
+    private void readSystemFlags() {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
+        mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
+                Settings.System.LOCK_TO_APP_ENABLED) != 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 c748efc..a0bb0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -29,6 +30,7 @@
 import android.view.ViewParent;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
@@ -71,6 +73,7 @@
     // Used to calculate when a tap is outside a task view rectangle.
     final int mWindowTouchSlop;
 
+    private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
     SwipeHelper mSwipeHelper;
     boolean mInterceptedBySwipeHelper;
 
@@ -79,19 +82,21 @@
         Resources res = context.getResources();
         ViewConfiguration configuration = ViewConfiguration.get(context);
         mContext = context;
+        mSv = sv;
+        mScroller = scroller;
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
         mScrollTouchSlop = configuration.getScaledTouchSlop();
         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
-        mSv = sv;
-        mScroller = scroller;
         mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
-
-        float densityScale = res.getDisplayMetrics().density;
         mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
-        mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale,
-                configuration.getScaledPagingTouchSlop());
-        mSwipeHelper.setMinAlpha(1f);
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context) {
+            @Override
+            protected float getSize(View v) {
+                return mSv.getWidth();
+            }
+        };
+        mSwipeHelper.setDisableHardwareLayers(true);
     }
 
     /** Velocity tracker helpers */
@@ -116,7 +121,7 @@
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
             if (tv.getVisibility() == View.VISIBLE) {
-                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
+                if (mSv.isTouchPointInView(x, y, tv)) {
                     return tv;
                 }
             }
@@ -207,7 +212,8 @@
                     if (DEBUG) {
                         Log.d(TAG, "scroll: " + curScrollP);
                     }
-                    EventBus.getDefault().send(new StackViewScrolledEvent(y - mLastY));
+                    mStackViewScrolledEvent.updateY(y - mLastY);
+                    EventBus.getDefault().send(mStackViewScrolledEvent);
                 }
 
                 mLastY = y;
@@ -343,7 +349,7 @@
     @Override
     public void onBeginDrag(View v) {
         TaskView tv = (TaskView) v;
-        mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX());
+
         // Disable clipping with the stack while we are swiping
         tv.setClipViewInStack(false);
         // Disallow touch events from this task view
@@ -356,8 +362,8 @@
     }
 
     @Override
-    public void onSwipeChanged(View v, float delta) {
-        // Do nothing
+    public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
+        return true;
     }
 
     @Override
@@ -375,7 +381,7 @@
     }
 
     @Override
-    public void onSnapBackCompleted(View v) {
+    public void onChildSnappedBack(View v) {
         TaskView tv = (TaskView) v;
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
@@ -387,4 +393,20 @@
     public void onDragCancelled(View v) {
         // Do nothing
     }
+
+    @Override
+    public View getChildContentView(View v) {
+        return v;
+    }
+
+    @Override
+    public boolean isAntiFalsingNeeded() {
+        return false;
+    }
+
+    @Override
+    public float getFalsingThresholdFactor() {
+        return 0;
+    }
+
 }
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 bc441b2..db4db63 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -257,7 +257,9 @@
         mTmpAnimators.clear();
         toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
         if (toAnimation.isImmediate()) {
-            setTaskProgress(toTransform.p);
+            if (Float.compare(getTaskProgress(), toTransform.p) != 0) {
+                setTaskProgress(toTransform.p);
+            }
             if (toAnimation.listener != null) {
                 toAnimation.listener.onAnimationEnd(null);
             }
@@ -286,7 +288,7 @@
 
         mActionButtonView.setScaleX(1f);
         mActionButtonView.setScaleY(1f);
-        mActionButtonView.setAlpha(1f);
+        mActionButtonView.setAlpha(0f);
         mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
     }
 
@@ -360,6 +362,10 @@
         updateDimFromTaskProgress();
     }
 
+    public TaskViewHeader getHeaderView() {
+        return mHeaderView;
+    }
+
     /** Returns the current task progress. */
     public float getTaskProgress() {
         return mTaskProgress;
@@ -455,7 +461,6 @@
                         .scaleY(1f)
                         .setDuration(fadeInDuration)
                         .setInterpolator(PhoneStatusBar.ALPHA_IN)
-                        .withLayer()
                         .start();
             }
         } else {
@@ -494,7 +499,6 @@
                                 mActionButtonView.setVisibility(View.INVISIBLE);
                             }
                         })
-                        .withLayer()
                         .start();
             }
         } else {
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 6a47424..e7717ac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -23,17 +23,20 @@
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
 import android.graphics.PixelFormat;
 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.util.AttributeSet;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import com.android.internal.logging.MetricsLogger;
 
@@ -51,12 +54,12 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
-
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
 
     private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f;
+    private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
 
     /**
      * A color drawable that draws a slight highlight at the top to help it stand out.
@@ -124,6 +127,7 @@
     ImageView mIconView;
     TextView mTitleView;
     int mMoveTaskTargetStackId = INVALID_STACK_ID;
+    ProgressBar mFocusTimerIndicator;
 
     // Header drawables
     Rect mTaskViewRect = new Rect();
@@ -132,9 +136,12 @@
     float mDimAlpha;
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
+    Drawable mLightFreeformIcon;
+    Drawable mDarkFreeformIcon;
+    Drawable mLightFullscreenIcon;
+    Drawable mDarkFullscreenIcon;
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
-    String mDismissContentDescription;
 
     // Header background
     private HighlightColorDrawable mBackground;
@@ -145,6 +152,10 @@
     Interpolator mFastOutSlowInInterpolator;
     Interpolator mFastOutLinearInInterpolator;
 
+    long mFocusIndicatorProgress;
+    private CountDownTimer mFocusTimerCountDown;
+    long mFocusTimerDuration;
+
     public TaskViewHeader(Context context) {
         this(context, null);
     }
@@ -165,12 +176,15 @@
         Resources res = context.getResources();
         mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
         mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
-        mDismissContentDescription = context.getString(
-                R.string.accessibility_recents_item_will_be_dismissed);
         mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
         mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
         mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
         mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
+        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
+        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);
+
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -182,6 +196,7 @@
         setBackground(mBackground);
         mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
         mDimLayerPaint.setAntiAlias(true);
+        mFocusTimerDuration = res.getInteger(R.integer.recents_auto_advance_duration);
     }
 
     @Override
@@ -193,6 +208,7 @@
         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) {
@@ -213,7 +229,9 @@
         mTaskViewRect.set(0, 0, width, height);
         boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
         int appIconWidth = mIconView.getMeasuredWidth();
-        int activityDescWidth = mTitleView.getMeasuredWidth();
+        int activityDescWidth = (mTask != null)
+                ? (int) mTitleView.getPaint().measureText(mTask.title)
+                : mTitleView.getMeasuredWidth();
         int dismissIconWidth = mDismissButton.getMeasuredWidth();
         int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE
                 ? mMoveTaskButton.getMeasuredWidth()
@@ -268,6 +286,41 @@
                 mCornerRadius, mCornerRadius, mDimLayerPaint);
     }
 
+    /** Starts the focus timer. */
+    public void startFocusTimerIndicator() {
+        mFocusTimerIndicator.setVisibility(View.VISIBLE);
+        mFocusTimerIndicator.setMax((int) mFocusTimerDuration);
+        if (mFocusTimerCountDown == null) {
+            mFocusTimerCountDown = new CountDownTimer(mFocusTimerDuration,
+                    FOCUS_INDICATOR_INTERVAL_MS) {
+                public void onTick(long millisUntilFinished) {
+                    mFocusTimerIndicator.setProgress((int) millisUntilFinished);
+                }
+
+                public void onFinish() {
+                    mFocusTimerIndicator.setProgress((int) mFocusTimerDuration);
+                }
+            }.start();
+        } else {
+            mFocusTimerCountDown.start();
+        }
+    }
+
+    /** Cancels the focus timer. */
+    public void cancelFocusTimerIndicator() {
+        if (mFocusTimerCountDown != null && mFocusTimerIndicator != null) {
+            mFocusTimerCountDown.cancel();
+            mFocusTimerIndicator.setProgress(0);
+            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    /** Returns the secondary color for a primary color. */
+    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
+        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
+        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
+    }
+
     /**
      * Sets the dim alpha, only used when we are not using hardware layers.
      * (see RecentsConfiguration.useHardwareLayers)
@@ -307,22 +360,21 @@
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
-        mDismissButton.setContentDescription(String.format(mDismissContentDescription,
-                t.contentDescription));
+        mDismissButton.setContentDescription(t.dismissDescription);
 
         // When freeform workspaces are enabled, then update the move-task button depending on the
         // current task
         if (ssp.hasFreeformWorkspaceSupport()) {
             if (t.isFreeformTask()) {
                 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
-                mMoveTaskButton.setImageResource(t.useLightOnPrimaryColor
-                        ? R.drawable.recents_move_task_fullscreen_light
-                        : R.drawable.recents_move_task_fullscreen_dark);
+                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
+                        ? mLightFullscreenIcon
+                        : mDarkFullscreenIcon);
             } else {
                 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
-                mMoveTaskButton.setImageResource(t.useLightOnPrimaryColor
-                        ? R.drawable.recents_move_task_freeform_light
-                        : R.drawable.recents_move_task_freeform_dark);
+                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
+                        ? mLightFreeformIcon
+                        : mDarkFreeformIcon);
             }
             if (mMoveTaskButton.getVisibility() != View.VISIBLE) {
                 mMoveTaskButton.setVisibility(View.VISIBLE);
@@ -330,6 +382,11 @@
             mMoveTaskButton.setOnClickListener(this);
         }
 
+        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()) {
             mIconView.setOnClickListener(this);
@@ -359,7 +416,10 @@
         }
     }
 
-    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
+    /**
+     * Mark this task view that the user does has not interacted with the stack after a certain
+     * time.
+     */
     void setNoUserInteractionState() {
         if (mDismissButton.getVisibility() != View.VISIBLE) {
             mDismissButton.animate().cancel();
@@ -368,7 +428,10 @@
         }
     }
 
-    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
+    /**
+     * Resets the state tracking that the user has not interacted with the stack after a certain
+     * time.
+     */
     void resetNoUserInteractionState() {
         mDismissButton.setVisibility(View.INVISIBLE);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 824d10a..c16703e8 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -363,21 +363,6 @@
         return mStartPosition + touchY - mStartY;
     }
 
-    private int invertDockSide(int dockSide) {
-        switch (dockSide) {
-            case WindowManager.DOCKED_LEFT:
-                return WindowManager.DOCKED_RIGHT;
-            case WindowManager.DOCKED_TOP:
-                return WindowManager.DOCKED_BOTTOM;
-            case WindowManager.DOCKED_RIGHT:
-                return WindowManager.DOCKED_LEFT;
-            case WindowManager.DOCKED_BOTTOM:
-                return WindowManager.DOCKED_TOP;
-            default:
-                return WindowManager.DOCKED_INVALID;
-        }
-    }
-
     private void alignTopLeft(Rect containingRect, Rect rect) {
         int width = rect.width();
         int height = rect.height();
@@ -409,8 +394,9 @@
 
         mLastResizeRect.set(mDockedRect);
         if (taskPosition != TASK_POSITION_SAME) {
-            calculateBoundsForPosition(position, invertDockSide(mDockSide), mOtherRect);
-            int dockSideInverted = invertDockSide(mDockSide);
+            calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
+                    mOtherRect);
+            int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
             int taskPositionDocked =
                     restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
             int taskPositionOther =
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index 161f873..2294d40 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -26,6 +26,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
 /**
@@ -50,6 +51,7 @@
                         | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
                 PixelFormat.TRANSLUCENT);
         mLp.setTitle(WINDOW_TITLE);
+        mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
         view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 2791dfc..67bb58a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -85,7 +85,8 @@
         @Override
         public void run() {
             try {
-                ActivityManagerNative.getDefault().moveTasksToFullscreenStack(DOCKED_STACK_ID);
+                ActivityManagerNative.getDefault().moveTasksToFullscreenStack(
+                        DOCKED_STACK_ID, false /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to remove stack: " + e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6efb774..ce4eff5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1954,8 +1954,9 @@
                     + " alertAgain=" + alertAgain);
         }
 
+        final StatusBarNotification oldNotification = entry.notification;
         entry.notification = notification;
-        mGroupManager.onEntryUpdated(entry, entry.notification);
+        mGroupManager.onEntryUpdated(entry, oldNotification);
 
         boolean updateSuccessful = false;
         if (applyInPlace) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 20a6e7c..52326e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -16,10 +16,11 @@
 
 package com.android.systemui.statusbar;
 
-import android.annotation.IdRes;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
@@ -28,13 +29,12 @@
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
-import android.widget.RadioGroup;
 import android.widget.SeekBar;
 import android.widget.TextView;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -123,10 +123,27 @@
         final TextView topicSummary = ((TextView) row.findViewById(R.id.summary));
         final TextView topicTitle = ((TextView) row.findViewById(R.id.title));
         mSeekBar = (SeekBar) row.findViewById(R.id.seekbar);
-        mSeekBar.setMax(4);
+        boolean systemApp = false;
+        try {
+            final PackageManager pm = BaseStatusBar.getPackageManagerForUser(
+                    getContext(), sbn.getUser().getIdentifier());
+            final PackageInfo info =
+                    pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
+            systemApp = Utils.isSystemPackage(pm, info);
+        } catch (PackageManager.NameNotFoundException e) {
+            // unlikely.
+        }
+        final int minProgress = systemApp ?
+                NotificationListenerService.Ranking.IMPORTANCE_LOW
+                : NotificationListenerService.Ranking.IMPORTANCE_NONE;
+        mSeekBar.setMax(NotificationListenerService.Ranking.IMPORTANCE_MAX);
         mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
             @Override
             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (progress < minProgress) {
+                    seekBar.setProgress(minProgress);
+                    progress = minProgress;
+                }
                 updateTitleAndSummary(progress);
                 if (fromUser) {
                     if (appUsesTopics) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
new file mode 100644
index 0000000..3e2c4c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.v4.util.SimpleArrayMap;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.ActivityStarter;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to populate data for CarNavigationBarView and handle user interactions.
+ * <p/>
+ * Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can customize
+ * the navigation buttons by updating arrays_car.xml appropriately in an overlay.
+ */
+class CarNavigationBarController {
+
+    // Each facet of the navigation bar maps to a set of package names or categories defined in
+    // arrays_car.xml. Package names for a given facet are delimited by ";"
+    private static final String FACET_FILTER_DEMILITER = ";";
+
+    private Context mContext;
+    private CarNavigationBarView mNavBar;
+    private ActivityStarter mActivityStarter;
+
+    // Set of categories each facet will filter on.
+    private List<String[]> mFacetCategories = new ArrayList<String[]>();
+    // Set of package names each facet will filter on.
+    private List<String[]> mFacetPackages = new ArrayList<String[]>();
+
+    private SimpleArrayMap<String, Integer> mFacetCategoryMap
+            = new SimpleArrayMap<String, Integer>();
+    private SimpleArrayMap<String, Integer> mFacetPackageMap
+            = new SimpleArrayMap<String, Integer>();
+
+    private List<Intent> mIntents = new ArrayList<Intent>();
+    private List<Intent> mLongPressIntents = new ArrayList<Intent>();
+
+    private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>();
+
+    private int mCurrentFacetIndex;
+
+    public CarNavigationBarController(Context context,
+                                      CarNavigationBarView navBar,
+                                      ActivityStarter activityStarter) {
+        mContext = context;
+        mNavBar = navBar;
+        mActivityStarter = activityStarter;
+        bind();
+    }
+
+    public void taskChanged(String packageName) {
+        // If the package name belongs to a filter, then highlight appropriate button in
+        // the navigation bar.
+        if (mFacetPackageMap.containsKey(packageName)) {
+            setCurrentFacet(mFacetPackageMap.get(packageName));
+        }
+
+        // Check if the package matches any of the categories for the facets
+        String category = getPackageCategory(packageName);
+        if (category != null) {
+            setCurrentFacet(mFacetCategoryMap.get(category));
+        }
+    }
+
+    private void bind() {
+        // Read up arrays_car.xml and populate the navigation bar here.
+        Resources r = mContext.getResources();
+        TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons);
+        TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris);
+        TypedArray longpressIntents =
+                r.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
+        TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters);
+
+        TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters);
+
+        if (icons.length() != intents.length()
+                || icons.length() != longpressIntents.length()
+                || icons.length() != facetPackageNames.length()
+                || icons.length() != facetCategories.length()) {
+            throw new RuntimeException("car_facet array lengths do not match");
+        }
+
+        for (int i = 0; i < icons.length(); i++) {
+            Drawable icon = icons.getDrawable(i);
+            try {
+                mIntents.add(i,
+                        Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME));
+
+                String longpressUri = longpressIntents.getString(i);
+                boolean hasLongpress = !longpressUri.isEmpty();
+                if (hasLongpress) {
+                    mLongPressIntents.add(i,
+                            Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME));
+                }
+
+                CarNavigationButton button = createNavButton(icon, i, hasLongpress);
+                mNavButtons.add(button);
+                mNavBar.addButton(button, createNavButton(icon, i, hasLongpress));
+
+                initFacetFilterMaps(i,
+                        facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
+                        facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("Malformed intent uri", e);
+            }
+        }
+    }
+
+    private void initFacetFilterMaps(int id, String[] packageNames, String[] categories){
+        mFacetCategories.add(categories);
+        for (int i = 0; i < categories.length; i++) {
+            mFacetCategoryMap.put(categories[i], id);
+        }
+
+        mFacetPackages.add(packageNames);
+        for (int i = 0; i < packageNames.length; i++) {
+            mFacetPackageMap.put(packageNames[i], id);
+        }
+    }
+
+    private String getPackageCategory(String packageName) {
+        PackageManager pm = mContext.getPackageManager();
+        int size = mFacetCategories.size();
+        // For each facet, check if the given package name matches one of its categories
+        for (int i = 0; i < size; i++) {
+            String[] categories = mFacetCategories.get(i);
+            for (int j = 0; j < categories.length; j++) {
+                String category = categories[j];
+                Intent intent = new Intent();
+                intent.setPackage(packageName);
+                intent.setAction(Intent.ACTION_MAIN);
+                intent.addCategory(category);
+                List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+                if (list.size() > 0) {
+                    // Cache this package name into facetPackageMap, so we won't have to query
+                    // all categories next time this package name shows up.
+                    mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
+                    return category;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method to check if a given facet has multiple packages associated with it.
+     * This can be resource defined package names or package names filtered by facet category.
+     */
+    private boolean facetHasMultiplePackages(int index) {
+        PackageManager pm = mContext.getPackageManager();
+
+        // Check if the packages defined for the filter actually exists on the device
+        String[] packages = mFacetPackages.get(index);
+        if (packages.length > 1) {
+            int count = 0;
+            for (int i = 0; i < packages.length; i++) {
+                count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
+                if (count > 1) {
+                    return true;
+                }
+            }
+        }
+
+        // If there weren't multiple packages defined for the facet, check the categories
+        // and see if they resolve to multiple package names
+        String categories[] = mFacetCategories.get(index);
+
+        int count = 0;
+        for (int i = 0; i < categories.length; i++) {
+            String category = categories[i];
+            Intent intent = new Intent();
+            intent.setAction(Intent.ACTION_MAIN);
+            intent.addCategory(category);
+            count += pm.queryIntentActivities(intent, 0).size();
+            if (count > 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void setCurrentFacet(int index) {
+        if (index == mCurrentFacetIndex) {
+            return;
+        }
+
+        if (mNavButtons.get(mCurrentFacetIndex) != null) {
+            mNavButtons.get(mCurrentFacetIndex)
+                    .setSelected(false /* selected */, false /* showMoreIcon */);
+        }
+
+        if (mNavButtons.get(index) != null) {
+            mNavButtons.get(index).setSelected(true /* selected */,
+                    facetHasMultiplePackages(index)  /* showMoreIcon */);
+        }
+        mCurrentFacetIndex = index;
+    }
+
+    private CarNavigationButton createNavButton(Drawable icon, final int id,
+                                                boolean longClickEnabled) {
+        CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
+                R.layout.car_navigation_button, null);
+        button.setResources(icon);
+        LinearLayout.LayoutParams lp =
+                new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
+        button.setLayoutParams(lp);
+
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setCurrentFacet(id);
+                onFacetClicked(id);
+            }
+        });
+
+        if (longClickEnabled) {
+            button.setLongClickable(true);
+            button.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    onFacetLongClicked(id);
+                    setCurrentFacet(id);
+                    return true;
+                }
+            });
+        } else {
+            button.setLongClickable(false);
+        }
+        return button;
+    }
+
+    private void startActivity(Intent intent) {
+        if (mActivityStarter != null && intent != null) {
+            mActivityStarter.startActivity(intent, true);
+        }
+    }
+
+    private void onFacetClicked(int index) {
+        // TODO: determine what data to pass to the trampoline, so it can start
+        // the default app or the lens picker.
+        startActivity(mIntents.get(index));
+    }
+
+    private void onFacetLongClicked(int index) {
+        // TODO: determine what data to pass to the trampoline, so it can start
+        // the default app or the lens picker.
+        startActivity(mLongPressIntents.get(index));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index e2d64b04..efc3646 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -17,36 +17,29 @@
 package com.android.systemui.statusbar.car;
 
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.R.color;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.ImageView.ScaleType;
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ActivityStarter;
 import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
-import com.android.systemui.statusbar.policy.KeyButtonView;
 
-import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
 
 /**
  * A custom navigation bar for the automotive use case.
  * <p>
- * The navigation bar in the automotive use case is more like a list of shortcuts, which we
- * expect to be customizable by the car OEMs. This implementation populates the nav_buttons layout
- * from resources rather than the layout file so customization would then mean updating
- * arrays_car.xml appropriately in an overlay.
+ * The navigation bar in the automotive use case is more like a list of shortcuts, rendered
+ * in a linear layout.
  */
 class CarNavigationBarView extends NavigationBarView {
-    private ActivityStarter mActivityStarter;
+    private LinearLayout mNavButtons;
+    private LinearLayout mLightsOutButtons;
 
     public CarNavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -54,83 +47,13 @@
 
     @Override
     public void onFinishInflate() {
-        // Read up arrays_car.xml and populate the navigation bar here.
-        Context context = getContext();
-        Resources r = getContext().getResources();
-        TypedArray icons = r.obtainTypedArray(R.array.car_shortcut_icons);
-        TypedArray intents = r.obtainTypedArray(R.array.car_shortcut_intent_uris);
-        TypedArray longpressIntents =
-                r.obtainTypedArray(R.array.car_shortcut_longpress_intent_uris);
-
-        if (icons.length() != intents.length()) {
-            throw new RuntimeException("car_shortcut_icons and car_shortcut_intents do not match");
-        }
-
-        LinearLayout navButtons = (LinearLayout) findViewById(R.id.nav_buttons);
-        LinearLayout lightsOut = (LinearLayout) findViewById(R.id.lights_out);
-
-        for (int i = 0; i < icons.length(); i++) {
-            Drawable icon = icons.getDrawable(i);
-
-            try {
-                Intent intent = Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME);
-                Intent longpress = null;
-                String longpressUri = longpressIntents.getString(i);
-                if (!longpressUri.isEmpty()) {
-                    longpress = Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME);
-                }
-
-                // nav_buttons and lights_out should match exactly.
-                navButtons.addView(makeButton(context, icon, intent, longpress));
-                lightsOut.addView(makeButton(context, icon, intent, longpress));
-            } catch (URISyntaxException e) {
-                throw new RuntimeException("Malformed intent uri", e);
-            }
-        }
+        mNavButtons = (LinearLayout) findViewById(R.id.nav_buttons);
+        mLightsOutButtons = (LinearLayout) findViewById(R.id.lights_out);
     }
 
-    private ImageButton makeButton(Context context, Drawable icon,
-            final Intent intent, final Intent longpress) {
-        ImageButton button = new ImageButton(context);
-
-        button.setImageDrawable(icon);
-        button.setScaleType(ScaleType.CENTER);
-        button.setBackgroundColor(color.transparent);
-        LinearLayout.LayoutParams lp =
-                new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-        button.setLayoutParams(lp);
-
-        button.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (mActivityStarter != null) {
-                    mActivityStarter.startActivity(intent, true);
-                }
-            }
-        });
-
-        // Long click handlers are optional.
-        if (longpress != null) {
-            button.setLongClickable(true);
-            button.setOnLongClickListener(new OnLongClickListener() {
-                @Override
-                public boolean onLongClick(View v) {
-                    if (mActivityStarter != null) {
-                        mActivityStarter.startActivity(longpress, true);
-                        return true;
-                    }
-                    return false;
-                }
-            });
-        } else {
-            button.setLongClickable(false);
-        }
-
-        return button;
-    }
-
-    public void setActivityStarter(ActivityStarter activityStarter) {
-        mActivityStarter = activityStarter;
+    public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
+        mNavButtons.addView(button);
+        mLightsOutButtons.addView(lightsOutButton);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
new file mode 100644
index 0000000..36b3a8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import com.android.keyguard.AlphaOptimizedImageButton;
+import com.android.systemui.R;
+
+/**
+ * A wrapper view for a car navigation facet, which includes a button icon and a drop down icon.
+ */
+public class CarNavigationButton extends RelativeLayout {
+    private static final float SELECTED_ALPHA = 1;
+    private static final float UNSELECTED_ALPHA = 0.7f;
+
+    private AlphaOptimizedImageButton mIcon;
+    private AlphaOptimizedImageButton mMoreIcon;
+
+    public CarNavigationButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mIcon = (AlphaOptimizedImageButton) findViewById(R.id.car_nav_button_icon);
+        mIcon.setClickable(false);
+        mIcon.setScaleType(ImageView.ScaleType.CENTER);
+        mIcon.setBackgroundColor(android.R.color.transparent);
+        mIcon.setAlpha(UNSELECTED_ALPHA);
+
+        mMoreIcon = (AlphaOptimizedImageButton) findViewById(R.id.car_nav_button_more_icon);
+        mMoreIcon.setClickable(false);
+        mMoreIcon.setScaleType(ImageView.ScaleType.CENTER);
+        mMoreIcon.setBackgroundColor(android.R.color.transparent);
+        mMoreIcon.setVisibility(INVISIBLE);
+        mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
+        mMoreIcon.setAlpha(UNSELECTED_ALPHA);
+    }
+
+    public void setResources(Drawable icon) {
+        mIcon.setImageDrawable(icon);
+    }
+
+    public void setSelected(boolean selected, boolean showMoreIcon) {
+        if (selected) {
+            mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : INVISIBLE);
+            mMoreIcon.setAlpha(SELECTED_ALPHA);
+            mIcon.setAlpha(SELECTED_ALPHA);
+        } else {
+            mMoreIcon.setVisibility(INVISIBLE);
+            mIcon.setAlpha(UNSELECTED_ALPHA);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 31631f8..f6f1f94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,30 +16,52 @@
 
 package com.android.systemui.statusbar.car;
 
+import android.app.ActivityManager;
+import android.app.ITaskStackListener;
 import android.content.Context;
 import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 /**
  * A status bar (and navigation bar) tailored for the automotive use case.
  */
 public class CarStatusBar extends PhoneStatusBar {
+    private SystemServicesProxy mSystemServicesProxy;
+    private TaskStackListenerImpl mTaskStackListener;
+    private Handler mHandler;
+
+    private CarNavigationBarView mCarNavigationBar;
+    private CarNavigationBarController mController;
+
+    @Override
+    public void start() {
+        super.start();
+        mHandler = new Handler();
+        mTaskStackListener = new TaskStackListenerImpl(mHandler);
+        mSystemServicesProxy = new SystemServicesProxy(mContext);
+        mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+    }
+
     @Override
     protected void addNavigationBar() {
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
-                    WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSLUCENT);
         lp.setTitle("CarNavigationBar");
         lp.windowAnimations = 0;
@@ -51,11 +73,11 @@
         if (mNavigationBarView != null) {
             return;
         }
-
-        CarNavigationBarView carNavBar =
+        mCarNavigationBar =
                 (CarNavigationBarView) View.inflate(context, R.layout.car_navigation_bar, null);
-        carNavBar.setActivityStarter(this);
-        mNavigationBarView = carNavBar;
+        mController = new CarNavigationBarController(context, mCarNavigationBar,
+                this /* ActivityStarter*/);
+        mNavigationBarView = mCarNavigationBar;
     }
 
     @Override
@@ -63,4 +85,40 @@
         // The navigation bar for a vehicle will not need to be repositioned, as it is always
         // set at the bottom.
     }
+
+    /**
+     * An implementation of ITaskStackListener, that listens for changes in the system task
+     * stack and notifies the navigation bar.
+     */
+    private class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+        private Handler mHandler;
+
+        public TaskStackListenerImpl(Handler handler) {
+            this.mHandler = handler;
+        }
+
+        @Override
+        public void onActivityPinned() {
+        }
+
+        @Override
+        public void onTaskStackChanged() {
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            ensureMainThread();
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+            mController.taskChanged(runningTaskInfo.baseActivity.getPackageName());
+        }
+
+        private void ensureMainThread() {
+            if (!Looper.getMainLooper().isCurrentThread()) {
+                throw new RuntimeException("Must be called on the UI thread");
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 99436a1..347ba3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,13 +41,13 @@
  */
 public class KeyguardBouncer {
 
-    private Context mContext;
-    private ViewMediatorCallback mCallback;
-    private LockPatternUtils mLockPatternUtils;
-    private ViewGroup mContainer;
+    protected Context mContext;
+    protected ViewMediatorCallback mCallback;
+    protected LockPatternUtils mLockPatternUtils;
+    protected ViewGroup mContainer;
     private StatusBarWindowManager mWindowManager;
-    private KeyguardHostView mKeyguardView;
-    private ViewGroup mRoot;
+    protected KeyguardHostView mKeyguardView;
+    protected ViewGroup mRoot;
     private boolean mShowingSoon;
     private int mBouncerPromptReason;
     private FalsingManager mFalsingManager;
@@ -134,7 +134,7 @@
     public void hide(boolean destroyView) {
         mFalsingManager.onBouncerHidden();
         cancelShowRunnable();
-         if (mKeyguardView != null) {
+        if (mKeyguardView != null) {
             mKeyguardView.cancelDismissAction();
             mKeyguardView.cleanUp();
         }
@@ -184,13 +184,13 @@
         mBouncerPromptReason = mCallback.getBouncerPromptReason();
     }
 
-    private void ensureView() {
+    protected void ensureView() {
         if (mRoot == null) {
             inflateView();
         }
     }
 
-    private void inflateView() {
+    protected void inflateView() {
         removeView();
         mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
         mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
@@ -201,7 +201,7 @@
         mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);
     }
 
-    private void removeView() {
+    protected void removeView() {
         if (mRoot != null && mRoot.getParent() == mContainer) {
             mContainer.removeView(mRoot);
             mRoot = null;
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 e5b4f4d..2db0804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -23,6 +23,7 @@
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 import com.android.internal.logging.MetricsLogger;
@@ -55,6 +56,7 @@
     private RecentsComponent mRecentsComponent;
     private Divider mDivider;
     private Context mContext;
+    private NavigationBarView mNavigationBarView;
     private boolean mIsVertical;
     private boolean mIsRTL;
 
@@ -63,6 +65,7 @@
     private final int mMinFlingVelocity;
     private int mTouchDownX;
     private int mTouchDownY;
+    private boolean mDownOnRecents;
     private VelocityTracker mVelocityTracker;
 
     private boolean mDockWindowEnabled;
@@ -79,9 +82,11 @@
         TunerService.get(context).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
     }
 
-    public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+    public void setComponents(RecentsComponent recentsComponent, Divider divider,
+            NavigationBarView navigationBarView) {
         mRecentsComponent = recentsComponent;
         mDivider = divider;
+        mNavigationBarView = navigationBarView;
     }
 
     public void setBarState(boolean isVertical, boolean isRTL) {
@@ -157,6 +162,11 @@
         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();
     }
 
     private boolean handleDragActionMoveEvent(MotionEvent event) {
@@ -172,8 +182,8 @@
             boolean touchSlopExceeded = !mIsVertical
                     ? yDiff > mScrollTouchSlop && yDiff > xDiff
                     : xDiff > mScrollTouchSlop && xDiff > yDiff;
-            if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
-                    == DOCKED_INVALID) {
+            if (mDownOnRecents && touchSlopExceeded
+                    && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
                 Rect initialBounds = null;
                 int dragMode = calculateDragMode();
                 int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
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 55c7cb7..6c0c0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -197,7 +197,7 @@
     }
 
     public void setComponents(RecentsComponent recentsComponent, Divider divider) {
-        mGestureHelper.setComponents(recentsComponent, divider);
+        mGestureHelper.setComponents(recentsComponent, divider, this);
     }
 
     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
new file mode 100644
index 0000000..405ef05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -0,0 +1,151 @@
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+
+import java.util.ArrayList;
+
+/**
+ * A controller for the space in the status bar to the left of the system icons. This area is
+ * normally reserved for notifications.
+ */
+public class NotificationIconAreaController {
+    private final NotificationColorUtil mNotificationColorUtil;
+
+    private int mIconSize;
+    private int mIconHPadding;
+    private int mIconTint = Color.WHITE;
+
+    private PhoneStatusBar mPhoneStatusBar;
+    protected View mNotificationIconArea;
+    private IconMerger mNotificationIcons;
+    private ImageView mMoreIcon;
+
+    public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
+        mPhoneStatusBar = phoneStatusBar;
+        mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+
+        initializeNotificationAreaViews(context);
+    }
+
+    /**
+     * Initializes the views that will represent the notification area.
+     */
+    protected void initializeNotificationAreaViews(Context context) {
+        Resources res = context.getResources();
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
+        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
+
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        mNotificationIconArea = layoutInflater.inflate(R.layout.notification_icon_area, null);
+
+        mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
+        mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+
+        mNotificationIcons =
+                (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
+        mNotificationIcons.setOverflowIndicator(mMoreIcon);
+    }
+
+    /**
+     * Returns the view that represents the notification area.
+     */
+    public View getNotificationInnerAreaView() {
+        return mNotificationIconArea;
+    }
+
+    /**
+     * Sets the color that should be used to tint any icons in the notification area. If this
+     * method is not called, the default tint is {@link Color#WHITE}.
+     */
+    public void setIconTint(int iconTint) {
+        mIconTint = iconTint;
+        mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+        applyNotificationIconsTint();
+    }
+
+    /**
+     * Updates the notifications with the given list of notifications to display.
+     */
+    public void updateNotificationIcons(NotificationData notificationData) {
+        final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                mIconSize + 2 * mIconHPadding, mPhoneStatusBar.getStatusBarHeight());
+
+        ArrayList<NotificationData.Entry> activeNotifications =
+                notificationData.getActiveNotifications();
+        final int size = activeNotifications.size();
+        ArrayList<StatusBarIconView> toShow = new ArrayList<>(size);
+
+        // Filter out ambient notifications and notification children.
+        for (int i = 0; i < size; i++) {
+            NotificationData.Entry ent = activeNotifications.get(i);
+            if (notificationData.isAmbient(ent.key)
+                    && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
+                continue;
+            }
+            if (!PhoneStatusBar.isTopLevelChild(ent)) {
+                continue;
+            }
+            toShow.add(ent.icon);
+        }
+
+        ArrayList<View> toRemove = new ArrayList<>();
+        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
+            View child = mNotificationIcons.getChildAt(i);
+            if (!toShow.contains(child)) {
+                toRemove.add(child);
+            }
+        }
+
+        final int toRemoveCount = toRemove.size();
+        for (int i = 0; i < toRemoveCount; i++) {
+            mNotificationIcons.removeView(toRemove.get(i));
+        }
+
+        for (int i = 0; i < toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mNotificationIcons.addView(v, i, params);
+            }
+        }
+
+        // Re-sort notification icons
+        final int childCount = mNotificationIcons.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View actual = mNotificationIcons.getChildAt(i);
+            StatusBarIconView expected = toShow.get(i);
+            if (actual == expected) {
+                continue;
+            }
+            mNotificationIcons.removeView(expected);
+            mNotificationIcons.addView(expected, i);
+        }
+
+        applyNotificationIconsTint();
+    }
+
+    /**
+     * Applies {@link #mIconTint} to the notification icons.
+     */
+    private void applyNotificationIconsTint() {
+        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
+            StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+            boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+            boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
+            if (colorize) {
+                v.setImageTintList(ColorStateList.valueOf(mIconTint));
+            }
+        }
+    }
+}
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 50e88d3..78497ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -82,6 +82,7 @@
 import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewStub;
 import android.view.WindowManager;
@@ -279,9 +280,9 @@
     VolumeComponent mVolumeComponent;
     KeyguardUserSwitcher mKeyguardUserSwitcher;
     FlashlightController mFlashlightController;
-    UserSwitcherController mUserSwitcherController;
+    protected UserSwitcherController mUserSwitcherController;
     NextAlarmController mNextAlarmController;
-    KeyguardMonitor mKeyguardMonitor;
+    protected KeyguardMonitor mKeyguardMonitor;
     BrightnessMirrorController mBrightnessMirrorController;
     AccessibilityController mAccessibilityController;
     FullscreenUserSwitcher mFullscreenUserSwitcher;
@@ -295,7 +296,7 @@
     StatusBarWindowView mStatusBarWindow;
     PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
-    private StatusBarWindowManager mStatusBarWindowManager;
+    protected StatusBarWindowManager mStatusBarWindowManager;
     private UnlockMethodCache mUnlockMethodCache;
     private DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
@@ -422,8 +423,8 @@
     private int mMaxKeyguardNotifications;
 
     private ViewMediatorCallback mKeyguardViewMediatorCallback;
-    private ScrimController mScrimController;
-    private DozeScrimController mDozeScrimController;
+    protected ScrimController mScrimController;
+    protected DozeScrimController mDozeScrimController;
 
     private final Runnable mAutohide = new Runnable() {
         @Override
@@ -437,7 +438,7 @@
     private boolean mWaitingForKeyguardExit;
     private boolean mDozing;
     private boolean mDozingRequested;
-    private boolean mScrimSrcModeEnabled;
+    protected boolean mScrimSrcModeEnabled;
 
     private Interpolator mLinearInterpolator = new LinearInterpolator();
     private Interpolator mBackdropInterpolator = new AccelerateDecelerateInterpolator();
@@ -866,7 +867,7 @@
         mKeyguardMonitor = new KeyguardMonitor(mContext);
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor,
-                    mHandler);
+                    mHandler, this);
             if (mUserSwitcherController.useFullscreenUserSwitcher()) {
                 mFullscreenUserSwitcher = new FullscreenUserSwitcher(this, mUserSwitcherController,
                         (ViewStub) mStatusBarWindow.findViewById(
@@ -1072,13 +1073,13 @@
         }
     }
 
-    private void startKeyguard() {
+    protected void startKeyguard() {
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mFingerprintUnlockController = new FingerprintUnlockController(mContext,
                 mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
                 mScrimController, this);
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
-                mStatusBarWindow, mStatusBarWindowManager, mScrimController,
+                getBouncerContainer(), mStatusBarWindowManager, mScrimController,
                 mFingerprintUnlockController);
         mKeyguardIndicationController.setStatusBarKeyguardViewManager(
                 mStatusBarKeyguardViewManager);
@@ -1096,6 +1097,10 @@
         return mStatusBarWindow;
     }
 
+    protected ViewGroup getBouncerContainer() {
+        return mStatusBarWindow;
+    }
+
     public int getStatusBarHeight() {
         if (mNaturalBarHeight < 0) {
             final Resources res = mContext.getResources();
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 5e98ec1..0eb0bf89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -36,14 +36,12 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -66,15 +64,15 @@
     private Interpolator mLinearOutSlowIn;
     private Interpolator mFastOutSlowIn;
     private DemoStatusIcons mDemoStatusIcons;
-    private NotificationColorUtil mNotificationColorUtil;
 
     private LinearLayout mSystemIconArea;
     private LinearLayout mStatusIcons;
     private SignalClusterView mSignalCluster;
     private LinearLayout mStatusIconsKeyguard;
-    private IconMerger mNotificationIcons;
-    private View mNotificationIconArea;
-    private ImageView mMoreIcon;
+
+    private NotificationIconAreaController mNotificationIconAreaController;
+    private View mNotificationIconAreaInner;
+
     private BatteryMeterView mBatteryMeterView;
     private TextView mClock;
 
@@ -110,14 +108,19 @@
             PhoneStatusBar phoneStatusBar) {
         mContext = context;
         mPhoneStatusBar = phoneStatusBar;
-        mNotificationColorUtil = NotificationColorUtil.getInstance(context);
         mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
         mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
         mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
-        mNotificationIconArea = statusBar.findViewById(R.id.notification_icon_area_inner);
-        mNotificationIcons = (IconMerger) statusBar.findViewById(R.id.notificationIcons);
-        mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon);
-        mNotificationIcons.setOverflowIndicator(mMoreIcon);
+
+        mNotificationIconAreaController =
+                new NotificationIconAreaController(context, phoneStatusBar);
+        mNotificationIconAreaInner =
+                mNotificationIconAreaController.getNotificationInnerAreaView();
+
+        ViewGroup notificationIconArea =
+                (ViewGroup) statusBar.findViewById(R.id.notification_icon_area);
+        notificationIconArea.addView(mNotificationIconAreaInner);
+
         mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
         mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
         mClock = (TextView) statusBar.findViewById(R.id.clock);
@@ -259,60 +262,7 @@
     }
 
     public void updateNotificationIcons(NotificationData notificationData) {
-        final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                mIconSize + 2*mIconHPadding, mPhoneStatusBar.getStatusBarHeight());
-
-        ArrayList<NotificationData.Entry> activeNotifications =
-                notificationData.getActiveNotifications();
-        final int N = activeNotifications.size();
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(N);
-
-        // Filter out ambient notifications and notification children.
-        for (int i = 0; i < N; i++) {
-            NotificationData.Entry ent = activeNotifications.get(i);
-            if (notificationData.isAmbient(ent.key)
-                    && !NotificationData.showNotificationEvenIfUnprovisioned(ent.notification)) {
-                continue;
-            }
-            if (!PhoneStatusBar.isTopLevelChild(ent)) {
-                continue;
-            }
-            toShow.add(ent.icon);
-        }
-
-        ArrayList<View> toRemove = new ArrayList<>();
-        for (int i=0; i<mNotificationIcons.getChildCount(); i++) {
-            View child = mNotificationIcons.getChildAt(i);
-            if (!toShow.contains(child)) {
-                toRemove.add(child);
-            }
-        }
-
-        final int toRemoveCount = toRemove.size();
-        for (int i = 0; i < toRemoveCount; i++) {
-            mNotificationIcons.removeView(toRemove.get(i));
-        }
-
-        for (int i=0; i<toShow.size(); i++) {
-            View v = toShow.get(i);
-            if (v.getParent() == null) {
-                mNotificationIcons.addView(v, i, params);
-            }
-        }
-
-        // Resort notification icons
-        final int childCount = mNotificationIcons.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View actual = mNotificationIcons.getChildAt(i);
-            StatusBarIconView expected = toShow.get(i);
-            if (actual == expected) {
-                continue;
-            }
-            mNotificationIcons.removeView(expected);
-            mNotificationIcons.addView(expected, i);
-        }
-
-        applyNotificationIconsTint();
+        mNotificationIconAreaController.updateNotificationIcons(notificationData);
     }
 
     public void hideSystemIconArea(boolean animate) {
@@ -324,11 +274,11 @@
     }
 
     public void hideNotificationIconArea(boolean animate) {
-        animateHide(mNotificationIconArea, animate);
+        animateHide(mNotificationIconAreaInner, animate);
     }
 
     public void showNotificationIconArea(boolean animate) {
-        animateShow(mNotificationIconArea, animate);
+        animateShow(mNotificationIconAreaInner, animate);
     }
 
     public void setClockVisibility(boolean visible) {
@@ -444,6 +394,7 @@
         mDarkIntensity = darkIntensity;
         mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
                 mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+        mNotificationIconAreaController.setIconTint(mIconTint);
         applyIconTint();
     }
 
@@ -461,21 +412,8 @@
             v.setImageTintList(ColorStateList.valueOf(mIconTint));
         }
         mSignalCluster.setIconTint(mIconTint, mDarkIntensity);
-        mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
         mBatteryMeterView.setDarkIntensity(mDarkIntensity);
         mClock.setTextColor(mIconTint);
-        applyNotificationIconsTint();
-    }
-
-    private void applyNotificationIconsTint() {
-        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
-            boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
-            boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
-            if (colorize) {
-                v.setImageTintList(ColorStateList.valueOf(mIconTint));
-            }
-        }
     }
 
     public void appTransitionPending() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f14f0d4..7a05b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -30,6 +30,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.RemoteInputController;
 
@@ -54,11 +55,11 @@
 
     private static String TAG = "StatusBarKeyguardViewManager";
 
-    private final Context mContext;
+    protected final Context mContext;
 
-    private LockPatternUtils mLockPatternUtils;
-    private ViewMediatorCallback mViewMediatorCallback;
-    private PhoneStatusBar mPhoneStatusBar;
+    protected LockPatternUtils mLockPatternUtils;
+    protected ViewMediatorCallback mViewMediatorCallback;
+    protected PhoneStatusBar mPhoneStatusBar;
     private ScrimController mScrimController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
@@ -67,17 +68,17 @@
 
     private boolean mDeviceInteractive = false;
     private boolean mScreenTurnedOn;
-    private KeyguardBouncer mBouncer;
-    private boolean mShowing;
-    private boolean mOccluded;
-    private boolean mRemoteInputActive;
+    protected KeyguardBouncer mBouncer;
+    protected boolean mShowing;
+    protected boolean mOccluded;
+    protected boolean mRemoteInputActive;
 
-    private boolean mFirstUpdate = true;
-    private boolean mLastShowing;
-    private boolean mLastOccluded;
+    protected boolean mFirstUpdate = true;
+    protected boolean mLastShowing;
+    protected boolean mLastOccluded;
     private boolean mLastBouncerShowing;
     private boolean mLastBouncerDismissible;
-    private boolean mLastRemoteInputActive;
+    protected boolean mLastRemoteInputActive;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private boolean mDeviceWillWakeUp;
@@ -99,8 +100,8 @@
         mStatusBarWindowManager = statusBarWindowManager;
         mScrimController = scrimController;
         mFingerprintUnlockController = fingerprintUnlockController;
-        mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils,
-                mStatusBarWindowManager, container);
+        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
+                mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container);
     }
 
     /**
@@ -118,7 +119,7 @@
      * Shows the notification keyguard or the bouncer depending on
      * {@link KeyguardBouncer#needsFullscreenBouncer()}.
      */
-    private void showBouncerOrKeyguard() {
+    protected void showBouncerOrKeyguard() {
         if (mBouncer.needsFullscreenBouncer()) {
 
             // The keyguard might be showing (already). So we need to hide it.
@@ -433,7 +434,7 @@
         }
     };
 
-    private void updateStates() {
+    protected void updateStates() {
         int vis = mContainer.getSystemUiVisibility();
         boolean showing = mShowing;
         boolean occluded = mOccluded;
@@ -451,9 +452,8 @@
             }
         }
 
-        boolean navBarVisible = (!(showing && !occluded) || bouncerShowing || remoteInputActive);
-        boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing
-                || mLastRemoteInputActive);
+        boolean navBarVisible = isNavBarVisible();
+        boolean lastNavBarVisible = getLastNavBarVisible();
         if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
             if (mPhoneStatusBar.getNavigationBarView() != null) {
                 if (navBarVisible) {
@@ -495,6 +495,20 @@
         mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
     }
 
+    /**
+     * @return Whether the navigation bar should be made visible based on the current state.
+     */
+    protected boolean isNavBarVisible() {
+        return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive;
+    }
+
+    /**
+     * @return Whether the navigation bar was made visible based on the last known state.
+     */
+    protected boolean getLastNavBarVisible() {
+        return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
+    }
+
     public boolean onMenuPressed() {
         return mBouncer.onMenuPressed();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
index 101a5f3..4f33d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
@@ -23,6 +23,9 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Shader;
@@ -41,6 +44,7 @@
     private float mFramePadding;
     private Bitmap mBitmap;
     private Drawable mDrawable;
+    private boolean mIsDisabled;
 
     private final Paint mFramePaint = new Paint();
     private final Paint mBitmapPaint = new Paint();
@@ -239,4 +243,28 @@
             mDrawable.setState(getDrawableState());
         }
     }
+
+    public void setDisabled(boolean disabled) {
+        if (mIsDisabled == disabled) {
+            return;
+        }
+        mIsDisabled = disabled;
+        int disabledColor = getContext().getColor(R.color.qs_tile_disabled_color);
+        PorterDuffColorFilter filter = new PorterDuffColorFilter(disabledColor,
+                PorterDuff.Mode.SRC_ATOP);
+        if (mBitmap != null) {
+            if (disabled) {
+                mBitmapPaint.setColorFilter(filter);
+            } else {
+                mBitmapPaint.setColorFilter(null);
+            }
+        } else if (mDrawable != null) {
+            if (disabled) {
+                mDrawable.setColorFilter(filter);
+            } else {
+                mDrawable.setColorFilter(null);
+            }
+        }
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index f3a3554..05d9626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -22,7 +22,9 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -33,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -48,11 +51,13 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.UserIcons;
+import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.BitmapHelper;
 import com.android.systemui.GuestResumeSessionReceiver;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.statusbar.phone.ActivityStarter;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.io.FileDescriptor;
@@ -61,6 +66,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
 /**
  * Keeps a list of all users on the device for user switching.
  */
@@ -88,6 +95,7 @@
             = new GuestResumeSessionReceiver();
     private final KeyguardMonitor mKeyguardMonitor;
     private final Handler mHandler;
+    private final ActivityStarter mActivityStarter;
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     private Dialog mExitGuestDialog;
@@ -99,11 +107,12 @@
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
 
     public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor,
-            Handler handler) {
+            Handler handler, ActivityStarter activityStarter) {
         mContext = context;
         mGuestResumeSessionReceiver.register(context);
         mKeyguardMonitor = keyguardMonitor;
         mHandler = handler;
+        mActivityStarter = activityStarter;
         mUserManager = UserManager.get(context);
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
@@ -206,25 +215,23 @@
                     }
                 }
 
-                boolean systemCanCreateUsers = !mUserManager.hasUserRestriction(
-                                UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
                 boolean currentUserCanCreateUsers = currentUserInfo != null
                         && (currentUserInfo.isAdmin()
-                                || currentUserInfo.id == UserHandle.USER_SYSTEM)
-                        && systemCanCreateUsers;
-                boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
-                boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
+                                || currentUserInfo.id == UserHandle.USER_SYSTEM);
+                boolean canCreateGuest = (currentUserCanCreateUsers || addUsersWhenLocked)
                         && guestRecord == null;
-                boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
+                boolean canCreateUser = (currentUserCanCreateUsers || addUsersWhenLocked)
                         && mUserManager.canAddMoreUsers();
                 boolean createIsRestricted = !addUsersWhenLocked;
 
                 if (!mSimpleUserSwitcher) {
                     if (guestRecord == null) {
                         if (canCreateGuest) {
-                            records.add(new UserRecord(null /* info */, null /* picture */,
+                            guestRecord = new UserRecord(null /* info */, null /* picture */,
                                     true /* isGuest */, false /* isCurrent */,
-                                    false /* isAddUser */, createIsRestricted));
+                                    false /* isAddUser */, createIsRestricted);
+                            checkIfAddUserDisallowed(guestRecord);
+                            records.add(guestRecord);
                         }
                     } else {
                         int index = guestRecord.isCurrent ? 0 : records.size();
@@ -233,9 +240,11 @@
                 }
 
                 if (!mSimpleUserSwitcher && canCreateUser) {
-                    records.add(new UserRecord(null /* info */, null /* picture */,
+                    UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
                             false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
-                            createIsRestricted));
+                            createIsRestricted);
+                    checkIfAddUserDisallowed(addUserRecord);
+                    records.add(addUserRecord);
                 }
 
                 return records;
@@ -289,6 +298,14 @@
         return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
     }
 
+    public void logoutCurrentUser() {
+        int currentUser = ActivityManager.getCurrentUser();
+        if (currentUser != UserHandle.USER_SYSTEM) {
+            switchToUserId(UserHandle.USER_SYSTEM);
+            stopUserId(currentUser);
+        }
+    }
+
     public void removeUserId(int userId) {
         if (userId == UserHandle.USER_SYSTEM) {
             Log.w(TAG, "User " + userId + " could not removed.");
@@ -331,6 +348,19 @@
         switchToUserId(id);
     }
 
+    public void switchTo(int userId) {
+        final int count = mUsers.size();
+        for (int i = 0; i < count; ++i) {
+            UserRecord record = mUsers.get(i);
+            if (record.info != null && record.info.id == userId) {
+                switchTo(record);
+                return;
+            }
+        }
+
+        Log.e(TAG, "Couldn't switch to user, id=" + userId);
+    }
+
     private void switchToUserId(int id) {
         try {
             pauseRefreshUsers();
@@ -395,11 +425,7 @@
                 }
                 return;
             } else if (ACTION_LOGOUT_USER.equals(intent.getAction())) {
-                int currentUser = ActivityManager.getCurrentUser();
-                if (currentUser != UserHandle.USER_SYSTEM) {
-                    switchToUserId(UserHandle.USER_SYSTEM);
-                    stopUserId(currentUser);
-                }
+                logoutCurrentUser();
             } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
                 if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
                     mExitGuestDialog.cancel();
@@ -594,6 +620,22 @@
         }
     }
 
+    private void checkIfAddUserDisallowed(UserRecord record) {
+        EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+                UserManager.DISALLOW_ADD_USER, UserHandle.myUserId());
+        if (admin != null) {
+            record.isDisabledByAdmin = true;
+            record.enforcedAdmin = admin;
+        } else {
+            record.isDisabledByAdmin = false;
+            record.enforcedAdmin = null;
+        }
+    }
+
+    public void startActivity(Intent intent) {
+        mActivityStarter.startActivity(intent, true);
+    }
+
     public static final class UserRecord {
         public final UserInfo info;
         public final Bitmap picture;
@@ -602,6 +644,8 @@
         public final boolean isAddUser;
         /** If true, the record is only visible to the owner and only when unlocked. */
         public final boolean isRestricted;
+        public boolean isDisabledByAdmin;
+        public EnforcedAdmin enforcedAdmin;
 
         public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
                 boolean isAddUser, boolean isRestricted) {
@@ -634,6 +678,10 @@
             if (isCurrent) sb.append(" <isCurrent>");
             if (picture != null) sb.append(" <hasPicture>");
             if (isRestricted) sb.append(" <isRestricted>");
+            if (isDisabledByAdmin) {
+                sb.append(" <isDisabledByAdmin>");
+                sb.append(" enforcedAdmin=" + enforcedAdmin);
+            }
             sb.append(')');
             return sb.toString();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
new file mode 100644
index 0000000..2aac69a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -0,0 +1,295 @@
+/*
+ * 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.tv.pip;
+
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states.
+ */
+public class PipManager {
+    private static final String TAG = "PipManager";
+    private static final boolean DEBUG = false;
+
+    private static PipManager sPipManager;
+
+    private static final int STATE_NO_PIP = 0;
+    private static final int STATE_PIP_OVERLAY = 1;
+    private static final int STATE_PIP_MENU = 2;
+
+    private Context mContext;
+    private IActivityManager mActivityManager;
+    private int mState = STATE_NO_PIP;
+    private final Handler mHandler = new Handler();
+    private List<Listener> mListeners = new ArrayList<>();
+    private Rect mPipBound;
+    private Rect mMenuModePipBound;
+    private boolean mInitialized;
+    private final Runnable mOnActivityPinnedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            StackInfo stackInfo = null;
+            try {
+                stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                if (stackInfo == null) {
+                    Log.w(TAG, "There is no pinned stack");
+                    return;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "getStackInfo failed", e);
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+            mState = STATE_PIP_OVERLAY;
+            launchPipOverlayActivity();
+        }
+    };
+    private final Runnable mOnTaskStackChanged = new Runnable() {
+        @Override
+        public void run() {
+            if (mState != STATE_NO_PIP) {
+                // TODO: check whether PIP task is closed.
+            }
+        }
+    };
+
+    private final BroadcastReceiver mPipButtonReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Log.d(TAG, "PIP button pressed");
+            if (!hasPipTasks()) {
+                startPip();
+            } else if (mState == STATE_PIP_OVERLAY) {
+                showPipMenu();
+            }
+        }
+    };
+
+    private PipManager() { }
+
+    /**
+     * Initializes {@link PipManager}.
+     */
+    public void initialize(Context context) {
+        if (mInitialized) {
+            return;
+        }
+        mInitialized = true;
+        mContext = context;
+        Resources res = context.getResources();
+        mPipBound = Rect.unflattenFromString(res.getString(
+                com.android.internal.R.string.config_defaultPictureInPictureBounds));
+        mMenuModePipBound = Rect.unflattenFromString(res.getString(
+                com.android.internal.R.string.config_centeredPictureInPictureBounds));
+
+        mActivityManager = ActivityManagerNative.getDefault();
+        TaskStackListener taskStackListener = new TaskStackListener();
+        IActivityManager iam = ActivityManagerNative.getDefault();
+        try {
+            iam.registerTaskStackListener(taskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "registerTaskStackListener failed", e);
+        }
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
+        mContext.registerReceiver(mPipButtonReceiver, intentFilter);
+    }
+
+    private void startPip() {
+        try {
+            mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBound);
+        } catch (RemoteException|IllegalArgumentException e) {
+            Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
+        }
+
+    }
+
+    /**
+     * Closes PIP (PIPped activity and PIP system UI).
+     */
+    public void closePip() {
+        mState = STATE_NO_PIP;
+        StackInfo stackInfo = null;
+        try {
+            stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            if (stackInfo == null) {
+                return;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getStackInfo failed", e);
+            return;
+        }
+        for (int taskId : stackInfo.taskIds) {
+            try {
+                mActivityManager.removeTask(taskId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "removeTask failed", e);
+            }
+        }
+    }
+
+    /**
+     * Moves the PIPped activity to the fullscreen and closes PIP system UI.
+     */
+    public void movePipToFullscreen() {
+        mState = STATE_NO_PIP;
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            mListeners.get(i).onMoveToFullscreen();
+        }
+        try {
+            mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "moveTasksToFullscreenStack failed", e);
+        }
+    }
+
+    /**
+     * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
+     * stack to the default PIP bound {@link com.android.internal.R.string
+     * .config_defaultPictureInPictureBounds}.
+     */
+    public void showPipOverlay() {
+        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();
+    }
+
+    /**
+     * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
+     * stack to the centered PIP bound {@link com.android.internal.R.string
+     * .config_centeredPictureInPictureBounds}.
+     */
+    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();
+        }
+        Intent intent = new Intent(mContext, PipMenuActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchStackId(PINNED_STACK_ID);
+        mContext.startActivity(intent, options.toBundle());
+    }
+
+    /**
+     * Adds {@link Listener}.
+     */
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes {@link Listener}.
+     */
+    public void removeListener(Listener listener) {
+        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);
+            return stackInfo != null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "getStackInfo failed", e);
+            return false;
+        }
+    }
+
+    private class TaskStackListener extends ITaskStackListener.Stub {
+        @Override
+        public void onTaskStackChanged() throws RemoteException {
+            // Post the message back to the UI thread.
+            mHandler.post(mOnTaskStackChanged);
+        }
+
+        @Override
+        public void onActivityPinned()  throws RemoteException {
+            // Post the message back to the UI thread.
+            mHandler.post(mOnActivityPinnedRunnable);
+        }
+    }
+
+    /**
+     * A listener interface to receive notification on changes in PIP.
+     */
+    public interface Listener {
+        /**
+         * Invoked when a PIPped activity is closed.
+         */
+        void onPipActivityClosed();
+        /**
+         * Invoked when the PIP menu gets shown.
+         */
+        void onShowPipMenu();
+        /**
+         * Invoked when the PIPped activity is returned back to the fullscreen.
+         */
+        void onMoveToFullscreen();
+    }
+
+    /**
+     * Gets an instance of {@link PipManager}.
+     */
+    public static PipManager getInstance() {
+        if (sPipManager == null) {
+            sPipManager = new PipManager();
+        }
+        return sPipManager;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
new file mode 100644
index 0000000..1248321
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.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 com.android.systemui.tv.pip;
+
+import android.app.Activity;
+import android.media.session.MediaController;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show the PIP menu to control PIP.
+ */
+public class PipMenuActivity extends Activity implements PipManager.Listener {
+    private static final String TAG = "PipMenuActivity";
+    private static final boolean DEBUG = false;
+
+    private final PipManager mPipManager = PipManager.getInstance();
+    private MediaController mMediaController;
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.tv_pip_menu);
+        mPipManager.addListener(this);
+        findViewById(R.id.full).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPipManager.movePipToFullscreen();
+            }
+        });
+        findViewById(R.id.exit).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPipManager.closePip();
+                finish();
+            }
+        });
+        findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPipManager.showPipOverlay();
+                finish();
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        mPipManager.removeListener(this);
+        super.onDestroy();
+    }
+
+    @Override
+    public void onBackPressed() {
+        mPipManager.showPipOverlay();
+        finish();
+    }
+
+    @Override
+    public void onPipActivityClosed() {
+        finish();
+    }
+
+    @Override
+    public void onShowPipMenu() { }
+
+    @Override
+    public void onMoveToFullscreen() {
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
new file mode 100644
index 0000000..de997a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tv.pip;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show an overlay on top of PIP activity to show how to pop up PIP menu.
+ */
+public class PipOverlayActivity extends Activity implements PipManager.Listener {
+    private static final String TAG = "PipOverlayActivity";
+    private static final boolean DEBUG = false;
+
+    private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+
+    private final PipManager mPipManager = PipManager.getInstance();
+    private final Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.tv_pip_overlay);
+        mPipManager.addListener(this);
+        final View overlayView = findViewById(R.id.guide_overlay);
+        // TODO: apply animation
+        overlayView.setVisibility(View.VISIBLE);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                overlayView.setVisibility(View.INVISIBLE);
+            }
+        }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mHandler.removeCallbacksAndMessages(null);
+        mPipManager.removeListener(this);
+    }
+
+    @Override
+    public void onPipActivityClosed() {
+        finish();
+    }
+
+    @Override
+    public void onShowPipMenu() {
+        finish();
+    }
+
+    @Override
+    public void onMoveToFullscreen() {
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
new file mode 100644
index 0000000..182b9b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tv.pip;
+
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+import com.android.systemui.SystemUI;
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+/**
+ * Controls the picture-in-picture window for TV devices.
+ */
+public class PipUI extends SystemUI {
+    private boolean mSupportPip;
+
+    @Override
+    public void start() {
+        PackageManager pm = mContext.getPackageManager();
+        mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)
+                && pm.hasSystemFeature(FEATURE_LEANBACK);
+        if (!mSupportPip) {
+            return;
+        }
+        PipManager pipManager = PipManager.getInstance();
+        pipManager.initialize(mContext);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (!mSupportPip) {
+            return;
+        }
+        // TODO: handle configuration change.
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 39d5952..28aeef7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -715,7 +715,7 @@
     }
 
     @Override
-    public IBinder getWindowToken(int windowId) {
+    public IBinder getWindowToken(int windowId, int userId) {
         mSecurityPolicy.enforceCallingPermission(
                 Manifest.permission.RETRIEVE_WINDOW_TOKEN,
                 GET_WINDOW_TOKEN);
@@ -724,8 +724,7 @@
             // share the accessibility state of the parent. The call below
             // performs the current profile parent resolution.
             final int resolvedUserId = mSecurityPolicy
-                    .resolveCallingUserIdEnforcingPermissionsLocked(
-                            UserHandle.getCallingUserId());
+                    .resolveCallingUserIdEnforcingPermissionsLocked(userId);
             if (resolvedUserId != mCurrentUserId) {
                 return null;
             }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 2264c69..aa15373 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -239,6 +239,11 @@
     // How long between attempts to perform a full-data backup of any given app
     static final long MIN_FULL_BACKUP_INTERVAL = 1000 * 60 * 60 * 24; // one day
 
+    // If an app is busy when we want to do a full-data backup, how long to defer the retry.
+    // This is fuzzed, so there are two parameters; backoff_min + Rand[0, backoff_fuzz)
+    static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60;  // one hour
+    static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2;  // two hours
+
     Context mContext;
     private PackageManager mPackageManager;
     IPackageManager mPackageManagerBinder;
@@ -4496,35 +4501,72 @@
                 return false;
             }
 
-            // At this point we know that we have work to do, just not right now.  Any
-            // exit without actually running backups will also require that we
+            // At this point we know that we have work to do, but possibly not right now.
+            // Any exit without actually running backups will also require that we
             // reschedule the job.
             boolean runBackup = true;
+            boolean headBusy;
 
-            if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
-                if (MORE_DEBUG) {
-                    Slog.i(TAG, "Preconditions not met; not running full backup");
-                }
-                runBackup = false;
-                // Typically this means we haven't run a key/value backup yet.  Back off
-                // full-backup operations by the key/value job's run interval so that
-                // next time we run, we are likely to be able to make progress.
-                latency = KeyValueBackupJob.BATCH_INTERVAL;
-            }
+            do {
+                headBusy = false;
 
-            if (runBackup) {
-                entry = mFullBackupQueue.get(0);
-                long timeSinceRun = now - entry.lastBackup;
-                runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
-                if (!runBackup) {
-                    // It's too early to back up the next thing in the queue, so bow out
+                if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
                     if (MORE_DEBUG) {
-                        Slog.i(TAG, "Device ready but too early to back up next app");
+                        Slog.i(TAG, "Preconditions not met; not running full backup");
                     }
-                    // Wait until the next app in the queue falls due for a full data backup
-                    latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+                    runBackup = false;
+                    // Typically this means we haven't run a key/value backup yet.  Back off
+                    // full-backup operations by the key/value job's run interval so that
+                    // next time we run, we are likely to be able to make progress.
+                    latency = KeyValueBackupJob.BATCH_INTERVAL;
                 }
-            }
+
+                if (runBackup) {
+                    entry = mFullBackupQueue.get(0);
+                    long timeSinceRun = now - entry.lastBackup;
+                    runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+                    if (!runBackup) {
+                        // It's too early to back up the next thing in the queue, so bow out
+                        if (MORE_DEBUG) {
+                            Slog.i(TAG, "Device ready but too early to back up next app");
+                        }
+                        // Wait until the next app in the queue falls due for a full data backup
+                        latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+                        break;  // we know we aren't doing work yet, so bail.
+                    }
+
+                    try {
+                        PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+                        headBusy = mActivityManager.isAppForeground(appInfo.applicationInfo.uid);
+
+                        if (headBusy) {
+                            final long nextEligible = System.currentTimeMillis()
+                                    + BUSY_BACKOFF_MIN_MILLIS
+                                    + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ);
+                            if (DEBUG_SCHEDULING) {
+                                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                                Slog.i(TAG, "Full backup time but " + entry.packageName
+                                        + " is busy; deferring to "
+                                        + sdf.format(new Date(nextEligible)));
+                            }
+                            // This relocates the app's entry from the head of the queue to
+                            // its order-appropriate position further down, so upon looping
+                            // a new candidate will be considered at the head.
+                            enqueueFullBackup(entry.packageName,
+                                    nextEligible - MIN_FULL_BACKUP_INTERVAL);
+                        }
+
+                    } catch (NameNotFoundException nnf) {
+                        // So, we think we want to back this up, but it turns out the package
+                        // in question is no longer installed.  We want to drop it from the
+                        // queue entirely and move on, but if there's nothing else in the queue
+                        // we should bail entirely.  headBusy cannot have been set to true yet.
+                        runBackup = (mFullBackupQueue.size() > 1);
+                    } catch (RemoteException e) {
+                        // Cannot happen; the Activity Manager is in the same process
+                    }
+                }
+            } while (headBusy);
 
             if (!runBackup) {
                 if (DEBUG_SCHEDULING) {
@@ -4539,7 +4581,7 @@
                 return false;
             }
 
-            // Okay, the top thing is runnable now.  Pop it off and get going.
+            // Okay, the top thing is ready for backup now.  Do it.
             mFullBackupQueue.remove(0);
             CountDownLatch latch = new CountDownLatch(1);
             String[] pkg = new String[] {entry.packageName};
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 37a6c02..2de5324 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1568,12 +1568,11 @@
         // load the global proxy at startup
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
 
-        // Try bringing up tracker, but if KeyStore isn't ready yet, wait
-        // for user to unlock device.
-        if (!updateLockdownVpn()) {
-            final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
-            mContext.registerReceiver(mUserPresentReceiver, filter);
-        }
+        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
+        // for user to unlock device too.
+        updateLockdownVpn();
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+        mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.ALL, filter, null, null);
 
         // Configure whether mobile data is always on.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
@@ -1586,10 +1585,16 @@
     private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            // User that sent this intent = user that was just unlocked
+            final int unlockedUser = getSendingUserId();
+
             // Try creating lockdown tracker, since user present usually means
             // unlocked keystore.
-            if (updateLockdownVpn()) {
-                mContext.unregisterReceiver(this);
+            if (mUserManager.getUserInfo(unlockedUser).isPrimary() &&
+                    LockdownVpnTracker.isEnabled()) {
+                updateLockdownVpn();
+            } else {
+                updateAlwaysOnVpn(unlockedUser);
             }
         }
     };
@@ -3258,6 +3263,76 @@
         }
     }
 
+    /**
+     * Sets up or tears down the always-on VPN for user {@param user} as appropriate.
+     *
+     * @return {@code false} in case of errors; {@code true} otherwise.
+     */
+    private boolean updateAlwaysOnVpn(int user) {
+        final String lockdownPackage = getAlwaysOnVpnPackage(user);
+        if (lockdownPackage == null) {
+            return true;
+        }
+
+        // Create an intent to start the VPN service declared in the app's manifest.
+        Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
+        serviceIntent.setPackage(lockdownPackage);
+
+        try {
+            return mContext.startServiceAsUser(serviceIntent, UserHandle.of(user)) != null;
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean setAlwaysOnVpnPackage(int userId, String packageName) {
+        enforceConnectivityInternalPermission();
+        enforceCrossUserPermission(userId);
+
+        // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+        if (LockdownVpnTracker.isEnabled()) {
+            return false;
+        }
+
+        // If the current VPN package is the same as the new one, this is a no-op
+        final String oldPackage = getAlwaysOnVpnPackage(userId);
+        if (TextUtils.equals(oldPackage, packageName)) {
+            return true;
+        }
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            if (!vpn.setAlwaysOnPackage(packageName)) {
+                return false;
+            }
+            if (!updateAlwaysOnVpn(userId)) {
+                vpn.setAlwaysOnPackage(null);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String getAlwaysOnVpnPackage(int userId) {
+        enforceConnectivityInternalPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getAlwaysOnPackage();
+        }
+    }
+
     @Override
     public int checkMobileProvisioning(int suggestedTimeOutMs) {
         // TODO: Remove?  Any reason to trigger a provisioning check?
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index bd95892..58a0356 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -50,11 +50,13 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IMaintenanceActivityListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -206,9 +208,13 @@
     private boolean mSyncActive;
     private boolean mJobsActive;
     private boolean mAlarmsActive;
+    private boolean mReportedMaintenanceActivity;
 
     public final AtomicFile mConfigFile;
 
+    private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
+            new RemoteCallbackList<IMaintenanceActivityListener>();
+
     /**
      * Package names the system has white-listed to opt out of power save restrictions,
      * except for device idle mode.
@@ -813,6 +819,7 @@
     static final int MSG_REPORT_IDLE_OFF = 4;
     static final int MSG_REPORT_ACTIVE = 5;
     static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
+    static final int MSG_REPORT_MAINTENANCE_ACTIVITY = 7;
 
     final class MyHandler extends Handler {
         MyHandler(Looper looper) {
@@ -902,6 +909,21 @@
                     int uid = msg.arg1;
                     checkTempAppWhitelistTimeout(uid);
                 } break;
+                case MSG_REPORT_MAINTENANCE_ACTIVITY: {
+                    boolean active = (msg.arg1 == 1);
+                    final int size = mMaintenanceActivityListeners.beginBroadcast();
+                    try {
+                        for (int i = 0; i < size; i++) {
+                            try {
+                                mMaintenanceActivityListeners.getBroadcastItem(i)
+                                        .onMaintenanceActivityChanged(active);
+                            } catch (RemoteException ignored) {
+                            }
+                        }
+                    } finally {
+                        mMaintenanceActivityListeners.finishBroadcast();
+                    }
+                } break;
             }
         }
     }
@@ -996,6 +1018,16 @@
             DeviceIdleController.this.downloadServiceInactive();
         }
 
+        @Override public boolean registerMaintenanceActivityListener(
+                IMaintenanceActivityListener listener) {
+            return DeviceIdleController.this.registerMaintenanceActivityListener(listener);
+        }
+
+        @Override public void unregisterMaintenanceActivityListener(
+                IMaintenanceActivityListener listener) {
+            DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
+        }
+
         @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DeviceIdleController.this.dump(fd, pw, args);
         }
@@ -1704,6 +1736,7 @@
     void downloadServiceActive(IBinder token) {
         synchronized (this) {
             mDownloadServiceActive = token;
+            reportMaintenanceActivityIfNeededLocked();
             try {
                 token.linkToDeath(new IBinder.DeathRecipient() {
                     @Override public void binderDied() {
@@ -1719,6 +1752,7 @@
     void downloadServiceInactive() {
         synchronized (this) {
             mDownloadServiceActive = null;
+            reportMaintenanceActivityIfNeededLocked();
             exitMaintenanceEarlyIfNeededLocked();
         }
     }
@@ -1726,6 +1760,7 @@
     void setSyncActive(boolean active) {
         synchronized (this) {
             mSyncActive = active;
+            reportMaintenanceActivityIfNeededLocked();
             if (!active) {
                 exitMaintenanceEarlyIfNeededLocked();
             }
@@ -1735,6 +1770,7 @@
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
+            reportMaintenanceActivityIfNeededLocked();
             if (!active) {
                 exitMaintenanceEarlyIfNeededLocked();
             }
@@ -1750,6 +1786,30 @@
         }
     }
 
+    boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener) {
+        synchronized (this) {
+            mMaintenanceActivityListeners.register(listener);
+            return mReportedMaintenanceActivity;
+        }
+    }
+
+    void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener) {
+        synchronized (this) {
+            mMaintenanceActivityListeners.unregister(listener);
+        }
+    }
+
+    void reportMaintenanceActivityIfNeededLocked() {
+        boolean active = mJobsActive | mSyncActive | (mDownloadServiceActive != null);
+        if (active == mReportedMaintenanceActivity) {
+            return;
+        }
+        mReportedMaintenanceActivity = active;
+        Message msg = mHandler.obtainMessage(MSG_REPORT_MAINTENANCE_ACTIVITY,
+                mReportedMaintenanceActivity ? 1 : 0, 0);
+        mHandler.sendMessage(msg);
+    }
+
     void exitMaintenanceEarlyIfNeededLocked() {
         if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
             if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f345d7e..0f12818 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2819,6 +2819,25 @@
         }
     }
 
+    @Override
+    public boolean someUserHasAccount(@NonNull final Account account) {
+        if (!UserHandle.isSameApp(Process.SYSTEM_UID, Binder.getCallingUid())) {
+            throw new SecurityException("Only system can check for accounts across users");
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            AccountAndUser[] allAccounts = getAllAccounts();
+            for (int i = allAccounts.length - 1; i >= 0; i--) {
+                if (allAccounts[i].account.equals(account)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private class GetAccountsByTypeAndFeatureSession extends Session {
         private final String[] mFeatures;
         private volatile Account[] mAccountsOfType = null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 093a33d..1d9bd91 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18,7 +18,6 @@
 
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
-
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.AssistUtils;
@@ -202,6 +201,7 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 import android.view.Display;
@@ -240,6 +240,7 @@
 import java.util.concurrent.atomic.AtomicLong;
 
 import dalvik.system.VMRuntime;
+
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -565,7 +566,7 @@
     /**
      * List of intents that were used to start the most recent tasks.
      */
-    private final RecentTasks mRecentTasks;
+    final RecentTasks mRecentTasks;
 
     /**
      * For addAppTask: cached of the last activity component that was added.
@@ -1053,11 +1054,6 @@
     final AppOpsService mAppOpsService;
 
     /**
-     * Save recent tasks information across reboots.
-     */
-    final TaskPersister mTaskPersister;
-
-    /**
      * Current configuration information.  HistoryRecord objects are given
      * a reference to this object to indicate which configuration they are
      * currently running in, so this object must be kept immutable.
@@ -2509,10 +2505,10 @@
 
         mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
-        mRecentTasks = new RecentTasks(this);
-        mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks);
+        mStackSupervisor = new ActivityStackSupervisor(this);
         mActivityStarter = new ActivityStarter(this, mStackSupervisor);
-        mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks);
+        mRecentTasks = new RecentTasks(this, mStackSupervisor);
+
 
         mProcessCpuThread = new Thread("CpuTracker") {
             @Override
@@ -2566,6 +2562,10 @@
         LocalServices.addService(ActivityManagerInternal.class, new LocalService());
     }
 
+    void onUserStoppedLocked(int userId) {
+        mRecentTasks.unloadUserRecentsLocked(userId);
+    }
+
     public void initPowerManagement() {
         mStackSupervisor.initPowerManagement();
         mBatteryStatsService.initPowerManagement();
@@ -7256,6 +7256,17 @@
     }
 
     @Override
+    public boolean isAppForeground(int uid) throws RemoteException {
+        synchronized (this) {
+            UidRecord uidRec = mActiveUids.get(uid);
+            if (uidRec == null || uidRec.idle) {
+                return false;
+            }
+            return uidRec.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+        }
+    }
+
+    @Override
     public boolean inMultiWindowMode(IBinder token) {
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -8851,6 +8862,8 @@
                     android.Manifest.permission.GET_DETAILED_TASKS)
                     == PackageManager.PERMISSION_GRANTED;
 
+            mRecentTasks.loadUserRecentsLocked(userId);
+
             final int recentsCount = mRecentTasks.size();
             ArrayList<ActivityManager.RecentTaskInfo> res =
                     new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
@@ -9013,8 +9026,9 @@
                 thumbnailInfo.taskHeight = displaySize.y;
                 thumbnailInfo.screenOrientation = mConfiguration.orientation;
 
-                TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo,
-                        intent, description, thumbnailInfo);
+                TaskRecord task = new TaskRecord(this,
+                        mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
+                        ainfo, intent, description, thumbnailInfo);
 
                 int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
                 if (trimIdx >= 0) {
@@ -9173,9 +9187,10 @@
                 passedIconFile.getName());
         if (!legitIconFile.getPath().equals(filePath)
                 || !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
-            throw new IllegalArgumentException("Bad file path: " + filePath);
+            throw new IllegalArgumentException("Bad file path: " + filePath
+                    + " passed for userId " + userId);
         }
-        return mTaskPersister.getTaskDescriptionIcon(filePath);
+        return mRecentTasks.getTaskDescriptionIcon(filePath);
     }
 
     @Override
@@ -11145,11 +11160,7 @@
 
     /** Pokes the task persister. */
     void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
-        if (task != null && task.stack != null && task.stack.isHomeStack()) {
-            // Never persist the home stack.
-            return;
-        }
-        mTaskPersister.wakeup(task, flush);
+        mRecentTasks.notifyTaskPersisterLocked(task, flush);
     }
 
     /** Notifies all listeners when the task stack has changed. */
@@ -12562,11 +12573,7 @@
             // security checks.
             mUserController.updateCurrentProfileIdsLocked();
 
-            mRecentTasks.clear();
-            mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds()));
-            mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
-            mTaskPersister.startPersisting();
-
+            mRecentTasks.onSystemReady();
             // Check to see if there are any update receivers to run.
             if (!mDidUpdate) {
                 if (mWaitingUpdate) {
@@ -12673,7 +12680,6 @@
 
         if (goingCallback != null) goingCallback.run();
 
-
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
                 Integer.toString(currentUserId), currentUserId);
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
@@ -18155,7 +18161,7 @@
     }
 
     @Override
-    public void moveTasksToFullscreenStack(int fromStackId) {
+    public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
         if (fromStackId == HOME_STACK_ID) {
             throw new IllegalArgumentException("You can't move tasks from the home stack.");
@@ -18165,9 +18171,18 @@
             final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
             if (stack != null) {
                 final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-                for (int i = tasks.size() - 1; i >= 0; i--) {
-                    mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
-                            FULLSCREEN_WORKSPACE_STACK_ID, 0);
+                final int size = tasks.size();
+                if (onTop) {
+                    for (int i = 0; i < size; i++) {
+                        mStackSupervisor.moveTaskToStackLocked(tasks.get(i).taskId,
+                                FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, !FORCE_FOCUS,
+                                "moveTasksToFullscreenStack", ANIMATE);
+                    }
+                } else {
+                    for (int i = size - 1; i >= 0; i--) {
+                        mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
+                                FULLSCREEN_WORKSPACE_STACK_ID, 0);
+                    }
                 }
             }
             Binder.restoreCallingIdentity(origId);
@@ -20818,10 +20833,6 @@
         return mUserController.stopUser(userId, force, callback);
     }
 
-    void onUserRemovedLocked(int userId) {
-        mRecentTasks.removeTasksForUserLocked(userId);
-    }
-
     @Override
     public UserInfo getCurrentUser() {
         return mUserController.getCurrentUser();
@@ -20876,6 +20887,10 @@
         return newInfo;
     }
 
+    public boolean isUserStopped(int userId) {
+        return mUserController.getStartedUserStateLocked(userId) == null;
+    }
+
     ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
         if (aInfo == null
                 || (userId < 1 && aInfo.applicationInfo.uid < UserHandle.PER_USER_RANGE)) {
@@ -21028,7 +21043,7 @@
         @Override
         public void onUserRemoved(int userId) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.onUserRemovedLocked(userId);
+                ActivityManagerService.this.onUserStoppedLocked(userId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 13c1417..8c99739 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -21,6 +21,8 @@
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.PrintWriter;
 
 class ActivityManagerShellCommand extends ShellCommand {
@@ -58,6 +60,8 @@
                     return runTrackAssociations(pw);
                 case "untrack-associations":
                     return runUntrackAssociations(pw);
+                case "is-user-stopped":
+                    return runIsUserStopped(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -67,6 +71,13 @@
         return -1;
     }
 
+    int runIsUserStopped(PrintWriter pw) {
+        int userId = UserHandle.parseUserArg(getNextArgRequired());
+        boolean stopped = mInternal.isUserStopped(userId);
+        pw.println(stopped);
+        return 0;
+    }
+
     int runForceStop(PrintWriter pw) throws RemoteException {
         int userId = UserHandle.USER_ALL;
 
@@ -107,7 +118,7 @@
     int runWrite(PrintWriter pw) {
         mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
-        mInternal.mTaskPersister.flush();
+        mInternal.mRecentTasks.flush();
         pw.println("All tasks persisted.");
         return 0;
     }
@@ -190,6 +201,8 @@
             pw.println("    Enable association tracking.");
             pw.println("  untrack-associations");
             pw.println("    Disable and clear association tracking.");
+            pw.println("  is-user-stopped <USER_ID>");
+            pw.println("    returns whether <USER_ID> has been stopped or not");
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 4101dde..ffb2fc4 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -5,6 +5,7 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 
 import android.app.ActivityManager.StackId;
 import android.content.Context;
@@ -51,7 +52,7 @@
         mLastLogTimeSecs = now;
 
         ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID);
-        if (stack != null && stack.isStackVisibleLocked()) {
+        if (stack != null && stack.getStackVisibilityLocked() != STACK_INVISIBLE) {
             mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
             return;
         }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index eb0945bf..b16bd2b 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1268,7 +1268,7 @@
             final String iconFilename = createImageFilename(createTime, task.taskId);
             final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
             final String iconFilePath = iconFile.getAbsolutePath();
-            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath);
+            service.mRecentTasks.saveImage(icon, iconFilePath);
             _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8e68aec..e3f4999a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -166,6 +166,14 @@
         DESTROYED
     }
 
+    // Stack is not considered visible.
+    static final int STACK_INVISIBLE = 0;
+    // Stack is considered visible
+    static final int STACK_VISIBLE = 1;
+    // Stack is considered visible, but only becuase it has activity that is visible behind other
+    // activities and there is a specific combination of stacks.
+    static final int STACK_VISIBLE_ACTIVITY_BEHIND = 2;
+
     final ActivityManagerService mService;
     final WindowManagerService mWindowManager;
     private final RecentTasks mRecentTasks;
@@ -1321,7 +1329,8 @@
         if (stacks != null) {
             for (int i = stacks.size() - 1; i >= 0; --i) {
                 ActivityStack stack = stacks.get(i);
-                if (stack != this && stack.isFocusable() && stack.isStackVisibleLocked()) {
+                if (stack != this && stack.isFocusable()
+                        && stack.getStackVisibilityLocked() != STACK_INVISIBLE) {
                     return stack;
                 }
             }
@@ -1363,14 +1372,17 @@
         return true;
     }
 
-    /** Returns true if the stack is considered visible. */
-    boolean isStackVisibleLocked() {
+    /**
+     * Returns stack's visibility: {@link #STACK_INVISIBLE}, {@link #STACK_VISIBLE} or
+     * {@link #STACK_VISIBLE_ACTIVITY_BEHIND}.
+     */
+    int getStackVisibilityLocked() {
         if (!isAttached()) {
-            return false;
+            return STACK_INVISIBLE;
         }
 
         if (mStackSupervisor.isFrontStack(this) || mStackSupervisor.isFocusedStack(this)) {
-            return true;
+            return STACK_VISIBLE;
         }
 
         final int stackIndex = mStacks.indexOf(this);
@@ -1378,12 +1390,12 @@
         if (stackIndex == mStacks.size() - 1) {
             Slog.wtf(TAG,
                     "Stack=" + this + " isn't front stack but is at the top of the stack list");
-            return false;
+            return STACK_INVISIBLE;
         }
 
         final boolean isLockscreenShown = mService.mLockScreenShown == LOCK_SCREEN_SHOWN;
         if (isLockscreenShown && !StackId.isAllowedOverLockscreen(mStackId)) {
-            return false;
+            return STACK_INVISIBLE;
         }
 
         final ActivityStack focusedStack = mStackSupervisor.getFocusedStack();
@@ -1394,17 +1406,18 @@
                 && !focusedStack.topActivity().fullscreen) {
             // The fullscreen stack should be visible if it has a visible behind activity behind
             // the home stack that is translucent.
-            return true;
+            return STACK_VISIBLE_ACTIVITY_BEHIND;
         }
 
         if (mStackId == DOCKED_STACK_ID) {
             // Docked stack is always visible, except in the case where the home activity
             // is the top running activity in the focused home stack.
             if (focusedStackId != HOME_STACK_ID) {
-                return true;
+                return STACK_VISIBLE;
             }
             ActivityRecord topHomeActivity = focusedStack.topRunningActivityLocked();
-            return topHomeActivity == null || !topHomeActivity.isHomeActivity();
+            return topHomeActivity == null || !topHomeActivity.isHomeActivity() ?
+                    STACK_VISIBLE : STACK_INVISIBLE;
         }
 
         // Find the first stack below focused stack that actually got something visible.
@@ -1416,7 +1429,7 @@
         if ((focusedStackId == DOCKED_STACK_ID || focusedStackId == PINNED_STACK_ID)
                 && stackIndex == belowFocusedIndex) {
             // Stacks directly behind the docked or pinned stack are always visible.
-            return true;
+            return STACK_VISIBLE;
         }
 
         if (focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
@@ -1425,7 +1438,7 @@
             // visible so they can act as a backdrop to the translucent activity.
             // For example, dialog activities
             if (stackIndex == belowFocusedIndex) {
-                return true;
+                return STACK_VISIBLE;
             }
             if (belowFocusedIndex >= 0) {
                 final ActivityStack stack = mStacks.get(belowFocusedIndex);
@@ -1433,14 +1446,14 @@
                         && stackIndex == (belowFocusedIndex - 1)) {
                     // The stack behind the docked or pinned stack is also visible so we can have a
                     // complete backdrop to the translucent activity when the docked stack is up.
-                    return true;
+                    return STACK_VISIBLE;
                 }
             }
         }
 
         if (StackId.isStaticStack(mStackId)) {
             // Visibility of any static stack should have been determined by the conditions above.
-            return false;
+            return STACK_INVISIBLE;
         }
 
         for (int i = stackIndex + 1; i < mStacks.size(); i++) {
@@ -1452,15 +1465,15 @@
 
             if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
                 // These stacks can't have any dynamic stacks visible behind them.
-                return false;
+                return STACK_INVISIBLE;
             }
 
             if (!hasTranslucentActivity(stack)) {
-                return false;
+                return STACK_INVISIBLE;
             }
         }
 
-        return true;
+        return STACK_VISIBLE;
     }
 
     final int rankTaskLayers(int baseLayer) {
@@ -1493,14 +1506,16 @@
         // If the top activity is not fullscreen, then we need to
         // make sure any activities under it are now visible.
         boolean aboveTop = top != null;
-        final boolean stackInvisible = !isStackVisibleLocked();
+        final int stackVisibility = getStackVisibilityLocked();
+        final boolean stackInvisible = stackVisibility != STACK_VISIBLE;
+        final boolean stackVisibleBehind = stackVisibility == STACK_VISIBLE_ACTIVITY_BEHIND;
         boolean behindFullscreenActivity = stackInvisible;
         boolean resumeNextActivity = isFocusable() && (isInStackLocked(starting) == null);
-
+        boolean behindTranslucentActivity = false;
+        final ActivityRecord visibleBehind = getVisibleBehindActivity();
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             final ArrayList<ActivityRecord> activities = task.mActivities;
-
             for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                 final ActivityRecord r = activities.get(activityNdx);
                 if (r.finishing) {
@@ -1513,10 +1528,13 @@
                 aboveTop = false;
                 // mLaunchingBehind: Activities launching behind are at the back of the task stack
                 // but must be drawn initially for the animation as though they were visible.
-                if ((!behindFullscreenActivity || r.mLaunchTaskBehind) && okToShowLocked(r)) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                            "Make visible? " + r + " finishing=" + r.finishing
-                            + " state=" + r.state);
+                final boolean activityVisibleBehind =
+                        (behindTranslucentActivity || stackVisibleBehind) && visibleBehind == r;
+                final boolean isVisible = (!behindFullscreenActivity || r.mLaunchTaskBehind
+                        || activityVisibleBehind) && okToShowLocked(r);
+                if (isVisible) {
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
+                            + " finishing=" + r.finishing + " state=" + r.state);
                     // First: if this is not the current activity being started, make
                     // sure it matches the current configuration.
                     if (r != starting) {
@@ -1545,15 +1563,23 @@
                     configChanges |= r.configChangeFlags;
                     behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
                             behindFullscreenActivity, task, r);
+                    if (behindFullscreenActivity && !r.fullscreen) {
+                        behindTranslucentActivity = true;
+                    }
                 } else {
-                    makeInvisible(stackInvisible, behindFullscreenActivity, r);
+                    if (DEBUG_VISIBILITY || true) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+                            + " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
+                            + stackInvisible + " behindFullscreenActivity="
+                            + behindFullscreenActivity + " mLaunchTaskBehind="
+                            + r.mLaunchTaskBehind);
+                    makeInvisible(r, visibleBehind);
                 }
             }
             if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
                 // The visibility of tasks and the activities they contain in freeform stack are
                 // determined individually unlike other stacks where the visibility or fullscreen
                 // status of an activity in a previous task affects other.
-                behindFullscreenActivity = stackInvisible;
+                behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
             }
         }
 
@@ -1601,11 +1627,7 @@
         return false;
     }
 
-    private void makeInvisible(boolean stackInvisible, boolean behindFullscreenActivity,
-            ActivityRecord r) {
-        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r + " finishing="
-                + r.finishing + " state=" + r.state + " stackInvisible=" + stackInvisible
-                + " behindFullscreenActivity=" + behindFullscreenActivity);
+    private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) {
         if (!r.visible) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
             return;
@@ -1631,7 +1653,7 @@
                 case PAUSED:
                     // This case created for transitioning activities from
                     // translucent to opaque {@link Activity#convertToOpaque}.
-                    if (getVisibleBehindActivity() == r) {
+                    if (visibleBehind == r) {
                         releaseBackgroundResources(r);
                     } else {
                         if (!mStackSupervisor.mStoppingActivities.contains(r)) {
@@ -1653,16 +1675,16 @@
     private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
             TaskRecord task, ActivityRecord r) {
         if (r.fullscreen) {
-            // At this point, nothing else needs to be shown in this task.
-            behindFullscreenActivity = true;
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                     + " stackInvisible=" + stackInvisible
                     + " behindFullscreenActivity=" + behindFullscreenActivity);
-        } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+            // At this point, nothing else needs to be shown in this task.
             behindFullscreenActivity = true;
+        } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
                     + " stackInvisible=" + stackInvisible
                     + " behindFullscreenActivity=" + behindFullscreenActivity);
+            behindFullscreenActivity = true;
         }
         return behindFullscreenActivity;
     }
@@ -2595,8 +2617,9 @@
                     if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
                             + " out to bottom task " + bottom.task);
                 } else {
-                    targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
-                            null, null, null, false);
+                    targetTask = createTaskRecord(
+                            mStackSupervisor.getNextTaskIdForUserLocked(target.userId),
+                            target.info, null, null, null, false);
                     targetTask.affinityIntent = target.intent;
                     if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
                             + " out to new task " + target.task);
@@ -3718,7 +3741,7 @@
     void releaseBackgroundResources(ActivityRecord r) {
         if (hasVisibleBehindActivity() &&
                 !mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) {
-            if (r == topRunningActivityLocked() && isStackVisibleLocked()) {
+            if (r == topRunningActivityLocked() && getStackVisibilityLocked() == STACK_VISIBLE) {
                 // Don't release the top activity if it has requested to run behind the next
                 // activity and the stack is currently visible.
                 return;
@@ -4802,7 +4825,8 @@
         final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
 
         final TaskRecord task = createTaskRecord(
-                mStackSupervisor.getNextTaskId(), r.info, r.intent, null, null, true);
+                mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
+                r.info, r.intent, null, null, true);
         r.setTask(task, null);
         task.addActivityToTop(r);
         setAppTask(r, task);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e837d9a..09bb9ab 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -262,10 +262,12 @@
     // For debugging to make sure the caller when acquiring/releasing our
     // wake lock is the system process.
     static final boolean VALIDATE_WAKE_LOCK_CALLER = false;
+    /** The number of distinct task ids that can be assigned to the tasks of a single user */
+    private static final int MAX_TASK_IDS_PER_USER = UserHandle.PER_USER_RANGE;
 
     final ActivityManagerService mService;
 
-    private final RecentTasks mRecentTasks;
+    private RecentTasks mRecentTasks;
 
     final ActivityStackSupervisorHandler mHandler;
 
@@ -276,9 +278,11 @@
     /** Counter for next free stack ID to use for dynamic activity stacks. */
     private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID;
 
-    /** Task identifier that activities are currently being started in.  Incremented each time a
-     * new task is created. */
-    private int mCurTaskId = 0;
+    /**
+     * Maps the task identifier that activities are currently being started in to the userId of the
+     * task. Each time a new task is created, the entry for the userId of the task is incremented
+     */
+    private final SparseIntArray mCurTaskIdForUser = new SparseIntArray(20);
 
     /** The current user */
     int mCurrentUser;
@@ -436,14 +440,17 @@
         }
     }
 
-    public ActivityStackSupervisor(ActivityManagerService service, RecentTasks recentTasks) {
+    public ActivityStackSupervisor(ActivityManagerService service) {
         mService = service;
-        mRecentTasks = recentTasks;
         mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
         mResizeDockedStackTimeout = new ResizeDockedStackTimeout(service, this, mHandler);
     }
 
+    void setRecentTasks(RecentTasks recentTasks) {
+        mRecentTasks = recentTasks;
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize our wakelocks afterwards.
@@ -685,20 +692,37 @@
         return null;
     }
 
-    void setNextTaskId(int taskId) {
-        if (taskId > mCurTaskId) {
-            mCurTaskId = taskId;
+    void setNextTaskIdForUserLocked(int taskId, int userId) {
+        final int currentTaskId = mCurTaskIdForUser.get(userId, -1);
+        if (taskId > currentTaskId) {
+            mCurTaskIdForUser.put(userId, taskId);
         }
     }
 
-    int getNextTaskId() {
-        do {
-            mCurTaskId++;
-            if (mCurTaskId <= 0) {
-                mCurTaskId = 1;
+    int getNextTaskIdForUserLocked(int userId) {
+        mRecentTasks.loadUserRecentsLocked(userId);
+        final int currentTaskId = mCurTaskIdForUser.get(userId, userId * MAX_TASK_IDS_PER_USER);
+        // for a userId u, a taskId can only be in the range
+        // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
+        // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
+        int candidateTaskId = currentTaskId;
+        while (anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
+                INVALID_STACK_ID) != null) {
+            candidateTaskId++;
+            if (candidateTaskId == (userId + 1) * MAX_TASK_IDS_PER_USER) {
+                // Wrap around as there will be smaller task ids that are available now.
+                candidateTaskId -= MAX_TASK_IDS_PER_USER;
             }
-        } while (anyTaskForIdLocked(mCurTaskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID) != null);
-        return mCurTaskId;
+            if (candidateTaskId == currentTaskId) {
+                // Something wrong!
+                // All MAX_TASK_IDS_PER_USER task ids are taken up by running tasks for this user
+                throw new IllegalStateException("Cannot get an available task id."
+                        + " Reached limit of " + MAX_TASK_IDS_PER_USER
+                        + " running tasks per user.");
+            }
+        }
+        mCurTaskIdForUser.put(userId, candidateTaskId);
+        return candidateTaskId;
     }
 
     ActivityRecord resumedAppLocked() {
@@ -1940,7 +1964,8 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
-                mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+                mWindowManager.getStackDockedModeBounds(
+                        HOME_STACK_ID, tempRect, true /* ignoreVisibilityOnKeyguardShowing */);
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
                     if (StackId.isResizeableByDockedStack(i)) {
                         ActivityStack otherStack = getStack(i);
@@ -2804,7 +2829,8 @@
         pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack);
                 pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack);
         pw.print(prefix); pw.println("mSleepTimeout=" + mSleepTimeout);
-        pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId);
+        pw.print(prefix);
+        pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
         pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
         pw.print(prefix); pw.println("mActivityContainers=" + mActivityContainers);
         pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString());
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 6fa8950..58c14f1 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -60,6 +60,7 @@
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
@@ -1420,7 +1421,8 @@
         }
 
         if (mReuseTask == null) {
-            final TaskRecord task = mTargetStack.createTaskRecord(mSupervisor.getNextTaskId(),
+            final TaskRecord task = mTargetStack.createTaskRecord(
+                    mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
                     mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent,
                     mVoiceSession, mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
@@ -1552,9 +1554,9 @@
             mTargetStack.moveToFront("addingToTopTask");
         }
         final ActivityRecord prev = mTargetStack.topActivity();
-        final TaskRecord task = prev != null ? prev.task
-                : mTargetStack.createTaskRecord(
-                mSupervisor.getNextTaskId(), mStartActivity.info, mIntent, null, null, true);
+        final TaskRecord task = (prev != null) ? prev.task : mTargetStack.createTaskRecord(
+                        mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
+                        mStartActivity.info, mIntent, null, null, true);
         mStartActivity.setTask(task, null);
         mWindowManager.moveTaskToTop(mStartActivity.task.taskId);
         if (DEBUG_TASKS) Slog.v(TAG_TASKS,
@@ -1701,7 +1703,7 @@
             // and if yes, we will launch into that stack. If not, we just put the new
             // activity into parent's stack, because we can't find a better place.
             final ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID);
-            if (stack != null && !stack.isStackVisibleLocked()) {
+            if (stack != null && stack.getStackVisibilityLocked() == STACK_INVISIBLE) {
                 // There is a docked stack, but it isn't visible, so we can't launch into that.
                 return null;
             } else {
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 52d23cf..a3c26cb 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -16,7 +16,12 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
 import android.app.ActivityManager;
@@ -27,11 +32,16 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.os.Environment;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 
+import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -47,18 +57,106 @@
     // Maximum number recent bitmaps to keep in memory.
     private static final int MAX_RECENT_BITMAPS = 3;
 
-    // Activity manager service.
-    private final ActivityManagerService mService;
+    /**
+     * Save recent tasks information across reboots.
+     */
+    private final TaskPersister mTaskPersister;
+    private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(5);
 
     // Mainly to avoid object recreation on multiple calls.
     private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
-    private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>();
-    private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>();
-    private final ActivityInfo tmpActivityInfo = new ActivityInfo();
-    private final ApplicationInfo tmpAppInfo = new ApplicationInfo();
+    private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
+    private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
+    private final ActivityInfo mTmpActivityInfo = new ActivityInfo();
+    private final ApplicationInfo mTmpAppInfo = new ApplicationInfo();
 
-    RecentTasks(ActivityManagerService service) {
-        mService = service;
+    RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
+        File systemDir = Environment.getDataSystemDirectory();
+        mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
+        mStackSupervisor.setRecentTasks(this);
+    }
+
+    /**
+     * Loads the persistent recentTasks for {@code userId} into {@link #mRecentTasks} from
+     * persistent storage. Does nothing if they are already loaded.
+     *
+     * @param userId the user Id
+     */
+    void loadUserRecentsLocked(int userId) {
+        if (!mUsersWithRecentsLoaded.get(userId)) {
+            Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
+            addAll(mTaskPersister.restoreTasksForUserLocked(userId));
+            cleanupLocked(userId);
+            mUsersWithRecentsLoaded.put(userId, true);
+        }
+    }
+
+    void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
+        if (task != null && task.stack != null && task.stack.isHomeStack()) {
+            // Never persist the home stack.
+            return;
+        }
+        mTaskPersister.wakeup(task, flush);
+    }
+
+    void onSystemReady() {
+        clear();
+        loadUserRecentsLocked(UserHandle.USER_SYSTEM);
+        startPersisting();
+    }
+
+    void startPersisting() {
+        mTaskPersister.startPersisting();
+    }
+
+    Bitmap getTaskDescriptionIcon(String path) {
+        return mTaskPersister.getTaskDescriptionIcon(path);
+    }
+
+    Bitmap getImageFromWriteQueue(String path) {
+        return mTaskPersister.getImageFromWriteQueue(path);
+    }
+
+    void saveImage(Bitmap image, String path) {
+        mTaskPersister.saveImage(image, path);
+    }
+
+    void flush() {
+        mTaskPersister.flush();
+    }
+
+    /**
+     * Returns all userIds for which recents from storage are loaded
+     *
+     * @return an array of userIds.
+     */
+    int[] usersWithRecentsLoadedLocked() {
+        int[] usersWithRecentsLoaded = new int[mUsersWithRecentsLoaded.size()];
+        int len = 0;
+        for (int i = 0; i < usersWithRecentsLoaded.length; i++) {
+            int userId = mUsersWithRecentsLoaded.keyAt(i);
+            if (mUsersWithRecentsLoaded.valueAt(i)) {
+                usersWithRecentsLoaded[len++] = userId;
+            }
+        }
+        if (len < usersWithRecentsLoaded.length) {
+            // should never happen.
+            return Arrays.copyOf(usersWithRecentsLoaded, len);
+        }
+        return usersWithRecentsLoaded;
+    }
+
+    /**
+     * Removes recent tasks for this user if they are loaded, does not do anything otherwise.
+     *
+     * @param userId the user id.
+     */
+    void unloadUserRecentsLocked(int userId) {
+        if (mUsersWithRecentsLoaded.get(userId)) {
+            Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
+            mUsersWithRecentsLoaded.delete(userId);
+            removeTasksForUserLocked(userId);
+        }
     }
 
     TaskRecord taskForIdLocked(int id) {
@@ -88,9 +186,6 @@
                 tr.removedFromRecents();
             }
         }
-
-        // Remove tasks from persistent storage.
-        mService.notifyTaskPersisterLocked(null, true);
     }
 
     /**
@@ -107,87 +202,86 @@
         }
 
         final IPackageManager pm = AppGlobals.getPackageManager();
-        final int[] users = (userId == UserHandle.USER_ALL)
-                ? mService.mUserController.getUsers() : new int[] { userId };
-        for (int userIdx = 0; userIdx < users.length; userIdx++) {
-            final int user = users[userIdx];
-            recentsCount = size() - 1;
-            for (int i = recentsCount; i >= 0; i--) {
-                TaskRecord task = get(i);
-                if (task.userId != user) {
-                    // Only look at tasks for the user ID of interest.
-                    continue;
-                }
-                if (task.autoRemoveRecents && task.getTopActivity() == null) {
-                    // This situation is broken, and we should just get rid of it now.
-                    remove(i);
-                    task.removedFromRecents();
-                    Slog.w(TAG, "Removing auto-remove without activity: " + task);
-                    continue;
-                }
-                // Check whether this activity is currently available.
-                if (task.realActivity != null) {
-                    ActivityInfo ai = tmpAvailActCache.get(task.realActivity);
+        for (int i = recentsCount - 1; i >= 0; i--) {
+            final TaskRecord task = get(i);
+            if (userId != UserHandle.USER_ALL && task.userId != userId) {
+                // Only look at tasks for the user ID of interest.
+                continue;
+            }
+            if (task.autoRemoveRecents && task.getTopActivity() == null) {
+                // This situation is broken, and we should just get rid of it now.
+                remove(i);
+                task.removedFromRecents();
+                Slog.w(TAG, "Removing auto-remove without activity: " + task);
+                continue;
+            }
+            // Check whether this activity is currently available.
+            if (task.realActivity != null) {
+                ActivityInfo ai = mTmpAvailActCache.get(task.realActivity);
+                if (ai == null) {
+                    try {
+                        // At this first cut, we're only interested in
+                        // activities that are fully runnable based on
+                        // current system state.
+                        ai = pm.getActivityInfo(task.realActivity,
+                                PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
+                    } catch (RemoteException e) {
+                        // Will never happen.
+                        continue;
+                    }
                     if (ai == null) {
+                        ai = mTmpActivityInfo;
+                    }
+                    mTmpAvailActCache.put(task.realActivity, ai);
+                }
+                if (ai == mTmpActivityInfo) {
+                    // This could be either because the activity no longer exists, or the
+                    // app is temporarily gone. For the former we want to remove the recents
+                    // entry; for the latter we want to mark it as unavailable.
+                    ApplicationInfo app = mTmpAvailAppCache
+                            .get(task.realActivity.getPackageName());
+                    if (app == null) {
                         try {
-                            // At this first cut, we're only interested in
-                            // activities that are fully runnable based on
-                            // current system state.
-                            ai = pm.getActivityInfo(task.realActivity,
-                                    PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user);
+                            app = pm.getApplicationInfo(task.realActivity.getPackageName(),
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
                         } catch (RemoteException e) {
                             // Will never happen.
                             continue;
                         }
-                        if (ai == null) {
-                            ai = tmpActivityInfo;
-                        }
-                        tmpAvailActCache.put(task.realActivity, ai);
-                    }
-                    if (ai == tmpActivityInfo) {
-                        // This could be either because the activity no longer exists, or the
-                        // app is temporarily gone.  For the former we want to remove the recents
-                        // entry; for the latter we want to mark it as unavailable.
-                        ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName());
                         if (app == null) {
-                            try {
-                                app = pm.getApplicationInfo(task.realActivity.getPackageName(),
-                                        PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
-                            } catch (RemoteException e) {
-                                // Will never happen.
-                                continue;
-                            }
-                            if (app == null) {
-                                app = tmpAppInfo;
-                            }
-                            tmpAvailAppCache.put(task.realActivity.getPackageName(), app);
+                            app = mTmpAppInfo;
                         }
-                        if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
-                            // Doesn't exist any more!  Good-bye.
-                            remove(i);
-                            task.removedFromRecents();
-                            Slog.w(TAG, "Removing no longer valid recent: " + task);
-                            continue;
-                        } else {
-                            // Otherwise just not available for now.
-                            if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
-                                    "Making recent unavailable: " + task);
-                            task.isAvailable = false;
-                        }
+                        mTmpAvailAppCache.put(task.realActivity.getPackageName(), app);
+                    }
+                    if (app == mTmpAppInfo
+                            || (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                        // Doesn't exist any more! Good-bye.
+                        remove(i);
+                        task.removedFromRecents();
+                        Slog.w(TAG, "Removing no longer valid recent: " + task);
+                        continue;
                     } else {
-                        if (!ai.enabled || !ai.applicationInfo.enabled
-                                || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
-                            if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
-                                    "Making recent unavailable: " + task
-                                    + " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled
-                                    + " flags=" + Integer.toHexString(ai.applicationInfo.flags)
-                                    + ")");
-                            task.isAvailable = false;
-                        } else {
-                            if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
-                                    "Making recent available: " + task);
-                            task.isAvailable = true;
-                        }
+                        // Otherwise just not available for now.
+                        if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
+                                "Making recent unavailable: " + task);
+                        task.isAvailable = false;
+                    }
+                } else {
+                    if (!ai.enabled || !ai.applicationInfo.enabled
+                            || (ai.applicationInfo.flags
+                                    & ApplicationInfo.FLAG_INSTALLED) == 0) {
+                        if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
+                                "Making recent unavailable: " + task
+                                        + " (enabled=" + ai.enabled + "/"
+                                        + ai.applicationInfo.enabled
+                                        + " flags="
+                                        + Integer.toHexString(ai.applicationInfo.flags)
+                                        + ")");
+                        task.isAvailable = false;
+                    } else {
+                        if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
+                                "Making recent available: " + task);
+                        task.isAvailable = true;
                     }
                 }
             }
@@ -341,7 +435,7 @@
                     // Simple case: this is not an affiliated task, so we just move it to the front.
                     remove(taskIndex);
                     add(0, task);
-                    mService.notifyTaskPersisterLocked(task, false);
+                    notifyTaskPersisterLocked(task, false);
                     if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
                             + " from " + taskIndex);
                     return;
@@ -501,7 +595,7 @@
                 // specified, then replace it with the existing recent task.
                 task = tr;
             }
-            mService.notifyTaskPersisterLocked(tr, false);
+            notifyTaskPersisterLocked(tr, false);
         }
 
         return -1;
@@ -551,7 +645,7 @@
         if (first.mNextAffiliate != null) {
             Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
             first.setNextAffiliate(null);
-            mService.notifyTaskPersisterLocked(first, false);
+            notifyTaskPersisterLocked(first, false);
         }
         // Everything in the middle is doubly linked from next to prev.
         final int tmpSize = mTmpRecents.size();
@@ -562,13 +656,13 @@
                 Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
                         " setting prev=" + prev);
                 next.setPrevAffiliate(prev);
-                mService.notifyTaskPersisterLocked(next, false);
+                notifyTaskPersisterLocked(next, false);
             }
             if (prev.mNextAffiliate != next) {
                 Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
                         " setting next=" + next);
                 prev.setNextAffiliate(next);
-                mService.notifyTaskPersisterLocked(prev, false);
+                notifyTaskPersisterLocked(prev, false);
             }
             prev.inRecents = true;
         }
@@ -577,7 +671,7 @@
         if (last.mPrevAffiliate != null) {
             Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
             last.setPrevAffiliate(null);
-            mService.notifyTaskPersisterLocked(last, false);
+            notifyTaskPersisterLocked(last, false);
         }
 
         // Insert the group back into mRecentTasks at start.
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9a00075..283939e 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -21,16 +21,18 @@
 import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.Process;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
-import android.os.Process;
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -42,12 +44,10 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-import libcore.io.IoUtils;
-
 public class TaskPersister {
     static final String TAG = "TaskPersister";
     static final boolean DEBUG = false;
@@ -113,7 +113,7 @@
     ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
 
     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
-            RecentTasks recentTasks) {
+            ActivityManagerService service, RecentTasks recentTasks) {
 
         final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
         if (legacyImagesDir.exists()) {
@@ -130,7 +130,7 @@
         }
 
         mStackSupervisor = stackSupervisor;
-        mService = stackSupervisor.mService;
+        mService = service;
         mRecentTasks = recentTasks;
         mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
     }
@@ -145,11 +145,15 @@
         final String taskString = Integer.toString(task.taskId);
         for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
             final WriteQueueItem item = mWriteQueue.get(queueNdx);
-            if (item instanceof ImageWriteQueueItem &&
-                    ((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) {
-                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
-                        " from write queue");
-                mWriteQueue.remove(queueNdx);
+            if (item instanceof ImageWriteQueueItem) {
+                final File thumbnailFile = new File(((ImageWriteQueueItem) item).mFilePath);
+                if (thumbnailFile.getName().startsWith(taskString)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
+                                " from write queue");
+                    }
+                    mWriteQueue.remove(queueNdx);
+                }
             }
         }
     }
@@ -185,7 +189,8 @@
                     mWriteQueue.add(new TaskWriteQueueItem(task));
                 }
             } else {
-                // Dummy.
+                // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
+                // notified.
                 mWriteQueue.add(new WriteQueueItem());
             }
             if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
@@ -323,8 +328,8 @@
         return null;
     }
 
-    private List<TaskRecord> restoreTasksForUserLocked(final int userId) {
-        final List<TaskRecord> tasks = new ArrayList<TaskRecord>();
+    List<TaskRecord> restoreTasksForUserLocked(final int userId) {
+        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
 
         File userTasksDir = getUserTasksDir(userId);
@@ -351,10 +356,10 @@
                         event != XmlPullParser.END_TAG) {
                     final String name = in.getName();
                     if (event == XmlPullParser.START_TAG) {
-                        if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
+                        if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
                         if (TAG_TASK.equals(name)) {
                             final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
-                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task="
+                            if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
                                     + task);
                             if (task != null) {
                                 // XXX Don't add to write queue... there is no reason to write
@@ -362,7 +367,7 @@
                                 // read the same thing again.
                                 // mWriteQueue.add(new TaskWriteQueueItem(task));
                                 final int taskId = task.taskId;
-                                mStackSupervisor.setNextTaskId(taskId);
+                                mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
                                 // Check if it's a valid user id. Don't add tasks for removed users.
                                 if (userId == task.userId) {
                                     task.isPersistable = true;
@@ -396,15 +401,6 @@
         if (!DEBUG) {
             removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
         }
-        return tasks;
-    }
-
-    ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
-        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
-
-        for (int userId : validUserIds) {
-            tasks.addAll(restoreTasksForUserLocked(userId));
-        }
 
         // Fix up task affiliation from taskIds
         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -413,9 +409,7 @@
             task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
         }
 
-        TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
-        tasks.toArray(tasksArray);
-        Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
+        Collections.sort(tasks, new Comparator<TaskRecord>() {
             @Override
             public int compare(TaskRecord lhs, TaskRecord rhs) {
                 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
@@ -428,8 +422,7 @@
                 }
             }
         });
-
-        return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
+        return tasks;
     }
 
     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
@@ -462,7 +455,13 @@
     }
 
     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
-        for (int userId : mService.getRunningUserIds()) {
+        int[] candidateUserIds;
+        synchronized (mService) {
+            // Remove only from directories of the users who have recents in memory synchronized
+            // with persistent storage.
+            candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
+        }
+        for (int userId : candidateUserIds) {
             removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
             removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index f7e30c0..be36f01 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -552,7 +552,7 @@
                     mLastThumbnailFile.delete();
                 }
             } else {
-                mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
+                mService.mRecentTasks.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
             }
             return true;
         }
@@ -564,7 +564,7 @@
         thumbs.thumbnailInfo = mLastThumbnailInfo;
         thumbs.thumbnailFileDescriptor = null;
         if (mLastThumbnail == null) {
-            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(
+            thumbs.mainThumbnail = mService.mRecentTasks.getImageFromWriteQueue(
                     mLastThumbnailFile.getAbsolutePath());
         }
         // Only load the thumbnail file if we don't have a thumbnail
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 56757ca..8c16872 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -249,6 +249,9 @@
             if (uss.state == UserState.STATE_RUNNING_LOCKED) {
                 uss.setState(UserState.STATE_RUNNING);
 
+                // Give user manager a chance to prepare app storage
+                mUserManager.onBeforeUnlockUser(userId);
+
                 mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0));
 
                 final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
@@ -422,6 +425,7 @@
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLocked();
 
+                mService.onUserStoppedLocked(userId);
                 // Clean up all state and processes associated with the user.
                 // Kill all the processes for the user.
                 forceStopUserLocked(userId, "finish user");
@@ -651,7 +655,8 @@
                 }
 
                 if (uss.state == UserState.STATE_BOOTING) {
-                    // Let user manager propagate user restrictions to other services.
+                    // Give user manager a chance to propagate user restrictions
+                    // to other services and prepare app storage
                     getUserManager().onBeforeStartUser(userId);
 
                     // Booting up a new user, need to tell system services about it.
@@ -1124,8 +1129,7 @@
             UserState uss = mStartedUsers.valueAt(i);
             if (uss.state != UserState.STATE_STOPPING
                     && uss.state != UserState.STATE_SHUTDOWN) {
-                mStartedUserArray[num] = mStartedUsers.keyAt(i);
-                num++;
+                mStartedUserArray[num++] = mStartedUsers.keyAt(i);
             }
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 5bd4f98..e957fc6 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -63,6 +63,7 @@
 import android.os.SystemService;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.text.TextUtils;
@@ -169,6 +170,58 @@
     }
 
     /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should exist and declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param newPackage the package to designate as always-on VPN supplier.
+     */
+    public synchronized boolean setAlwaysOnPackage(String packageName) {
+        enforceControlPermissionOrInternalCaller();
+
+        // Disconnect current VPN.
+        prepareInternal(VpnConfig.LEGACY_VPN);
+
+        // Pre-authorize new always-on VPN package.
+        if (packageName != null) {
+            if (!setPackageAuthorization(packageName, true)) {
+                return false;
+            }
+        }
+
+        // Save the new package name in Settings.Secure.
+        final long token = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.ALWAYS_ON_VPN_APP, packageName, mUserHandle);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return true;
+    }
+
+    /**
+     * @return the package name of the VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set or always-on VPN is controlled through
+     *         lockdown instead.
+     * @hide
+     */
+    public synchronized String getAlwaysOnPackage() {
+        enforceControlPermissionOrInternalCaller();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
      * Prepare for a VPN application. This method is designed to solve
      * race conditions. It first compares the current prepared package
      * with {@code oldPackage}. If they are the same, the prepared
@@ -270,14 +323,14 @@
     /**
      * Set whether a package has the ability to launch VPNs without user intervention.
      */
-    public void setPackageAuthorization(String packageName, boolean authorized) {
+    public boolean setPackageAuthorization(String packageName, boolean authorized) {
         // Check if the caller is authorized.
-        enforceControlPermission();
+        enforceControlPermissionOrInternalCaller();
 
         int uid = getAppUid(packageName, mUserHandle);
         if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) {
             // Authorization for nonexistent packages (or fake ones) can't be updated.
-            return;
+            return false;
         }
 
         long token = Binder.clearCallingIdentity();
@@ -286,11 +339,13 @@
                     (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
             appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName,
                     authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+            return true;
         } catch (Exception e) {
             Log.wtf(TAG, "Failed to set app ops for package " + packageName + ", uid " + uid, e);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+        return false;
     }
 
     private boolean isVpnUserPreConsented(String packageName) {
@@ -743,6 +798,13 @@
         mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller");
     }
 
+    private void enforceControlPermissionOrInternalCaller() {
+        // Require caller to be either an application with CONTROL_VPN permission or a process
+        // in the system server.
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN,
+                "Unauthorized Caller");
+    }
+
     private class Connection implements ServiceConnection {
         private IBinder mService;
 
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 78618ce..2eb9095 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2682,6 +2682,31 @@
                         }
                         continue;
                     }
+                    String packageName = getPackageName(op.target);
+                    ApplicationInfo ai = null;
+                    if (packageName != null) {
+                        try {
+                            ai = mContext.getPackageManager().getApplicationInfo(packageName,
+                                    PackageManager.GET_UNINSTALLED_PACKAGES
+                                            | PackageManager.GET_DISABLED_COMPONENTS);
+                        } catch (NameNotFoundException e) {
+                            operationIterator.remove();
+                            mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                            continue;
+                        }
+                    }
+                    // If app is considered idle, then skip for now and backoff
+                    if (ai != null
+                            && mAppIdleMonitor.isAppIdle(packageName, ai.uid, op.target.userId)) {
+                        increaseBackoffSetting(op);
+                        op.appIdle = true;
+                        if (isLoggable) {
+                            Log.v(TAG, "Sync backing off idle app " + packageName);
+                        }
+                        continue;
+                    } else {
+                        op.appIdle = false;
+                    }
                     if (!isOperationValidLocked(op)) {
                         operationIterator.remove();
                         mSyncStorageEngine.deleteFromPending(op.pendingOperation);
@@ -2700,28 +2725,6 @@
                         }
                         continue;
                     }
-                    String packageName = getPackageName(op.target);
-                    ApplicationInfo ai = null;
-                    if (packageName != null) {
-                        try {
-                            ai = mContext.getPackageManager().getApplicationInfo(packageName,
-                                    PackageManager.GET_UNINSTALLED_PACKAGES
-                                    | PackageManager.GET_DISABLED_COMPONENTS);
-                        } catch (NameNotFoundException e) {
-                        }
-                    }
-                    // If app is considered idle, then skip for now and backoff
-                    if (ai != null
-                            && mAppIdleMonitor.isAppIdle(packageName, ai.uid, op.target.userId)) {
-                        increaseBackoffSetting(op);
-                        op.appIdle = true;
-                        if (isLoggable) {
-                            Log.v(TAG, "Sync backing off idle app " + packageName);
-                        }
-                        continue;
-                    } else {
-                        op.appIdle = false;
-                    }
                     // Add this sync to be run.
                     operations.add(op);
                 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 3530d80..24519db 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -16,14 +16,21 @@
 
 package com.android.server.job;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
-import android.app.job.IJobScheduler;
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
+import android.app.job.IJobScheduler;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -42,12 +49,12 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.Process;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.ArrayUtils;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.AppIdleController;
@@ -58,15 +65,6 @@
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.TimeController;
 
-import libcore.util.EmptyArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * Responsible for taking jobs representing work to be performed by a client app, and determining
  * based on the criteria specified when that job should be run against the client application's
@@ -130,7 +128,7 @@
      */
     final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
 
-    int[] mStartedUsers = EmptyArray.INT;
+    final ArrayList<Integer> mStartedUsers = new ArrayList<>();
 
     final JobHandler mHandler;
     final JobSchedulerStub mJobSchedulerStub;
@@ -161,9 +159,8 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            Slog.d(TAG, "Receieved: " + action);
-            if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+            Slog.d(TAG, "Receieved: " + intent.getAction());
+            if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                 // If this is an outright uninstall rather than the first half of an
                 // app update sequence, cancel the jobs associated with the app.
                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
@@ -173,21 +170,18 @@
                     }
                     cancelJobsForUid(uidRemoved, true);
                 }
-            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+            } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 if (DEBUG) {
                     Slog.d(TAG, "Removing jobs for user: " + userId);
                 }
                 cancelJobsForUser(userId);
-            } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
-                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+            } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
+                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
                 updateIdleMode(mPowerManager != null
                         ? (mPowerManager.isDeviceIdleMode()
-                                || mPowerManager.isLightDeviceIdleMode())
+                        || mPowerManager.isLightDeviceIdleMode())
                         : false);
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-                // Kick off pending jobs for any apps that re-appeared
-                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
             }
         }
     };
@@ -209,20 +203,14 @@
 
     @Override
     public void onStartUser(int userHandle) {
-        mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
-        // Let's kick any outstanding jobs for this user.
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-    }
-
-    @Override
-    public void onUnlockUser(int userHandle) {
+        mStartedUsers.add(userHandle);
         // Let's kick any outstanding jobs for this user.
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
     }
 
     @Override
     public void onStopUser(int userHandle) {
-        mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+        mStartedUsers.remove(Integer.valueOf(userHandle));
     }
 
     /**
@@ -233,7 +221,14 @@
      * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
      */
     public int schedule(JobInfo job, int uId) {
+        return scheduleAsPackage(job, uId, null, -1);
+    }
+
+    public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId) {
         JobStatus jobStatus = new JobStatus(job, uId);
+        if (packageName != null) {
+            jobStatus.setSource(packageName, userId);
+        }
         cancelJob(uId, job.getId());
         try {
             if (ActivityManagerNative.getDefault().getAppStartMode(uId,
@@ -329,7 +324,7 @@
             // Remove from pending queue.
             mPendingJobs.remove(cancelled);
             // Cancel if running.
-            stopJobOnServiceContextLocked(cancelled);
+            stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
             reportActive();
         }
     }
@@ -357,7 +352,7 @@
                         JobServiceContext jsc = mActiveServices.get(i);
                         final JobStatus executing = jsc.getRunningJob();
                         if (executing != null) {
-                            jsc.cancelExecutingJob();
+                            jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
                         }
                     }
                 } else {
@@ -382,7 +377,7 @@
         if (mPendingJobs.size() <= 0) {
             for (int i=0; i<mActiveServices.size(); i++) {
                 JobServiceContext jsc = mActiveServices.get(i);
-                if (!jsc.isAvailable()) {
+                if (jsc.getRunningJob() != null) {
                     active = true;
                     break;
                 }
@@ -429,24 +424,17 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            // Register for package removals and user removals.
+            // Register br for package removals and user removals.
             final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
             filter.addDataScheme("package");
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
-
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
             userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
             userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
-
-            final IntentFilter storageFilter = new IntentFilter();
-            storageFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-            getContext().registerReceiverAsUser(
-                    mBroadcastReceiver, UserHandle.ALL, storageFilter, null, null);
-
-            mPowerManager = getContext().getSystemService(PowerManager.class);
+            mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
             try {
                 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
                         ActivityManager.UID_OBSERVER_IDLE);
@@ -526,12 +514,12 @@
         return removed;
     }
 
-    private boolean stopJobOnServiceContextLocked(JobStatus job) {
+    private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
         for (int i=0; i<mActiveServices.size(); i++) {
             JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus executing = jsc.getRunningJob();
             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
-                jsc.cancelExecutingJob();
+                jsc.cancelExecutingJob(reason);
                 return true;
             }
         }
@@ -731,6 +719,7 @@
          */
         private void queueReadyJobsForExecutionLockedH() {
             ArraySet<JobStatus> jobs = mJobs.getJobs();
+            mPendingJobs.clear();
             if (DEBUG) {
                 Slog.d(TAG, "queuing all ready jobs for execution:");
             }
@@ -741,8 +730,9 @@
                         Slog.d(TAG, "    queued " + job.toShortString());
                     }
                     mPendingJobs.add(job);
-                } else if (isReadyToBeCancelledLocked(job)) {
-                    stopJobOnServiceContextLocked(job);
+                } else if (areJobConstraintsNotSatisfied(job)) {
+                    stopJobOnServiceContextLocked(job,
+                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                 }
             }
             if (DEBUG) {
@@ -765,8 +755,9 @@
          * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
          */
         private void maybeQueueReadyJobsForExecutionLockedH() {
+            mPendingJobs.clear();
             int chargingCount = 0;
-            int idleCount = 0;
+            int idleCount =  0;
             int backoffCount = 0;
             int connectivityCount = 0;
             List<JobStatus> runnableJobs = null;
@@ -801,8 +792,9 @@
                         runnableJobs = new ArrayList<>();
                     }
                     runnableJobs.add(job);
-                } else if (isReadyToBeCancelledLocked(job)) {
-                    stopJobOnServiceContextLocked(job);
+                } else if (areJobConstraintsNotSatisfied(job)) {
+                    stopJobOnServiceContextLocked(job,
+                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                 }
             }
             if (backoffCount > 0 ||
@@ -821,11 +813,6 @@
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                 }
             }
-            if (DEBUG) {
-                Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
-                connectivityCount + " charging=" + chargingCount + " tot=" +
-                        runnableJobs.size());
-            }
         }
 
         /**
@@ -834,31 +821,18 @@
          *      - It's not pending.
          *      - It's not already running on a JSC.
          *      - The user that requested the job is running.
-         *      - The component is enabled and runnable.
          */
         private boolean isReadyToBeExecutedLocked(JobStatus job) {
             final boolean jobReady = job.isReady();
             final boolean jobPending = mPendingJobs.contains(job);
             final boolean jobActive = isCurrentlyActiveLocked(job);
-
-            final int userId = job.getUserId();
-            final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
-            final boolean componentPresent;
-            try {
-                componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
-                        job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                        userId) != null);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-
+            final boolean userRunning = mStartedUsers.contains(job.getUserId());
             if (DEBUG) {
                 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
                         + " ready=" + jobReady + " pending=" + jobPending
-                        + " active=" + jobActive + " userStarted=" + userStarted
-                        + " componentPresent=" + componentPresent);
+                        + " active=" + jobActive + " userRunning=" + userRunning);
             }
-            return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
+            return userRunning && jobReady && !jobPending && !jobActive;
         }
 
         /**
@@ -866,7 +840,7 @@
          *      - It's not ready
          *      - It's running on a JSC.
          */
-        private boolean isReadyToBeCancelledLocked(JobStatus job) {
+        private boolean areJobConstraintsNotSatisfied(JobStatus job) {
             return !job.isReady() && isCurrentlyActiveLocked(job);
         }
 
@@ -881,46 +855,121 @@
                     // If device is idle, we will not schedule jobs to run.
                     return;
                 }
-                Iterator<JobStatus> it = mPendingJobs.iterator();
                 if (DEBUG) {
                     Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
                 }
-                while (it.hasNext()) {
-                    JobStatus nextPending = it.next();
-                    JobServiceContext availableContext = null;
-                    for (int i=0; i<mActiveServices.size(); i++) {
-                        JobServiceContext jsc = mActiveServices.get(i);
-                        final JobStatus running = jsc.getRunningJob();
-                        if (running != null && running.matches(nextPending.getUid(),
-                                nextPending.getJobId())) {
-                            // Already running this job for this uId, skip.
-                            availableContext = null;
-                            break;
-                        }
-                        if (jsc.isAvailable()) {
-                            availableContext = jsc;
-                        }
-                    }
-                    if (availableContext != null) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "About to run job "
-                                    + nextPending.getJob().getService().toString());
-                        }
-                        if (!availableContext.executeRunnableJob(nextPending)) {
-                            if (DEBUG) {
-                                Slog.d(TAG, "Error executing " + nextPending);
-                            }
-                            mJobs.remove(nextPending);
-                        }
-                        it.remove();
-                    }
-                }
+                assignJobsToContextsH();
                 reportActive();
             }
         }
     }
 
     /**
+     * Takes jobs from pending queue and runs them on available contexts.
+     * If no contexts are available, preempts lower priority jobs to
+     * run higher priority ones.
+     * Lock on mJobs before calling this function.
+     */
+    private void assignJobsToContextsH() {
+        if (DEBUG) {
+            Slog.d(TAG, printPendingQueue());
+        }
+
+        // This array essentially stores the state of mActiveServices array.
+        // ith index stores the job present on the ith JobServiceContext.
+        // We manipulate this array until we arrive at what jobs should be running on
+        // what JobServiceContext.
+        JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
+        // Indicates whether we need to act on this jobContext id
+        boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
+        int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+        for (int i=0; i<mActiveServices.size(); i++) {
+            contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
+            preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
+        }
+        if (DEBUG) {
+            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
+        }
+        Iterator<JobStatus> it = mPendingJobs.iterator();
+        while (it.hasNext()) {
+            JobStatus nextPending = it.next();
+
+            // If job is already running, go to next job.
+            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
+            if (jobRunningContext != -1) {
+                continue;
+            }
+
+            // Find a context for nextPending. The context should be available OR
+            // it should have lowest priority among all running jobs
+            // (sharing the same Uid as nextPending)
+            int minPriority = Integer.MAX_VALUE;
+            int minPriorityContextId = -1;
+            for (int i=0; i<mActiveServices.size(); i++) {
+                JobStatus job = contextIdToJobMap[i];
+                int preferredUid = preferredUidForContext[i];
+                if (job == null && (preferredUid == nextPending.getUid() ||
+                        preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
+                    minPriorityContextId = i;
+                    break;
+                }
+                if (job.getUid() != nextPending.getUid()) {
+                    continue;
+                }
+                if (job.getPriority() >= nextPending.getPriority()) {
+                    continue;
+                }
+                if (minPriority > nextPending.getPriority()) {
+                    minPriority = nextPending.getPriority();
+                    minPriorityContextId = i;
+                }
+            }
+            if (minPriorityContextId != -1) {
+                contextIdToJobMap[minPriorityContextId] = nextPending;
+                act[minPriorityContextId] = true;
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
+        }
+        for (int i=0; i<mActiveServices.size(); i++) {
+            boolean preservePreferredUid = false;
+            if (act[i]) {
+                JobStatus js = mActiveServices.get(i).getRunningJob();
+                if (js != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
+                    }
+                    // preferredUid will be set to uid of currently running job.
+                    mActiveServices.get(i).preemptExecutingJob();
+                    preservePreferredUid = true;
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "About to run job on context "
+                                + String.valueOf(i) + ", job: " + contextIdToJobMap[i]);
+                    }
+                    if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) {
+                        Slog.d(TAG, "Error executing " + contextIdToJobMap[i]);
+                    }
+                    mPendingJobs.remove(contextIdToJobMap[i]);
+                }
+            }
+            if (!preservePreferredUid) {
+                mActiveServices.get(i).clearPreferredUid();
+            }
+        }
+    }
+
+    int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
+        for (int i=0; i<map.length; i++) {
+            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Binder stub trampoline implementation
      */
     final class JobSchedulerStub extends IJobScheduler.Stub {
@@ -936,8 +985,7 @@
             final IPackageManager pm = AppGlobals.getPackageManager();
             final ComponentName service = job.getService();
             try {
-                ServiceInfo si = pm.getServiceInfo(service,
-                        PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(uid));
+                ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
                 if (si == null) {
                     throw new IllegalArgumentException("No such service " + service);
                 }
@@ -1001,6 +1049,25 @@
         }
 
         @Override
+        public int scheduleAsPackage(JobInfo job, String packageName, int userId)
+                throws RemoteException {
+            if (DEBUG) {
+                Slog.d(TAG, "Scheduling job: " + job.toString() + " on behalf of " + packageName);
+            }
+            final int uid = Binder.getCallingUid();
+            if (uid != Process.SYSTEM_UID) {
+                throw new IllegalArgumentException("Only system process is allowed"
+                        + "to set packageName");
+            }
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.scheduleAsPackage(job, uid, packageName, userId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public List<JobInfo> getAllPendingJobs() throws RemoteException {
             final int uid = Binder.getCallingUid();
 
@@ -1050,12 +1117,41 @@
                 Binder.restoreCallingIdentity(identityToken);
             }
         }
+    };
+
+    private String printContextIdToJobMap(JobStatus[] map, String initial) {
+        StringBuilder s = new StringBuilder(initial + ": ");
+        for (int i=0; i<map.length; i++) {
+            s.append("(")
+                    .append(map[i] == null? -1: map[i].getJobId())
+                    .append(map[i] == null? -1: map[i].getUid())
+                    .append(")" );
+        }
+        return s.toString();
+    }
+
+    private String printPendingQueue() {
+        StringBuilder s = new StringBuilder("Pending queue: ");
+        Iterator<JobStatus> it = mPendingJobs.iterator();
+        while (it.hasNext()) {
+            JobStatus js = it.next();
+            s.append("(")
+                    .append(js.getJob().getId())
+                    .append(", ")
+                    .append(js.getUid())
+                    .append(") ");
+        }
+        return s.toString();
     }
 
     void dumpInternal(PrintWriter pw) {
         final long now = SystemClock.elapsedRealtime();
         synchronized (mJobs) {
-            pw.println("Started users: " + Arrays.toString(mStartedUsers));
+            pw.print("Started users: ");
+            for (int i=0; i<mStartedUsers.size(); i++) {
+                pw.print("u" + mStartedUsers.get(i) + " ");
+            }
+            pw.println();
             pw.println("Registered jobs:");
             if (mJobs.size() > 0) {
                 ArraySet<JobStatus> jobs = mJobs.getJobs();
@@ -1071,15 +1167,12 @@
                 mControllers.get(i).dumpControllerState(pw);
             }
             pw.println();
-            pw.println("Pending:");
-            for (int i=0; i<mPendingJobs.size(); i++) {
-                pw.println(mPendingJobs.get(i).hashCode());
-            }
+            pw.println(printPendingQueue());
             pw.println();
             pw.println("Active jobs:");
             for (int i=0; i<mActiveServices.size(); i++) {
                 JobServiceContext jsc = mActiveServices.get(i);
-                if (jsc.isAvailable()) {
+                if (jsc.getRunningJob() == null) {
                     continue;
                 } else {
                     final long timeout = jsc.getTimeoutElapsed();
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 5376043..c359c4d 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -59,7 +60,6 @@
  * To mitigate this, tearing down the context removes all messages from the handler, including any
  * tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
  * calls to the client after they've specified jobFinished().
- *
  */
 public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
@@ -95,6 +95,8 @@
     /** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
     private static final int MSG_SHUTDOWN_EXECUTION = 4;
 
+    public static final int NO_PREFERRED_UID = -1;
+
     private final Handler mCallbackHandler;
     /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
     private final JobCompletedListener mCompletedListener;
@@ -117,7 +119,8 @@
      * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
      */
     private JobStatus mRunningJob;
-    /** Binder to the client service. */
+    /** Used to store next job to run when current job is to be preempted. */
+    private int mPreferredUid;
     IJobService service;
 
     private final Object mLock = new Object();
@@ -138,17 +141,19 @@
 
     @VisibleForTesting
     JobServiceContext(Context context, IBatteryStats batteryStats,
-            JobCompletedListener completedListener, Looper looper) {
+                      JobCompletedListener completedListener, Looper looper) {
         mContext = context;
         mBatteryStats = batteryStats;
         mCallbackHandler = new JobServiceHandler(looper);
         mCompletedListener = completedListener;
         mAvailable = true;
+        mVerb = VERB_FINISHED;
+        mPreferredUid = NO_PREFERRED_UID;
     }
 
     /**
-     * Give a job to this context for execution. Callers must first check {@link #isAvailable()}
-     * to make sure this is a valid context.
+     * Give a job to this context for execution. Callers must first check {@link #getRunningJob()}
+     * and ensure it is null to make sure this is a valid context.
      * @param job The status of the job that we are going to run.
      * @return True if the job is valid and is running. False if the job cannot be executed.
      */
@@ -159,6 +164,8 @@
                 return false;
             }
 
+            mPreferredUid = NO_PREFERRED_UID;
+
             mRunningJob = job;
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
@@ -184,7 +191,7 @@
                 return false;
             }
             try {
-                mBatteryStats.noteJobStart(job.getName(), job.getUid());
+                mBatteryStats.noteJobStart(job.getName(), job.getSourceUid());
             } catch (RemoteException e) {
                 // Whatever.
             }
@@ -206,17 +213,22 @@
     }
 
     /** Called externally when a job that was scheduled for execution should be cancelled. */
-    void cancelExecutingJob() {
-        mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
+    void cancelExecutingJob(int reason) {
+        mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
     }
 
-    /**
-     * @return Whether this context is available to handle incoming work.
-     */
-    boolean isAvailable() {
-        synchronized (mLock) {
-            return mAvailable;
-        }
+    void preemptExecutingJob() {
+        Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
+        m.arg1 = JobParameters.REASON_PREEMPT;
+        m.sendToTarget();
+    }
+
+    int getPreferredUid() {
+        return mPreferredUid;
+    }
+
+    void clearPreferredUid() {
+        mPreferredUid = NO_PREFERRED_UID;
     }
 
     long getExecutionStartTimeElapsed() {
@@ -344,6 +356,11 @@
                     }
                     break;
                 case MSG_CANCEL:
+                    mParams.setStopReason(message.arg1);
+                    if (message.arg1 == JobParameters.REASON_PREEMPT) {
+                        mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
+                                NO_PREFERRED_UID;
+                    }
                     handleCancelH();
                     break;
                 case MSG_TIMEOUT:
@@ -481,6 +498,7 @@
 
         /** Process MSG_TIMEOUT here. */
         private void handleOpTimeoutH() {
+            mParams.setStopReason(JobParameters.REASON_TIMEOUT);
             switch (mVerb) {
                 case VERB_BINDING:
                     Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
@@ -549,7 +567,7 @@
                 }
                 completedJob = mRunningJob;
                 try {
-                    mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
+                    mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getSourceUid());
                 } catch (RemoteException e) {
                     // Whatever.
                 }
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index b8aa9dd..c88f5d7 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -312,7 +312,7 @@
                         Slog.d(TAG, "Saving job " + jobStatus.getJobId());
                     }
                     out.startTag(null, "job");
-                    addIdentifierAttributesToJobTag(out, jobStatus);
+                    addAttributesToJobTag(out, jobStatus);
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getExtras(), out);
@@ -337,13 +337,20 @@
             }
         }
 
-        /** Write out a tag with data comprising the required fields of this job and its client. */
-        private void addIdentifierAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
+        /** Write out a tag with data comprising the required fields and priority of this job and
+         * its client.
+         */
+        private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
                 throws IOException {
             out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
             out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
             out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
+            if (jobStatus.getSourcePackageName() != null) {
+                out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
+            }
+            out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
             out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
+            out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
         }
 
         private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
@@ -361,9 +368,9 @@
             PersistableBundle copy = (PersistableBundle) bundle.clone();
             Set<String> keySet = bundle.keySet();
             for (String key: keySet) {
-                PersistableBundle b = copy.getPersistableBundle(key);
-                if (b != null) {
-                    PersistableBundle bCopy = deepCopyBundle(b, maxDepth-1);
+                Object o = copy.get(key);
+                if (o instanceof PersistableBundle) {
+                    PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
                     copy.putPersistableBundle(key, bCopy);
                 }
             }
@@ -539,18 +546,27 @@
         private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
                 IOException {
             JobInfo.Builder jobBuilder;
-            int uid;
+            int uid, userId;
 
-            // Read out job identifier attributes.
+            // Read out job identifier attributes and priority.
             try {
                 jobBuilder = buildBuilderFromXml(parser);
                 jobBuilder.setPersisted(true);
                 uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
+
+                String val = parser.getAttributeValue(null, "priority");
+                if (val != null) {
+                    jobBuilder.setPriority(Integer.valueOf(val));
+                }
+                val = parser.getAttributeValue(null, "sourceUserId");
+                userId = val == null ? -1 : Integer.valueOf(val);
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Error parsing job's required fields, skipping");
                 return null;
             }
 
+            final String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
+
             int eventType;
             // Read out constraints tag.
             do {
@@ -664,8 +680,12 @@
             jobBuilder.setExtras(extras);
             parser.nextTag(); // Consume </extras>
 
-            return new JobStatus(
+            JobStatus js = new JobStatus(
                     jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
+            if (userId != -1) {
+                js.setSource(sourcePackageName, userId);
+            }
+            return js;
         }
 
         private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 6fc02f6..c09e06c 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -65,9 +65,9 @@
     public void maybeStartTrackingJob(JobStatus jobStatus) {
         synchronized (mTrackedTasks) {
             mTrackedTasks.add(jobStatus);
-            String packageName = jobStatus.job.getService().getPackageName();
+            String packageName = jobStatus.getSourcePackageName();
             final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
-                    jobStatus.uId, jobStatus.getUserId());
+                    jobStatus.getSourceUid(), jobStatus.getSourceUserId());
             if (DEBUG) {
                 Slog.d(LOG_TAG, "Start tracking, setting idle state of "
                         + packageName + " to " + appIdle);
@@ -89,7 +89,7 @@
         pw.println("Parole On: " + mAppIdleParoleOn);
         synchronized (mTrackedTasks) {
             for (JobStatus task : mTrackedTasks) {
-                pw.print(task.job.getService().getPackageName());
+                pw.print(task.getSourcePackageName());
                 pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get());
                 pw.print(", ");
             }
@@ -106,9 +106,9 @@
             }
             mAppIdleParoleOn = isAppIdleParoleOn;
             for (JobStatus task : mTrackedTasks) {
-                String packageName = task.job.getService().getPackageName();
+                String packageName = task.getSourcePackageName();
                 final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
-                        task.uId, task.getUserId());
+                        task.getSourceUid(), task.getSourceUserId());
                 if (DEBUG) {
                     Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
                 }
@@ -133,8 +133,8 @@
                     return;
                 }
                 for (JobStatus task : mTrackedTasks) {
-                    if (task.job.getService().getPackageName().equals(packageName)
-                            && task.getUserId() == userId) {
+                    if (task.getSourcePackageName().equals(packageName)
+                            && task.getSourceUserId() == userId) {
                         if (task.appNotIdleConstraintSatisfied.get() != !idle) {
                             if (DEBUG) {
                                 Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 92df851..fe5e8c9 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -34,14 +34,13 @@
 public class IdleController extends StateController {
     private static final String TAG = "IdleController";
 
-    // Policy: we decide that we're "idle" if the device has been unused /
-    // screen off or dreaming for at least this long
-    private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
-    private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice
-
     private static final String ACTION_TRIGGER_IDLE =
             "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
 
+    // Policy: we decide that we're "idle" if the device has been unused /
+    // screen off or dreaming for at least this long
+    private long mInactivityIdleThreshold;
+    private long mIdleWindowSlop;
     final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
     IdlenessTracker mIdleTracker;
 
@@ -100,6 +99,10 @@
      * significant state changes occur
      */
     private void initIdleStateTracking() {
+        mInactivityIdleThreshold = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
+        mIdleWindowSlop = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
         mIdleTracker = new IdlenessTracker();
         mIdleTracker.startTracking();
     }
@@ -168,14 +171,14 @@
                 // alarm that will tell us when we have decided the device is
                 // truly idle.
                 final long nowElapsed = SystemClock.elapsedRealtime();
-                final long when = nowElapsed + INACTIVITY_IDLE_THRESHOLD;
+                final long when = nowElapsed + mInactivityIdleThreshold;
                 if (DEBUG) {
                     Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
                             + when);
                 }
                 mScreenOn = false;
                 mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                        when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
+                        when, mIdleWindowSlop, mIdleTriggerIntent);
             } else if (action.equals(ACTION_TRIGGER_IDLE)) {
                 // idle time starts now. Do not set mIdle if screen is on.
                 if (!mIdle && !mScreenOn) {
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 060a93e..a621e6a 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -16,9 +16,11 @@
 
 package com.android.server.job.controllers;
 
+import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
@@ -47,6 +49,10 @@
     final String name;
     final String tag;
 
+    String sourcePackageName;
+    int sourceUserId = -1;
+    int sourceUid = -1;
+
     // Constraints.
     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
     final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
@@ -77,6 +83,7 @@
     private JobStatus(JobInfo job, int uId, int numFailures) {
         this.job = job;
         this.uId = uId;
+        this.sourceUid = uId;
         this.name = job.getService().flattenToShortString();
         this.tag = "*job*/" + this.name;
         this.numFailures = numFailures;
@@ -146,6 +153,21 @@
         return job.getService();
     }
 
+    public String getSourcePackageName() {
+        return sourcePackageName != null ? sourcePackageName : job.getService().getPackageName();
+    }
+
+    public int getSourceUid() {
+        return sourceUid;
+    }
+
+    public int getSourceUserId() {
+        if (sourceUserId == -1) {
+            sourceUserId = getUserId();
+        }
+        return sourceUserId;
+    }
+
     public int getUserId() {
         return UserHandle.getUserId(uId);
     }
@@ -166,6 +188,10 @@
         return job.getExtras();
     }
 
+    public int getPriority() {
+        return job.getPriority();
+    }
+
     public boolean hasConnectivityConstraint() {
         return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
     }
@@ -259,6 +285,22 @@
         }
     }
 
+    public void setSource(String sourcePackageName, int sourceUserId) {
+        this.sourcePackageName = sourcePackageName;
+        this.sourceUserId = sourceUserId;
+        try {
+            sourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
+                    sourceUserId);
+        } catch (RemoteException ex) {
+            // Can't happen, PackageManager runs in the same process.
+        }
+        if (sourceUid == -1) {
+            sourceUid = uId;
+            this.sourceUserId = getUserId();
+            this.sourcePackageName = null;
+        }
+    }
+
     /**
      * Convenience function to identify a job uniquely without pulling all the data that
      * {@link #toString()} returns.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 018bf2d..b1fe68c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1727,6 +1727,14 @@
         }
 
         @Override
+        public int getRuleInstanceCount(ComponentName owner) throws RemoteException {
+            Preconditions.checkNotNull(owner, "Owner is null");
+            enforceSystemOrSystemUI("getRuleInstanceCount");
+
+            return mZenModeHelper.getCurrentInstanceCount(owner);
+        }
+
+        @Override
         public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
             enforcePolicyAccess(pkg, "setInterruptionFilter");
             final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f7043a6..1d91fb7 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,7 +27,10 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
@@ -45,7 +48,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
-import android.service.notification.IConditionListener;
+import android.service.notification.ConditionProviderService;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
@@ -91,6 +94,7 @@
     private final ZenModeConditions mConditions;
     private final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
     private final Metrics mMetrics = new Metrics();
+    private final ConditionProviders.Config mServiceConfig;
 
     private int mZenMode;
     private int mUser = UserHandle.USER_SYSTEM;
@@ -113,6 +117,7 @@
         mSettingsObserver.observe();
         mFiltering = new ZenModeFiltering(mContext);
         mConditions = new ZenModeConditions(this, conditionProviders);
+        mServiceConfig = conditionProviders.getConfig();
     }
 
     public Looper getLooper() {
@@ -197,7 +202,7 @@
             config.user = user;
         }
         synchronized (mConfig) {
-            setConfig(config, "onUserSwitched");
+            setConfigLocked(config, "onUserSwitched");
         }
         cleanUpZenRules();
     }
@@ -257,22 +262,34 @@
     }
 
     public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
+        if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+            throw new IllegalArgumentException("Rule already exists");
+        }
+        if (!isSystemRule(automaticZenRule)) {
+            ServiceInfo owner = getServiceInfo(automaticZenRule.getOwner());
+            if (owner == null) {
+                throw new IllegalArgumentException("Owner is not a condition provider service");
+            }
+
+            final int ruleInstanceLimit = owner.metaData.getInt(
+                    ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
+            if (ruleInstanceLimit > 0 && ruleInstanceLimit
+                    < (getCurrentInstanceCount(automaticZenRule.getOwner()) + 1)) {
+                throw new IllegalArgumentException("Rule instance limit exceeded");
+            }
+        }
+
         ZenModeConfig newConfig;
         synchronized (mConfig) {
             if (mConfig == null) return null;
             if (DEBUG) {
-                Log.d(TAG,
-                        "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" + reason);
-            }
-            if (!TextUtils.isEmpty(automaticZenRule.getId())) {
-                throw new IllegalArgumentException("Rule already exists");
+                Log.d(TAG, "addAutomaticZenRule rule= " + automaticZenRule + " reason=" + reason);
             }
             newConfig = mConfig.copy();
-
             ZenRule rule = new ZenRule();
             populateZenRule(automaticZenRule, rule, true);
             newConfig.automaticRules.put(rule.id, rule);
-            if (setConfig(newConfig, reason, true)) {
+            if (setConfigLocked(newConfig, reason, true)) {
                 return createAutomaticZenRule(rule);
             } else {
                 return null;
@@ -302,7 +319,7 @@
             }
             populateZenRule(automaticZenRule, rule, false);
             newConfig.automaticRules.put(ruleId, rule);
-            return setConfig(newConfig, reason, true);
+            return setConfigLocked(newConfig, reason, true);
         }
     }
 
@@ -320,7 +337,7 @@
                 throw new SecurityException(
                         "Cannot delete rules not owned by your condition provider");
             }
-            return setConfig(newConfig, reason, true);
+            return setConfigLocked(newConfig, reason, true);
         }
     }
 
@@ -336,10 +353,22 @@
                     newConfig.automaticRules.removeAt(i);
                 }
             }
-            return setConfig(newConfig, reason, true);
+            return setConfigLocked(newConfig, reason, true);
         }
     }
 
+    public int getCurrentInstanceCount(ComponentName owner) {
+        int count = 0;
+        synchronized (mConfig) {
+            for (ZenRule rule : mConfig.automaticRules.values()) {
+                if (rule.component != null && rule.component.equals(owner)) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
     public boolean canManageAutomaticZenRule(ZenRule rule) {
         final int callingUid = Binder.getCallingUid();
         if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
@@ -361,6 +390,29 @@
         }
     }
 
+    private boolean isSystemRule(AutomaticZenRule rule) {
+        return ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
+    }
+
+    private ServiceInfo getServiceInfo(ComponentName owner) {
+        Intent queryIntent = new Intent();
+        queryIntent.setComponent(owner);
+        List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
+                queryIntent,
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                UserHandle.getCallingUserId());
+        if (installedServices != null) {
+            for (int i = 0, count = installedServices.size(); i < count; i++) {
+                ResolveInfo resolveInfo = installedServices.get(i);
+                ServiceInfo info = resolveInfo.serviceInfo;
+                if (mServiceConfig.bindPermission.equals(info.permission)) {
+                    return info;
+                }
+            }
+        }
+        return null;
+    }
+
     private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) {
         if (isNew) {
             rule.id = ZenModeConfig.newRuleId();
@@ -413,7 +465,7 @@
                 newRule.conditionId = conditionId;
                 newConfig.manualRule = newRule;
             }
-            setConfig(newConfig, reason, setRingerMode);
+            setConfigLocked(newConfig, reason, setRingerMode);
         }
     }
 
@@ -478,7 +530,7 @@
             }
             if (DEBUG) Log.d(TAG, "readXml");
             synchronized (mConfig) {
-                setConfig(config, "readXml");
+                setConfigLocked(config, "readXml");
             }
         }
     }
@@ -507,7 +559,7 @@
         synchronized (mConfig) {
             final ZenModeConfig newConfig = mConfig.copy();
             newConfig.applyNotificationPolicy(policy);
-            setConfig(newConfig, "setNotificationPolicy");
+            setConfigLocked(newConfig, "setNotificationPolicy");
         }
     }
 
@@ -530,7 +582,7 @@
                     }
                 }
             }
-            setConfig(newConfig, "cleanUpZenRules");
+            setConfigLocked(newConfig, "cleanUpZenRules");
         }
     }
 
@@ -543,30 +595,30 @@
         }
     }
 
-    public boolean setConfig(ZenModeConfig config, String reason) {
-        return setConfig(config, reason, true /*setRingerMode*/);
+    public boolean setConfigLocked(ZenModeConfig config, String reason) {
+        return setConfigLocked(config, reason, true /*setRingerMode*/);
     }
 
     public void setConfigAsync(ZenModeConfig config, String reason) {
         mHandler.postSetConfig(config, reason);
     }
 
-    private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+    private boolean setConfigLocked(ZenModeConfig config, String reason, boolean setRingerMode) {
         final long identity = Binder.clearCallingIdentity();
         try {
             if (config == null || !config.isValid()) {
-                Log.w(TAG, "Invalid config in setConfig; " + config);
+                Log.w(TAG, "Invalid config in setConfigLocked; " + config);
                 return false;
             }
             if (config.user != mUser) {
                 // simply store away for background users
                 mConfigs.put(config.user, config);
-                if (DEBUG) Log.d(TAG, "setConfig: store config for user " + config.user);
+                if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
                 return true;
             }
             mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
             mConfigs.put(config.user, config);
-            if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+            if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
             ZenLog.traceConfig(reason, mConfig, config);
             final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
                     getNotificationPolicy(config));
@@ -1041,7 +1093,7 @@
                 case MSG_SET_CONFIG:
                     ConfigMessageData configData = (ConfigMessageData)msg.obj;
                     synchronized (mConfig) {
-                        setConfig(configData.config, configData.reason);
+                        setConfigLocked(configData.config, configData.reason);
                     }
                     break;
             }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 190eca6..508bf91 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageStats;
@@ -28,6 +29,9 @@
 
 import dalvik.system.VMRuntime;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 public final class Installer extends SystemService {
     private static final String TAG = "Installer";
 
@@ -46,8 +50,17 @@
     /** Run the application with the JIT compiler */
     public static final int DEXOPT_USEJIT       = 1 << 5;
 
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FLAG_DE_STORAGE,
+            FLAG_CE_STORAGE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StorageFlags {}
+
     public static final int FLAG_DE_STORAGE = 1 << 0;
     public static final int FLAG_CE_STORAGE = 1 << 1;
+
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 2;
     public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 3;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index d04eedc..f243e63 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -25,6 +25,11 @@
 public class PackageManagerException extends Exception {
     public final int error;
 
+    public PackageManagerException(String detailMessage) {
+        super(detailMessage);
+        this.error = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+    }
+
     public PackageManagerException(int error, String detailMessage) {
         super(detailMessage);
         this.error = error;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f777faf..6277310 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -46,7 +46,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
-import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
@@ -76,7 +75,6 @@
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
-
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -192,7 +190,6 @@
 import android.security.SystemKeyStore;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.StructStat;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -227,6 +224,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
 import com.android.server.IntentResolver;
@@ -234,6 +232,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
+import com.android.server.pm.Installer.StorageFlags;
 import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
@@ -316,6 +315,7 @@
     private static final boolean DEBUG_ABI_SELECTION = false;
     private static final boolean DEBUG_EPHEMERAL = false;
     private static final boolean DEBUG_TRIAGED_MISSING = false;
+    private static final boolean DEBUG_APP_DATA = false;
 
     static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
 
@@ -518,9 +518,6 @@
     // If mac_permissions.xml was found for seinfo labeling.
     boolean mFoundPolicyFile;
 
-    // If a recursive restorecon of /data/data/<pkg> is needed.
-    private boolean mShouldRestoreconData = SELinuxMMAC.shouldRestorecon();
-
     private final EphemeralApplicationRegistry mEphemeralApplicationRegistry;
 
     public static final class SharedLibraryEntry {
@@ -978,6 +975,30 @@
     private static final String TAG_DEFAULT_APPS = "da";
     private static final String TAG_INTENT_FILTER_VERIFICATION = "iv";
 
+    private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
+    private static final String TAG_ALL_GRANTS = "rt-grants";
+    private static final String TAG_GRANT = "grant";
+    private static final String ATTR_PACKAGE_NAME = "pkg";
+
+    private static final String TAG_PERMISSION = "perm";
+    private static final String ATTR_PERMISSION_NAME = "name";
+    private static final String ATTR_IS_GRANTED = "g";
+    private static final String ATTR_USER_SET = "set";
+    private static final String ATTR_USER_FIXED = "fixed";
+    private static final String ATTR_REVOKE_ON_UPGRADE = "rou";
+
+    // System/policy permission grants are not backed up
+    private static final int SYSTEM_RUNTIME_GRANT_MASK =
+            FLAG_PERMISSION_POLICY_FIXED
+            | FLAG_PERMISSION_SYSTEM_FIXED
+            | FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+
+    // And we back up these user-adjusted states
+    private static final int USER_RUNTIME_GRANT_MASK =
+            FLAG_PERMISSION_USER_SET
+            | FLAG_PERMISSION_USER_FIXED
+            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
     final @Nullable String mRequiredVerifierPackage;
     final @Nullable String mRequiredInstallerPackage;
 
@@ -1490,16 +1511,25 @@
                                 deleteOld = true;
                             }
 
-                            // If this app is a browser and it's newly-installed for some
-                            // users, clear any default-browser state in those users
+
+                            // Work that needs to happen on first install within each user
                             if (firstUsers.length > 0) {
-                                // the app's nature doesn't depend on the user, so we can just
-                                // check its browser nature in any user and generalize.
-                                if (packageIsBrowser(packageName, firstUsers[0])) {
+                                for (int userId : firstUsers) {
                                     synchronized (mPackages) {
-                                        for (int userId : firstUsers) {
-                                            mSettings.setDefaultBrowserPackageNameLPw(null, userId);
+                                        // If this app is a browser and it's newly-installed for
+                                        // some users, clear any default-browser state in those
+                                        // users.  The app's nature doesn't depend on the user,
+                                        // so we can just check its browser nature in any user
+                                        // and generalize.
+                                        if (packageIsBrowser(packageName, firstUsers[0])) {
+                                            mSettings.setDefaultBrowserPackageNameLPw(
+                                                    null, userId);
                                         }
+
+                                        // We may also need to apply pending (restored) runtime
+                                        // permission grants within these users.
+                                        mSettings.applyPendingPermissionGrantsLPw(
+                                                packageName, userId);
                                     }
                                 }
                             }
@@ -1750,7 +1780,7 @@
         @Override
         public void onVolumeForgotten(String fsUuid) {
             if (TextUtils.isEmpty(fsUuid)) {
-                Slog.w(TAG, "Forgetting internal storage is probably a mistake; ignoring");
+                Slog.e(TAG, "Forgetting internal storage is probably a mistake; ignoring");
                 return;
             }
 
@@ -2342,6 +2372,17 @@
                 }
             }
 
+            // Prepare storage for system user really early during boot,
+            // since core system apps like SettingsProvider and SystemUI
+            // can't wait for user to start
+            final int flags;
+            if (StorageManager.isFileBasedEncryptionEnabled()) {
+                flags = Installer.FLAG_DE_STORAGE;
+            } else {
+                flags = Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE;
+            }
+            reconcileAppsData(StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.USER_SYSTEM, flags);
+
             // If this is first boot after an OTA, and a normal boot, then
             // we need to clear code cache directories.
             if (mIsUpgrade && !onlyCore) {
@@ -6719,23 +6760,6 @@
         return true;
     }
 
-    private void createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo)
-            throws PackageManagerException {
-        // TODO: triage flags as part of 26466827
-        final int appId = UserHandle.getAppId(uid);
-        final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
-
-        try {
-            final int[] users = sUserManager.getUserIds();
-            for (int user : users) {
-                mInstaller.createAppData(volumeUuid, packageName, user, flags, appId, seinfo);
-            }
-        } catch (InstallerException e) {
-            throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
-                    "Failed to prepare data directory", e);
-        }
-    }
-
     private boolean removeDataDirsLI(String volumeUuid, String packageName) {
         // TODO: triage flags as part of 26466827
         final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
@@ -6765,6 +6789,23 @@
         }
     }
 
+    void destroyAppDataLI(String volumeUuid, String packageName, int userId, int flags) {
+        try {
+            mInstaller.destroyAppData(volumeUuid, packageName, userId, flags);
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to destroy app data", e);
+        }
+    }
+
+    void restoreconAppDataLI(String volumeUuid, String packageName, int userId, int flags,
+            int appId, String seinfo) {
+        try {
+            mInstaller.restoreconAppData(volumeUuid, packageName, userId, flags, appId, seinfo);
+        } catch (InstallerException e) {
+            Slog.e(TAG, "Failed to restorecon for " + packageName + ": " + e);
+        }
+    }
+
     private void deleteCodeCacheDirsLI(String volumeUuid, String packageName) {
         // TODO: triage flags as part of 26466827
         final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
@@ -7274,104 +7315,8 @@
                 pkg.applicationInfo.uid);
 
         if (pkg != mPlatformPackage) {
-            // This is a normal package, need to make its data directory.
-            final File dataPath = Environment.getDataUserCredentialEncryptedPackageDirectory(
-                    pkg.volumeUuid, UserHandle.USER_SYSTEM, pkg.packageName);
-
-            // TOOD: switch to ensure various directories
-
-            boolean uidError = false;
-            if (dataPath.exists()) {
-                int currentUid = 0;
-                try {
-                    StructStat stat = Os.stat(dataPath.getPath());
-                    currentUid = stat.st_uid;
-                } catch (ErrnoException e) {
-                    Slog.e(TAG, "Couldn't stat path " + dataPath.getPath(), e);
-                }
-
-                // If we have mismatched owners for the data path, we have a problem.
-                if (currentUid != pkg.applicationInfo.uid) {
-                    boolean recovered = false;
-                    if (((parseFlags & PackageParser.PARSE_IS_SYSTEM) != 0
-                            || (scanFlags & SCAN_BOOTING) != 0)) {
-                        // If this is a system app, we can at least delete its
-                        // current data so the application will still work.
-                        boolean res = removeDataDirsLI(pkg.volumeUuid, pkgName);
-                        if (res) {
-                            // TODO: Kill the processes first
-                            // Old data gone!
-                            String prefix = (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0
-                                    ? "System package " : "Third party package ";
-                            String msg = prefix + pkg.packageName
-                                    + " has changed from uid: "
-                                    + currentUid + " to "
-                                    + pkg.applicationInfo.uid + "; old data erased";
-                            reportSettingsProblem(Log.WARN, msg);
-                            recovered = true;
-                        }
-                        if (!recovered) {
-                            mHasSystemUidErrors = true;
-                        }
-                    } else {
-                        // If we allow this install to proceed, we will be broken.
-                        // Abort, abort!
-                        throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED,
-                                "Expected data to be owned by UID " + pkg.applicationInfo.uid
-                                        + " but found " + currentUid);
-                    }
-                    if (!recovered) {
-                        pkg.applicationInfo.dataDir = "/mismatched_uid/settings_"
-                            + pkg.applicationInfo.uid + "/fs_"
-                            + currentUid;
-                        pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir;
-                        pkg.applicationInfo.nativeLibraryRootDir = pkg.applicationInfo.dataDir;
-                        String msg = "Package " + pkg.packageName
-                                + " has mismatched uid: "
-                                + currentUid + " on disk, "
-                                + pkg.applicationInfo.uid + " in settings";
-                        // writer
-                        synchronized (mPackages) {
-                            mSettings.mReadMessages.append(msg);
-                            mSettings.mReadMessages.append('\n');
-                            uidError = true;
-                            if (!pkgSetting.uidError) {
-                                reportSettingsProblem(Log.ERROR, msg);
-                            }
-                        }
-                    }
-                }
-
-                // Ensure that directories are prepared
-                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
-                        pkg.applicationInfo.seinfo);
-
-                if (mShouldRestoreconData) {
-                    Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
-                    // TODO: extend this to restorecon over all users
-                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-                    // TODO: triage flags as part of 26466827
-                    final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
-                    try {
-                        mInstaller.restoreconAppData(pkg.volumeUuid, pkg.packageName,
-                                UserHandle.USER_SYSTEM, flags, appId, pkg.applicationInfo.seinfo);
-                    } catch (InstallerException e) {
-                        Slog.w(TAG, "Failed to restorecon " + pkg.packageName, e);
-                    }
-                }
-            } else {
-                if (DEBUG_PACKAGE_SCANNING) {
-                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
-                        Log.v(TAG, "Want this data dir: " + dataPath);
-                }
-                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
-                        pkg.applicationInfo.seinfo);
-            }
-
             // Get all of our default paths setup
             pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
-
-            pkgSetting.uidError = uidError;
         }
 
         final String path = scanFile.getPath();
@@ -7405,49 +7350,6 @@
             setNativeLibraryPaths(pkg);
         }
 
-        if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
-        final int[] userIds = sUserManager.getUserIds();
-        synchronized (mInstallLock) {
-            // Make sure all user data directories are ready to roll; we're okay
-            // if they already exist
-            if (!TextUtils.isEmpty(pkg.volumeUuid)) {
-                for (int userId : userIds) {
-                    if (userId != UserHandle.USER_SYSTEM) {
-                        // TODO: triage flags as part of 26466827
-                        final int flags = Installer.FLAG_CE_STORAGE | Installer.FLAG_DE_STORAGE;
-                        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-                        try {
-                            mInstaller.createAppData(pkg.volumeUuid, pkg.packageName, userId,
-                                    flags, appId, pkg.applicationInfo.seinfo);
-                        } catch (InstallerException e) {
-                            throw PackageManagerException.from(e);
-                        }
-                    }
-                }
-            }
-
-            // Create a native library symlink only if we have native libraries
-            // and if the native libraries are 32 bit libraries. We do not provide
-            // this symlink for 64 bit libraries.
-            if (pkg.applicationInfo.primaryCpuAbi != null &&
-                    !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "linkNativeLib");
-                try {
-                    final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
-                    for (int userId : userIds) {
-                        try {
-                            mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
-                                    nativeLibPath, userId);
-                        } catch (InstallerException e) {
-                            throw PackageManagerException.from(e);
-                        }
-                    }
-                } finally {
-                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-                }
-            }
-        }
-
         // This is a special case for the "system" package, where the ABI is
         // dictated by the zygote configuration (and init.rc). We should keep track
         // of this ABI so that we can deal with "normal" applications that run under
@@ -10216,7 +10118,7 @@
 
         long callingId = Binder.clearCallingIdentity();
         try {
-            boolean sendAdded = false;
+            boolean installed = false;
 
             // writer
             synchronized (mPackages) {
@@ -10228,11 +10130,14 @@
                     pkgSetting.setInstalled(true, userId);
                     pkgSetting.setHidden(false, userId);
                     mSettings.writePackageRestrictionsLPr(userId);
-                    sendAdded = true;
+                    if (pkgSetting.pkg != null) {
+                        prepareAppDataAfterInstall(pkgSetting.pkg);
+                    }
+                    installed = true;
                 }
             }
 
-            if (sendAdded) {
+            if (installed) {
                 sendPackageAddedForUser(packageName, pkgSetting, userId);
             }
         } finally {
@@ -12358,6 +12263,8 @@
                     System.currentTimeMillis(), user);
 
             updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
+            prepareAppDataAfterInstall(newPackage);
+
             // delete the partially installed application. the data directory will have to be
             // restored if it was already existing
             if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -12514,6 +12421,7 @@
                         scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
                 updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
                         perUserInstalled, res, user);
+                prepareAppDataAfterInstall(newPackage);
                 updatedSettings = true;
             } catch (PackageManagerException e) {
                 res.setError("Package couldn't be installed in " + pkg.codePath, e);
@@ -12645,6 +12553,7 @@
             if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                 updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
                         perUserInstalled, res, user);
+                prepareAppDataAfterInstall(newPackage);
                 updatedSettings = true;
             }
 
@@ -13682,6 +13591,8 @@
             return false;
         }
 
+        prepareAppDataAfterInstall(newPkg);
+
         // writer
         synchronized (mPackages) {
             PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
@@ -14813,7 +14724,7 @@
             }
             return;
         }
-
+Slog.v(TAG, ":: restoreFromXml() : got to tag " + parser.getName());
         // this is supposed to be TAG_PREFERRED_BACKUP
         if (!expectedStartTag.equals(parser.getName())) {
             if (DEBUG_BACKUP) {
@@ -14824,6 +14735,7 @@
 
         // skip interfering stuff, then we're aligned with the backing implementation
         while ((type = parser.next()) == XmlPullParser.TEXT) { }
+Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
         functor.apply(parser, userId);
     }
 
@@ -15011,6 +14923,192 @@
     }
 
     @Override
+    public byte[] getPermissionGrantBackup(int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call getPermissionGrantBackup()");
+        }
+
+        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+        try {
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null, true);
+            serializer.startTag(null, TAG_PERMISSION_BACKUP);
+
+            synchronized (mPackages) {
+                serializeRuntimePermissionGrantsLPr(serializer, userId);
+            }
+
+            serializer.endTag(null, TAG_PERMISSION_BACKUP);
+            serializer.endDocument();
+            serializer.flush();
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Unable to write default apps for backup", e);
+            }
+            return null;
+        }
+
+        return dataStream.toByteArray();
+    }
+
+    @Override
+    public void restorePermissionGrants(byte[] backup, int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call restorePermissionGrants()");
+        }
+
+        try {
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+            restoreFromXml(parser, userId, TAG_PERMISSION_BACKUP,
+                    new BlobXmlRestorer() {
+                        @Override
+                        public void apply(XmlPullParser parser, int userId)
+                                throws XmlPullParserException, IOException {
+                            synchronized (mPackages) {
+                                processRestoredPermissionGrantsLPr(parser, userId);
+                            }
+                        }
+                    } );
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+            }
+        }
+    }
+
+    private void serializeRuntimePermissionGrantsLPr(XmlSerializer serializer, final int userId)
+            throws IOException {
+        serializer.startTag(null, TAG_ALL_GRANTS);
+
+        final int N = mSettings.mPackages.size();
+        for (int i = 0; i < N; i++) {
+            final PackageSetting ps = mSettings.mPackages.valueAt(i);
+            boolean pkgGrantsKnown = false;
+
+            PermissionsState packagePerms = ps.getPermissionsState();
+
+            for (PermissionState state : packagePerms.getRuntimePermissionStates(userId)) {
+                final int grantFlags = state.getFlags();
+                // only look at grants that are not system/policy fixed
+                if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) == 0) {
+                    final boolean isGranted = state.isGranted();
+                    // And only back up the user-twiddled state bits
+                    if (isGranted || (grantFlags & USER_RUNTIME_GRANT_MASK) != 0) {
+                        final String packageName = mSettings.mPackages.keyAt(i);
+                        if (!pkgGrantsKnown) {
+                            serializer.startTag(null, TAG_GRANT);
+                            serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
+                            pkgGrantsKnown = true;
+                        }
+
+                        final boolean userSet =
+                                (grantFlags & FLAG_PERMISSION_USER_SET) != 0;
+                        final boolean userFixed =
+                                (grantFlags & FLAG_PERMISSION_USER_FIXED) != 0;
+                        final boolean revoke =
+                                (grantFlags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+
+                        serializer.startTag(null, TAG_PERMISSION);
+                        serializer.attribute(null, ATTR_PERMISSION_NAME, state.getName());
+                        if (isGranted) {
+                            serializer.attribute(null, ATTR_IS_GRANTED, "true");
+                        }
+                        if (userSet) {
+                            serializer.attribute(null, ATTR_USER_SET, "true");
+                        }
+                        if (userFixed) {
+                            serializer.attribute(null, ATTR_USER_FIXED, "true");
+                        }
+                        if (revoke) {
+                            serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+                        }
+                        serializer.endTag(null, TAG_PERMISSION);
+                    }
+                }
+            }
+
+            if (pkgGrantsKnown) {
+                serializer.endTag(null, TAG_GRANT);
+            }
+        }
+
+        serializer.endTag(null, TAG_ALL_GRANTS);
+    }
+
+    private void processRestoredPermissionGrantsLPr(XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        String pkgName = null;
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            final String tagName = parser.getName();
+            if (tagName.equals(TAG_GRANT)) {
+                pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                if (DEBUG_BACKUP) {
+                    Slog.v(TAG, "+++ Restoring grants for package " + pkgName);
+                }
+            } else if (tagName.equals(TAG_PERMISSION)) {
+
+                final boolean isGranted = "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED));
+                final String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
+
+                int newFlagSet = 0;
+                if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+                    newFlagSet |= FLAG_PERMISSION_USER_SET;
+                }
+                if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+                    newFlagSet |= FLAG_PERMISSION_USER_FIXED;
+                }
+                if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+                    newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                }
+                if (DEBUG_BACKUP) {
+                    Slog.v(TAG, "  + Restoring grant: pkg=" + pkgName + " perm=" + permName
+                            + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+                }
+                final PackageSetting ps = mSettings.mPackages.get(pkgName);
+                if (ps != null) {
+                    // Already installed so we apply the grant immediately
+                    if (DEBUG_BACKUP) {
+                        Slog.v(TAG, "        + already installed; applying");
+                    }
+                    PermissionsState perms = ps.getPermissionsState();
+                    BasePermission bp = mSettings.mPermissions.get(permName);
+                    if (bp != null) {
+                        if (isGranted) {
+                            perms.grantRuntimePermission(bp, userId);
+                        }
+                        if (newFlagSet != 0) {
+                            perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+                        }
+                    }
+                } else {
+                    // Need to wait for post-restore install to apply the grant
+                    if (DEBUG_BACKUP) {
+                        Slog.v(TAG, "        - not yet installed; saving for later");
+                    }
+                    mSettings.processRestoredPermissionGrantLPr(pkgName, permName,
+                            isGranted, newFlagSet, userId);
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <" + TAG_PERMISSION_BACKUP + ">: " + tagName);
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        scheduleWriteSettingsLocked();
+        mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+    }
+
+    @Override
     public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
             int sourceUserId, int targetUserId, int flags) {
         mContext.enforceCallingOrSelfPermission(
@@ -16022,6 +16120,10 @@
                 mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin);
             }
 
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS) && packageName == null) {
+                mSettings.dumpRestoredPermissionGrantsLPr(pw, dumpState);
+            }
+
             if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) {
                 // XXX should handle packageName != null by dumping only install data that
                 // the given package is involved with.
@@ -16169,10 +16271,6 @@
      */
     public void scanAvailableAsecs() {
         updateExternalMediaStatusInner(true, false, false);
-        if (mShouldRestoreconData) {
-            SELinuxMMAC.setRestoreconDone();
-            mShouldRestoreconData = false;
-        }
     }
 
     /*
@@ -16493,22 +16591,31 @@
     }
 
     private void loadPrivatePackagesInner(VolumeInfo vol) {
+        final String volumeUuid = vol.fsUuid;
+        if (TextUtils.isEmpty(volumeUuid)) {
+            Slog.e(TAG, "Loading internal storage is probably a mistake; ignoring");
+            return;
+        }
+
         final ArrayList<ApplicationInfo> loaded = new ArrayList<>();
         final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
 
         final VersionInfo ver;
         final List<PackageSetting> packages;
         synchronized (mPackages) {
-            ver = mSettings.findOrCreateVersion(vol.fsUuid);
-            packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+            ver = mSettings.findOrCreateVersion(volumeUuid);
+            packages = mSettings.getVolumePackagesLPr(volumeUuid);
         }
 
+        // TODO: introduce a new concept similar to "frozen" to prevent these
+        // apps from being launched until after data has been fully reconciled
         for (PackageSetting ps : packages) {
             synchronized (mInstallLock) {
                 final PackageParser.Package pkg;
                 try {
                     pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null);
                     loaded.add(pkg.applicationInfo);
+
                 } catch (PackageManagerException e) {
                     Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
                 }
@@ -16519,14 +16626,27 @@
             }
         }
 
+        // Reconcile app data for all started/unlocked users
+        final UserManager um = mContext.getSystemService(UserManager.class);
+        for (UserInfo user : um.getUsers()) {
+            if (um.isUserUnlocked(user.id)) {
+                reconcileAppsData(volumeUuid, user.id,
+                        Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE);
+            } else if (um.isUserRunning(user.id)) {
+                reconcileAppsData(volumeUuid, user.id, Installer.FLAG_DE_STORAGE);
+            } else {
+                continue;
+            }
+        }
+
         synchronized (mPackages) {
             int updateFlags = UPDATE_PERMISSIONS_ALL;
             if (ver.sdkVersion != mSdkVersion) {
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
-                        + mSdkVersion + "; regranting permissions for " + vol.fsUuid);
+                        + mSdkVersion + "; regranting permissions for " + volumeUuid);
                 updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, vol.fsUuid, updateFlags);
+            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -16548,10 +16668,16 @@
     }
 
     private void unloadPrivatePackagesInner(VolumeInfo vol) {
+        final String volumeUuid = vol.fsUuid;
+        if (TextUtils.isEmpty(volumeUuid)) {
+            Slog.e(TAG, "Unloading internal storage is probably a mistake; ignoring");
+            return;
+        }
+
         final ArrayList<ApplicationInfo> unloaded = new ArrayList<>();
         synchronized (mInstallLock) {
         synchronized (mPackages) {
-            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(volumeUuid);
             for (PackageSetting ps : packages) {
                 if (ps.pkg == null) continue;
 
@@ -16635,6 +16761,37 @@
         }
     }
 
+    private void assertPackageKnown(String volumeUuid, String packageName)
+            throws PackageManagerException {
+        synchronized (mPackages) {
+            final PackageSetting ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                throw new PackageManagerException("Package " + packageName + " is unknown");
+            } else if (!TextUtils.equals(volumeUuid, ps.volumeUuid)) {
+                throw new PackageManagerException(
+                        "Package " + packageName + " found on unknown volume " + volumeUuid
+                                + "; expected volume " + ps.volumeUuid);
+            }
+        }
+    }
+
+    private void assertPackageKnownAndInstalled(String volumeUuid, String packageName, int userId)
+            throws PackageManagerException {
+        synchronized (mPackages) {
+            final PackageSetting ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                throw new PackageManagerException("Package " + packageName + " is unknown");
+            } else if (!TextUtils.equals(volumeUuid, ps.volumeUuid)) {
+                throw new PackageManagerException(
+                        "Package " + packageName + " found on unknown volume " + volumeUuid
+                                + "; expected volume " + ps.volumeUuid);
+            } else if (!ps.getInstalled(userId)) {
+                throw new PackageManagerException(
+                        "Package " + packageName + " not installed for user " + userId);
+            }
+        }
+    }
+
     /**
      * Examine all apps present on given mounted volume, and destroy apps that
      * aren't expected, either due to uninstallation or reinstallation on
@@ -16651,37 +16808,223 @@
                 continue;
             }
 
-            boolean destroyApp = false;
-            String packageName = null;
             try {
                 final PackageLite pkg = PackageParser.parsePackageLite(file,
                         PackageParser.PARSE_MUST_BE_APK);
-                packageName = pkg.packageName;
+                assertPackageKnown(volumeUuid, pkg.packageName);
 
-                synchronized (mPackages) {
-                    final PackageSetting ps = mSettings.mPackages.get(packageName);
-                    if (ps == null) {
-                        logCriticalInfo(Log.WARN, "Destroying " + packageName + " on + "
-                                + volumeUuid + " because we found no install record");
-                        destroyApp = true;
-                    } else if (!TextUtils.equals(volumeUuid, ps.volumeUuid)) {
-                        logCriticalInfo(Log.WARN, "Destroying " + packageName + " on "
-                                + volumeUuid + " because we expected it on " + ps.volumeUuid);
-                        destroyApp = true;
-                    }
+            } catch (PackageParserException | PackageManagerException e) {
+                logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
+                synchronized (mInstallLock) {
+                    removeCodePathLI(file);
                 }
+            }
+        }
+    }
 
-            } catch (PackageParserException e) {
-                logCriticalInfo(Log.WARN, "Destroying " + file + " due to parse failure: " + e);
-                destroyApp = true;
+    /**
+     * Reconcile all app data for the given user.
+     * <p>
+     * Verifies that directories exist and that ownership and labeling is
+     * correct for all installed apps on all mounted volumes.
+     */
+    void reconcileAppsData(int userId, @StorageFlags int flags) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+            final String volumeUuid = vol.getFsUuid();
+            reconcileAppsData(volumeUuid, userId, flags);
+        }
+    }
+
+    /**
+     * Reconcile all app data on given mounted volume.
+     * <p>
+     * Destroys app data that isn't expected, either due to uninstallation or
+     * reinstallation on another volume.
+     * <p>
+     * Verifies that directories exist and that ownership and labeling is
+     * correct for all installed apps.
+     */
+    private void reconcileAppsData(String volumeUuid, int userId, @StorageFlags int flags) {
+        Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
+                + Integer.toHexString(flags));
+
+        final File ceDir = Environment.getDataUserCredentialEncryptedDirectory(volumeUuid, userId);
+        final File deDir = Environment.getDataUserDeviceEncryptedDirectory(volumeUuid, userId);
+
+        boolean restoreconNeeded = false;
+
+        // First look for stale data that doesn't belong, and check if things
+        // have changed since we did our last restorecon
+        if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+            if (!isUserKeyUnlocked(userId)) {
+                throw new RuntimeException(
+                        "Yikes, someone asked us to reconcile CE storage while " + userId
+                                + " was still locked; this would have caused massive data loss!");
             }
 
-            if (destroyApp) {
-                synchronized (mInstallLock) {
-                    if (packageName != null) {
-                        removeDataDirsLI(volumeUuid, packageName);
+            restoreconNeeded |= SELinuxMMAC.isRestoreconNeeded(ceDir);
+
+            final File[] files = FileUtils.listFilesOrEmpty(ceDir);
+            for (File file : files) {
+                final String packageName = file.getName();
+                try {
+                    assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
+                } catch (PackageManagerException e) {
+                    logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
+                    synchronized (mInstallLock) {
+                        destroyAppDataLI(volumeUuid, packageName, userId,
+                                Installer.FLAG_CE_STORAGE);
                     }
-                    removeCodePathLI(file);
+                }
+            }
+        }
+        if ((flags & Installer.FLAG_DE_STORAGE) != 0) {
+            restoreconNeeded |= SELinuxMMAC.isRestoreconNeeded(deDir);
+
+            final File[] files = FileUtils.listFilesOrEmpty(deDir);
+            for (File file : files) {
+                final String packageName = file.getName();
+                try {
+                    assertPackageKnownAndInstalled(volumeUuid, packageName, userId);
+                } catch (PackageManagerException e) {
+                    logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e);
+                    synchronized (mInstallLock) {
+                        destroyAppDataLI(volumeUuid, packageName, userId,
+                                Installer.FLAG_DE_STORAGE);
+                    }
+                }
+            }
+        }
+
+        // Ensure that data directories are ready to roll for all packages
+        // installed for this volume and user
+        final List<PackageSetting> packages;
+        synchronized (mPackages) {
+            packages = mSettings.getVolumePackagesLPr(volumeUuid);
+        }
+        int preparedCount = 0;
+        for (PackageSetting ps : packages) {
+            final String packageName = ps.name;
+            if (ps.pkg == null) {
+                Slog.w(TAG, "Odd, missing scanned package " + packageName);
+                // TODO: might be due to legacy ASEC apps; we should circle back
+                // and reconcile again once they're scanned
+                continue;
+            }
+
+            if (ps.getInstalled(userId)) {
+                prepareAppData(volumeUuid, userId, flags, ps.pkg, restoreconNeeded);
+                preparedCount++;
+            }
+        }
+
+        if (restoreconNeeded) {
+            if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+                SELinuxMMAC.setRestoreconDone(ceDir);
+            }
+            if ((flags & Installer.FLAG_DE_STORAGE) != 0) {
+                SELinuxMMAC.setRestoreconDone(deDir);
+            }
+        }
+
+        Slog.v(TAG, "reconcileAppsData finished " + preparedCount
+                + " packages; restoreconNeeded was " + restoreconNeeded);
+    }
+
+    /**
+     * Prepare app data for the given app just after it was installed or
+     * upgraded. This method carefully only touches users that it's installed
+     * for, and it forces a restorecon to handle any seinfo changes.
+     * <p>
+     * Verifies that directories exist and that ownership and labeling is
+     * correct for all installed apps. If there is an ownership mismatch, it
+     * will try recovering system apps by wiping data; third-party app data is
+     * left intact.
+     */
+    private void prepareAppDataAfterInstall(PackageParser.Package pkg) {
+        final PackageSetting ps;
+        synchronized (mPackages) {
+            ps = mSettings.mPackages.get(pkg.packageName);
+        }
+
+        final UserManager um = mContext.getSystemService(UserManager.class);
+        for (UserInfo user : um.getUsers()) {
+            final int flags;
+            if (um.isUserUnlocked(user.id)) {
+                flags = Installer.FLAG_DE_STORAGE | Installer.FLAG_CE_STORAGE;
+            } else if (um.isUserRunning(user.id)) {
+                flags = Installer.FLAG_DE_STORAGE;
+            } else {
+                continue;
+            }
+
+            if (ps.getInstalled(user.id)) {
+                // Whenever an app changes, force a restorecon of its data
+                // TODO: when user data is locked, mark that we're still dirty
+                prepareAppData(pkg.volumeUuid, user.id, flags, pkg, true);
+            }
+        }
+    }
+
+    /**
+     * Prepare app data for the given app.
+     * <p>
+     * Verifies that directories exist and that ownership and labeling is
+     * correct for all installed apps. If there is an ownership mismatch, this
+     * will try recovering system apps by wiping data; third-party app data is
+     * left intact.
+     */
+    private void prepareAppData(String volumeUuid, int userId, @StorageFlags int flags,
+            PackageParser.Package pkg, boolean restoreconNeeded) {
+        if (DEBUG_APP_DATA) {
+            Slog.v(TAG, "prepareAppData for " + pkg.packageName + " u" + userId + " 0x"
+                    + Integer.toHexString(flags) + (restoreconNeeded ? " restoreconNeeded" : ""));
+        }
+
+        final String packageName = pkg.packageName;
+        final ApplicationInfo app = pkg.applicationInfo;
+        final int appId = UserHandle.getAppId(app.uid);
+
+        Preconditions.checkNotNull(app.seinfo);
+
+        synchronized (mInstallLock) {
+            try {
+                mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+                        appId, app.seinfo);
+            } catch (InstallerException e) {
+                if (app.isSystemApp()) {
+                    logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+                            + ", but trying to recover: " + e);
+                    destroyAppDataLI(volumeUuid, packageName, userId, flags);
+                    try {
+                        mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+                                appId, app.seinfo);
+                        logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+                    } catch (InstallerException e2) {
+                        logCriticalInfo(Log.DEBUG, "Recovery failed!");
+                    }
+                } else {
+                    Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+                }
+            }
+
+            if (restoreconNeeded) {
+                restoreconAppDataLI(volumeUuid, packageName, userId, flags, appId, app.seinfo);
+            }
+
+            if ((flags & Installer.FLAG_CE_STORAGE) != 0) {
+                // Create a native library symlink only if we have native libraries
+                // and if the native libraries are 32 bit libraries. We do not provide
+                // this symlink for 64 bit libraries.
+                if (app.primaryCpuAbi != null && !VMRuntime.is64BitAbi(app.primaryCpuAbi)) {
+                    final String nativeLibPath = app.nativeLibraryDir;
+                    try {
+                        mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
+                                nativeLibPath, userId);
+                    } catch (InstallerException e) {
+                        Slog.e(TAG, "Failed to link native for " + packageName + ": " + e);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 903d12b..aa10c08 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -19,20 +19,24 @@
 import android.content.pm.PackageParser;
 import android.content.pm.Signature;
 import android.os.Environment;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 import android.util.Slog;
 import android.util.Xml;
 
 import libcore.io.IoUtils;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -41,9 +45,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 /**
  * Centralized access to SELinux MMAC (middleware MAC) implementation. This
  * class is responsible for loading the appropriate mac_permissions.xml file
@@ -63,42 +64,21 @@
     // to synchronize access during policy load and access attempts.
     private static List<Policy> sPolicies = new ArrayList<>();
 
-    // Data policy override version file.
-    private static final String DATA_VERSION_FILE =
-            Environment.getDataDirectory() + "/security/current/selinux_version";
+    /** Path to version on rootfs */
+    private static final File VERSION_FILE = new File("/selinux_version");
 
-    // Base policy version file.
-    private static final String BASE_VERSION_FILE = "/selinux_version";
+    /** Path to MAC permissions on system image */
+    private static final File MAC_PERMISSIONS = new File(Environment.getRootDirectory(),
+            "/etc/security/mac_permissions.xml");
 
-    // Whether override security policies should be loaded.
-    private static final boolean USE_OVERRIDE_POLICY = useOverridePolicy();
+    /** Path to app contexts on rootfs */
+    private static final File SEAPP_CONTEXTS = new File("/seapp_contexts");
 
-    // Data override mac_permissions.xml policy file.
-    private static final String DATA_MAC_PERMISSIONS =
-            Environment.getDataDirectory() + "/security/current/mac_permissions.xml";
+    /** Calculated hash of {@link #SEAPP_CONTEXTS} */
+    private static final byte[] SEAPP_CONTEXTS_HASH = returnHash(SEAPP_CONTEXTS);
 
-    // Base mac_permissions.xml policy file.
-    private static final String BASE_MAC_PERMISSIONS =
-            Environment.getRootDirectory() + "/etc/security/mac_permissions.xml";
-
-    // Determine which mac_permissions.xml file to use.
-    private static final String MAC_PERMISSIONS = USE_OVERRIDE_POLICY ?
-            DATA_MAC_PERMISSIONS : BASE_MAC_PERMISSIONS;
-
-    // Data override seapp_contexts policy file.
-    private static final String DATA_SEAPP_CONTEXTS =
-            Environment.getDataDirectory() + "/security/current/seapp_contexts";
-
-    // Base seapp_contexts policy file.
-    private static final String BASE_SEAPP_CONTEXTS = "/seapp_contexts";
-
-    // Determine which seapp_contexts file to use.
-    private static final String SEAPP_CONTEXTS = USE_OVERRIDE_POLICY ?
-            DATA_SEAPP_CONTEXTS : BASE_SEAPP_CONTEXTS;
-
-    // Stores the hash of the last used seapp_contexts file.
-    private static final String SEAPP_HASH_FILE =
-            Environment.getDataDirectory().toString() + "/system/seapp_hash";
+    /** Attribute where {@link #SEAPP_CONTEXTS_HASH} is stored */
+    private static final String XATTR_SEAPP_HASH = "user.seapp_hash";
 
     // Append privapp to existing seinfo label
     private static final String PRIVILEGED_APP_STR = ":privapp";
@@ -332,71 +312,42 @@
     }
 
     /**
-     * Determines if a recursive restorecon on /data/data and /data/user is needed.
-     * It does this by comparing the SHA-1 of the seapp_contexts file against the
-     * stored hash at /data/system/seapp_hash.
+     * Determines if a recursive restorecon on the given package data directory
+     * is needed. It does this by comparing the SHA-1 of the seapp_contexts file
+     * against the stored hash in an xattr.
+     * <p>
+     * Note that the xattr isn't in the 'security' namespace, so this should
+     * only be run on directories owned by the system.
      *
      * @return Returns true if the restorecon should occur or false otherwise.
      */
-    public static boolean shouldRestorecon() {
-        // Any error with the seapp_contexts file should be fatal
-        byte[] currentHash = null;
+    public static boolean isRestoreconNeeded(File file) {
         try {
-            currentHash = returnHash(SEAPP_CONTEXTS);
-        } catch (IOException ioe) {
-            Slog.e(TAG, "Error with hashing seapp_contexts.", ioe);
-            return false;
+            final byte[] buf = new byte[20];
+            final int len = Os.getxattr(file.getAbsolutePath(), XATTR_SEAPP_HASH, buf);
+            if ((len == 20) && Arrays.equals(SEAPP_CONTEXTS_HASH, buf)) {
+                return false;
+            }
+        } catch (ErrnoException e) {
+            if (e.errno != OsConstants.ENODATA) {
+                Slog.e(TAG, "Failed to read seapp hash for " + file, e);
+            }
         }
 
-        // Push past any error with the stored hash file
-        byte[] storedHash = null;
-        try {
-            storedHash = IoUtils.readFileAsByteArray(SEAPP_HASH_FILE);
-        } catch (IOException ioe) {
-            Slog.w(TAG, "Error opening " + SEAPP_HASH_FILE + ". Assuming first boot.");
-        }
-
-        return (storedHash == null || !MessageDigest.isEqual(storedHash, currentHash));
+        return true;
     }
 
     /**
-     * Stores the SHA-1 of the seapp_contexts to /data/system/seapp_hash.
+     * Stores the SHA-1 of the seapp_contexts into an xattr.
+     * <p>
+     * Note that the xattr isn't in the 'security' namespace, so this should
+     * only be run on directories owned by the system.
      */
-    public static void setRestoreconDone() {
+    public static void setRestoreconDone(File file) {
         try {
-            final byte[] currentHash = returnHash(SEAPP_CONTEXTS);
-            dumpHash(new File(SEAPP_HASH_FILE), currentHash);
-        } catch (IOException ioe) {
-            Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe);
-        }
-    }
-
-    /**
-     * Dump the contents of a byte array to a specified file.
-     *
-     * @param file The file that receives the byte array content.
-     * @param content A byte array that will be written to the specified file.
-     * @throws IOException if any failed I/O operation occured.
-     *         Included is the failure to atomically rename the tmp
-     *         file used in the process.
-     */
-    private static void dumpHash(File file, byte[] content) throws IOException {
-        FileOutputStream fos = null;
-        File tmp = null;
-        try {
-            tmp = File.createTempFile("seapp_hash", ".journal", file.getParentFile());
-            tmp.setReadable(true);
-            fos = new FileOutputStream(tmp);
-            fos.write(content);
-            fos.getFD().sync();
-            if (!tmp.renameTo(file)) {
-                throw new IOException("Failure renaming " + file.getCanonicalPath());
-            }
-        } finally {
-            if (tmp != null) {
-                tmp.delete();
-            }
-            IoUtils.closeQuietly(fos);
+            Os.setxattr(file.getAbsolutePath(), XATTR_SEAPP_HASH, SEAPP_CONTEXTS_HASH, 0);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Failed to persist seapp hash in " + file, e);
         }
     }
 
@@ -405,33 +356,15 @@
      *
      * @param file The path to the file given as a string.
      * @return Returns the SHA-1 of the file as a byte array.
-     * @throws IOException if any failed I/O operations occured.
      */
-    private static byte[] returnHash(String file) throws IOException {
+    private static byte[] returnHash(File file) {
         try {
-            final byte[] contents = IoUtils.readFileAsByteArray(file);
+            final byte[] contents = IoUtils.readFileAsByteArray(file.getAbsolutePath());
             return MessageDigest.getInstance("SHA-1").digest(contents);
-        } catch (NoSuchAlgorithmException nsae) {
-            throw new RuntimeException(nsae);  // impossible
+        } catch (IOException | NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
         }
     }
-
-    private static boolean useOverridePolicy() {
-        try {
-            final String overrideVersion = IoUtils.readFileAsString(DATA_VERSION_FILE);
-            final String baseVersion = IoUtils.readFileAsString(BASE_VERSION_FILE);
-            if (overrideVersion.equals(baseVersion)) {
-                return true;
-            }
-            Slog.e(TAG, "Override policy version '" + overrideVersion + "' doesn't match " +
-                   "base version '" + baseVersion + "'. Skipping override policy files.");
-        } catch (FileNotFoundException fnfe) {
-            // Override version file doesn't have to exist so silently ignore.
-        } catch (IOException ioe) {
-            Slog.w(TAG, "Skipping override policy files.", ioe);
-        }
-        return false;
-    }
 }
 
 /**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9fef515..3dee70d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -22,6 +22,9 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
@@ -32,7 +35,6 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.os.Process.PACKAGE_INFO_GID;
 import static android.os.Process.SYSTEM_UID;
-
 import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
 
 import android.annotation.NonNull;
@@ -217,6 +219,22 @@
     private static final String ATTR_SDK_VERSION = "sdkVersion";
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
 
+    // Bookkeeping for restored permission grants
+    private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
+    // package name: ATTR_PACKAGE_NAME
+    private static final String TAG_PERMISSION_ENTRY = "perm";
+    // permission name: ATTR_NAME
+    // permission granted (boolean): ATTR_GRANTED
+    private static final String ATTR_USER_SET = "set";
+    private static final String ATTR_USER_FIXED = "fixed";
+    private static final String ATTR_REVOKE_ON_UPGRADE = "rou";
+
+    // Flag mask of restored permission grants that are applied at install time
+    private static final int USER_RUNTIME_GRANT_MASK =
+            FLAG_PERMISSION_USER_SET
+            | FLAG_PERMISSION_USER_FIXED
+            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
     private final Object mLock;
 
     private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
@@ -238,6 +256,26 @@
     private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
             new ArrayMap<String, IntentFilterVerificationInfo>();
 
+    // Bookkeeping for restored user permission grants
+    final class RestoredPermissionGrant {
+        String permissionName;
+        boolean granted;
+        int grantBits;
+
+        RestoredPermissionGrant(String name, boolean isGranted, int theGrantBits) {
+            permissionName = name;
+            granted = isGranted;
+            grantBits = theGrantBits;
+        }
+    }
+
+    // This would be more compact as a flat array of restored grants or something, but we
+    // may have quite a few, especially during early device lifetime, and avoiding all those
+    // linear lookups will be important.
+    private final SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>
+            mRestoredUserGrants =
+                new SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>();
+
     private static int mFirstAvailableUid = 0;
 
     /** Map from volume UUID to {@link VersionInfo} */
@@ -388,7 +426,7 @@
         return mPackages.get(name);
     }
 
-    void setInstallStatus(String pkgName, int status) {
+    void setInstallStatus(String pkgName, final int status) {
         PackageSetting p = mPackages.get(pkgName);
         if(p != null) {
             if(p.getInstallStatus() != status) {
@@ -397,6 +435,43 @@
         }
     }
 
+    void applyPendingPermissionGrantsLPw(String packageName, int userId) {
+        ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+                mRestoredUserGrants.get(userId);
+        if (grantsByPackage == null || grantsByPackage.size() == 0) {
+            return;
+        }
+
+        ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(packageName);
+        if (grants == null || grants.size() == 0) {
+            return;
+        }
+
+        final PackageSetting ps = mPackages.get(packageName);
+        if (ps == null) {
+            Slog.e(TAG, "Can't find supposedly installed package " + packageName);
+            return;
+        }
+        final PermissionsState perms = ps.getPermissionsState();
+
+        for (RestoredPermissionGrant grant : grants) {
+            BasePermission bp = mPermissions.get(grant.permissionName);
+            if (bp != null) {
+                if (grant.granted) {
+                    perms.grantRuntimePermission(bp, userId);
+                }
+                perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, grant.grantBits);
+            }
+        }
+
+        // And remove it from the pending-grant bookkeeping
+        grantsByPackage.remove(packageName);
+        if (grantsByPackage.size() < 1) {
+            mRestoredUserGrants.remove(userId);
+        }
+        writeRuntimePermissionsForUserLPr(userId, false);
+    }
+
     void setInstallerPackageName(String pkgName, String installerPkgName) {
         PackageSetting p = mPackages.get(pkgName);
         if (p != null) {
@@ -1725,6 +1800,14 @@
         }
     }
 
+    // Specifically for backup/restore
+    public void processRestoredPermissionGrantLPr(String pkgName, String permission,
+            boolean isGranted, int restoredFlagSet, int userId)
+            throws IOException, XmlPullParserException {
+        mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
+                pkgName, permission, isGranted, restoredFlagSet, userId);
+    }
+
     void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, TAG_DEFAULT_APPS);
@@ -3893,7 +3976,6 @@
      * given {@link VolumeInfo#fsUuid}.
      */
     List<PackageSetting> getVolumePackagesLPr(String volumeUuid) {
-        Preconditions.checkNotNull(volumeUuid);
         ArrayList<PackageSetting> res = new ArrayList<>();
         for (int i = 0; i < mPackages.size(); i++) {
             final PackageSetting setting = mPackages.valueAt(i);
@@ -4410,6 +4492,48 @@
         pw.print(mReadMessages.toString());
     }
 
+    void dumpRestoredPermissionGrantsLPr(PrintWriter pw, DumpState dumpState) {
+        if (mRestoredUserGrants.size() > 0) {
+            pw.println();
+            pw.println("Restored (pending) permission grants:");
+            for (int userIndex = 0; userIndex < mRestoredUserGrants.size(); userIndex++) {
+                ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+                        mRestoredUserGrants.valueAt(userIndex);
+                if (grantsByPackage != null && grantsByPackage.size() > 0) {
+                    final int userId = mRestoredUserGrants.keyAt(userIndex);
+                    pw.print("  User "); pw.println(userId);
+
+                    for (int pkgIndex = 0; pkgIndex < grantsByPackage.size(); pkgIndex++) {
+                        ArraySet<RestoredPermissionGrant> grants = grantsByPackage.valueAt(pkgIndex);
+                        if (grants != null && grants.size() > 0) {
+                            final String pkgName = grantsByPackage.keyAt(pkgIndex);
+                            pw.print("    "); pw.print(pkgName); pw.println(" :");
+
+                            for (RestoredPermissionGrant g : grants) {
+                                pw.print("      ");
+                                pw.print(g.permissionName);
+                                if (g.granted) {
+                                    pw.print(" GRANTED");
+                                }
+                                if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+                                    pw.print(" user_set");
+                                }
+                                if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+                                    pw.print(" user_fixed");
+                                }
+                                if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+                                    pw.print(" revoke_on_upgrade");
+                                }
+                                pw.println();
+                            }
+                        }
+                    }
+                }
+            }
+            pw.println();
+        }
+    }
+
     private static void dumpSplitNames(PrintWriter pw, PackageParser.Package pkg) {
         if (pkg == null) {
             pw.print("unknown");
@@ -4507,7 +4631,6 @@
 
     private final class RuntimePermissionPersistence {
         private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
-
         private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
 
         private final Handler mHandler = new MyHandler();
@@ -4624,6 +4747,7 @@
                 serializer.setFeature(
                         "http://xmlpull.org/v1/doc/features.html#indent-output", true);
                 serializer.startDocument(null, true);
+
                 serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
 
                 String fingerprint = mFingerprints.get(userId);
@@ -4652,6 +4776,51 @@
                 }
 
                 serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+
+                // Now any restored permission grants that are waiting for the apps
+                // in question to be installed.  These are stored as per-package
+                // TAG_RESTORED_RUNTIME_PERMISSIONS blocks, each containing some
+                // number of individual permission grant entities.
+                if (mRestoredUserGrants.get(userId) != null) {
+                    ArrayMap<String, ArraySet<RestoredPermissionGrant>> restoredGrants =
+                            mRestoredUserGrants.get(userId);
+                    if (restoredGrants != null) {
+                        final int pkgCount = restoredGrants.size();
+                        for (int i = 0; i < pkgCount; i++) {
+                            final ArraySet<RestoredPermissionGrant> pkgGrants =
+                                    restoredGrants.valueAt(i);
+                            if (pkgGrants != null && pkgGrants.size() > 0) {
+                                final String pkgName = restoredGrants.keyAt(i);
+                                serializer.startTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+                                serializer.attribute(null, ATTR_PACKAGE_NAME, pkgName);
+
+                                final int N = pkgGrants.size();
+                                for (int z = 0; z < N; z++) {
+                                    RestoredPermissionGrant g = pkgGrants.valueAt(z);
+                                    serializer.startTag(null, TAG_PERMISSION_ENTRY);
+                                    serializer.attribute(null, ATTR_NAME, g.permissionName);
+
+                                    if (g.granted) {
+                                        serializer.attribute(null, ATTR_GRANTED, "true");
+                                    }
+
+                                    if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+                                        serializer.attribute(null, ATTR_USER_SET, "true");
+                                    }
+                                    if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+                                        serializer.attribute(null, ATTR_USER_FIXED, "true");
+                                    }
+                                    if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+                                        serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+                                    }
+                                    serializer.endTag(null, TAG_PERMISSION_ENTRY);
+                                }
+                                serializer.endTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+                            }
+                        }
+                    }
+                }
+
                 serializer.endDocument();
                 destination.finishWrite(out);
 
@@ -4725,6 +4894,31 @@
             }
         }
 
+        // Backup/restore support
+
+        public void rememberRestoredUserGrantLPr(String pkgName, String permission,
+                boolean isGranted, int restoredFlagSet, int userId) {
+            // This change will be remembered at write-settings time
+            ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+                    mRestoredUserGrants.get(userId);
+            if (grantsByPackage == null) {
+                grantsByPackage = new ArrayMap<String, ArraySet<RestoredPermissionGrant>>();
+                mRestoredUserGrants.put(userId, grantsByPackage);
+            }
+
+            ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(pkgName);
+            if (grants == null) {
+                grants = new ArraySet<RestoredPermissionGrant>();
+                grantsByPackage.put(pkgName, grants);
+            }
+
+            RestoredPermissionGrant grant = new RestoredPermissionGrant(permission,
+                    isGranted, restoredFlagSet);
+            grants.add(grant);
+        }
+
+        // Private internals
+
         private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
                 throws IOException, XmlPullParserException {
             final int outerDepth = parser.getDepth();
@@ -4764,6 +4958,46 @@
                         }
                         parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
                     } break;
+
+                    case TAG_RESTORED_RUNTIME_PERMISSIONS: {
+                        final String pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                        parseRestoredRuntimePermissionsLPr(parser, pkgName, userId);
+                    } break;
+                }
+            }
+        }
+
+        private void parseRestoredRuntimePermissionsLPr(XmlPullParser parser,
+                final String pkgName, final int userId) throws IOException, XmlPullParserException {
+            final int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                switch (parser.getName()) {
+                    case TAG_PERMISSION_ENTRY: {
+                        final String permName = parser.getAttributeValue(null, ATTR_NAME);
+                        final boolean isGranted = "true".equals(
+                                parser.getAttributeValue(null, ATTR_GRANTED));
+
+                        int permBits = 0;
+                        if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+                            permBits |= FLAG_PERMISSION_USER_SET;
+                        }
+                        if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+                            permBits |= FLAG_PERMISSION_USER_FIXED;
+                        }
+                        if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+                            permBits |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                        }
+
+                        if (isGranted || permBits != 0) {
+                            rememberRestoredUserGrantLPr(pkgName, permName, isGranted, permBits, userId);
+                        }
+                    } break;
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ce6b369..13d7c35 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -43,6 +43,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -59,7 +60,6 @@
 import android.system.OsConstants;
 import android.util.AtomicFile;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -75,6 +75,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
+import com.android.server.pm.Installer.StorageFlags;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -107,7 +108,7 @@
  */
 public class UserManagerService extends IUserManager.Stub {
     private static final String LOG_TAG = "UserManagerService";
-    static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    static final boolean DBG = true; // 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";
@@ -124,6 +125,8 @@
     private static final String ATTR_USER_VERSION = "version";
     private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
     private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId";
+    private static final String ATTR_SEED_ACCOUNT_NAME = "seedAccountName";
+    private static final String ATTR_SEED_ACCOUNT_TYPE = "seedAccountType";
     private static final String TAG_GUEST_RESTRICTIONS = "guestRestrictions";
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
@@ -131,6 +134,7 @@
     private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
+    private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
     private static final String ATTR_KEY = "key";
     private static final String ATTR_VALUE_TYPE = "type";
     private static final String ATTR_MULTIPLE = "m";
@@ -183,16 +187,35 @@
     private final File mUsersDir;
     private final File mUserListFile;
 
-    @GuardedBy("mUsersLock")
-    private final SparseArray<UserInfo> mUsers = new SparseArray<>();
-
     /**
-     * This collection contains each user's account name if the user chose to set one up
-     * during the initial user creation process.  Keeping this information separate from mUsers
-     * to avoid accidentally leak it.
+     * User-related information that is used for persisting to flash. Only UserInfo is
+     * directly exposed to other system apps.
      */
+    private static class UserData {
+        // Basic user information and properties
+        UserInfo info;
+        // Account name used when there is a strong association between a user and an account
+        String account;
+        // Account information for seeding into a newly created user. This could also be
+        // used for login validation for an existing user, for updating their credentials.
+        // In the latter case, data may not need to be persisted as it is only valid for the
+        // current login session.
+        String seedAccountName;
+        String seedAccountType;
+        PersistableBundle seedAccountOptions;
+        // Whether to perist the seed account information to be available after a boot
+        boolean persistSeedData;
+
+        void clearSeedAccountData() {
+            seedAccountName = null;
+            seedAccountType = null;
+            seedAccountOptions = null;
+            persistSeedData = false;
+        }
+    }
+
     @GuardedBy("mUsersLock")
-    private final SparseArray<String> mUserAccounts = new SparseArray<>();
+    private final SparseArray<UserData> mUsers = new SparseArray<>();
 
     /**
      * User restrictions set via UserManager.  This doesn't include restrictions set by
@@ -331,7 +354,7 @@
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                UserInfo ui = mUsers.valueAt(i);
+                UserInfo ui = mUsers.valueAt(i).info;
                 if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) {
                     partials.add(ui);
                 }
@@ -359,20 +382,25 @@
     public String getUserAccount(int userId) {
         checkManageUserAndAcrossUsersFullPermission("get user account");
         synchronized (mUsersLock) {
-            return mUserAccounts.get(userId);
+            return mUsers.get(userId).account;
         }
     }
 
     @Override
     public void setUserAccount(int userId, String accountName) {
         checkManageUserAndAcrossUsersFullPermission("set user account");
-        UserInfo userToUpdate = null;
+        UserData userToUpdate = null;
         synchronized (mPackagesLock) {
             synchronized (mUsersLock) {
-                String currentAccount = mUserAccounts.get(userId);
+                final UserData userData = mUsers.get(userId);
+                if (userData == null) {
+                    Slog.e(LOG_TAG, "User not found for setting user account: u" + userId);
+                    return;
+                }
+                String currentAccount = userData.account;
                 if (!Objects.equal(currentAccount, accountName)) {
-                    mUserAccounts.put(userId, accountName);
-                    userToUpdate = mUsers.get(userId);
+                    userData.account = accountName;
+                    userToUpdate = userData;
                 }
             }
 
@@ -388,7 +416,7 @@
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                UserInfo ui = mUsers.valueAt(i);
+                UserInfo ui = mUsers.valueAt(i).info;
                 if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) {
                     return ui;
                 }
@@ -404,7 +432,7 @@
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                UserInfo ui = mUsers.valueAt(i);
+                UserInfo ui = mUsers.valueAt(i).info;
                 if (ui.partial) {
                     continue;
                 }
@@ -441,7 +469,7 @@
         }
         final int userSize = mUsers.size();
         for (int i = 0; i < userSize; i++) {
-            UserInfo profile = mUsers.valueAt(i);
+            UserInfo profile = mUsers.valueAt(i).info;
             if (!isProfileOf(user, profile)) {
                 continue;
             }
@@ -557,7 +585,7 @@
             }
             if (profile.isQuietModeEnabled() != enableQuietMode) {
                 profile.flags ^= UserInfo.FLAG_QUIET_MODE;
-                writeUserLP(profile);
+                writeUserLP(getUserDataLU(profile.id));
                 changed = true;
             }
         }
@@ -593,7 +621,7 @@
             }
             if (info != null && !info.isEnabled()) {
                 info.flags ^= UserInfo.FLAG_DISABLED;
-                writeUserLP(info);
+                writeUserLP(getUserDataLU(info.id));
             }
         }
     }
@@ -633,13 +661,22 @@
      * Should be locked on mUsers before calling this.
      */
     private UserInfo getUserInfoLU(int userId) {
-        UserInfo ui = mUsers.get(userId);
+        final UserData userData = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
-        if (ui != null && ui.partial && !mRemovingUserIds.get(userId)) {
+        if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
             Slog.w(LOG_TAG, "getUserInfo: unknown user #" + userId);
             return null;
         }
-        return ui;
+        return userData != null ? userData.info : null;
+    }
+
+    private UserData getUserDataLU(int userId) {
+        final UserData userData = mUsers.get(userId);
+        // If it is partial and not in the process of being removed, return as unknown user.
+        if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
+            return null;
+        }
+        return userData;
     }
 
     /**
@@ -648,6 +685,17 @@
      */
     private UserInfo getUserInfoNoChecks(int userId) {
         synchronized (mUsersLock) {
+            final UserData userData = mUsers.get(userId);
+            return userData != null ? userData.info : null;
+        }
+    }
+
+    /**
+     * Obtains {@link #mUsersLock} and return UserData from mUsers.
+     * <p>No permissions checking or any addition checks are made</p>
+     */
+    private UserData getUserDataNoChecks(int userId) {
+        synchronized (mUsersLock) {
             return mUsers.get(userId);
         }
     }
@@ -662,14 +710,14 @@
         checkManageUsersPermission("rename users");
         boolean changed = false;
         synchronized (mPackagesLock) {
-            UserInfo info = getUserInfoNoChecks(userId);
-            if (info == null || info.partial) {
+            UserData userData = getUserDataNoChecks(userId);
+            if (userData == null || userData.info.partial) {
                 Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
                 return;
             }
-            if (name != null && !name.equals(info.name)) {
-                info.name = name;
-                writeUserLP(info);
+            if (name != null && !name.equals(userData.info.name)) {
+                userData.info.name = name;
+                writeUserLP(userData);
                 changed = true;
             }
         }
@@ -684,13 +732,13 @@
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mPackagesLock) {
-                UserInfo info = getUserInfoNoChecks(userId);
-                if (info == null || info.partial) {
+                UserData userData = getUserDataNoChecks(userId);
+                if (userData == null || userData.info.partial) {
                     Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
                     return;
                 }
-                writeBitmapLP(info, bitmap);
-                writeUserLP(info);
+                writeBitmapLP(userData.info, bitmap);
+                writeUserLP(userData);
             }
             sendUserInfoChangedBroadcast(userId);
         } finally {
@@ -737,20 +785,20 @@
     public void makeInitialized(int userId) {
         checkManageUsersPermission("makeInitialized");
         boolean scheduleWriteUser = false;
-        UserInfo info;
+        UserData userData;
         synchronized (mUsersLock) {
-            info = mUsers.get(userId);
-            if (info == null || info.partial) {
+            userData = mUsers.get(userId);
+            if (userData == null || userData.info.partial) {
                 Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
                 return;
             }
-            if ((info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
-                info.flags |= UserInfo.FLAG_INITIALIZED;
+            if ((userData.info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
+                userData.info.flags |= UserInfo.FLAG_INITIALIZED;
                 scheduleWriteUser = true;
             }
         }
         if (scheduleWriteUser) {
-            scheduleWriteUser(info);
+            scheduleWriteUser(userData);
         }
     }
 
@@ -826,7 +874,7 @@
                 writeUserListLP();
             }
             if (localChanged) {
-                writeUserLP(getUserInfoNoChecks(userId));
+                writeUserLP(getUserDataNoChecks(userId));
             }
         }
 
@@ -941,7 +989,7 @@
 
             if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
                 mBaseUserRestrictions.put(userId, newRestrictions);
-                scheduleWriteUser(getUserInfoNoChecks(userId));
+                scheduleWriteUser(getUserDataNoChecks(userId));
             }
         }
 
@@ -1089,7 +1137,7 @@
         final int totalUserCount = mUsers.size();
         // Skip over users being removed
         for (int i = 0; i < totalUserCount; i++) {
-            UserInfo user = mUsers.valueAt(i);
+            UserInfo user = mUsers.valueAt(i).info;
             if (!mRemovingUserIds.get(user.id)
                     && !user.isGuest() && !user.partial) {
                 aliveUserCount++;
@@ -1207,7 +1255,7 @@
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
                     && type != XmlPullParser.END_DOCUMENT) {
-                ;
+                // Skip
             }
 
             if (type != XmlPullParser.START_TAG) {
@@ -1235,16 +1283,15 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_USER)) {
                         String id = parser.getAttributeValue(null, ATTR_ID);
-                        Pair<UserInfo, String> userPair = readUserLP(Integer.parseInt(id));
 
-                        if (userPair != null) {
-                            UserInfo user = userPair.first;
-                            String account = userPair.second;
+                        UserData userData = readUserLP(Integer.parseInt(id));
+
+                        if (userData != null) {
                             synchronized (mUsersLock) {
-                                mUsers.put(user.id, user);
-                                mUserAccounts.put(user.id, account);
-                                if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
-                                    mNextSerialNumber = user.id + 1;
+                                mUsers.put(userData.info.id, userData);
+                                if (mNextSerialNumber < 0
+                                        || mNextSerialNumber <= userData.info.id) {
+                                    mNextSerialNumber = userData.info.id + 1;
                                 }
                             }
                         }
@@ -1288,20 +1335,21 @@
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
-            UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
-            if ("Primary".equals(user.name)) {
-                user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
-                scheduleWriteUser(user);
+            UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM);
+            if ("Primary".equals(userData.info.name)) {
+                userData.info.name =
+                        mContext.getResources().getString(com.android.internal.R.string.owner_name);
+                scheduleWriteUser(userData);
             }
             userVersion = 1;
         }
 
         if (userVersion < 2) {
             // Owner should be marked as initialized
-            UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
-            if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
-                user.flags |= UserInfo.FLAG_INITIALIZED;
-                scheduleWriteUser(user);
+            UserData userData = getUserDataNoChecks(UserHandle.USER_SYSTEM);
+            if ((userData.info.flags & UserInfo.FLAG_INITIALIZED) == 0) {
+                userData.info.flags |= UserInfo.FLAG_INITIALIZED;
+                scheduleWriteUser(userData);
             }
             userVersion = 2;
         }
@@ -1320,12 +1368,13 @@
             final boolean splitSystemUser = UserManager.isSplitSystemUser();
             synchronized (mUsersLock) {
                 for (int i = 0; i < mUsers.size(); i++) {
-                    UserInfo user = mUsers.valueAt(i);
+                    UserData userData = mUsers.valueAt(i);
                     // In non-split mode, only user 0 can have restricted profiles
-                    if (!splitSystemUser && user.isRestricted()
-                            && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
-                        user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
-                        scheduleWriteUser(user);
+                    if (!splitSystemUser && userData.info.isRestricted()
+                            && (userData.info.restrictedProfileParentId
+                                    == UserInfo.NO_PROFILE_GROUP_ID)) {
+                        userData.info.restrictedProfileParentId = UserHandle.USER_SYSTEM;
+                        scheduleWriteUser(userData);
                     }
                 }
             }
@@ -1355,8 +1404,10 @@
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM,
                 mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
                 flags);
+        UserData userData = new UserData();
+        userData.info = system;
         synchronized (mUsersLock) {
-            mUsers.put(system.id, system);
+            mUsers.put(system.id, userData);
         }
         mNextSerialNumber = MIN_USER_ID;
         mUserVersion = USER_VERSION;
@@ -1370,17 +1421,17 @@
         initDefaultGuestRestrictions();
 
         writeUserListLP();
-        writeUserLP(system);
+        writeUserLP(userData);
     }
 
-    private void scheduleWriteUser(UserInfo userInfo) {
+    private void scheduleWriteUser(UserData UserData) {
         if (DBG) {
             debug("scheduleWriteUser");
         }
         // No need to wrap it within a lock -- worst case, we'll just post the same message
         // twice.
-        if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
-            Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
+        if (!mHandler.hasMessages(WRITE_USER_MSG, UserData)) {
+            Message msg = mHandler.obtainMessage(WRITE_USER_MSG, UserData);
             mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
         }
     }
@@ -1392,12 +1443,12 @@
      *   <name>Primary</name>
      * </user>
      */
-    private void writeUserLP(UserInfo userInfo) {
+    private void writeUserLP(UserData userData) {
         if (DBG) {
-            debug("writeUserLP " + userInfo);
+            debug("writeUserLP " + userData);
         }
         FileOutputStream fos = null;
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userData.info.id + XML_SUFFIX));
         try {
             fos = userFile.startWrite();
             final BufferedOutputStream bos = new BufferedOutputStream(fos);
@@ -1408,6 +1459,7 @@
             serializer.startDocument(null, true);
             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
+            final UserInfo userInfo = userData.info;
             serializer.startTag(null, TAG_USER);
             serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
             serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
@@ -1432,6 +1484,15 @@
                 serializer.attribute(null, ATTR_RESTRICTED_PROFILE_PARENT_ID,
                         Integer.toString(userInfo.restrictedProfileParentId));
             }
+            // Write seed data
+            if (userData.persistSeedData) {
+                if (userData.seedAccountName != null) {
+                    serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME, userData.seedAccountName);
+                }
+                if (userData.seedAccountType != null) {
+                    serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
+                }
+            }
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
@@ -1442,23 +1503,24 @@
                         mDevicePolicyLocalUserRestrictions.get(userInfo.id),
                         TAG_DEVICE_POLICY_RESTRICTIONS);
             }
-            // Update the account field if it is set.
-            String account;
-            synchronized (mUsersLock) {
-                account = mUserAccounts.get(userInfo.id);
-            }
-            if (account != null) {
+
+            if (userData.account != null) {
                 serializer.startTag(null, TAG_ACCOUNT);
-                serializer.text(account);
+                serializer.text(userData.account);
                 serializer.endTag(null, TAG_ACCOUNT);
             }
 
+            if (userData.persistSeedData && userData.seedAccountOptions != null) {
+                serializer.startTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+                userData.seedAccountOptions.saveToXml(serializer);
+                serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+            }
             serializer.endTag(null, TAG_USER);
 
             serializer.endDocument();
             userFile.finishWrite(fos);
         } catch (Exception ioe) {
-            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+            Slog.e(LOG_TAG, "Error writing user info " + userData.info.id + "\n" + ioe);
             userFile.failWrite(fos);
         }
     }
@@ -1505,7 +1567,7 @@
             synchronized (mUsersLock) {
                 userIdsToWrite = new int[mUsers.size()];
                 for (int i = 0; i < userIdsToWrite.length; i++) {
-                    UserInfo user = mUsers.valueAt(i);
+                    UserInfo user = mUsers.valueAt(i).info;
                     userIdsToWrite[i] = user.id;
                 }
             }
@@ -1525,7 +1587,7 @@
         }
     }
 
-    private Pair<UserInfo, String> readUserLP(int id) {
+    private UserData readUserLP(int id) {
         int flags = 0;
         int serialNumber = id;
         String name = null;
@@ -1537,6 +1599,10 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean guestToRemove = false;
+        boolean persistSeedData = false;
+        String seedAccountName = null;
+        String seedAccountType = null;
+        PersistableBundle seedAccountOptions = null;
         Bundle baseRestrictions = new Bundle();
         Bundle localRestrictions = new Bundle();
 
@@ -1550,7 +1616,7 @@
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
                     && type != XmlPullParser.END_DOCUMENT) {
-                ;
+                // Skip
             }
 
             if (type != XmlPullParser.START_TAG) {
@@ -1582,6 +1648,12 @@
                     guestToRemove = true;
                 }
 
+                seedAccountName = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_NAME);
+                seedAccountType = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_TYPE);
+                if (seedAccountName != null || seedAccountType != null) {
+                    persistSeedData = true;
+                }
+
                 int outerDepth = parser.getDepth();
                 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -1603,10 +1675,14 @@
                         if (type == XmlPullParser.TEXT) {
                             account = parser.getText();
                         }
+                    } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
+                        seedAccountOptions = PersistableBundle.restoreFromXml(parser);
+                        persistSeedData = true;
                     }
                 }
             }
 
+            // Create the UserInfo object that gets passed around
             UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
             userInfo.serialNumber = serialNumber;
             userInfo.creationTime = creationTime;
@@ -1615,12 +1691,21 @@
             userInfo.guestToRemove = guestToRemove;
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
+
+            // Create the UserData object that's internal to this class
+            UserData userData = new UserData();
+            userData.info = userInfo;
+            userData.account = account;
+            userData.seedAccountName = seedAccountName;
+            userData.seedAccountType = seedAccountType;
+            userData.persistSeedData = persistSeedData;
+            userData.seedAccountOptions = seedAccountOptions;
+
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.put(id, baseRestrictions);
                 mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
             }
-            return new Pair<>(userInfo, account);
-
+            return userData;
         } catch (IOException ioe) {
         } catch (XmlPullParserException pe) {
         } finally {
@@ -1665,26 +1750,6 @@
     }
 
     /**
-     * Removes all the restrictions files (res_<packagename>) for a given user.
-     * Does not do any permissions checking.
-     */
-    private void cleanAppRestrictions(int userId) {
-        synchronized (mPackagesLock) {
-            File dir = Environment.getUserSystemDirectory(userId);
-            String[] files = dir.list();
-            if (files == null) return;
-            for (String fileName : files) {
-                if (fileName.startsWith(RESTRICTIONS_FILE_PREFIX)) {
-                    File resFile = new File(dir, fileName);
-                    if (resFile.exists()) {
-                        resFile.delete();
-                    }
-                }
-            }
-        }
-    }
-
-    /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
      */
     private void cleanAppRestrictionsForPackage(String pkg, int userId) {
@@ -1722,13 +1787,14 @@
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
         final long ident = Binder.clearCallingIdentity();
         UserInfo userInfo;
+        UserData userData;
         final int userId;
         try {
             synchronized (mPackagesLock) {
-                UserInfo parent = null;
+                UserData parent = null;
                 if (parentId != UserHandle.USER_NULL) {
                     synchronized (mUsersLock) {
-                        parent = getUserInfoLU(parentId);
+                        parent = getUserDataLU(parentId);
                     }
                     if (parent == null) return null;
                 }
@@ -1757,7 +1823,7 @@
                                 + "specified");
                         return null;
                     }
-                    if (!parent.canHaveProfile()) {
+                    if (!parent.info.canHaveProfile()) {
                         Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
                                 + "created for the specified parent user id " + parentId);
                         return null;
@@ -1775,7 +1841,7 @@
                     }
                 }
 
-                if (parent != null && parent.isEphemeral()) {
+                if (parent != null && parent.info.isEphemeral()) {
                     flags |= UserInfo.FLAG_EPHEMERAL;
                 }
                 userId = getNextAvailableId();
@@ -1784,24 +1850,26 @@
                 long now = System.currentTimeMillis();
                 userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
                 userInfo.partial = true;
+                userData = new UserData();
+                userData.info = userInfo;
                 Environment.getUserSystemDirectory(userInfo.id).mkdirs();
                 synchronized (mUsersLock) {
-                    mUsers.put(userId, userInfo);
+                    mUsers.put(userId, userData);
                 }
                 writeUserListLP();
                 if (parent != null) {
                     if (isManagedProfile) {
-                        if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
-                            parent.profileGroupId = parent.id;
+                        if (parent.info.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.info.profileGroupId = parent.info.id;
                             writeUserLP(parent);
                         }
-                        userInfo.profileGroupId = parent.profileGroupId;
+                        userInfo.profileGroupId = parent.info.profileGroupId;
                     } else if (isRestricted) {
-                        if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
-                            parent.restrictedProfileParentId = parent.id;
+                        if (parent.info.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+                            parent.info.restrictedProfileParentId = parent.info.id;
                             writeUserLP(parent);
                         }
-                        userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
+                        userInfo.restrictedProfileParentId = parent.info.restrictedProfileParentId;
                     }
                 }
             }
@@ -1821,7 +1889,7 @@
             mPm.createNewUser(userId);
             userInfo.partial = false;
             synchronized (mPackagesLock) {
-                writeUserLP(userInfo);
+                writeUserLP(userData);
             }
             updateUserIds();
             Bundle restrictions = new Bundle();
@@ -1848,6 +1916,7 @@
     /**
      * @hide
      */
+    @Override
     public UserInfo createRestrictedProfile(String name, int parentUserId) {
         checkManageUsersPermission("setupRestrictedProfile");
         final UserInfo user = createProfileForUser(name, UserInfo.FLAG_RESTRICTED, parentUserId);
@@ -1872,7 +1941,7 @@
         synchronized (mUsersLock) {
             final int size = mUsers.size();
             for (int i = 0; i < size; i++) {
-                final UserInfo user = mUsers.valueAt(i);
+                final UserInfo user = mUsers.valueAt(i).info;
                 if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
                     return user;
                 }
@@ -1887,6 +1956,7 @@
      * @param userHandle the userid of the current guest
      * @return whether the user could be marked for deletion
      */
+    @Override
     public boolean markGuestForDeletion(int userHandle) {
         checkManageUsersPermission("Only the system can remove users");
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
@@ -1897,15 +1967,15 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            final UserInfo user;
+            final UserData userData;
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    user = mUsers.get(userHandle);
-                    if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+                    userData = mUsers.get(userHandle);
+                    if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
                         return false;
                     }
                 }
-                if (!user.isGuest()) {
+                if (!userData.info.isGuest()) {
                     return false;
                 }
                 // We set this to a guest user that is to be removed. This is a temporary state
@@ -1913,11 +1983,11 @@
                 // removed. This user will still show up in getUserInfo() calls.
                 // If we don't get around to removing this Guest user, it will be purged on next
                 // startup.
-                user.guestToRemove = true;
+                userData.info.guestToRemove = true;
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                user.flags |= UserInfo.FLAG_DISABLED;
-                writeUserLP(user);
+                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                writeUserLP(userData);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1930,6 +2000,7 @@
      * after the user's processes have been terminated.
      * @param userHandle the user's id
      */
+    @Override
     public boolean removeUser(int userHandle) {
         checkManageUsersPermission("Only the system can remove users");
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
@@ -1940,7 +2011,7 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            final UserInfo user;
+            final UserData userData;
             int currentUser = ActivityManager.getCurrentUser();
             if (currentUser == userHandle) {
                 Log.w(LOG_TAG, "Current user cannot be removed");
@@ -1948,8 +2019,8 @@
             }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    user = mUsers.get(userHandle);
-                    if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+                    userData = mUsers.get(userHandle);
+                    if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
                         return false;
                     }
 
@@ -1967,18 +2038,18 @@
                 // Set this to a partially created user, so that the user will be purged
                 // on next startup, in case the runtime stops now before stopping and
                 // removing the user completely.
-                user.partial = true;
+                userData.info.partial = true;
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                user.flags |= UserInfo.FLAG_DISABLED;
-                writeUserLP(user);
+                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                writeUserLP(userData);
             }
 
-            if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
-                    && user.isManagedProfile()) {
+            if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+                    && userData.info.isManagedProfile()) {
                 // Send broadcast to notify system that the user removed was a
                 // managed user.
-                sendProfileRemovedBroadcast(user.profileGroupId, user.id);
+                sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id);
             }
 
             if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
@@ -2023,6 +2094,7 @@
                                         + userHandle);
                             }
                             new Thread() {
+                                @Override
                                 public void run() {
                                     // Clean up any ActivityManager state
                                     LocalServices.getService(ActivityManagerInternal.class)
@@ -2047,7 +2119,6 @@
         // Remove this user from the list
         synchronized (mUsersLock) {
             mUsers.remove(userHandle);
-            mUserAccounts.delete(userHandle);
             mIsUserManaged.delete(userHandle);
         }
         synchronized (mRestrictionsLock) {
@@ -2064,7 +2135,13 @@
             writeUserListLP();
         }
         updateUserIds();
-        removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
+        File userDir = Environment.getUserSystemDirectory(userHandle);
+        File renamedUserDir = Environment.getUserSystemDirectory(UserHandle.USER_NULL - userHandle);
+        if (userDir.renameTo(renamedUserDir)) {
+            removeDirectoryRecursive(renamedUserDir);
+        } else {
+            removeDirectoryRecursive(userDir);
+        }
     }
 
     private void removeDirectoryRecursive(File parent) {
@@ -2126,29 +2203,6 @@
         }
     }
 
-    private void unhideAllInstalledAppsForUser(final int userHandle) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                List<ApplicationInfo> apps =
-                        mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES,
-                                userHandle).getList();
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    for (ApplicationInfo appInfo : apps) {
-                        if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0
-                                && (appInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN)
-                                        != 0) {
-                            mPm.setApplicationHiddenSettingAsUser(appInfo.packageName, false,
-                                    userHandle);
-                        }
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        });
-    }
     private int getUidForPackage(String packageName) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -2378,14 +2432,14 @@
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).partial) {
+                if (!mUsers.valueAt(i).info.partial) {
                     num++;
                 }
             }
             final int[] newUsers = new int[num];
             int n = 0;
             for (int i = 0; i < userSize; i++) {
-                if (!mUsers.valueAt(i).partial) {
+                if (!mUsers.valueAt(i).info.partial) {
                     newUsers[n++] = mUsers.keyAt(i);
                 }
             }
@@ -2394,28 +2448,41 @@
     }
 
     /**
-     * Called right before a user starts.  This will not be called for the system user.
+     * Called right before a user is started. This gives us a chance to prepare
+     * app storage and apply any user restrictions.
      */
     public void onBeforeStartUser(int userId) {
-        synchronized (mRestrictionsLock) {
-            applyUserRestrictionsLR(userId);
+        mPm.reconcileAppsData(userId, Installer.FLAG_DE_STORAGE);
+
+        if (userId != UserHandle.USER_SYSTEM) {
+            synchronized (mRestrictionsLock) {
+                applyUserRestrictionsLR(userId);
+            }
         }
     }
 
     /**
+     * Called right before a user is unlocked. This gives us a chance to prepare
+     * app storage.
+     */
+    public void onBeforeUnlockUser(int userId) {
+        mPm.reconcileAppsData(userId, Installer.FLAG_CE_STORAGE);
+    }
+
+    /**
      * Make a note of the last started time of a user and do some cleanup.
      * @param userId the user that was just foregrounded
      */
     public void onUserForeground(int userId) {
-        UserInfo user = getUserInfoNoChecks(userId);
-        if (user == null || user.partial) {
+        UserData userData = getUserDataNoChecks(userId);
+        if (userData == null || userData.info.partial) {
             Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
             return;
         }
         long now = System.currentTimeMillis();
         if (now > EPOCH_PLUS_30_YEARS) {
-            user.lastLoggedInTime = now;
-            scheduleWriteUser(user);
+            userData.info.lastLoggedInTime = now;
+            scheduleWriteUser(userData);
         }
     }
 
@@ -2507,6 +2574,91 @@
     }
 
     @Override
+    public void setSeedAccountData(int userId, String accountName, String accountType,
+            PersistableBundle accountOptions, boolean persist) {
+        checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data");
+        synchronized (mPackagesLock) {
+            final UserData userData;
+            synchronized (mUsersLock) {
+                userData = getUserDataLU(userId);
+                if (userData == null) {
+                    Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
+                    return;
+                }
+                userData.seedAccountName = accountName;
+                userData.seedAccountType = accountType;
+                userData.seedAccountOptions = accountOptions;
+                userData.persistSeedData = persist;
+            }
+            if (persist) {
+                writeUserLP(userData);
+            }
+        }
+    }
+
+    @Override
+    public String getSeedAccountName() throws RemoteException {
+        checkManageUsersPermission("Cannot get seed account information");
+        synchronized (mUsersLock) {
+            UserData userData = getUserDataLU(UserHandle.getCallingUserId());
+            return userData.seedAccountName;
+        }
+    }
+
+    @Override
+    public String getSeedAccountType() throws RemoteException {
+        checkManageUsersPermission("Cannot get seed account information");
+        synchronized (mUsersLock) {
+            UserData userData = getUserDataLU(UserHandle.getCallingUserId());
+            return userData.seedAccountType;
+        }
+    }
+
+    @Override
+    public PersistableBundle getSeedAccountOptions() throws RemoteException {
+        checkManageUsersPermission("Cannot get seed account information");
+        synchronized (mUsersLock) {
+            UserData userData = getUserDataLU(UserHandle.getCallingUserId());
+            return userData.seedAccountOptions;
+        }
+    }
+
+    @Override
+    public void clearSeedAccountData() throws RemoteException {
+        checkManageUsersPermission("Cannot clear seed account information");
+        synchronized (mPackagesLock) {
+            UserData userData;
+            synchronized (mUsersLock) {
+                userData = getUserDataLU(UserHandle.getCallingUserId());
+                if (userData == null) return;
+                userData.clearSeedAccountData();
+            }
+            writeUserLP(userData);
+        }
+    }
+
+    @Override
+    public boolean someUserHasSeedAccount(String accountName, String accountType)
+            throws RemoteException {
+        checkManageUsersPermission("Cannot check seed account information");
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                final UserData data = mUsers.valueAt(i);
+                if (data.info.isInitialized()) continue;
+                if (data.seedAccountName == null || !data.seedAccountName.equals(accountName)) {
+                    continue;
+                }
+                if (data.seedAccountType == null || !data.seedAccountType.equals(accountType)) {
+                    continue;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
         (new Shell()).exec(this, in, out, err, args, resultReceiver);
@@ -2563,35 +2715,36 @@
             synchronized (mUsersLock) {
                 pw.println("Users:");
                 for (int i = 0; i < mUsers.size(); i++) {
-                    UserInfo user = mUsers.valueAt(i);
-                    if (user == null) {
+                    UserData userData = mUsers.valueAt(i);
+                    if (userData == null) {
                         continue;
                     }
-                    final int userId = user.id;
-                    pw.print("  "); pw.print(user);
-                    pw.print(" serialNo="); pw.print(user.serialNumber);
+                    UserInfo userInfo = userData.info;
+                    final int userId = userInfo.id;
+                    pw.print("  "); pw.print(userInfo);
+                    pw.print(" serialNo="); pw.print(userInfo.serialNumber);
                     if (mRemovingUserIds.get(userId)) {
                         pw.print(" <removing> ");
                     }
-                    if (user.partial) {
+                    if (userInfo.partial) {
                         pw.print(" <partial>");
                     }
                     pw.println();
                     pw.print("    Created: ");
-                    if (user.creationTime == 0) {
+                    if (userInfo.creationTime == 0) {
                         pw.println("<unknown>");
                     } else {
                         sb.setLength(0);
-                        TimeUtils.formatDuration(now - user.creationTime, sb);
+                        TimeUtils.formatDuration(now - userInfo.creationTime, sb);
                         sb.append(" ago");
                         pw.println(sb);
                     }
                     pw.print("    Last logged in: ");
-                    if (user.lastLoggedInTime == 0) {
+                    if (userInfo.lastLoggedInTime == 0) {
                         pw.println("<unknown>");
                     } else {
                         sb.setLength(0);
-                        TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+                        TimeUtils.formatDuration(now - userInfo.lastLoggedInTime, sb);
                         sb.append(" ago");
                         pw.println(sb);
                     }
@@ -2600,22 +2753,35 @@
                     pw.println("    Restrictions:");
                     synchronized (mRestrictionsLock) {
                         UserRestrictionsUtils.dumpRestrictions(
-                                pw, "      ", mBaseUserRestrictions.get(user.id));
+                                pw, "      ", mBaseUserRestrictions.get(userInfo.id));
                         pw.println("    Device policy local restrictions:");
                         UserRestrictionsUtils.dumpRestrictions(
-                                pw, "      ", mDevicePolicyLocalUserRestrictions.get(user.id));
+                                pw, "      ", mDevicePolicyLocalUserRestrictions.get(userInfo.id));
                         pw.println("    Effective restrictions:");
                         UserRestrictionsUtils.dumpRestrictions(
-                                pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
+                                pw, "      ", mCachedEffectiveUserRestrictions.get(userInfo.id));
                     }
-                    pw.println();
-                    String accountName = mUserAccounts.get(userId);
-                    if (accountName != null) {
-                        pw.print("    Account name: " + accountName);
+
+                    if (userData.account != null) {
+                        pw.print("    Account name: " + userData.account);
                         pw.println();
                     }
+
+                    if (userData.seedAccountName != null) {
+                        pw.print("    Seed account name: " + userData.seedAccountName);
+                        pw.println();
+                        if (userData.seedAccountType != null) {
+                            pw.print("         account type: " + userData.seedAccountType);
+                            pw.println();
+                        }
+                        if (userData.seedAccountOptions != null) {
+                            pw.print("         account options exist");
+                            pw.println();
+                        }
+                    }
                 }
             }
+            pw.println();
             pw.println("  Device policy global restrictions:");
             synchronized (mRestrictionsLock) {
                 UserRestrictionsUtils
@@ -2630,6 +2796,10 @@
                 pw.println();
                 pw.println("  Device managed: " + mIsDeviceManaged);
             }
+            // Dump some capabilities
+            pw.println();
+            pw.println("  Max users: " + UserManager.getMaxSupportedUsers());
+            pw.println("  Supports switchable users: " + UserManager.supportsMultipleUsers());
         }
     }
 
@@ -2641,10 +2811,10 @@
                 case WRITE_USER_MSG:
                     removeMessages(WRITE_USER_MSG, msg.obj);
                     synchronized (mPackagesLock) {
-                        int userId = ((UserInfo) msg.obj).id;
-                        UserInfo userInfo = getUserInfoNoChecks(userId);
-                        if (userInfo != null) {
-                            writeUserLP(userInfo);
+                        int userId = ((UserData) msg.obj).info.id;
+                        UserData userData = getUserDataNoChecks(userId);
+                        if (userData != null) {
+                            writeUserLP(userData);
                         }
                     }
             }
@@ -2682,10 +2852,10 @@
                 invalidateEffectiveUserRestrictionsLR(userId);
             }
 
-            final UserInfo userInfo = getUserInfoNoChecks(userId);
+            final UserData userData = getUserDataNoChecks(userId);
             synchronized (mPackagesLock) {
-                if (userInfo != null) {
-                    writeUserLP(userInfo);
+                if (userData != null) {
+                    writeUserLP(userData);
                 } else {
                     Slog.w(LOG_TAG, "UserInfo not found for " + userId);
                 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9c629bd..806c4ca 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -183,6 +183,8 @@
     static final int LONG_PRESS_HOME_NOTHING = 0;
     static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
     static final int LONG_PRESS_HOME_ASSIST = 2;
+    static final int LONG_PRESS_HOME_PICTURE_IN_PICTURE = 3;
+    static final int LAST_LONG_PRESS_HOME_BEHAVIOR = LONG_PRESS_HOME_PICTURE_IN_PICTURE;
 
     static final int DOUBLE_TAP_HOME_NOTHING = 0;
     static final int DOUBLE_TAP_HOME_RECENT_SYSTEM_UI = 1;
@@ -1313,16 +1315,26 @@
         }
     }
 
-    private void handleLongPressOnHome(int deviceId) {
-        if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) {
-            mHomeConsumed = true;
-            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+    private void handleLongPressOnHome(int deviceId, KeyEvent event) {
+        if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_NOTHING) {
+            return;
+        }
+        mHomeConsumed = true;
+        performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
 
-            if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) {
+        switch (mLongPressOnHomeBehavior) {
+            case LONG_PRESS_HOME_RECENT_SYSTEM_UI:
                 toggleRecentApps();
-            } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST) {
+                break;
+            case LONG_PRESS_HOME_ASSIST:
                 launchAssistAction(null, deviceId);
-            }
+                break;
+            case LONG_PRESS_HOME_PICTURE_IN_PICTURE:
+                handlePipKey(event);
+                break;
+            default:
+                Log.w(TAG, "Not defined home long press behavior: " + mLongPressOnHomeBehavior);
+                break;
         }
     }
 
@@ -1333,6 +1345,13 @@
         }
     }
 
+    private void handlePipKey(KeyEvent event) {
+        if (DEBUG_INPUT) Log.d(TAG, "handlePipKey event=" + event);
+        Intent intent = new Intent(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1625,7 +1644,7 @@
         mLongPressOnHomeBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnHomeBehavior);
         if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING ||
-                mLongPressOnHomeBehavior > LONG_PRESS_HOME_ASSIST) {
+                mLongPressOnHomeBehavior > LAST_LONG_PRESS_HOME_BEHAVIOR) {
             mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
         }
 
@@ -2851,7 +2870,7 @@
                 }
             } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                 if (!keyguardOn) {
-                    handleLongPressOnHome(event.getDeviceId());
+                    handleLongPressOnHome(event.getDeviceId(), event);
                 }
             }
             return -1;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 42b8721..1d498e1 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -664,11 +664,16 @@
         public boolean isDeviceLocked(int userId) throws RemoteException {
             userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                     false /* allowAll */, true /* requireFull */, "isDeviceLocked", null);
-            if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
-                userId = resolveProfileParent(userId);
-            }
 
-            return isDeviceLockedInner(userId);
+            long token = Binder.clearCallingIdentity();
+            try {
+                if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+                    userId = resolveProfileParent(userId);
+                }
+                return isDeviceLockedInner(userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
@@ -778,6 +783,7 @@
 
         @Override
         public void setDeviceLockedForUser(int userId, boolean value) {
+            enforceReportPermission();
             mHandler.obtainMessage(MSG_SET_DEVICE_LOCKED, value ? 1 : 0, userId)
                     .sendToTarget();
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 751f871..46240783 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -67,6 +67,7 @@
     // Whether we're performing an entering animation with a saved surface.
     boolean mAnimatingWithSavedSurface;
 
+
     Task mTask;
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -477,6 +478,15 @@
      */
     void unfreezeBounds() {
         mFrozenBounds.remove();
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            final WindowState win = windows.get(i);
+            win.mLayoutNeeded = true;
+            win.setDisplayLayoutNeeded();
+            if (!service.mResizingWindows.contains(win)) {
+                service.mResizingWindows.add(win);
+            }
+        }
+        service.mWindowPlacerLocked.performSurfacePlacement();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1b6957d..ac38424 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -203,15 +203,14 @@
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewFlags,
             int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
-            Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Configuration
-                    outConfig,
-            Surface outSurface) {
+            Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
+            Configuration outConfig, Surface outSurface) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         int res = mService.relayoutWindow(this, window, seq, attrs,
                 requestedWidth, requestedHeight, viewFlags, flags,
                 outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
-                outStableInsets, outsets, outConfig, outSurface);
+                outStableInsets, outsets, outBackdropFrame, outConfig, outSurface);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
                 + Binder.getCallingPid());
         return res;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 832a298..d9667a1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -425,7 +425,7 @@
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
-    public void getBounds(Rect out) {
+    void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
             // since it is already what we want to represent to the rest of the system.
@@ -433,9 +433,8 @@
             return;
         }
 
-        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
-        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
-        // system.
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+        // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
         mStack.getDisplayContent().getLogicalDisplayRect(out);
     }
 
@@ -523,19 +522,25 @@
             return;
         }
 
-        // Device rotation changed. We don't want the task to move around on the screen when
-        // this happens, so update the task bounds so it stays in the same place.
+        // Device rotation changed.
+        // - Reset the bounds to the pre-scroll bounds as whatever scrolling was done is no longer
+        // valid.
+        // - Rotate the bounds and notify activity manager if the task can be resized independently
+        // from its stack. The stack will take care of task rotation for the other case.
         mTmpRect2.set(mPreScrollBounds);
+
+        if (!StackId.isTaskResizeAllowed(mStack.mStackId)) {
+            setBounds(mTmpRect2, mOverrideConfig);
+            return;
+        }
+
         displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (setBounds(mTmpRect2, mOverrideConfig) != BOUNDS_CHANGE_NONE) {
-            // Post message to inform activity manager of the bounds change simulating
-            // a one-way call. We do this to prevent a deadlock between window manager
-            // lock and activity manager lock been held. Only tasks within the freeform stack
-            // are resizeable independently of their stack resizing.
-            if (mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
-                mService.mH.sendMessage(mService.mH.obtainMessage(
-                        RESIZE_TASK, mTaskId, RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mPreScrollBounds));
-            }
+            // Post message to inform activity manager of the bounds change simulating a one-way
+            // call. We do this to prevent a deadlock between window manager lock and activity
+            // manager lock been held.
+            mService.mH.obtainMessage(RESIZE_TASK, mTaskId,
+                    RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mPreScrollBounds).sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 632c033..a75f2c9 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -105,12 +105,6 @@
         return mTasks;
     }
 
-    void resizeWindows() {
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            mTasks.get(taskNdx).resizeWindows();
-        }
-    }
-
     /**
      * Set the bounds of the stack and its containing tasks.
      * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -137,11 +131,11 @@
                     // it might no longer fully cover the stack area.
                     // Save the old bounds and re-apply the scroll. This adjusts the bounds to
                     // fit the new stack bounds.
-                    task.setBounds(bounds, config);
+                    task.resizeLocked(bounds, config, false /* forced */);
                     task.getBounds(mTmpRect);
                     task.scrollLocked(mTmpRect);
                 } else {
-                    task.setBounds(bounds, config);
+                    task.resizeLocked(bounds, config, false /* forced */);
                     task.setTempInsetBounds(
                             taskTempInsetBounds != null ? taskTempInsetBounds.get(task.mTaskId)
                                     : null);
@@ -491,7 +485,7 @@
         }
     }
 
-    void getStackDockedModeBoundsLocked(Rect outBounds) {
+    void getStackDockedModeBoundsLocked(Rect outBounds, boolean ignoreVisibilityOnKeyguardShowing) {
         if (!StackId.isResizeableByDockedStack(mStackId) || mDisplayContent == null) {
             outBounds.set(mBounds);
             return;
@@ -503,11 +497,11 @@
             throw new IllegalStateException(
                     "Calling getStackDockedModeBoundsLocked() when there is no docked stack.");
         }
-        if (!dockedStack.isVisibleLocked()) {
+        if (!dockedStack.isVisibleLocked(ignoreVisibilityOnKeyguardShowing)) {
             // The docked stack is being dismissed, but we caught before it finished being
             // dismissed. In that case we want to treat it as if it is not occupying any space and
             // let others occupy the whole display.
-            mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            mDisplayContent.getLogicalDisplayRect(outBounds);
             return;
         }
 
@@ -788,10 +782,19 @@
     }
 
     boolean isVisibleLocked() {
+        return isVisibleLocked(false);
+    }
+
+    boolean isVisibleLocked(boolean ignoreVisibilityOnKeyguardShowing) {
         final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded();
         if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) {
-            return false;
+            // The keyguard is showing and the stack shouldn't show on top of the keyguard.
+            // We return false for visibility except in cases where the caller wants us to return
+            // true for visibility when the keyguard is showing. One example, is if the docked
+            // is being resized due to orientation while the keyguard is on.
+            return ignoreVisibilityOnKeyguardShowing;
         }
+
         for (int i = mTasks.size() - 1; i >= 0; i--) {
             Task task = mTasks.get(i);
             for (int j = task.mAppTokens.size() - 1; j >= 0; j--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7d142ec..adc2da4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2561,8 +2561,8 @@
             WindowManager.LayoutParams attrs, int requestedWidth,
             int requestedHeight, int viewVisibility, int flags,
             Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
-            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
-            Surface outSurface) {
+            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
+            Configuration outConfig, Surface outSurface) {
         int result = 0;
         boolean configChanged;
         boolean hasStatusBarPermission =
@@ -2749,6 +2749,7 @@
             outVisibleInsets.set(win.mVisibleInsets);
             outStableInsets.set(win.mStableInsets);
             outOutsets.set(win.mOutsets);
+            outBackdropFrame.set(win.getBackdropFrame(win.mFrame));
             if (localLOGV) Slog.v(
                 TAG_WM, "Relayout given client " + client.asBinder()
                 + ", requestedWidth=" + requestedWidth
@@ -2869,7 +2870,12 @@
                 result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
             }
         }
-        result |= win.isDragResizing() ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING : 0;
+        final boolean freeformResizing = win.isDragResizing()
+                && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_FREEFORM;
+        final boolean dockedResizing = win.isDragResizing()
+                && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+        result |= freeformResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
+        result |= dockedResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
         if (win.isAnimatingWithSavedSurface()) {
             // If we're animating with a saved surface now, request client to report draw.
             // We still need to know when the real thing is drawn.
@@ -4795,11 +4801,12 @@
         }
     }
 
-    public void getStackDockedModeBounds(int stackId, Rect bounds) {
+    public void getStackDockedModeBounds(
+            int stackId, Rect bounds, boolean ignoreVisibilityOnKeyguardShowing) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);
             if (stack != null) {
-                stack.getStackDockedModeBoundsLocked(bounds);
+                stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibilityOnKeyguardShowing);
                 return;
             }
             bounds.setEmpty();
@@ -4858,7 +4865,6 @@
             }
             if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
                     && stack.isVisibleLocked()) {
-                stack.resizeWindows();
                 stack.getDisplayContent().layoutNeeded = true;
                 mWindowPlacerLocked.performSurfacePlacement();
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index afbaf00..e8a02b0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -729,6 +729,9 @@
                 // will return the correct value to the renderer.
                 mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
                 mContentFrame.set(mFrame);
+                if (!mFrame.equals(mLastFrame)) {
+                    mMovedByResize = true;
+                }
             }
         } else {
             mContentFrame.set(Math.max(mContentFrame.left, frame.left),
@@ -752,15 +755,7 @@
                 Math.max(frame.right - mOverscanFrame.right, 0),
                 Math.max(frame.bottom - mOverscanFrame.bottom, 0));
 
-        mContentInsets.set(mContentFrame.left - frame.left,
-                mContentFrame.top - frame.top,
-                frame.right - mContentFrame.right,
-                frame.bottom - mContentFrame.bottom);
 
-        mVisibleInsets.set(mVisibleFrame.left - frame.left,
-                mVisibleFrame.top - frame.top,
-                frame.right - mVisibleFrame.right,
-                frame.bottom - mVisibleFrame.bottom);
 
         if (mAttrs.type == TYPE_DOCK_DIVIDER) {
 
@@ -770,7 +765,22 @@
                     Math.max(mStableFrame.top - mDisplayFrame.top, 0),
                     Math.max(mDisplayFrame.right - mStableFrame.right, 0),
                     Math.max(mDisplayFrame.bottom - mStableFrame.bottom, 0));
+
+            // The divider doesn't care about insets in any case, so set it to empty so we don't
+            // trigger a relayout when moving it.
+            mContentInsets.setEmpty();
+            mVisibleInsets.setEmpty();
         } else {
+            mContentInsets.set(mContentFrame.left - frame.left,
+                    mContentFrame.top - frame.top,
+                    frame.right - mContentFrame.right,
+                    frame.bottom - mContentFrame.bottom);
+
+            mVisibleInsets.set(mVisibleFrame.left - frame.left,
+                    mVisibleFrame.top - frame.top,
+                    frame.right - mVisibleFrame.right,
+                    frame.bottom - mVisibleFrame.bottom);
+
             mStableInsets.set(Math.max(mStableFrame.left - frame.left, 0),
                     Math.max(mStableFrame.top - frame.top, 0),
                     Math.max(frame.right - mStableFrame.right, 0),
@@ -1045,61 +1055,59 @@
     }
 
     /**
-     * Is this window visible?  It is not visible if there is no
-     * surface, or we are in the process of running an exit animation
-     * that will remove the surface, or its app token has been hidden.
+     * Does the minimal check for visibility. Callers generally want to use one of the public
+     * methods as they perform additional checks on the app token.
+     * TODO: See if there are other places we can use this check below instead of duplicating...
      */
-    @Override
-    public boolean isVisibleLw() {
-        final AppWindowToken atoken = mAppToken;
+    private boolean isVisibleUnchecked() {
         return mHasSurface && mPolicyVisibility && !mAttachedHidden
-                && (atoken == null || !atoken.hiddenRequested)
-                && !mExiting && !mDestroying;
+                && !mExiting && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
     }
 
     /**
-     * Like {@link #isVisibleLw}, but also counts a window that is currently
-     * "hidden" behind the keyguard as visible.  This allows us to apply
-     * things like window flags that impact the keyguard.
-     * XXX I am starting to think we need to have ANOTHER visibility flag
-     * for this "hidden behind keyguard" state rather than overloading
-     * mPolicyVisibility.  Ungh.
+     * Is this window visible?  It is not visible if there is no surface, or we are in the process
+     * of running an exit animation that will remove the surface, or its app token has been hidden.
+     */
+    @Override
+    public boolean isVisibleLw() {
+        return (mAppToken == null || !mAppToken.hiddenRequested) && isVisibleUnchecked();
+    }
+
+    /**
+     * Like {@link #isVisibleLw}, but also counts a window that is currently "hidden" behind the
+     * keyguard as visible.  This allows us to apply things like window flags that impact the
+     * keyguard. XXX I am starting to think we need to have ANOTHER visibility flag for this
+     * "hidden behind keyguard" state rather than overloading mPolicyVisibility.  Ungh.
      */
     @Override
     public boolean isVisibleOrBehindKeyguardLw() {
-        if (mRootToken.waitingToShow &&
-                mService.mAppTransition.isTransitionSet()) {
+        if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
             return false;
         }
         final AppWindowToken atoken = mAppToken;
         final boolean animating = atoken != null && atoken.mAppAnimator.animation != null;
         return mHasSurface && !mDestroying && !mExiting
                 && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
-                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
-                                && !mRootToken.hidden)
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
                         || mWinAnimator.mAnimation != null || animating);
     }
 
     /**
-     * Is this window visible, ignoring its app token?  It is not visible
-     * if there is no surface, or we are in the process of running an exit animation
-     * that will remove the surface.
+     * Is this window visible, ignoring its app token? It is not visible if there is no surface,
+     * or we are in the process of running an exit animation that will remove the surface.
      */
     public boolean isWinVisibleLw() {
-        final AppWindowToken atoken = mAppToken;
-        return mHasSurface && mPolicyVisibility && !mAttachedHidden
-                && (atoken == null || !atoken.hiddenRequested || atoken.mAppAnimator.animating)
-                && !mExiting && !mDestroying;
+        return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+                && isVisibleUnchecked();
     }
 
     /**
-     * The same as isVisible(), but follows the current hidden state of
-     * the associated app token, not the pending requested hidden state.
+     * The same as isVisible(), but follows the current hidden state of the associated app token,
+     * not the pending requested hidden state.
      */
     boolean isVisibleNow() {
-        return mHasSurface && mPolicyVisibility && !mAttachedHidden
-                && (!mRootToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
-                && !mExiting && !mDestroying;
+        return (!mRootToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+                && isVisibleUnchecked();
     }
 
     /**
@@ -1158,8 +1166,7 @@
             return false;
         }
         return mHasSurface && mPolicyVisibility && !mDestroying
-                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
-                                && !mRootToken.hidden)
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
                         || mWinAnimator.mAnimation != null
                         || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
     }
@@ -1179,8 +1186,7 @@
             return false;
         }
         return mHasSurface && !mDestroying
-                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
-                                && !mRootToken.hidden)
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
                         || mWinAnimator.mAnimation != null
                         || ((atoken != null) && (atoken.mAppAnimator.animation != null)
                                 && !mWinAnimator.isDummyAnimation()));
@@ -1993,21 +1999,24 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
-    private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
-            Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
-            Configuration newConfig) throws RemoteException {
-        DisplayInfo displayInfo = getDisplayInfo();
-        mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+    Rect getBackdropFrame(Rect frame) {
         // When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
         // start even if we haven't received the relayout window, so that the client requests
         // the relayout sooner. When dragging stops, backDropFrame needs to stay fullscreen
         // until the window to small size, otherwise the multithread renderer will shift last
         // one or more frame to wrong offset. So here we send fullscreen backdrop if either
         // isDragResizing() or isDragResizeChanged() is true.
+        DisplayInfo displayInfo = getDisplayInfo();
+        mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
         boolean resizing = isDragResizing() || isDragResizeChanged();
-        final Rect backDropFrame = (inFreeformWorkspace() || !resizing) ? frame : mTmpRect;
+        return (inFreeformWorkspace() || !resizing) ? frame : mTmpRect;
+    }
+
+    private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
+            Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+            Configuration newConfig) throws RemoteException {
         mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
-                reportDraw, newConfig, backDropFrame);
+                reportDraw, newConfig, getBackdropFrame(frame));
     }
 
     public void registerFocusObserver(IWindowFocusObserver observer) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2ec6f3c..1ada0ac 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2683,7 +2683,7 @@
                 return admin != null ? admin.passwordQuality : mode;
             }
 
-            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle) && !parent) {
+            if (isSeparateProfileChallengeEnabled(userHandle) && !parent) {
                 // If a Work Challenge is in use, only return its restrictions.
                 DevicePolicyData policy = getUserDataUnchecked(userHandle);
                 final int N = policy.mAdminList.size();
@@ -2703,7 +2703,7 @@
                     // Only aggregate data for the parent profile plus the non-work challenge
                     // enabled profiles.
                     if (!(userInfo.isManagedProfile()
-                            && mLockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id))) {
+                            && isSeparateProfileChallengeEnabled(userInfo.id))) {
                         DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
                         final int N = policy.mAdminList.size();
                         for (int i = 0; i < N; i++) {
@@ -2719,6 +2719,15 @@
         }
     }
 
+    private boolean isSeparateProfileChallengeEnabled(int userHandle) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            return mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     @Override
     public void setPasswordMinimumLength(ComponentName who, int length) {
         if (!mHasFeature) {
@@ -3290,7 +3299,7 @@
             ComponentName adminComponentName = admin.info.getComponent();
             // TODO: Include the Admin sdk level check in LockPatternUtils check.
             ComponentName who = !isAdminApiLevelMOrBelow(adminComponentName, userHandle)
-                    && mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)
+                    && isSeparateProfileChallengeEnabled(userHandle)
                         ? adminComponentName : null;
             if (policy.mActivePasswordQuality < getPasswordQuality(who, userHandle, parent)
                     || policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) {
@@ -3964,6 +3973,42 @@
         }
     }
 
+    @Override
+    public boolean setAlwaysOnVpnPackage(ComponentName admin, String vpnPackage)
+            throws SecurityException {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        }
+
+        final int userId = mInjector.userHandleGetCallingUserId();
+        final long token = mInjector.binderClearCallingIdentity();
+        try{
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            return connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public String getAlwaysOnVpnPackage(ComponentName admin)
+            throws SecurityException {
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        }
+
+        final int userId = mInjector.userHandleGetCallingUserId();
+        final long token = mInjector.binderClearCallingIdentity();
+        try{
+            ConnectivityManager connectivityManager = (ConnectivityManager)
+                    mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+            return connectivityManager.getAlwaysOnVpnPackageForUser(userId);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(token);
+        }
+    }
+
     private void wipeDataLocked(boolean wipeExtRequested, String reason) {
         if (wipeExtRequested) {
             StorageManager sm = (StorageManager) mContext.getSystemService(
@@ -4093,7 +4138,7 @@
         }
         enforceFullCrossUsersPermission(userHandle);
         // Managed Profile password can only be changed when per user encryption is present.
-        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
+        if (!isSeparateProfileChallengeEnabled(userHandle)) {
             enforceNotManagedProfile(userHandle, "set the active password");
         }
 
@@ -4939,7 +4984,7 @@
                             // If we are being asked explictly about this user
                             // return all disabled features even if its a managed profile.
                             which |= admin.disabledKeyguardFeatures;
-                        } else if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(
+                        } else if (!isSeparateProfileChallengeEnabled(
                                 userInfo.id)) {
                             // Otherwise a managed profile is only allowed to disable
                             // some features on the parent user, and we only aggregate them if
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 79786d3..ee5f23f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -129,6 +129,8 @@
             "com.android.server.midi.MidiService$Lifecycle";
     private static final String WIFI_SERVICE_CLASS =
             "com.android.server.wifi.WifiService";
+    private static final String WIFI_NAN_SERVICE_CLASS =
+            "com.android.server.wifi.nan.WifiNanService";
     private static final String WIFI_P2P_SERVICE_CLASS =
             "com.android.server.wifi.p2p.WifiP2pService";
     private static final String ETHERNET_SERVICE_CLASS =
@@ -738,6 +740,11 @@
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+                if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_NAN)) {
+                    mSystemServiceManager.startService(WIFI_NAN_SERVICE_CLASS);
+                } else {
+                    Slog.i(TAG, "No Wi-Fi NAN Service (NAN support Not Present)");
+                }
                 mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
                 mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
                 mSystemServiceManager.startService(
@@ -1026,9 +1033,7 @@
                 mSystemServiceManager.startService(TvInputManagerService.class);
             }
 
-            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
-                mSystemServiceManager.startService(MediaResourceMonitorService.class);
-            }
+            mSystemServiceManager.startService(MediaResourceMonitorService.class);
 
             if (!disableNonCoreServices) {
                 traceBeginAndSlog("StartMediaRouterService");
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 0af1525..5ee4066 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -863,8 +863,7 @@
         }
 
         private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
-            if (printerId == null || printerId.getServiceName() == null
-                    || !printerId.getServiceName().equals(serviceName)) {
+            if (printerId == null || !printerId.getServiceName().equals(serviceName)) {
                 throw new IllegalArgumentException("Invalid printer id: " + printerId);
             }
         }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index eed326e..3ae1072 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -27,7 +27,9 @@
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
-
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
     <uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
@@ -102,6 +104,8 @@
             </intent-filter>
         </receiver>
 
+        <service android:name="com.android.server.job.MockPriorityJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" />
     </application>
 
     <instrumentation
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
new file mode 100644
index 0000000..625fe77
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.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.server.am;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+public class ActivityManagerTest extends AndroidTestCase {
+
+    IActivityManager service;
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        service = ActivityManagerNative.getDefault();
+    }
+
+    public void testTaskIdsForRunningUsers() throws RemoteException {
+        for(int userId : service.getRunningUserIds()) {
+            testTaskIdsForUser(userId);
+        }
+    }
+
+    private void testTaskIdsForUser(int userId) throws RemoteException {
+        List<ActivityManager.RecentTaskInfo> recentTasks = service.getRecentTasks(
+                100, 0, userId);
+        if(recentTasks != null) {
+            for(ActivityManager.RecentTaskInfo recentTask : recentTasks) {
+                int taskId = recentTask.persistentId;
+                assertEquals("The task id " + taskId + " should not belong to user " + userId,
+                        taskId / UserHandle.PER_USER_RANGE, userId);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 312b1b0..ae0a25e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -179,7 +179,21 @@
                 loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS);
         // Assert late runtime was clamped to be now + period*2.
         assertTrue("Early runtime wasn't correctly clamped.",
-                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS*2);
+                loaded.getEarliestRunTime() <= newNowElapsed + TEN_SECONDS * 2);
+    }
+
+    public void testPriorityPersisted() throws Exception {
+        JobInfo.Builder b = new Builder(92, mComponent)
+                .setOverrideDeadline(5000)
+                .setPriority(42)
+                .setPersisted(true);
+        final JobStatus js = new JobStatus(b.build(), SOME_UID);
+        mTaskStoreUnderTest.add(js);
+        Thread.sleep(IO_WAIT);
+        final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
+        JobStatus loaded = jobStatusSet.iterator().next();
+        assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
new file mode 100644
index 0000000..3ea86f2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job;
+
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+@TargetApi(24)
+public class MockPriorityJobService extends JobService {
+    private static final String TAG = "MockPriorityJobService";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.e(TAG, "Created test service.");
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        Log.i(TAG, "Test job executing: " + params.getJobId());
+        TestEnvironment.getTestEnvironment().executedEvents.add(
+                new TestEnvironment.Event(TestEnvironment.EVENT_START_JOB, params.getJobId()));
+        return true;  // Job not finished
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        Log.i(TAG, "Test job stop executing: " + params.getJobId());
+        int reason = params.getStopReason();
+        int event = TestEnvironment.EVENT_STOP_JOB;
+        Log.d(TAG, "stop reason: " + String.valueOf(reason));
+        if (reason == JobParameters.REASON_PREEMPT) {
+            event = TestEnvironment.EVENT_PREEMPT_JOB;
+            Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
+        }
+        TestEnvironment.getTestEnvironment().executedEvents
+                .add(new TestEnvironment.Event(event, params.getJobId()));
+        return false;  // Do not reschedule
+    }
+
+    public static class TestEnvironment {
+
+        public static final int EVENT_START_JOB = 0;
+        public static final int EVENT_PREEMPT_JOB = 1;
+        public static final int EVENT_STOP_JOB = 2;
+
+        private static TestEnvironment kTestEnvironment;
+
+        private ArrayList<Event> executedEvents = new ArrayList<Event>();
+
+        public static TestEnvironment getTestEnvironment() {
+            if (kTestEnvironment == null) {
+                kTestEnvironment = new TestEnvironment();
+            }
+            return kTestEnvironment;
+        }
+
+        public static class Event {
+            public int event;
+            public int jobId;
+
+            public Event() {
+            }
+
+            public Event(int event, int jobId) {
+                this.event = event;
+                this.jobId = jobId;
+            }
+
+            @Override
+            public boolean equals(Object other) {
+                if (other instanceof Event) {
+                    Event otherEvent = (Event) other;
+                    return otherEvent.event == event && otherEvent.jobId == jobId;
+                }
+                return false;
+            }
+        }
+
+        public void setUp() {
+            executedEvents.clear();
+        }
+
+        public ArrayList<Event> getExecutedEvents() {
+            return executedEvents;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
new file mode 100644
index 0000000..63bccfa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+import com.android.server.job.MockPriorityJobService.TestEnvironment;
+import com.android.server.job.MockPriorityJobService.TestEnvironment.Event;
+
+import java.util.ArrayList;
+
+@TargetApi(24)
+public class PrioritySchedulingTest extends AndroidTestCase {
+    /** Environment that notifies of JobScheduler callbacks. */
+    static TestEnvironment kTestEnvironment = TestEnvironment.getTestEnvironment();
+    /** Handle for the service which receives the execution callbacks from the JobScheduler. */
+    static ComponentName kJobServiceComponent;
+    JobScheduler mJobScheduler;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        kTestEnvironment.setUp();
+        kJobServiceComponent = new ComponentName(getContext(), MockPriorityJobService.class);
+        mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        mJobScheduler.cancelAll();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mJobScheduler.cancelAll();
+        super.tearDown();
+    }
+
+    public void testLowerPriorityJobPreempted() throws Exception {
+        JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
+                .setPriority(1)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
+                .setPriority(1)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
+                .setPriority(1)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
+                .setPriority(2)
+                .setMinimumLatency(2000L)
+                .setOverrideDeadline(7000L)
+                .build();
+        mJobScheduler.schedule(job1);
+        mJobScheduler.schedule(job2);
+        mJobScheduler.schedule(job3);
+        mJobScheduler.schedule(job4);
+        Thread.sleep(10000);  // Wait for job 4 to preempt one of the lower priority jobs
+
+        Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
+        ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents();
+        boolean wasJob4Executed = executedEvents.contains(job4Execution);
+        boolean wasSomeJobPreempted = false;
+        for (Event event: executedEvents) {
+            if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
+                wasSomeJobPreempted = true;
+                break;
+            }
+        }
+        assertTrue("No job was preempted.", wasSomeJobPreempted);
+        assertTrue("Lower priority jobs were not preempted.",  wasJob4Executed);
+    }
+
+    public void testHigherPriorityJobNotPreempted() throws Exception {
+        JobInfo job1 = new JobInfo.Builder(111, kJobServiceComponent)
+                .setPriority(2)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job2 = new JobInfo.Builder(222, kJobServiceComponent)
+                .setPriority(2)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job3 = new JobInfo.Builder(333, kJobServiceComponent)
+                .setPriority(2)
+                .setOverrideDeadline(7000L)
+                .build();
+        JobInfo job4 = new JobInfo.Builder(444, kJobServiceComponent)
+                .setPriority(1)
+                .setMinimumLatency(2000L)
+                .setOverrideDeadline(7000L)
+                .build();
+        mJobScheduler.schedule(job1);
+        mJobScheduler.schedule(job2);
+        mJobScheduler.schedule(job3);
+        mJobScheduler.schedule(job4);
+        Thread.sleep(10000);  // Wait for job 4 to preempt one of the higher priority jobs
+
+        Event job4Execution = new Event(TestEnvironment.EVENT_START_JOB, 444);
+        boolean wasJob4Executed = kTestEnvironment.getExecutedEvents().contains(job4Execution);
+        assertFalse("Higher priority job was preempted.", wasJob4Executed);
+    }
+}
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
new file mode 100644
index 0000000..b7e78d1
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable ParcelableCallAnalytics;
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
new file mode 100644
index 0000000..e7c9672
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -0,0 +1,184 @@
+/*
+ * 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.telecom;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public class ParcelableCallAnalytics implements Parcelable {
+    public static final int CALLTYPE_UNKNOWN = 0;
+    public static final int CALLTYPE_INCOMING = 1;
+    public static final int CALLTYPE_OUTGOING = 2;
+
+    // Constants for call technology
+    public static final int CDMA_PHONE = 0x1;
+    public static final int GSM_PHONE = 0x2;
+    public static final int IMS_PHONE = 0x4;
+    public static final int SIP_PHONE = 0x8;
+    public static final int THIRD_PARTY_PHONE = 0x10;
+
+    public static final long MILLIS_IN_5_MINUTES = 1000 * 60 * 5;
+    public static final long MILLIS_IN_1_SECOND = 1000;
+
+    public static final int STILL_CONNECTED = -1;
+
+    public static final Parcelable.Creator<ParcelableCallAnalytics> CREATOR =
+            new Parcelable.Creator<ParcelableCallAnalytics> () {
+
+                @Override
+                public ParcelableCallAnalytics createFromParcel(Parcel in) {
+                    return new ParcelableCallAnalytics(in);
+                }
+
+                @Override
+                public ParcelableCallAnalytics[] newArray(int size) {
+                    return new ParcelableCallAnalytics[size];
+                }
+            };
+
+    // The start time of the call in milliseconds since Jan. 1, 1970, rounded to the nearest
+    // 5 minute increment.
+    private final long startTimeMillis;
+
+    // The duration of the call, in milliseconds.
+    private final long callDurationMillis;
+
+    // ONE OF calltype_unknown, calltype_incoming, or calltype_outgoing
+    private final int callType;
+
+    // true if the call came in while another call was in progress or if the user dialed this call
+    // while in the middle of another call.
+    private final boolean isAdditionalCall;
+
+    // true if the call was interrupted by an incoming or outgoing call.
+    private final boolean isInterrupted;
+
+    // bitmask denoting which technologies a call used.
+    private final int callTechnologies;
+
+    // Any of the DisconnectCause codes, or STILL_CONNECTED.
+    private final int callTerminationCode;
+
+    // Whether the call is an emergency call
+    private final boolean isEmergencyCall;
+
+    // The package name of the connection service that this call used.
+    private final String connectionService;
+
+    // Whether the call object was created from an existing connection.
+    private final boolean isCreatedFromExistingConnection;
+
+    public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType,
+            boolean isAdditionalCall, boolean isInterrupted, int callTechnologies,
+            int callTerminationCode, boolean isEmergencyCall, String connectionService,
+            boolean isCreatedFromExistingConnection) {
+        this.startTimeMillis = startTimeMillis;
+        this.callDurationMillis = callDurationMillis;
+        this.callType = callType;
+        this.isAdditionalCall = isAdditionalCall;
+        this.isInterrupted = isInterrupted;
+        this.callTechnologies = callTechnologies;
+        this.callTerminationCode = callTerminationCode;
+        this.isEmergencyCall = isEmergencyCall;
+        this.connectionService = connectionService;
+        this.isCreatedFromExistingConnection = isCreatedFromExistingConnection;
+    }
+
+    public ParcelableCallAnalytics(Parcel in) {
+        startTimeMillis = in.readLong();
+        callDurationMillis = in.readLong();
+        callType = in.readInt();
+        isAdditionalCall = readByteAsBoolean(in);
+        isInterrupted = readByteAsBoolean(in);
+        callTechnologies = in.readInt();
+        callTerminationCode = in.readInt();
+        isEmergencyCall = readByteAsBoolean(in);
+        connectionService = in.readString();
+        isCreatedFromExistingConnection = readByteAsBoolean(in);
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(startTimeMillis);
+        out.writeLong(callDurationMillis);
+        out.writeInt(callType);
+        writeBooleanAsByte(out, isAdditionalCall);
+        writeBooleanAsByte(out, isInterrupted);
+        out.writeInt(callTechnologies);
+        out.writeInt(callTerminationCode);
+        writeBooleanAsByte(out, isEmergencyCall);
+        out.writeString(connectionService);
+        writeBooleanAsByte(out, isCreatedFromExistingConnection);
+    }
+
+    public long getStartTimeMillis() {
+        return startTimeMillis;
+    }
+
+    public long getCallDurationMillis() {
+        return callDurationMillis;
+    }
+
+    public int getCallType() {
+        return callType;
+    }
+
+    public boolean isAdditionalCall() {
+        return isAdditionalCall;
+    }
+
+    public boolean isInterrupted() {
+        return isInterrupted;
+    }
+
+    public int getCallTechnologies() {
+        return callTechnologies;
+    }
+
+    public int getCallTerminationCode() {
+        return callTerminationCode;
+    }
+
+    public boolean isEmergencyCall() {
+        return isEmergencyCall;
+    }
+
+    public String getConnectionService() {
+        return connectionService;
+    }
+
+    public boolean isCreatedFromExistingConnection() {
+        return isCreatedFromExistingConnection;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private static void writeBooleanAsByte(Parcel out, boolean b) {
+        out.writeByte((byte) (b ? 1 : 0));
+    }
+
+    private static boolean readByteAsBoolean(Parcel in) {
+        return (in.readByte() == 1);
+    }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9eb1665..d45b26f 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -14,6 +14,7 @@
 
 package android.telecom;
 
+import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
@@ -1377,6 +1378,27 @@
         }
     }
 
+    /**
+     * Dumps telecom analytics for uploading.
+     *
+     * @return
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.DUMP)
+    public List<ParcelableCallAnalytics> dumpAnalytics() {
+        ITelecomService service = getTelecomService();
+        List<ParcelableCallAnalytics> result = null;
+        if (service != null) {
+            try {
+                result = service.dumpCallAnalytics();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error dumping call analytics", e);
+            }
+        }
+        return result;
+    }
+
     private ITelecomService getTelecomService() {
         if (mTelecomServiceOverride != null) {
             return mTelecomServiceOverride;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 856e210..af36b3e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
+import android.telecom.ParcelableCallAnalytics;
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
 import android.os.Bundle;
@@ -143,6 +144,11 @@
      */
     String getSystemDialerPackage();
 
+    /**
+    * @see TelecomServiceImpl#dumpCallAnalytics
+    */
+    List<ParcelableCallAnalytics> dumpCallAnalytics();
+
     //
     // Internal system apis relating to call management.
     //
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 80c5b1e..10815b3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -495,6 +495,13 @@
      */
     public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
 
+    /**
+     * Boolean indicating if intent for emergency call state changes should be broadcast
+     * @hide
+     */
+    public static final String KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL =
+            "broadcast_emergency_call_state_changes_bool";
+
     // These variables are used by the MMS service and exposed through another API, {@link
     // SmsManager}. The variable names and string values are copied from there.
     public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -638,6 +645,7 @@
         sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, "");
         sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
+        sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
 
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 913cb18..43fe9d1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3998,19 +3998,6 @@
 
     /** @hide */
     @SystemApi
-    public boolean isSimPinEnabled() {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.isSimPinEnabled(getOpPackageName());
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#isSimPinEnabled", e);
-        }
-        return false;
-    }
-
-    /** @hide */
-    @SystemApi
     public boolean supplyPin(String pin) {
         try {
             ITelephony telephony = getITelephony();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d1badc9..62f294c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -182,13 +182,6 @@
     boolean isRadioOnForSubscriber(int subId, String callingPackage);
 
     /**
-     * Check if the SIM pin lock is enabled.
-     * @return true if the SIM pin lock is enabled.
-     * @param callingPackage The package making the call.
-     */
-    boolean isSimPinEnabled(String callingPackage);
-
-    /**
      * Supply a pin to unlock the SIM.  Blocks until a result is determined.
      * @param pin The pin to check.
      * @return whether the operation was a success.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index a183de5..ecd89ed 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -35,17 +35,17 @@
         IDLE, RINGING, OFFHOOK;
     };
 
-   /**
-     * The state of a data connection.
-     * <ul>
-     * <li>CONNECTED = IP traffic should be available</li>
-     * <li>CONNECTING = Currently setting up data connection</li>
-     * <li>DISCONNECTED = IP not available</li>
-     * <li>SUSPENDED = connection is created but IP traffic is
-     *                 temperately not available. i.e. voice call is in place
-     *                 in 2G network</li>
-     * </ul>
-     */
+    /**
+      * The state of a data connection.
+      * <ul>
+      * <li>CONNECTED = IP traffic should be available</li>
+      * <li>CONNECTING = Currently setting up data connection</li>
+      * <li>DISCONNECTED = IP not available</li>
+      * <li>SUSPENDED = connection is created but IP traffic is
+      *                 temperately not available. i.e. voice call is in place
+      *                 in 2G network</li>
+      * </ul>
+      */
     public enum DataState {
         CONNECTED, CONNECTING, DISCONNECTED, SUSPENDED;
     };
@@ -89,6 +89,7 @@
     public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
     public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
     public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
+    public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
 
     public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
 
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index d2e4de3..c70f8cf 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -75,6 +75,7 @@
      */
     public static final String ACTION_RADIO_TECHNOLOGY_CHANGED
             = "android.intent.action.RADIO_TECHNOLOGY";
+
     /**
      * <p>Broadcast Action: The emergency callback mode is changed.
      * <ul>
@@ -94,6 +95,28 @@
      */
     public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
             = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+
+    /**
+     * <p>Broadcast Action: The emergency call state is changed.
+     * <ul>
+     *   <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call,
+     *   false otherwise</li>
+     * </ul>
+     * <p class="note">
+     * You can <em>not</em> receive this through components declared
+     * in manifests, only by explicitly registering for it with
+     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+     * android.content.IntentFilter) Context.registerReceiver()}.
+     *
+     * <p class="note">
+     * Requires no permission.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
+            = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+
     /**
      * Broadcast Action: The phone's signal strength has changed. The intent will have the
      * following extra values:</p>
diff --git a/tests/FeatureSplit/base/AndroidManifest.xml b/tests/FeatureSplit/base/AndroidManifest.xml
index 989e802..e82b3b9 100644
--- a/tests/FeatureSplit/base/AndroidManifest.xml
+++ b/tests/FeatureSplit/base/AndroidManifest.xml
@@ -16,7 +16,15 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.split.feature">
-    <application android:label="@string/app_title"
-        android:hasCode="false">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+    <application android:label="@string/app_title">
+        <activity android:name=".ActivityMain" android:label="Feature Base">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/FeatureSplit/base/res/layout/main.xml b/tests/FeatureSplit/base/res/layout/main.xml
new file mode 100644
index 0000000..f01b920
--- /dev/null
+++ b/tests/FeatureSplit/base/res/layout/main.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="16dp">
+    <TextView android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:textAppearance="?android:textAppearanceLarge" />
+</RelativeLayout>
diff --git a/tests/FeatureSplit/base/res/values/values.xml b/tests/FeatureSplit/base/res/values/values.xml
index 564d301..854a8bb 100644
--- a/tests/FeatureSplit/base/res/values/values.xml
+++ b/tests/FeatureSplit/base/res/values/values.xml
@@ -16,6 +16,7 @@
 
 <resources>
     <string name="app_title">FeatureSplit APK</string>
+    <string name="base">Base</string>
 
     <item type="id" name="test_id"/>
     <integer name="test_integer">100</integer>
diff --git a/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java b/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java
new file mode 100644
index 0000000..6cca7c3
--- /dev/null
+++ b/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.split.feature;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+        ((TextView) findViewById(R.id.text)).setText(R.string.base);
+    }
+}
+
diff --git a/tests/FeatureSplit/feature1/Android.mk b/tests/FeatureSplit/feature1/Android.mk
index adfb575..aa222dd 100644
--- a/tests/FeatureSplit/feature1/Android.mk
+++ b/tests/FeatureSplit/feature1/Android.mk
@@ -22,10 +22,12 @@
 LOCAL_MODULE_TAGS := tests
 
 featureOf := FeatureSplitBase
+
+LOCAL_APK_LIBRARIES := $(featureOf)
 featureOfApk := $(call intermediates-dir-for,APPS,$(featureOf))/package.apk
 localRStamp := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)/src/R.stamp
 $(localRStamp): $(featureOfApk)
 
-LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk)
+LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk) --custom-package com.android.test.split.feature.one
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index 2aadc6d..42619b6 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -17,5 +17,15 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.split.feature"
     featureName="feature1">
-    <application android:hasCode="false" />
+
+    <uses-sdk android:minSdkVersion="21" />
+
+    <application>
+        <activity android:name=".one.One" android:label="Feature One">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
 </manifest>
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 70eb56a..10dbd97 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -15,6 +15,7 @@
 -->
 
 <resources>
+    <string name="feature_string">Feature1</string>
     <item type="id" name="test_id2"/>
     <integer name="test_integer2">200</integer>
     <color name="test_color2">#00ff00</color>
diff --git a/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java b/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java
new file mode 100644
index 0000000..def1339
--- /dev/null
+++ b/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.split.feature.one;
+
+import com.android.test.split.feature.ActivityMain;
+
+import android.widget.TextView;
+import android.os.Bundle;
+
+public class One extends ActivityMain {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ((TextView) findViewById(com.android.test.split.feature.R.id.text))
+                .setText(R.string.feature_string);
+    }
+}
diff --git a/tests/FeatureSplit/feature2/Android.mk b/tests/FeatureSplit/feature2/Android.mk
index e69cbe8..1a0322b 100644
--- a/tests/FeatureSplit/feature2/Android.mk
+++ b/tests/FeatureSplit/feature2/Android.mk
@@ -24,6 +24,8 @@
 featureOf := FeatureSplitBase
 featureAfter := FeatureSplit1
 
+LOCAL_APK_LIBRARIES := $(featureOf)
+
 featureOfApk := $(call intermediates-dir-for,APPS,$(featureOf))/package.apk
 featureAfterApk := $(call intermediates-dir-for,APPS,$(featureAfter))/package.apk
 localRStamp := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)/src/R.stamp
@@ -31,5 +33,6 @@
 
 LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk)
 LOCAL_AAPT_FLAGS += --feature-after $(featureAfterApk)
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.two
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature2/AndroidManifest.xml b/tests/FeatureSplit/feature2/AndroidManifest.xml
index d139900..b50044a 100644
--- a/tests/FeatureSplit/feature2/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature2/AndroidManifest.xml
@@ -17,5 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.split.feature"
     featureName="feature2">
+
+    <uses-sdk android:minSdkVersion="21" />
+
     <application android:hasCode="false"/>
 </manifest>
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 576f076..4d9ba6c 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -2119,7 +2119,7 @@
                 indentStr);
     }
 
-    return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
+    return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
 }
 
 static status_t writeResourceLoadedCallback(
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 2a4c020..51b9cec 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -523,7 +523,10 @@
     }
 
     bool processFile(const std::string& path, bool override) {
-        if (util::stringEndsWith<char>(path, ".flata")) {
+        if (util::stringEndsWith<char>(path, ".flata") ||
+                util::stringEndsWith<char>(path, ".jar") ||
+                util::stringEndsWith<char>(path, ".jack") ||
+                util::stringEndsWith<char>(path, ".zip")) {
             return mergeArchive(path, override);
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 63411b0..e6fb620 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -197,7 +197,12 @@
 
         mRenderResources = renderResources;
         mConfig = config;
-        mAssets = new BridgeAssetManager();
+        AssetManager systemAssetManager = AssetManager.getSystem();
+        if (systemAssetManager instanceof BridgeAssetManager) {
+            mAssets = (BridgeAssetManager) systemAssetManager;
+        } else {
+            throw new AssertionError("Creating BridgeContext without initializing Bridge");
+        }
         mAssets.setAssetRepository(assets);
 
         mApplicationInfo = new ApplicationInfo();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 5c73fb6a..aae5ac4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -89,7 +89,8 @@
     @Override
     public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2,
             int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5,
-            Rect rect6, Configuration configuration, Surface surface) throws RemoteException {
+            Rect rect6, Rect rect7, Configuration configuration, Surface surface)
+            throws RemoteException {
         // pass for now.
         return 0;
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
index 9aab340..8c60bae 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -114,7 +114,7 @@
                         if (value != null) {
                             if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
                                 Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
-                                        "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+                                        "Wrong Adapter Item value class for IS_CHECKED. Expected Boolean, got %s",
                                         value.getClass().getName()), null);
                             } else {
                                 cb.setChecked((Boolean) value);
@@ -134,7 +134,7 @@
                         if (value != null) {
                             if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
                                 Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
-                                        "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s",
+                                        "Wrong Adapter Item value class for SRC. Expected Boolean, got %s",
                                         value.getClass().getName()), null);
                             } else {
                                 // FIXME
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index dea86bf..b1b3759 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -380,10 +380,11 @@
     }
 
     /**
-     * Create a new rendering session and test that rendering given layout on nexus 5
-     * doesn't throw any exceptions and matches the provided image.
-     * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time
-     * indicates how far in the future is.
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
+     * <p>
+     * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
+     * how far in the future is.
      */
     private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
             throws ClassNotFoundException {
@@ -414,8 +415,8 @@
     }
 
     /**
-     * Create a new rendering session and test that rendering given layout on nexus 5
-     * doesn't throw any exceptions and matches the provided image.
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
      */
     private void renderAndVerify(SessionParams params, String goldenFileName)
             throws ClassNotFoundException {
@@ -423,7 +424,7 @@
     }
 
     /**
-     * Create a new rendering session and test that rendering given layout on nexus 5
+     * Create a new rendering session and test that rendering the given layout on nexus 5
      * doesn't throw any exceptions and matches the provided image.
      */
     private void renderAndVerify(String layoutFileName, String goldenFileName)
@@ -432,7 +433,7 @@
     }
 
     /**
-     * Create a new rendering session and test that rendering given layout on given device
+     * Create a new rendering session and test that rendering the given layout on given device
      * doesn't throw any exceptions and matches the provided image.
      */
     private void renderAndVerify(String layoutFileName, String goldenFileName,
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 972dcb2..0c06ae8 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -24,6 +24,7 @@
 import android.net.StaticIpConfiguration;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
 import java.util.Arrays;
@@ -327,6 +328,13 @@
 
     /**
      * @hide
+     * This network configuration is visible to and usable by other users on the
+     * same device.
+     */
+    public boolean shared;
+
+    /**
+     * @hide
      */
     private IpConfiguration mIpConfiguration;
 
@@ -1103,6 +1111,7 @@
         mIpConfiguration = new IpConfiguration();
         lastUpdateUid = -1;
         creatorUid = -1;
+        shared = true;
     }
 
     /**
@@ -1488,6 +1497,9 @@
             key = mCachedConfigKey;
         } else if (providerFriendlyName != null) {
             key = FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+            if (!shared) {
+                key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
+            }
         } else {
             if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
                 key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
@@ -1499,6 +1511,9 @@
             } else {
                 key = SSID + KeyMgmt.strings[KeyMgmt.NONE];
             }
+            if (!shared) {
+                key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
+            }
             mCachedConfigKey = key;
         }
         return key;
@@ -1511,27 +1526,6 @@
         return configKey(false);
     }
 
-    /** @hide
-     * return the config key string based on a scan result
-     */
-    static public String configKey(ScanResult result) {
-        String key = "\"" + result.SSID + "\"";
-
-        if (result.capabilities.contains("WEP")) {
-            key = key + "-WEP";
-        }
-
-        if (result.capabilities.contains("PSK")) {
-            key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_PSK];
-        }
-
-        if (result.capabilities.contains("EAP")) {
-            key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_EAP];
-        }
-
-        return key;
-    }
-
     /** @hide */
     public IpConfiguration getIpConfiguration() {
         return mIpConfiguration;
@@ -1593,6 +1587,11 @@
         return 0;
     }
 
+    /** @hide */
+    public boolean isVisibleToUser(int userId) {
+        return shared || (UserHandle.getUserId(creatorUid) == userId);
+    }
+
     /** copy constructor {@hide} */
     public WifiConfiguration(WifiConfiguration source) {
         if (source != null) {
@@ -1676,6 +1675,7 @@
             noInternetAccessExpected = source.noInternetAccessExpected;
             creationTime = source.creationTime;
             updateTime = source.updateTime;
+            shared = source.shared;
         }
     }
 
@@ -1747,6 +1747,7 @@
         dest.writeInt(userApproved);
         dest.writeInt(numNoInternetAccessReports);
         dest.writeInt(noInternetAccessExpected ? 1 : 0);
+        dest.writeInt(shared ? 1 : 0);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1814,6 +1815,7 @@
                 config.userApproved = in.readInt();
                 config.numNoInternetAccessReports = in.readInt();
                 config.noInternetAccessExpected = in.readInt() != 0;
+                config.shared = in.readInt() != 0;
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7a5a74f..c1269f9 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1007,7 +1007,7 @@
 
     /**
      * @return true if this adapter supports Neighbour Awareness Network APIs
-     * @hide
+     * @hide PROPOSED_NAN_API
      */
     public boolean isNanSupported() {
         return isFeatureSupported(WIFI_FEATURE_NAN);
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.aidl b/wifi/java/android/net/wifi/nan/ConfigRequest.aidl
new file mode 100644
index 0000000..38dddc2
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable ConfigRequest;
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
new file mode 100644
index 0000000..23e3754
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Defines a request object to configure a Wi-Fi NAN network. Built using
+ * {@link ConfigRequest.Builder}. Configuration is requested using
+ * {@link WifiNanManager#requestConfig(ConfigRequest)}. Note that the actual
+ * achieved configuration may be different from the requested configuration -
+ * since multiple applications may request different configurations.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class ConfigRequest implements Parcelable {
+    /**
+     * Lower range of possible cluster ID.
+     *
+     * @hide
+     */
+    public static final int CLUSTER_ID_MIN = 0;
+
+    /**
+     * Upper range of possible cluster ID.
+     *
+     * @hide
+     */
+    public static final int CLUSTER_ID_MAX = 0xFFFF;
+
+    /**
+     * Indicates whether 5G band support is requested.
+     *
+     * @hide
+     */
+    public final boolean mSupport5gBand;
+
+    /**
+     * Specifies the desired master preference.
+     *
+     * @hide
+     */
+    public final int mMasterPreference;
+
+    /**
+     * Specifies the desired lower range of the cluster ID. Must be lower then
+     * {@link ConfigRequest#mClusterHigh}.
+     *
+     * @hide
+     */
+    public final int mClusterLow;
+
+    /**
+     * Specifies the desired higher range of the cluster ID. Must be higher then
+     * {@link ConfigRequest#mClusterLow}.
+     *
+     * @hide
+     */
+    public final int mClusterHigh;
+
+    private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
+            int clusterHigh) {
+        mSupport5gBand = support5gBand;
+        mMasterPreference = masterPreference;
+        mClusterLow = clusterLow;
+        mClusterHigh = clusterHigh;
+    }
+
+    @Override
+    public String toString() {
+        return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
+                + mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
+                + mClusterHigh + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSupport5gBand ? 1 : 0);
+        dest.writeInt(mMasterPreference);
+        dest.writeInt(mClusterLow);
+        dest.writeInt(mClusterHigh);
+    }
+
+    public static final Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
+        @Override
+        public ConfigRequest[] newArray(int size) {
+            return new ConfigRequest[size];
+        }
+
+        @Override
+        public ConfigRequest createFromParcel(Parcel in) {
+            boolean support5gBand = in.readInt() != 0;
+            int masterPreference = in.readInt();
+            int clusterLow = in.readInt();
+            int clusterHigh = in.readInt();
+            return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof ConfigRequest)) {
+            return false;
+        }
+
+        ConfigRequest lhs = (ConfigRequest) o;
+
+        return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
+                && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + (mSupport5gBand ? 1 : 0);
+        result = 31 * result + mMasterPreference;
+        result = 31 * result + mClusterLow;
+        result = 31 * result + mClusterHigh;
+
+        return result;
+    }
+
+    /**
+     * Builder used to build {@link ConfigRequest} objects.
+     */
+    public static final class Builder {
+        private boolean mSupport5gBand;
+        private int mMasterPreference;
+        private int mClusterLow;
+        private int mClusterHigh;
+
+        /**
+         * Default constructor for the Builder.
+         */
+        public Builder() {
+            mSupport5gBand = false;
+            mMasterPreference = 0;
+            mClusterLow = 0;
+            mClusterHigh = CLUSTER_ID_MAX;
+        }
+
+        /**
+         * Specify whether 5G band support is required in this request.
+         *
+         * @param support5gBand Support for 5G band is required.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setSupport5gBand(boolean support5gBand) {
+            mSupport5gBand = support5gBand;
+            return this;
+        }
+
+        /**
+         * Specify the Master Preference requested. The permitted range is 0 to
+         * 255 with 1 and 255 excluded (reserved).
+         *
+         * @param masterPreference The requested master preference
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setMasterPreference(int masterPreference) {
+            if (masterPreference < 0) {
+                throw new IllegalArgumentException(
+                        "Master Preference specification must be non-negative");
+            }
+            if (masterPreference == 1 || masterPreference == 255 || masterPreference > 255) {
+                throw new IllegalArgumentException("Master Preference specification must not "
+                        + "exceed 255 or use 1 or 255 (reserved values)");
+            }
+
+            mMasterPreference = masterPreference;
+            return this;
+        }
+
+        /**
+         * The Cluster ID is generated randomly for new NAN networks. Specify
+         * the lower range of the cluster ID. The upper range is specified using
+         * the {@link ConfigRequest.Builder#setClusterHigh(int)}. The permitted
+         * range is 0 to the value specified by
+         * {@link ConfigRequest.Builder#setClusterHigh(int)}. Equality is
+         * permitted which restricts the Cluster ID to the specified value.
+         *
+         * @param clusterLow The lower range of the generated cluster ID.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setClusterLow(..).setClusterHigh(..)}.
+         */
+        public Builder setClusterLow(int clusterLow) {
+            if (clusterLow < CLUSTER_ID_MIN) {
+                throw new IllegalArgumentException("Cluster specification must be non-negative");
+            }
+            if (clusterLow > CLUSTER_ID_MAX) {
+                throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+            }
+
+            mClusterLow = clusterLow;
+            return this;
+        }
+
+        /**
+         * The Cluster ID is generated randomly for new NAN networks. Specify
+         * the lower upper of the cluster ID. The lower range is specified using
+         * the {@link ConfigRequest.Builder#setClusterLow(int)}. The permitted
+         * range is the value specified by
+         * {@link ConfigRequest.Builder#setClusterLow(int)} to 0xFFFF. Equality
+         * is permitted which restricts the Cluster ID to the specified value.
+         *
+         * @param clusterHigh The upper range of the generated cluster ID.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setClusterLow(..).setClusterHigh(..)}.
+         */
+        public Builder setClusterHigh(int clusterHigh) {
+            if (clusterHigh < CLUSTER_ID_MIN) {
+                throw new IllegalArgumentException("Cluster specification must be non-negative");
+            }
+            if (clusterHigh > CLUSTER_ID_MAX) {
+                throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+            }
+
+            mClusterHigh = clusterHigh;
+            return this;
+        }
+
+        /**
+         * Build {@link ConfigRequest} given the current requests made on the
+         * builder.
+         */
+        public ConfigRequest build() {
+            if (mClusterLow > mClusterHigh) {
+                throw new IllegalArgumentException(
+                        "Invalid argument combination - must have Cluster Low <= Cluster High");
+            }
+
+            return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
new file mode 100644
index 0000000..13efc36
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.net.wifi.nan.ConfigRequest;
+
+/**
+ * Callback interface that WifiNanManager implements
+ *
+ * {@hide}
+ */
+oneway interface IWifiNanEventListener
+{
+    void onConfigCompleted(in ConfigRequest completedConfig);
+    void onConfigFailed(int reason);
+    void onNanDown(int reason);
+    void onIdentityChanged();
+}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
new file mode 100644
index 0000000..ff3d29f
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -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 android.net.wifi.nan;
+
+import android.app.PendingIntent;
+
+import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.nan.IWifiNanEventListener;
+import android.net.wifi.nan.IWifiNanSessionListener;
+import android.net.wifi.nan.PublishData;
+import android.net.wifi.nan.PublishSettings;
+import android.net.wifi.nan.SubscribeData;
+import android.net.wifi.nan.SubscribeSettings;
+
+/**
+ * Interface that WifiNanService implements
+ *
+ * {@hide}
+ */
+interface IWifiNanManager
+{
+    // client API
+    void connect(in IBinder binder, in IWifiNanEventListener listener, int events);
+    void disconnect(in IBinder binder);
+    void requestConfig(in ConfigRequest configRequest);
+
+    // session API
+    int createSession(in IWifiNanSessionListener listener, int events);
+    void publish(int sessionId, in PublishData publishData, in PublishSettings publishSettings);
+    void subscribe(int sessionId, in SubscribeData subscribeData,
+            in SubscribeSettings subscribeSettings);
+    void sendMessage(int sessionId, int peerId, in byte[] message, int messageLength);
+    void stopSession(int sessionId);
+    void destroySession(int sessionId);
+}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
new file mode 100644
index 0000000..773f83b
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+/**
+ * Callback interface that WifiNanManager implements
+ *
+ * {@hide}
+ */
+oneway interface IWifiNanSessionListener
+{
+    void onPublishFail(int reason);
+    void onPublishTerminated(int reason);
+
+    void onSubscribeFail(int reason);
+    void onSubscribeTerminated(int reason);
+
+    void onMatch(int peerId, in byte[] serviceSpecificInfo,
+            int serviceSpecificInfoLength, in byte[] matchFilter, int matchFilterLength);
+
+    void onMessageSendSuccess();
+    void onMessageSendFail(int reason);
+    void onMessageReceived(int peerId, in byte[] message, int messageLength);
+}
diff --git a/wifi/java/android/net/wifi/nan/PublishData.aidl b/wifi/java/android/net/wifi/nan/PublishData.aidl
new file mode 100644
index 0000000..15e4ddf
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable PublishData;
diff --git a/wifi/java/android/net/wifi/nan/PublishData.java b/wifi/java/android/net/wifi/nan/PublishData.java
new file mode 100644
index 0000000..80119eb
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishData.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Defines the data for a NAN publish session. Built using
+ * {@link PublishData.Builder}. Publish is done using
+ * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
+ * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
+ * @hide PROPOSED_NAN_API
+ */
+public class PublishData implements Parcelable {
+    /**
+     * @hide
+     */
+    public final String mServiceName;
+
+    /**
+     * @hide
+     */
+    public final int mServiceSpecificInfoLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mServiceSpecificInfo;
+
+    /**
+     * @hide
+     */
+    public final int mTxFilterLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mTxFilter;
+
+    /**
+     * @hide
+     */
+    public final int mRxFilterLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mRxFilter;
+
+    private PublishData(String serviceName, byte[] serviceSpecificInfo,
+            int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
+            int rxFilterLength) {
+        mServiceName = serviceName;
+        mServiceSpecificInfoLength = serviceSpecificInfoLength;
+        mServiceSpecificInfo = serviceSpecificInfo;
+        mTxFilterLength = txFilterLength;
+        mTxFilter = txFilter;
+        mRxFilterLength = rxFilterLength;
+        mRxFilter = rxFilter;
+    }
+
+    @Override
+    public String toString() {
+        return "PublishData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
+                + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
+                + "', mTxFilter="
+                + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
+                + ", mRxFilter="
+                + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
+                + "']";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mServiceName);
+        dest.writeInt(mServiceSpecificInfoLength);
+        if (mServiceSpecificInfoLength != 0) {
+            dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
+        }
+        dest.writeInt(mTxFilterLength);
+        if (mTxFilterLength != 0) {
+            dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
+        }
+        dest.writeInt(mRxFilterLength);
+        if (mRxFilterLength != 0) {
+            dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
+        }
+    }
+
+    public static final Creator<PublishData> CREATOR = new Creator<PublishData>() {
+        @Override
+        public PublishData[] newArray(int size) {
+            return new PublishData[size];
+        }
+
+        @Override
+        public PublishData createFromParcel(Parcel in) {
+            String serviceName = in.readString();
+            int ssiLength = in.readInt();
+            byte[] ssi = new byte[ssiLength];
+            if (ssiLength != 0) {
+                in.readByteArray(ssi);
+            }
+            int txFilterLength = in.readInt();
+            byte[] txFilter = new byte[txFilterLength];
+            if (txFilterLength != 0) {
+                in.readByteArray(txFilter);
+            }
+            int rxFilterLength = in.readInt();
+            byte[] rxFilter = new byte[rxFilterLength];
+            if (rxFilterLength != 0) {
+                in.readByteArray(rxFilter);
+            }
+
+            return new PublishData(serviceName, ssi, ssiLength, txFilter, txFilterLength, rxFilter,
+                    rxFilterLength);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PublishData)) {
+            return false;
+        }
+
+        PublishData lhs = (PublishData) o;
+
+        if (!mServiceName.equals(lhs.mServiceName)
+                || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
+                || mTxFilterLength != lhs.mTxFilterLength
+                || mRxFilterLength != lhs.mRxFilterLength) {
+            return false;
+        }
+
+        if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
+            for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
+                if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
+                    return false;
+                }
+            }
+        } else if (mServiceSpecificInfoLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        if (mTxFilter != null && lhs.mTxFilter != null) {
+            for (int i = 0; i < mTxFilterLength; ++i) {
+                if (mTxFilter[i] != lhs.mTxFilter[i]) {
+                    return false;
+                }
+            }
+        } else if (mTxFilterLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        if (mRxFilter != null && lhs.mRxFilter != null) {
+            for (int i = 0; i < mRxFilterLength; ++i) {
+                if (mRxFilter[i] != lhs.mRxFilter[i]) {
+                    return false;
+                }
+            }
+        } else if (mRxFilterLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + mServiceName.hashCode();
+        result = 31 * result + mServiceSpecificInfoLength;
+        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+        result = 31 * result + mTxFilterLength;
+        result = 31 * result + Arrays.hashCode(mTxFilter);
+        result = 31 * result + mRxFilterLength;
+        result = 31 * result + Arrays.hashCode(mRxFilter);
+
+        return result;
+    }
+
+    /**
+     * Builder used to build {@link PublishData} objects.
+     */
+    public static final class Builder {
+        private String mServiceName;
+        private int mServiceSpecificInfoLength;
+        private byte[] mServiceSpecificInfo = new byte[0];
+        private int mTxFilterLength;
+        private byte[] mTxFilter = new byte[0];
+        private int mRxFilterLength;
+        private byte[] mRxFilter = new byte[0];
+
+        /**
+         * Specify the service name of the publish session. The actual on-air
+         * value is a 6 byte hashed representation of this string.
+         *
+         * @param serviceName The service name for the publish session.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceName(String serviceName) {
+            mServiceName = serviceName;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the publish session. This is
+         * a free-form byte array available to the application to send
+         * additional information as part of the discovery operation - i.e. it
+         * will not be used to determine whether a publish/subscribe match
+         * occurs.
+         *
+         * @param serviceSpecificInfo A byte-array for the service-specific
+         *            information field.
+         * @param serviceSpecificInfoLength The length of the byte-array to be
+         *            used.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
+                int serviceSpecificInfoLength) {
+            if (serviceSpecificInfoLength != 0 && (serviceSpecificInfo == null
+                    || serviceSpecificInfo.length < serviceSpecificInfoLength)) {
+                throw new IllegalArgumentException("Non-matching combination of "
+                        + "serviceSpecificInfo and serviceSpecificInfoLength");
+            }
+            mServiceSpecificInfoLength = serviceSpecificInfoLength;
+            mServiceSpecificInfo = serviceSpecificInfo;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the publish session - same
+         * as {@link PublishData.Builder#setServiceSpecificInfo(byte[], int)}
+         * but obtaining the data from a String.
+         *
+         * @param serviceSpecificInfoStr The service specific information string
+         *            to be included (as a byte array) in the publish
+         *            information.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
+            mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
+            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+            return this;
+        }
+
+        /**
+         * The transmit filter for an active publish session
+         * {@link PublishSettings.Builder#setPublishType(int)} and
+         * {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}. Included in
+         * transmitted publish packets and used by receivers (subscribers) to
+         * determine whether they match - in addition to just relying on the
+         * service name.
+         * <p>
+         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
+         * is available to form and parse.
+         *
+         * @param txFilter The byte-array containing the LV formatted transmit
+         *            filter.
+         * @param txFilterLength The number of bytes in the transmit filter
+         *            argument.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
+            if (txFilterLength != 0 && (txFilter == null || txFilter.length < txFilterLength)) {
+                throw new IllegalArgumentException(
+                        "Non-matching combination of txFilter and txFilterLength");
+            }
+            mTxFilter = txFilter;
+            mTxFilterLength = txFilterLength;
+            return this;
+        }
+
+        /**
+         * The transmit filter for a passive publish session
+         * {@link PublishSettings.Builder#setPublishType(int)} and
+         * {@link PublishSettings#PUBLISH_TYPE_SOLICITED}. Used by the publisher
+         * to determine whether they match transmitted subscriber packets
+         * (active subscribers) - in addition to just relying on the service
+         * name.
+         * <p>
+         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
+         * is available to form and parse.
+         *
+         * @param rxFilter The byte-array containing the LV formatted receive
+         *            filter.
+         * @param rxFilterLength The number of bytes in the receive filter
+         *            argument.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
+            if (rxFilterLength != 0 && (rxFilter == null || rxFilter.length < rxFilterLength)) {
+                throw new IllegalArgumentException(
+                        "Non-matching combination of rxFilter and rxFilterLength");
+            }
+            mRxFilter = rxFilter;
+            mRxFilterLength = rxFilterLength;
+            return this;
+        }
+
+        /**
+         * Build {@link PublishData} given the current requests made on the
+         * builder.
+         */
+        public PublishData build() {
+            return new PublishData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
+                    mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.aidl b/wifi/java/android/net/wifi/nan/PublishSettings.aidl
new file mode 100644
index 0000000..ff69293
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable PublishSettings;
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.java b/wifi/java/android/net/wifi/nan/PublishSettings.java
new file mode 100644
index 0000000..bbc5340
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishSettings.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Defines the settings (configuration) for a NAN publish session. Built using
+ * {@link PublishSettings.Builder}. Publish is done using
+ * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
+ * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class PublishSettings implements Parcelable {
+
+    /**
+     * Defines an unsolicited publish session - i.e. a publish session where
+     * publish packets are transmitted over-the-air. Configuration is done using
+     * {@link PublishSettings.Builder#setPublishType(int)}.
+     */
+    public static final int PUBLISH_TYPE_UNSOLICITED = 0;
+
+    /**
+     * Defines a solicited publish session - i.e. a publish session where
+     * publish packets are not transmitted over-the-air and the device listens
+     * and matches to transmitted subscribe packets. Configuration is done using
+     * {@link PublishSettings.Builder#setPublishType(int)}.
+     */
+    public static final int PUBLISH_TYPE_SOLICITED = 1;
+
+    /**
+     * @hide
+     */
+    public final int mPublishType;
+
+    /**
+     * @hide
+     */
+    public final int mPublishCount;
+
+    /**
+     * @hide
+     */
+    public final int mTtlSec;
+
+    private PublishSettings(int publishType, int publichCount, int ttlSec) {
+        mPublishType = publishType;
+        mPublishCount = publichCount;
+        mTtlSec = ttlSec;
+    }
+
+    @Override
+    public String toString() {
+        return "PublishSettings [mPublishType=" + mPublishType + ", mPublishCount=" + mPublishCount
+                + ", mTtlSec=" + mTtlSec + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPublishType);
+        dest.writeInt(mPublishCount);
+        dest.writeInt(mTtlSec);
+    }
+
+    public static final Creator<PublishSettings> CREATOR = new Creator<PublishSettings>() {
+        @Override
+        public PublishSettings[] newArray(int size) {
+            return new PublishSettings[size];
+        }
+
+        @Override
+        public PublishSettings createFromParcel(Parcel in) {
+            int publishType = in.readInt();
+            int publishCount = in.readInt();
+            int ttlSec = in.readInt();
+            return new PublishSettings(publishType, publishCount, ttlSec);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PublishSettings)) {
+            return false;
+        }
+
+        PublishSettings lhs = (PublishSettings) o;
+
+        return mPublishType == lhs.mPublishType && mPublishCount == lhs.mPublishCount
+                && mTtlSec == lhs.mTtlSec;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + mPublishType;
+        result = 31 * result + mPublishCount;
+        result = 31 * result + mTtlSec;
+
+        return result;
+    }
+
+    /**
+     * Builder used to build {@link PublishSettings} objects.
+     */
+    public static final class Builder {
+        int mPublishType;
+        int mPublishCount;
+        int mTtlSec;
+
+        /**
+         * Sets the type of the publish session: solicited (aka active - publish
+         * packets are transmitted over-the-air), or unsolicited (aka passive -
+         * no publish packets are transmitted, a match is made against an active
+         * subscribe session whose packets are transmitted over-the-air).
+         *
+         * @param publishType Publish session type: solicited (
+         *            {@link PublishSettings#PUBLISH_TYPE_SOLICITED}) or
+         *            unsolicited (
+         *            {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}).
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setPublishType(int publishType) {
+            if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) {
+                throw new IllegalArgumentException("Invalid publishType - " + publishType);
+            }
+            mPublishType = publishType;
+            return this;
+        }
+
+        /**
+         * Sets the number of times a solicited (
+         * {@link PublishSettings.Builder#setPublishType(int)}) publish session
+         * will transmit a packet. When the count is reached an event will be
+         * generated for {@link WifiNanSessionListener#onPublishTerminated(int)}
+         * with reason={@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
+         *
+         * @param publishCount Number of publish packets to transmit.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setPublishCount(int publishCount) {
+            if (publishCount < 0) {
+                throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
+            }
+            mPublishCount = publishCount;
+            return this;
+        }
+
+        /**
+         * Sets the time interval (in seconds) a solicited (
+         * {@link PublishSettings.Builder#setPublishCount(int)}) publish session
+         * will be alive - i.e. transmitting a packet. When the TTL is reached
+         * an event will be generated for
+         * {@link WifiNanSessionListener#onPublishTerminated(int)} with reason=
+         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
+         *
+         * @param ttlSec Lifetime of a publish session in seconds.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTtlSec(int ttlSec) {
+            if (ttlSec < 0) {
+                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+            }
+            mTtlSec = ttlSec;
+            return this;
+        }
+
+        /**
+         * Build {@link PublishSettings} given the current requests made on the
+         * builder.
+         */
+        public PublishSettings build() {
+            return new PublishSettings(mPublishType, mPublishCount, mTtlSec);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.aidl b/wifi/java/android/net/wifi/nan/SubscribeData.aidl
new file mode 100644
index 0000000..662fdb8
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable SubscribeData;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.java b/wifi/java/android/net/wifi/nan/SubscribeData.java
new file mode 100644
index 0000000..cd6e918
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeData.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Defines the data for a NAN subscribe session. Built using
+ * {@link SubscribeData.Builder}. Subscribe is done using
+ * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
+ * or
+ * {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
+ * @hide PROPOSED_NAN_API
+ */
+public class SubscribeData implements Parcelable {
+    /**
+     * @hide
+     */
+    public final String mServiceName;
+
+    /**
+     * @hide
+     */
+    public final int mServiceSpecificInfoLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mServiceSpecificInfo;
+
+    /**
+     * @hide
+     */
+    public final int mTxFilterLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mTxFilter;
+
+    /**
+     * @hide
+     */
+    public final int mRxFilterLength;
+
+    /**
+     * @hide
+     */
+    public final byte[] mRxFilter;
+
+    private SubscribeData(String serviceName, byte[] serviceSpecificInfo,
+            int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
+            int rxFilterLength) {
+        mServiceName = serviceName;
+        mServiceSpecificInfoLength = serviceSpecificInfoLength;
+        mServiceSpecificInfo = serviceSpecificInfo;
+        mTxFilterLength = txFilterLength;
+        mTxFilter = txFilter;
+        mRxFilterLength = rxFilterLength;
+        mRxFilter = rxFilter;
+    }
+
+    @Override
+    public String toString() {
+        return "SubscribeData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
+                + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
+                + "', mTxFilter="
+                + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
+                + ", mRxFilter="
+                + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
+                + "']";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mServiceName);
+        dest.writeInt(mServiceSpecificInfoLength);
+        if (mServiceSpecificInfoLength != 0) {
+            dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
+        }
+        dest.writeInt(mTxFilterLength);
+        if (mTxFilterLength != 0) {
+            dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
+        }
+        dest.writeInt(mRxFilterLength);
+        if (mRxFilterLength != 0) {
+            dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
+        }
+    }
+
+    public static final Creator<SubscribeData> CREATOR = new Creator<SubscribeData>() {
+        @Override
+        public SubscribeData[] newArray(int size) {
+            return new SubscribeData[size];
+        }
+
+        @Override
+        public SubscribeData createFromParcel(Parcel in) {
+            String serviceName = in.readString();
+            int ssiLength = in.readInt();
+            byte[] ssi = new byte[ssiLength];
+            if (ssiLength != 0) {
+                in.readByteArray(ssi);
+            }
+            int txFilterLength = in.readInt();
+            byte[] txFilter = new byte[txFilterLength];
+            if (txFilterLength != 0) {
+                in.readByteArray(txFilter);
+            }
+            int rxFilterLength = in.readInt();
+            byte[] rxFilter = new byte[rxFilterLength];
+            if (rxFilterLength != 0) {
+                in.readByteArray(rxFilter);
+            }
+
+            return new SubscribeData(serviceName, ssi, ssiLength, txFilter, txFilterLength,
+                    rxFilter, rxFilterLength);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof SubscribeData)) {
+            return false;
+        }
+
+        SubscribeData lhs = (SubscribeData) o;
+
+        if (!mServiceName.equals(lhs.mServiceName)
+                || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
+                || mTxFilterLength != lhs.mTxFilterLength
+                || mRxFilterLength != lhs.mRxFilterLength) {
+            return false;
+        }
+
+        if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
+            for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
+                if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
+                    return false;
+                }
+            }
+        } else if (mServiceSpecificInfoLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        if (mTxFilter != null && lhs.mTxFilter != null) {
+            for (int i = 0; i < mTxFilterLength; ++i) {
+                if (mTxFilter[i] != lhs.mTxFilter[i]) {
+                    return false;
+                }
+            }
+        } else if (mTxFilterLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        if (mRxFilter != null && lhs.mRxFilter != null) {
+            for (int i = 0; i < mRxFilterLength; ++i) {
+                if (mRxFilter[i] != lhs.mRxFilter[i]) {
+                    return false;
+                }
+            }
+        } else if (mRxFilterLength != 0) {
+            return false; // invalid != invalid
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + mServiceName.hashCode();
+        result = 31 * result + mServiceSpecificInfoLength;
+        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+        result = 31 * result + mTxFilterLength;
+        result = 31 * result + Arrays.hashCode(mTxFilter);
+        result = 31 * result + mRxFilterLength;
+        result = 31 * result + Arrays.hashCode(mRxFilter);
+
+        return result;
+    }
+
+    /**
+     * Builder used to build {@link SubscribeData} objects.
+     */
+    public static final class Builder {
+        private String mServiceName;
+        private int mServiceSpecificInfoLength;
+        private byte[] mServiceSpecificInfo = new byte[0];
+        private int mTxFilterLength;
+        private byte[] mTxFilter = new byte[0];
+        private int mRxFilterLength;
+        private byte[] mRxFilter = new byte[0];
+
+        /**
+         * Specify the service name of the subscribe session. The actual on-air
+         * value is a 6 byte hashed representation of this string.
+         *
+         * @param serviceName The service name for the subscribe session.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceName(String serviceName) {
+            mServiceName = serviceName;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the subscribe session. This
+         * is a free-form byte array available to the application to send
+         * additional information as part of the discovery operation - i.e. it
+         * will not be used to determine whether a publish/subscribe match
+         * occurs.
+         *
+         * @param serviceSpecificInfo A byte-array for the service-specific
+         *            information field.
+         * @param serviceSpecificInfoLength The length of the byte-array to be
+         *            used.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
+                int serviceSpecificInfoLength) {
+            mServiceSpecificInfoLength = serviceSpecificInfoLength;
+            mServiceSpecificInfo = serviceSpecificInfo;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the subscribe session - same
+         * as {@link SubscribeData.Builder#setServiceSpecificInfo(byte[], int)}
+         * but obtaining the data from a String.
+         *
+         * @param serviceSpecificInfoStr The service specific information string
+         *            to be included (as a byte array) in the subscribe
+         *            information.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
+            mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
+            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+            return this;
+        }
+
+        /**
+         * The transmit filter for an active subscribe session
+         * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
+         * {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}. Included in
+         * transmitted subscribe packets and used by receivers (passive
+         * publishers) to determine whether they match - in addition to just
+         * relying on the service name.
+         * <p>
+         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
+         * is available to form and parse.
+         *
+         * @param txFilter The byte-array containing the LV formatted transmit
+         *            filter.
+         * @param txFilterLength The number of bytes in the transmit filter
+         *            argument.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
+            mTxFilter = txFilter;
+            mTxFilterLength = txFilterLength;
+            return this;
+        }
+
+        /**
+         * The transmit filter for a passive subsribe session
+         * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
+         * {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}. Used by the
+         * subscriber to determine whether they match transmitted publish
+         * packets - in addition to just relying on the service name.
+         * <p>
+         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
+         * is available to form and parse.
+         *
+         * @param rxFilter The byte-array containing the LV formatted receive
+         *            filter.
+         * @param rxFilterLength The number of bytes in the receive filter
+         *            argument.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
+            mRxFilter = rxFilter;
+            mRxFilterLength = rxFilterLength;
+            return this;
+        }
+
+        /**
+         * Build {@link SubscribeData} given the current requests made on the
+         * builder.
+         */
+        public SubscribeData build() {
+            return new SubscribeData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
+                    mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl b/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
new file mode 100644
index 0000000..44849bc
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+parcelable SubscribeSettings;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.java b/wifi/java/android/net/wifi/nan/SubscribeSettings.java
new file mode 100644
index 0000000..5c4f8fb
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeSettings.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Defines the settings (configuration) for a NAN subscribe session. Built using
+ * {@link SubscribeSettings.Builder}. Subscribe is done using
+ * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
+ * or {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class SubscribeSettings implements Parcelable {
+
+    /**
+     * Defines a passive subscribe session - i.e. a subscribe session where
+     * subscribe packets are not transmitted over-the-air and the device listens
+     * and matches to transmitted publish packets. Configuration is done using
+     * {@link SubscribeSettings.Builder#setSubscribeType(int)}.
+     */
+    public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
+
+    /**
+     * Defines an active subscribe session - i.e. a subscribe session where
+     * subscribe packets are transmitted over-the-air. Configuration is done
+     * using {@link SubscribeSettings.Builder#setSubscribeType(int)}.
+     */
+    public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
+
+    /**
+     * @hide
+     */
+    public final int mSubscribeType;
+
+    /**
+     * @hide
+     */
+    public final int mSubscribeCount;
+
+    /**
+     * @hide
+     */
+    public final int mTtlSec;
+
+    private SubscribeSettings(int subscribeType, int publichCount, int ttlSec) {
+        mSubscribeType = subscribeType;
+        mSubscribeCount = publichCount;
+        mTtlSec = ttlSec;
+    }
+
+    @Override
+    public String toString() {
+        return "SubscribeSettings [mSubscribeType=" + mSubscribeType + ", mSubscribeCount="
+                + mSubscribeCount + ", mTtlSec=" + mTtlSec + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSubscribeType);
+        dest.writeInt(mSubscribeCount);
+        dest.writeInt(mTtlSec);
+    }
+
+    public static final Creator<SubscribeSettings> CREATOR = new Creator<SubscribeSettings>() {
+        @Override
+        public SubscribeSettings[] newArray(int size) {
+            return new SubscribeSettings[size];
+        }
+
+        @Override
+        public SubscribeSettings createFromParcel(Parcel in) {
+            int subscribeType = in.readInt();
+            int subscribeCount = in.readInt();
+            int ttlSec = in.readInt();
+            return new SubscribeSettings(subscribeType, subscribeCount, ttlSec);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof SubscribeSettings)) {
+            return false;
+        }
+
+        SubscribeSettings lhs = (SubscribeSettings) o;
+
+        return mSubscribeType == lhs.mSubscribeType && mSubscribeCount == lhs.mSubscribeCount
+                && mTtlSec == lhs.mTtlSec;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + mSubscribeType;
+        result = 31 * result + mSubscribeCount;
+        result = 31 * result + mTtlSec;
+
+        return result;
+    }
+
+    /**
+     * Builder used to build {@link SubscribeSettings} objects.
+     */
+    public static final class Builder {
+        int mSubscribeType;
+        int mSubscribeCount;
+        int mTtlSec;
+
+        /**
+         * Sets the type of the subscribe session: active (subscribe packets are
+         * transmitted over-the-air), or passive (no subscribe packets are
+         * transmitted, a match is made against a solicited/active publish
+         * session whose packets are transmitted over-the-air).
+         *
+         * @param subscribeType Subscribe session type: active (
+         *            {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}) or
+         *            passive ( {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}
+         *            ).
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setSubscribeType(int subscribeType) {
+            if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
+                throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
+            }
+            mSubscribeType = subscribeType;
+            return this;
+        }
+
+        /**
+         * Sets the number of times an active (
+         * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
+         * session will transmit a packet. When the count is reached an event
+         * will be generated for
+         * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
+         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
+         *
+         * @param subscribeCount Number of subscribe packets to transmit.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setSubscribeCount(int subscribeCount) {
+            if (subscribeCount < 0) {
+                throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
+            }
+            mSubscribeCount = subscribeCount;
+            return this;
+        }
+
+        /**
+         * Sets the time interval (in seconds) an active (
+         * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
+         * session will be alive - i.e. transmitting a packet. When the TTL is
+         * reached an event will be generated for
+         * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
+         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
+         *
+         * @param ttlSec Lifetime of a subscribe session in seconds.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTtlSec(int ttlSec) {
+            if (ttlSec < 0) {
+                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+            }
+            mTtlSec = ttlSec;
+            return this;
+        }
+
+        /**
+         * Build {@link SubscribeSettings} given the current requests made on
+         * the builder.
+         */
+        public SubscribeSettings build() {
+            return new SubscribeSettings(mSubscribeType, mSubscribeCount, mTtlSec);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/TlvBufferUtils.java b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
new file mode 100644
index 0000000..ea8785a
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import libcore.io.Memory;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteOrder;
+import java.util.Iterator;
+
+/**
+ * Utility class to construct and parse byte arrays using the TLV format -
+ * Type/Length/Value format. The utilities accept a configuration of the size of
+ * the Type field and the Length field. A Type field size of 0 is allowed -
+ * allowing usage for LV (no T) array formats.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class TlvBufferUtils {
+    private TlvBufferUtils() {
+        // no reason to ever create this class
+    }
+
+    /**
+     * Utility class to construct byte arrays using the TLV format -
+     * Type/Length/Value.
+     * <p>
+     * A constructor is created specifying the size of the Type (T) and Length
+     * (L) fields. A specification of zero size T field is allowed - resulting
+     * in LV type format.
+     * <p>
+     * The byte array is either provided (using
+     * {@link TlvConstructor#wrap(byte[])}) or allocated (using
+     * {@link TlvConstructor#allocate(int)}).
+     * <p>
+     * Values are added to the structure using the {@code TlvConstructor.put*()}
+     * methods.
+     * <p>
+     * The final byte array is obtained using {@link TlvConstructor#getArray()}
+     * and {@link TlvConstructor#getActualLength()} methods.
+     */
+    public static class TlvConstructor {
+        private int mTypeSize;
+        private int mLengthSize;
+
+        private byte[] mArray;
+        private int mArrayLength;
+        private int mPosition;
+
+        /**
+         * Define a TLV constructor with the specified size of the Type (T) and
+         * Length (L) fields.
+         *
+         * @param typeSize Number of bytes used for the Type (T) field. Values
+         *            of 0, 1, or 2 bytes are allowed. A specification of 0
+         *            bytes implies that the field being constructed has the LV
+         *            format rather than the TLV format.
+         * @param lengthSize Number of bytes used for the Length (L) field.
+         *            Values of 1 or 2 bytes are allowed.
+         */
+        public TlvConstructor(int typeSize, int lengthSize) {
+            if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
+                throw new IllegalArgumentException(
+                        "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
+            }
+            mTypeSize = typeSize;
+            mLengthSize = lengthSize;
+        }
+
+        /**
+         * Set the byte array to be used to construct the TLV.
+         *
+         * @param array Byte array to be formatted.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor wrap(byte[] array) {
+            mArray = array;
+            mArrayLength = array.length;
+            return this;
+        }
+
+        /**
+         * Allocates a new byte array to be used ot construct a TLV.
+         *
+         * @param capacity The size of the byte array to be allocated.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor allocate(int capacity) {
+            mArray = new byte[capacity];
+            mArrayLength = capacity;
+            return this;
+        }
+
+        /**
+         * Copies a byte into the TLV with the indicated type. For an LV
+         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+         * TlvConstructor(int, int)} ) the type field is ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param b The byte to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putByte(int type, byte b) {
+            checkLength(1);
+            addHeader(type, 1);
+            mArray[mPosition++] = b;
+            return this;
+        }
+
+        /**
+         * Copies a byte array into the TLV with the indicated type. For an LV
+         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+         * TlvConstructor(int, int)} ) the type field is ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param array The array to be copied into the TLV structure.
+         * @param offset Start copying from the array at the specified offset.
+         * @param length Copy the specified number (length) of bytes from the
+         *            array.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putByteArray(int type, byte[] array, int offset, int length) {
+            checkLength(length);
+            addHeader(type, length);
+            System.arraycopy(array, offset, mArray, mPosition, length);
+            mPosition += length;
+            return this;
+        }
+
+        /**
+         * Copies a byte array into the TLV with the indicated type. For an LV
+         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+         * TlvConstructor(int, int)} ) the type field is ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param array The array to be copied (in full) into the TLV structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putByteArray(int type, byte[] array) {
+            return putByteArray(type, array, 0, array.length);
+        }
+
+        /**
+         * Places a zero length element (i.e. Length field = 0) into the TLV.
+         * For an LV formatted structure (i.e. typeLength=0 in
+         * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
+         * ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putZeroLengthElement(int type) {
+            checkLength(0);
+            addHeader(type, 0);
+            return this;
+        }
+
+        /**
+         * Copies short into the TLV with the indicated type. For an LV
+         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+         * TlvConstructor(int, int)} ) the type field is ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param data The short to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putShort(int type, short data) {
+            checkLength(2);
+            addHeader(type, 2);
+            Memory.pokeShort(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
+            mPosition += 2;
+            return this;
+        }
+
+        /**
+         * Copies integer into the TLV with the indicated type. For an LV
+         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+         * TlvConstructor(int, int)} ) the type field is ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param data The integer to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putInt(int type, int data) {
+            checkLength(4);
+            addHeader(type, 4);
+            Memory.pokeInt(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
+            mPosition += 4;
+            return this;
+        }
+
+        /**
+         * Copies a String's byte representation into the TLV with the indicated
+         * type. For an LV formatted structure (i.e. typeLength=0 in
+         * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
+         * ignored.
+         *
+         * @param type The value to be placed into the Type field.
+         * @param data The string whose bytes are to be inserted into the
+         *            structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public TlvConstructor putString(int type, String data) {
+            return putByteArray(type, data.getBytes(), 0, data.length());
+        }
+
+        /**
+         * Returns the constructed TLV formatted byte-array. Note that the
+         * returned array is the fully wrapped (
+         * {@link TlvConstructor#wrap(byte[])}) or allocated (
+         * {@link TlvConstructor#allocate(int)}) array - which isn't necessarily
+         * the actual size of the formatted data. Use
+         * {@link TlvConstructor#getActualLength()} to obtain the size of the
+         * formatted data.
+         *
+         * @return The byte array containing the TLV formatted structure.
+         */
+        public byte[] getArray() {
+            return mArray;
+        }
+
+        /**
+         * Returns the size of the TLV formatted portion of the wrapped or
+         * allocated byte array. The array itself is returned with
+         * {@link TlvConstructor#getArray()}.
+         *
+         * @return The size of the TLV formatted portion of the byte array.
+         */
+        public int getActualLength() {
+            return mPosition;
+        }
+
+        private void checkLength(int dataLength) {
+            if (mPosition + mTypeSize + mLengthSize + dataLength > mArrayLength) {
+                throw new BufferOverflowException();
+            }
+        }
+
+        private void addHeader(int type, int length) {
+            if (mTypeSize == 1) {
+                mArray[mPosition] = (byte) type;
+            } else if (mTypeSize == 2) {
+                Memory.pokeShort(mArray, mPosition, (short) type, ByteOrder.BIG_ENDIAN);
+            }
+            mPosition += mTypeSize;
+
+            if (mLengthSize == 1) {
+                mArray[mPosition] = (byte) length;
+            } else if (mLengthSize == 2) {
+                Memory.pokeShort(mArray, mPosition, (short) length, ByteOrder.BIG_ENDIAN);
+            }
+            mPosition += mLengthSize;
+        }
+    }
+
+    /**
+     * Utility class used when iterating over a TLV formatted byte-array. Use
+     * {@link TlvIterable} to iterate over array. A {@link TlvElement}
+     * represents each entry in a TLV formatted byte-array.
+     */
+    public static class TlvElement {
+        /**
+         * The Type (T) field of the current TLV element. Note that for LV
+         * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
+         * this field is undefined.
+         */
+        public int mType;
+
+        /**
+         * The Length (L) field of the current TLV element.
+         */
+        public int mLength;
+
+        /**
+         * The Value (V) field - a raw byte array representing the current TLV
+         * element where the entry starts at {@link TlvElement#mOffset}.
+         */
+        public byte[] mRefArray;
+
+        /**
+         * The offset to be used into {@link TlvElement#mRefArray} to access the
+         * raw data representing the current TLV element.
+         */
+        public int mOffset;
+
+        private TlvElement(int type, int length, byte[] refArray, int offset) {
+            mType = type;
+            mLength = length;
+            mRefArray = refArray;
+            mOffset = offset;
+        }
+
+        /**
+         * Utility function to return a byte representation of a TLV element of
+         * length 1. Note: an attempt to call this function on a TLV item whose
+         * {@link TlvElement#mLength} is != 1 will result in an exception.
+         *
+         * @return byte representation of current TLV element.
+         */
+        public byte getByte() {
+            if (mLength != 1) {
+                throw new IllegalArgumentException(
+                        "Accesing a byte from a TLV element of length " + mLength);
+            }
+            return mRefArray[mOffset];
+        }
+
+        /**
+         * Utility function to return a short representation of a TLV element of
+         * length 2. Note: an attempt to call this function on a TLV item whose
+         * {@link TlvElement#mLength} is != 2 will result in an exception.
+         *
+         * @return short representation of current TLV element.
+         */
+        public short getShort() {
+            if (mLength != 2) {
+                throw new IllegalArgumentException(
+                        "Accesing a short from a TLV element of length " + mLength);
+            }
+            return Memory.peekShort(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+        }
+
+        /**
+         * Utility function to return an integer representation of a TLV element
+         * of length 4. Note: an attempt to call this function on a TLV item
+         * whose {@link TlvElement#mLength} is != 4 will result in an exception.
+         *
+         * @return integer representation of current TLV element.
+         */
+        public int getInt() {
+            if (mLength != 4) {
+                throw new IllegalArgumentException(
+                        "Accesing an int from a TLV element of length " + mLength);
+            }
+            return Memory.peekInt(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+        }
+
+        /**
+         * Utility function to return a String representation of a TLV element.
+         *
+         * @return String repersentation of the current TLV element.
+         */
+        public String getString() {
+            return new String(mRefArray, mOffset, mLength);
+        }
+    }
+
+    /**
+     * Utility class to iterate over a TLV formatted byte-array.
+     */
+    public static class TlvIterable implements Iterable<TlvElement> {
+        private int mTypeSize;
+        private int mLengthSize;
+        private byte[] mArray;
+        private int mArrayLength;
+
+        /**
+         * Constructs a TlvIterable object - specifying the format of the TLV
+         * (the sizes of the Type and Length fields), and the byte array whose
+         * data is to be parsed.
+         *
+         * @param typeSize Number of bytes used for the Type (T) field. Valid
+         *            values are 0 (i.e. indicating the format is LV rather than
+         *            TLV), 1, and 2 bytes.
+         * @param lengthSize Number of bytes sued for the Length (L) field.
+         *            Values values are 1 or 2 bytes.
+         * @param array The TLV formatted byte-array to parse.
+         * @param length The number of bytes of the array to be used in the
+         *            parsing.
+         */
+        public TlvIterable(int typeSize, int lengthSize, byte[] array, int length) {
+            if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
+                throw new IllegalArgumentException(
+                        "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
+            }
+            mTypeSize = typeSize;
+            mLengthSize = lengthSize;
+            mArray = array;
+            mArrayLength = length;
+        }
+
+        /**
+         * Prints out a parsed representation of the TLV-formatted byte array.
+         * Whenever possible bytes, shorts, and integer are printed out (for
+         * fields whose length is 1, 2, or 4 respectively).
+         */
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+
+            builder.append("[");
+            boolean first = true;
+            for (TlvElement tlv : this) {
+                if (!first) {
+                    builder.append(",");
+                }
+                first = false;
+                builder.append(" (");
+                if (mTypeSize != 0) {
+                    builder.append("T=" + tlv.mType + ",");
+                }
+                builder.append("L=" + tlv.mLength + ") ");
+                if (tlv.mLength == 0) {
+                    builder.append("<null>");
+                } else if (tlv.mLength == 1) {
+                    builder.append(tlv.getByte());
+                } else if (tlv.mLength == 2) {
+                    builder.append(tlv.getShort());
+                } else if (tlv.mLength == 4) {
+                    builder.append(tlv.getInt());
+                } else {
+                    builder.append("<bytes>");
+                }
+                if (tlv.mLength != 0) {
+                    builder.append(" (S='" + tlv.getString() + "')");
+                }
+            }
+            builder.append("]");
+
+            return builder.toString();
+        }
+
+        /**
+         * Returns an iterator to step through a TLV formatted byte-array. The
+         * individual elements returned by the iterator are {@link TlvElement}.
+         */
+        @Override
+        public Iterator<TlvElement> iterator() {
+            return new Iterator<TlvElement>() {
+                private int mOffset = 0;
+
+                @Override
+                public boolean hasNext() {
+                    return mOffset < mArrayLength;
+                }
+
+                @Override
+                public TlvElement next() {
+                    int type = 0;
+                    if (mTypeSize == 1) {
+                        type = mArray[mOffset];
+                    } else if (mTypeSize == 2) {
+                        type = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
+                    }
+                    mOffset += mTypeSize;
+
+                    int length = 0;
+                    if (mLengthSize == 1) {
+                        length = mArray[mOffset];
+                    } else if (mLengthSize == 2) {
+                        length = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
+                    }
+                    mOffset += mLengthSize;
+
+                    TlvElement tlv = new TlvElement(type, length, mArray, mOffset);
+                    mOffset += length;
+                    return tlv;
+                }
+
+                @Override
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
new file mode 100644
index 0000000..eae0a55
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
@@ -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.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Base class for NAN events callbacks. Should be extended by applications
+ * wanting notifications. These are callbacks applying to the NAN connection as
+ * a whole - not to specific publish or subscribe sessions - for that see
+ * {@link WifiNanSessionListener}.
+ * <p>
+ * During registration specify which specific events are desired using a set of
+ * {@code NanEventListener.LISTEN_*} flags OR'd together. Only those events will
+ * be delivered to the registered listener. Override those callbacks
+ * {@code NanEventListener.on*} for the registered events.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanEventListener {
+    private static final String TAG = "WifiNanEventListener";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    /**
+     * Configuration completion callback event registration flag. Corresponding
+     * callback is {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)}.
+     */
+    public static final int LISTEN_CONFIG_COMPLETED = 0x1 << 0;
+
+    /**
+     * Configuration failed callback event registration flag. Corresponding
+     * callback is {@link WifiNanEventListener#onConfigFailed(int)}.
+     */
+    public static final int LISTEN_CONFIG_FAILED = 0x1 << 1;
+
+    /**
+     * NAN cluster is down callback event registration flag. Corresponding
+     * callback is {@link WifiNanEventListener#onNanDown(int)}.
+     */
+    public static final int LISTEN_NAN_DOWN = 0x1 << 2;
+
+    /**
+     * NAN identity has changed event registration flag. This may be due to
+     * joining a cluster, starting a cluster, or discovery interface change. The
+     * implication is that peers you've been communicating with may no longer
+     * recognize you and you need to re-establish your identity. Corresponding
+     * callback is {@link WifiNanEventListener#onIdentityChanged()}.
+     */
+    public static final int LISTEN_IDENTITY_CHANGED = 0x1 << 3;
+
+    private final Handler mHandler;
+
+    /**
+     * Constructs a {@link WifiNanEventListener} using the looper of the current
+     * thread. I.e. all callbacks will be delivered on the current thread.
+     */
+    public WifiNanEventListener() {
+        this(Looper.myLooper());
+    }
+
+    /**
+     * Constructs a {@link WifiNanEventListener} using the specified looper. I.e.
+     * all callbacks will delivered on the thread of the specified looper.
+     *
+     * @param looper The looper on which to execute the callbacks.
+     */
+    public WifiNanEventListener(Looper looper) {
+        if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
+                switch (msg.what) {
+                    case LISTEN_CONFIG_COMPLETED:
+                        WifiNanEventListener.this.onConfigCompleted((ConfigRequest) msg.obj);
+                        break;
+                    case LISTEN_CONFIG_FAILED:
+                        WifiNanEventListener.this.onConfigFailed(msg.arg1);
+                        break;
+                    case LISTEN_NAN_DOWN:
+                        WifiNanEventListener.this.onNanDown(msg.arg1);
+                        break;
+                    case LISTEN_IDENTITY_CHANGED:
+                        WifiNanEventListener.this.onIdentityChanged();
+                        break;
+                }
+            }
+        };
+    }
+
+    /**
+     * Called when NAN configuration is completed. Event will only be delivered
+     * if registered using {@link WifiNanEventListener#LISTEN_CONFIG_COMPLETED}. A
+     * dummy (empty implementation printing out a warning). Make sure to
+     * override if registered.
+     *
+     * @param completedConfig The actual configuration request which was
+     *            completed. Note that it may be different from that requested
+     *            by the application. The service combines configuration
+     *            requests from all applications.
+     */
+    public void onConfigCompleted(ConfigRequest completedConfig) {
+        Log.w(TAG, "onConfigCompleted: called in stub - override if interested or disable");
+    }
+
+    /**
+     * Called when NAN configuration failed. Event will only be delivered if
+     * registered using {@link WifiNanEventListener#LISTEN_CONFIG_FAILED}. A dummy
+     * (empty implementation printing out a warning). Make sure to override if
+     * registered.
+     *
+     * @param reason Failure reason code, see {@code NanSessionListener.FAIL_*}.
+     */
+    public void onConfigFailed(int reason) {
+        Log.w(TAG, "onConfigFailed: called in stub - override if interested or disable");
+    }
+
+    /**
+     * Called when NAN cluster is down. Event will only be delivered if
+     * registered using {@link WifiNanEventListener#LISTEN_NAN_DOWN}. A dummy (empty
+     * implementation printing out a warning). Make sure to override if
+     * registered.
+     *
+     * @param reason Reason code for event, see {@code NanSessionListener.FAIL_*}.
+     */
+    public void onNanDown(int reason) {
+        Log.w(TAG, "onNanDown: called in stub - override if interested or disable");
+    }
+
+    /**
+     * Called when NAN identity has changed. This may be due to joining a
+     * cluster, starting a cluster, or discovery interface change. The
+     * implication is that peers you've been communicating with may no longer
+     * recognize you and you need to re-establish your identity. Event will only
+     * be delivered if registered using
+     * {@link WifiNanEventListener#LISTEN_IDENTITY_CHANGED}. A dummy (empty
+     * implementation printing out a warning). Make sure to override if
+     * registered.
+     */
+    public void onIdentityChanged() {
+        if (VDBG) Log.v(TAG, "onIdentityChanged: called in stub - override if interested");
+    }
+
+    /**
+     * {@hide}
+     */
+    public IWifiNanEventListener callback = new IWifiNanEventListener.Stub() {
+        @Override
+        public void onConfigCompleted(ConfigRequest completedConfig) {
+            if (VDBG) Log.v(TAG, "onConfigCompleted: configRequest=" + completedConfig);
+
+            Message msg = mHandler.obtainMessage(LISTEN_CONFIG_COMPLETED);
+            msg.obj = completedConfig;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onConfigFailed(int reason) {
+            if (VDBG) Log.v(TAG, "onConfigFailed: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_CONFIG_FAILED);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onNanDown(int reason) {
+            if (VDBG) Log.v(TAG, "onNanDown: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_NAN_DOWN);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onIdentityChanged() {
+            if (VDBG) Log.v(TAG, "onIdentityChanged");
+
+            Message msg = mHandler.obtainMessage(LISTEN_IDENTITY_CHANGED);
+            mHandler.sendMessage(msg);
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
new file mode 100644
index 0000000..877f993
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides the primary API for managing Wi-Fi NAN operation:
+ * including discovery and data-links. Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(String)
+ * Context.getSystemService(Context.WIFI_NAN_SERVICE)}.
+ * <p>
+ * The class provides access to:
+ * <ul>
+ * <li>Configure a NAN connection and register for events.
+ * <li>Create publish and subscribe sessions.
+ * <li>Create NAN network specifier to be used to create a NAN network.
+ * </ul>
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanManager {
+    private static final String TAG = "WifiNanManager";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private IBinder mBinder;
+
+    private IWifiNanManager mService;
+
+    /**
+     * {@hide}
+     */
+    public WifiNanManager(IWifiNanManager service) {
+        mService = service;
+    }
+
+    /**
+     * Re-connect to the Wi-Fi NAN service - enabling the application to execute
+     * {@link WifiNanManager} APIs. Application don't normally need to call this
+     * API since it is executed in the constructor. However, applications which
+     * have explicitly {@link WifiNanManager#disconnect()} need to call this
+     * function to re-connect.
+     *
+     * @param listener A listener extended from {@link WifiNanEventListener}.
+     * @param events The set of events to be delivered to the {@code listener}.
+     *            OR'd event flags from {@link WifiNanEventListener
+     *            NanEventListener.LISTEN*}.
+     */
+    public void connect(WifiNanEventListener listener, int events) {
+        try {
+            if (VDBG) Log.v(TAG, "connect()");
+            if (listener == null) {
+                throw new IllegalArgumentException("Invalid listener - must not be null");
+            }
+            if (mBinder == null) {
+                mBinder = new Binder();
+            }
+            mService.connect(mBinder, listener.callback, events);
+        } catch (RemoteException e) {
+            Log.w(TAG, "connect RemoteException (FYI - ignoring): " + e);
+        }
+    }
+
+    /**
+     * Disconnect from the Wi-Fi NAN service and destroy all outstanding
+     * operations - i.e. all publish and subscribes are terminated, any
+     * outstanding data-link is shut-down, and all requested NAN configurations
+     * are cancelled.
+     * <p>
+     * An application may then re-connect using
+     * {@link WifiNanManager#connect(WifiNanEventListener, int)} .
+     */
+    public void disconnect() {
+        try {
+            if (VDBG) Log.v(TAG, "disconnect()");
+            mService.disconnect(mBinder);
+            mBinder = null;
+        } catch (RemoteException e) {
+            Log.w(TAG, "disconnect RemoteException (FYI - ignoring): " + e);
+        }
+    }
+
+    /**
+     * Requests a NAN configuration, specified by {@link ConfigRequest}. Note
+     * that NAN is a shared resource and the device can only be a member of a
+     * single cluster. Thus the service may merge configuration requests from
+     * multiple applications and configure NAN differently from individual
+     * requests.
+     * <p>
+     * The {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)} will be
+     * called when configuration is completed (if a listener is registered for
+     * this specific event).
+     *
+     * @param configRequest The requested NAN configuration.
+     */
+    public void requestConfig(ConfigRequest configRequest) {
+        if (VDBG) Log.v(TAG, "requestConfig(): configRequest=" + configRequest);
+        try {
+            mService.requestConfig(configRequest);
+        } catch (RemoteException e) {
+            Log.w(TAG, "requestConfig RemoteException (FYI - ignoring): " + e);
+        }
+    }
+
+    /**
+     * Request a NAN publish session. The results of the publish session
+     * operation will result in callbacks to the indicated listener:
+     * {@link WifiNanSessionListener NanSessionListener.on*}.
+     *
+     * @param publishData The {@link PublishData} specifying the contents of the
+     *            publish session.
+     * @param publishSettings The {@link PublishSettings} specifying the
+     *            settings for the publish session.
+     * @param listener The {@link WifiNanSessionListener} derived objects to be used
+     *            for the event callbacks specified by {@code events}.
+     * @param events The list of events to be delivered to the {@code listener}
+     *            object. An OR'd value of {@link WifiNanSessionListener
+     *            NanSessionListener.LISTEN_*}.
+     * @return The {@link WifiNanPublishSession} which can be used to further
+     *         control the publish session.
+     */
+    public WifiNanPublishSession publish(PublishData publishData, PublishSettings publishSettings,
+            WifiNanSessionListener listener, int events) {
+        return publishRaw(publishData, publishSettings, listener,
+                events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
+    }
+
+    /**
+     * Same as publish(*) but does not modify the event flag
+     *
+     * @hide
+     */
+    public WifiNanPublishSession publishRaw(PublishData publishData,
+            PublishSettings publishSettings, WifiNanSessionListener listener, int events) {
+        if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
+
+        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
+                && publishData.mRxFilterLength != 0) {
+            throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
+                    + "publishes (active) can't have an Rx filter");
+        }
+        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
+                && publishData.mTxFilterLength != 0) {
+            throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
+                    + "publishes (passive) can't have a Tx filter");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("Invalid listener - must not be null");
+        }
+
+        int sessionId;
+
+        try {
+            sessionId = mService.createSession(listener.callback, events);
+            if (DBG) Log.d(TAG, "publish: session created - sessionId=" + sessionId);
+            mService.publish(sessionId, publishData, publishSettings);
+        } catch (RemoteException e) {
+            Log.w(TAG, "createSession/publish RemoteException: " + e);
+            return null;
+        }
+
+        return new WifiNanPublishSession(this, sessionId);
+    }
+
+    /**
+     * {@hide}
+     */
+    public void publish(int sessionId, PublishData publishData, PublishSettings publishSettings) {
+        if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
+
+        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
+                && publishData.mRxFilterLength != 0) {
+            throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
+                    + "publishes (active) can't have an Rx filter");
+        }
+        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
+                && publishData.mTxFilterLength != 0) {
+            throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
+                    + "publishes (passive) can't have a Tx filter");
+        }
+
+        try {
+            mService.publish(sessionId, publishData, publishSettings);
+        } catch (RemoteException e) {
+            Log.w(TAG, "publish RemoteException: " + e);
+        }
+    }
+    /**
+     * Request a NAN subscribe session. The results of the subscribe session
+     * operation will result in callbacks to the indicated listener:
+     * {@link WifiNanSessionListener NanSessionListener.on*}.
+     *
+     * @param subscribeData The {@link SubscribeData} specifying the contents of
+     *            the subscribe session.
+     * @param subscribeSettings The {@link SubscribeSettings} specifying the
+     *            settings for the subscribe session.
+     * @param listener The {@link WifiNanSessionListener} derived objects to be used
+     *            for the event callbacks specified by {@code events}.
+     * @param events The list of events to be delivered to the {@code listener}
+     *            object. An OR'd value of {@link WifiNanSessionListener
+     *            NanSessionListener.LISTEN_*}.
+     * @return The {@link WifiNanSubscribeSession} which can be used to further
+     *         control the subscribe session.
+     */
+    public WifiNanSubscribeSession subscribe(SubscribeData subscribeData,
+            SubscribeSettings subscribeSettings,
+            WifiNanSessionListener listener, int events) {
+        return subscribeRaw(subscribeData, subscribeSettings, listener,
+                events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
+    }
+
+    /**
+     * Same as subscribe(*) but does not modify the event flag
+     *
+     * @hide
+     */
+    public WifiNanSubscribeSession subscribeRaw(SubscribeData subscribeData,
+            SubscribeSettings subscribeSettings, WifiNanSessionListener listener, int events) {
+        if (VDBG) {
+            Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
+        }
+
+        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
+                && subscribeData.mRxFilterLength != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
+        }
+        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
+                && subscribeData.mTxFilterLength != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
+        }
+
+        int sessionId;
+
+        try {
+            sessionId = mService.createSession(listener.callback, events);
+            if (DBG) Log.d(TAG, "subscribe: session created - sessionId=" + sessionId);
+            mService.subscribe(sessionId, subscribeData, subscribeSettings);
+        } catch (RemoteException e) {
+            Log.w(TAG, "createSession/subscribe RemoteException: " + e);
+            return null;
+        }
+
+        return new WifiNanSubscribeSession(this, sessionId);
+    }
+
+    /**
+     * {@hide}
+     */
+    public void subscribe(int sessionId, SubscribeData subscribeData,
+            SubscribeSettings subscribeSettings) {
+        if (VDBG) {
+            Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
+        }
+
+        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
+                && subscribeData.mRxFilterLength != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
+        }
+        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
+                && subscribeData.mTxFilterLength != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
+        }
+
+        try {
+            mService.subscribe(sessionId, subscribeData, subscribeSettings);
+        } catch (RemoteException e) {
+            Log.w(TAG, "subscribe RemoteException: " + e);
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public void stopSession(int sessionId) {
+        if (DBG) Log.d(TAG, "Stop NAN session #" + sessionId);
+
+        try {
+            mService.stopSession(sessionId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "stopSession RemoteException (FYI - ignoring): " + e);
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public void destroySession(int sessionId) {
+        if (DBG) Log.d(TAG, "Destroy NAN session #" + sessionId);
+
+        try {
+            mService.destroySession(sessionId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "destroySession RemoteException (FYI - ignoring): " + e);
+        }
+    }
+
+    /**
+     * {@hide}
+     */
+    public void sendMessage(int sessionId, int peerId, byte[] message, int messageLength) {
+        try {
+            if (VDBG) {
+                Log.v(TAG, "sendMessage(): sessionId=" + sessionId + ", peerId=" + peerId
+                        + ", messageLength=" + messageLength);
+            }
+            mService.sendMessage(sessionId, peerId, message, messageLength);
+        } catch (RemoteException e) {
+            Log.w(TAG, "subscribe RemoteException (FYI - ignoring): " + e);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
new file mode 100644
index 0000000..81b38f4
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+/**
+ * A representation of a NAN publish session. Created when
+ * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
+ * is executed. The object can be used to stop and re-start (re-configure) the
+ * publish session.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanPublishSession extends WifiNanSession {
+    /**
+     * {@hide}
+     */
+    public WifiNanPublishSession(WifiNanManager manager, int sessionId) {
+        super(manager, sessionId);
+    }
+
+    /**
+     * Restart/re-configure the publish session. Note that the
+     * {@link WifiNanSessionListener} is not replaced - the same listener used at
+     * creation is still used.
+     *
+     * @param publishData The data ({@link PublishData}) to publish.
+     * @param publishSettings The settings ({@link PublishSettings}) of the
+     *            publish session.
+     */
+    public void publish(PublishData publishData, PublishSettings publishSettings) {
+        mManager.publish(mSessionId, publishData, publishSettings);
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java
new file mode 100644
index 0000000..c6b384e
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.util.Log;
+
+/**
+ * A representation of a single publish or subscribe NAN session. This object
+ * will not be created directly - only its child classes are available:
+ * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanSession {
+    private static final String TAG = "WifiNanSession";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    /**
+     * {@hide}
+     */
+    protected WifiNanManager mManager;
+
+    /**
+     * {@hide}
+     */
+    protected int mSessionId;
+
+    /**
+     * {@hide}
+     */
+    private boolean mDestroyed;
+
+    /**
+     * {@hide}
+     */
+    public WifiNanSession(WifiNanManager manager, int sessionId) {
+        if (VDBG) Log.v(TAG, "New client created: manager=" + manager + ", sessionId=" + sessionId);
+
+        mManager = manager;
+        mSessionId = sessionId;
+        mDestroyed = false;
+    }
+
+    /**
+     * Terminate the current publish or subscribe session - i.e. stop
+     * transmitting packet on-air (for an active session) or listening for
+     * matches (for a passive session). Note that the session may still receive
+     * incoming messages and may be re-configured/re-started at a later time.
+     */
+    public void stop() {
+        mManager.stopSession(mSessionId);
+    }
+
+    /**
+     * Destroy the current publish or subscribe session. Performs a
+     * {@link WifiNanSession#stop()} function but in addition destroys the session -
+     * it will not be able to receive any messages or to be restarted at a later
+     * time.
+     */
+    public void destroy() {
+        mManager.destroySession(mSessionId);
+        mDestroyed = true;
+    }
+
+    /**
+     * {@hide}
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        if (!mDestroyed) {
+            Log.w(TAG, "WifiNanSession mSessionId=" + mSessionId
+                            + " was not explicitly destroyed. The session may use resources until "
+                            + "destroyed so step should be done explicitly");
+        }
+        destroy();
+    }
+
+    /**
+     * Sends a message to the specified destination. Message transmission is
+     * part of the current discovery session - i.e. executed subsequent to a
+     * publish/subscribe
+     * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
+     * event.
+     *
+     * @param peerId The peer's ID for the message. Must be a result of an
+     *            {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
+     *            event.
+     * @param message The message to be transmitted.
+     * @param messageLength The number of bytes from the {@code message} to be
+     *            transmitted.
+     */
+    public void sendMessage(int peerId, byte[] message, int messageLength) {
+        mManager.sendMessage(mSessionId, peerId, message, messageLength);
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
new file mode 100644
index 0000000..c9d08c7
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * Base class for NAN session events callbacks. Should be extended by
+ * applications wanting notifications. The callbacks are registered when a
+ * publish or subscribe session is created using
+ * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
+ * or
+ * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
+ * . These are callbacks applying to a specific NAN session. Events
+ * corresponding to the NAN link are delivered using {@link WifiNanEventListener}.
+ * <p>
+ * A single listener is registered at session creation - it cannot be replaced.
+ * <p>
+ * During registration specify which specific events are desired using a set of
+ * {@code NanSessionListener.LISTEN_*} flags OR'd together. Only those events
+ * will be delivered to the registered listener. Override those callbacks
+ * {@code NanSessionListener.on*} for the registered events.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanSessionListener {
+    private static final String TAG = "WifiNanSessionListener";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    /**
+     * Publish fail callback event registration flag. Corresponding callback is
+     * {@link WifiNanSessionListener#onPublishFail(int)}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_PUBLISH_FAIL = 0x1 << 0;
+
+    /**
+     * Publish terminated callback event registration flag. Corresponding
+     * callback is {@link WifiNanSessionListener#onPublishTerminated(int)}.
+     */
+    public static final int LISTEN_PUBLISH_TERMINATED = 0x1 << 1;
+
+    /**
+     * Subscribe fail callback event registration flag. Corresponding callback
+     * is {@link WifiNanSessionListener#onSubscribeFail(int)}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_SUBSCRIBE_FAIL = 0x1 << 2;
+
+    /**
+     * Subscribe terminated callback event registration flag. Corresponding
+     * callback is {@link WifiNanSessionListener#onSubscribeTerminated(int)}.
+     */
+    public static final int LISTEN_SUBSCRIBE_TERMINATED = 0x1 << 3;
+
+    /**
+     * Match (discovery: publish or subscribe) callback event registration flag.
+     * Corresponding callback is
+     * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_MATCH = 0x1 << 4;
+
+    /**
+     * Message sent successfully callback event registration flag. Corresponding
+     * callback is {@link WifiNanSessionListener#onMessageSendSuccess()}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_MESSAGE_SEND_SUCCESS = 0x1 << 5;
+
+    /**
+     * Message sending failure callback event registration flag. Corresponding
+     * callback is {@link WifiNanSessionListener#onMessageSendFail(int)}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_MESSAGE_SEND_FAIL = 0x1 << 6;
+
+    /**
+     * Message received callback event registration flag. Corresponding callback
+     * is {@link WifiNanSessionListener#onMessageReceived(int, byte[], int)}.
+     *
+     * @hide
+     */
+    public static final int LISTEN_MESSAGE_RECEIVED = 0x1 << 7;
+
+    /**
+     * List of hidden events: which are mandatory - i.e. they will be added to
+     * every request.
+     *
+     * @hide
+     */
+    public static final int LISTEN_HIDDEN_FLAGS = LISTEN_PUBLISH_FAIL | LISTEN_SUBSCRIBE_FAIL
+            | LISTEN_MATCH | LISTEN_MESSAGE_SEND_SUCCESS | LISTEN_MESSAGE_SEND_FAIL
+            | LISTEN_MESSAGE_RECEIVED;
+
+    /**
+     * Failure reason flag for {@link WifiNanEventListener} and
+     * {@link WifiNanSessionListener} callbacks. Indicates no resources to execute
+     * the requested operation.
+     */
+    public static final int FAIL_REASON_NO_RESOURCES = 0;
+
+    /**
+     * Failure reason flag for {@link WifiNanEventListener} and
+     * {@link WifiNanSessionListener} callbacks. Indicates invalid argument in the
+     * requested operation.
+     */
+    public static final int FAIL_REASON_INVALID_ARGS = 1;
+
+    /**
+     * Failure reason flag for {@link WifiNanEventListener} and
+     * {@link WifiNanSessionListener} callbacks. Indicates a message is transmitted
+     * without a match (i.e. a discovery) occurring first.
+     */
+    public static final int FAIL_REASON_NO_MATCH_SESSION = 2;
+
+    /**
+     * Failure reason flag for {@link WifiNanEventListener} and
+     * {@link WifiNanSessionListener} callbacks. Indicates an unspecified error
+     * occurred during the operation.
+     */
+    public static final int FAIL_REASON_OTHER = 3;
+
+    /**
+     * Failure reason flag for
+     * {@link WifiNanSessionListener#onPublishTerminated(int)} and
+     * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
+     * Indicates that publish or subscribe session is done - i.e. all the
+     * requested operations (per {@link PublishSettings} or
+     * {@link SubscribeSettings}) have been executed.
+     */
+    public static final int TERMINATE_REASON_DONE = 0;
+
+    /**
+     * Failure reason flag for
+     * {@link WifiNanSessionListener#onPublishTerminated(int)} and
+     * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
+     * Indicates that publish or subscribe session is terminated due to a
+     * failure.
+     */
+    public static final int TERMINATE_REASON_FAIL = 1;
+
+    private static final String MESSAGE_BUNDLE_KEY_PEER_ID = "peer_id";
+    private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
+    private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
+
+    private final Handler mHandler;
+
+    /**
+     * Constructs a {@link WifiNanSessionListener} using the looper of the current
+     * thread. I.e. all callbacks will be delivered on the current thread.
+     */
+    public WifiNanSessionListener() {
+        this(Looper.myLooper());
+    }
+
+    /**
+     * Constructs a {@link WifiNanSessionListener} using the specified looper. I.e.
+     * all callbacks will delivered on the thread of the specified looper.
+     *
+     * @param looper The looper on which to execute the callbacks.
+     */
+    public WifiNanSessionListener(Looper looper) {
+        if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
+                switch (msg.what) {
+                    case LISTEN_PUBLISH_FAIL:
+                        WifiNanSessionListener.this.onPublishFail(msg.arg1);
+                        break;
+                    case LISTEN_PUBLISH_TERMINATED:
+                        WifiNanSessionListener.this.onPublishTerminated(msg.arg1);
+                        break;
+                    case LISTEN_SUBSCRIBE_FAIL:
+                        WifiNanSessionListener.this.onSubscribeFail(msg.arg1);
+                        break;
+                    case LISTEN_SUBSCRIBE_TERMINATED:
+                        WifiNanSessionListener.this.onSubscribeTerminated(msg.arg1);
+                        break;
+                    case LISTEN_MATCH:
+                        WifiNanSessionListener.this.onMatch(
+                                msg.getData().getInt(MESSAGE_BUNDLE_KEY_PEER_ID),
+                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1,
+                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2), msg.arg2);
+                        break;
+                    case LISTEN_MESSAGE_SEND_SUCCESS:
+                        WifiNanSessionListener.this.onMessageSendSuccess();
+                        break;
+                    case LISTEN_MESSAGE_SEND_FAIL:
+                        WifiNanSessionListener.this.onMessageSendFail(msg.arg1);
+                        break;
+                    case LISTEN_MESSAGE_RECEIVED:
+                        WifiNanSessionListener.this.onMessageReceived(msg.arg2,
+                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1);
+                        break;
+                }
+            }
+        };
+    }
+
+    /**
+     * Called when a publish operation fails. It is dummy method (empty
+     * implementation printing out a log message). Override to implement your
+     * custom response.
+     *
+     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
+     *            codes.
+     */
+    public void onPublishFail(int reason) {
+        if (VDBG) Log.v(TAG, "onPublishFail: called in stub - override if interested");
+    }
+
+    /**
+     * Called when a publish operation terminates. Event will only be delivered
+     * if registered using {@link WifiNanSessionListener#LISTEN_PUBLISH_TERMINATED}.
+     * A dummy (empty implementation printing out a warning). Make sure to
+     * override if registered.
+     *
+     * @param reason The termination reason using
+     *            {@code NanSessionListener.TERMINATE_*} codes.
+     */
+    public void onPublishTerminated(int reason) {
+        Log.w(TAG, "onPublishTerminated: called in stub - override if interested or disable");
+    }
+
+    /**
+     * Called when a subscribe operation fails. It is dummy method (empty
+     * implementation printing out a log message). Override to implement your
+     * custom response.
+     *
+     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
+     *            codes.
+     */
+    public void onSubscribeFail(int reason) {
+        if (VDBG) Log.v(TAG, "onSubscribeFail: called in stub - override if interested");
+    }
+
+    /**
+     * Called when a subscribe operation terminates. Event will only be
+     * delivered if registered using
+     * {@link WifiNanSessionListener#LISTEN_SUBSCRIBE_TERMINATED}. A dummy (empty
+     * implementation printing out a warning). Make sure to override if
+     * registered.
+     *
+     * @param reason The termination reason using
+     *            {@code NanSessionListener.TERMINATE_*} codes.
+     */
+    public void onSubscribeTerminated(int reason) {
+        Log.w(TAG, "onSubscribeTerminated: called in stub - override if interested or disable");
+    }
+
+    /**
+     * Called when a discovery (publish or subscribe) operation results in a
+     * match - i.e. when a peer is discovered. It is dummy method (empty
+     * implementation printing out a log message). Override to implement your
+     * custom response.
+     *
+     * @param peerId The ID of the peer matching our discovery operation.
+     * @param serviceSpecificInfo The service specific information (arbitrary
+     *            byte array) provided by the peer as part of its discovery
+     *            packet.
+     * @param serviceSpecificInfoLength The length of the service specific
+     *            information array.
+     * @param matchFilter The filter (Tx on advertiser and Rx on listener) which
+     *            resulted in this match.
+     * @param matchFilterLength The length of the match filter array.
+     */
+    public void onMatch(int peerId, byte[] serviceSpecificInfo,
+            int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
+        if (VDBG) Log.v(TAG, "onMatch: called in stub - override if interested");
+    }
+
+    /**
+     * Called when a message is transmitted successfully - i.e. when we know
+     * that it was received successfully (corresponding to an ACK being
+     * received). It is dummy method (empty implementation printing out a log
+     * message). Override to implement your custom response.
+     * <p>
+     * Note that either this callback or
+     * {@link WifiNanSessionListener#onMessageSendFail(int)} will be received -
+     * never both.
+     */
+    public void onMessageSendSuccess() {
+        if (VDBG) Log.v(TAG, "onMessageSendSuccess: called in stub - override if interested");
+    }
+
+    /**
+     * Called when a message transmission fails - i.e. when no ACK is received.
+     * The hardware will usually attempt to re-transmit several times - this
+     * event is received after all retries are exhausted. There is a possibility
+     * that message was received by the destination successfully but the ACK was
+     * lost. It is dummy method (empty implementation printing out a log
+     * message). Override to implement your custom response.
+     * <p>
+     * Note that either this callback or
+     * {@link WifiNanSessionListener#onMessageSendSuccess()} will be received -
+     * never both
+     *
+     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
+     *            codes.
+     */
+    public void onMessageSendFail(int reason) {
+        if (VDBG) Log.v(TAG, "onMessageSendFail: called in stub - override if interested");
+    }
+
+    /**
+     * Called when a message is received from a discovery session peer. It is
+     * dummy method (empty implementation printing out a log message). Override
+     * to implement your custom response.
+     *
+     * @param peerId The ID of the peer sending the message.
+     * @param message A byte array containing the message.
+     * @param messageLength The length of the byte array containing the relevant
+     *            message bytes.
+     */
+    public void onMessageReceived(int peerId, byte[] message, int messageLength) {
+        if (VDBG) Log.v(TAG, "onMessageReceived: called in stub - override if interested");
+    }
+
+    /**
+     * {@hide}
+     */
+    public IWifiNanSessionListener callback = new IWifiNanSessionListener.Stub() {
+        @Override
+        public void onPublishFail(int reason) {
+            if (VDBG) Log.v(TAG, "onPublishFail: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_FAIL);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onPublishTerminated(int reason) {
+            if (VDBG) Log.v(TAG, "onPublishResponse: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_TERMINATED);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onSubscribeFail(int reason) {
+            if (VDBG) Log.v(TAG, "onSubscribeFail: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_FAIL);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onSubscribeTerminated(int reason) {
+            if (VDBG) Log.v(TAG, "onSubscribeTerminated: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_TERMINATED);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMatch(int peerId, byte[] serviceSpecificInfo,
+                int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
+            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+            Bundle data = new Bundle();
+            data.putInt(MESSAGE_BUNDLE_KEY_PEER_ID, peerId);
+            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
+            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
+
+            Message msg = mHandler.obtainMessage(LISTEN_MATCH);
+            msg.arg1 = serviceSpecificInfoLength;
+            msg.arg2 = matchFilterLength;
+            msg.setData(data);
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageSendSuccess() {
+            if (VDBG) Log.v(TAG, "onMessageSendSuccess");
+
+            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_SEND_SUCCESS);
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageSendFail(int reason) {
+            if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_SEND_FAIL);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageReceived(int peerId, byte[] message, int messageLength) {
+            if (VDBG) {
+                Log.v(TAG, "onMessageReceived: peerId='" + peerId + "', messageLength="
+                        + messageLength);
+            }
+
+            Bundle data = new Bundle();
+            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
+
+            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_RECEIVED);
+            msg.arg1 = messageLength;
+            msg.arg2 = peerId;
+            msg.setData(data);
+            mHandler.sendMessage(msg);
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
new file mode 100644
index 0000000..7dfdd32
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+/**
+ * A representation of a NAN subscribe session. Created when
+ * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
+ * is executed. The object can be used to stop and re-start (re-configure) the
+ * subscribe session.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanSubscribeSession extends WifiNanSession {
+    /**
+     * {@hide}
+     */
+    public WifiNanSubscribeSession(WifiNanManager manager, int sessionId) {
+        super(manager, sessionId);
+    }
+
+    /**
+     * Restart/re-configure the subscribe session. Note that the
+     * {@link WifiNanSessionListener} is not replaced - the same listener used at
+     * creation is still used.
+     *
+     * @param subscribeData The data ({@link SubscribeData}) to subscribe.
+     * @param subscribeSettings The settings ({@link SubscribeSettings}) of the
+     *            subscribe session.
+     */
+    public void subscribe(SubscribeData subscribeData, SubscribeSettings subscribeSettings) {
+        mManager.subscribe(mSessionId, subscribeData, subscribeSettings);
+    }
+}