Merge "Add IC#closeConnection()." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 5906f99..5ceb759 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -416,9 +416,11 @@
     field public static final int contentAuthority = 16843408; // 0x1010290
     field public static final int contentDescription = 16843379; // 0x1010273
     field public static final int contentInsetEnd = 16843860; // 0x1010454
+    field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
     field public static final int contentInsetLeft = 16843861; // 0x1010455
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
+    field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
     field public static final int contextClickable = 16844007; // 0x10104e7
     field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
     field public static final int controlX1 = 16843772; // 0x10103fc
@@ -576,6 +578,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceHasOverlappingRendering = 16844068; // 0x1010524
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -5852,7 +5855,7 @@
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
-    method public java.lang.String getDeviceOwnerLockScreenInfo();
+    method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public java.lang.String getLongSupportMessage(android.content.ComponentName);
@@ -5890,7 +5893,7 @@
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
-    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -5923,7 +5926,7 @@
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
-    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+    method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -9998,6 +10001,7 @@
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
     method public java.lang.String getPackageName();
+    method public java.lang.String getText();
     method public java.lang.String getTitle();
     method public int getWeight();
     method public boolean hasIconFile();
@@ -10025,6 +10029,7 @@
     method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
     method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setWeight(int);
   }
@@ -30367,7 +30372,7 @@
     method public final boolean isDestroyed();
     method public final boolean isPrinterDiscoveryStarted();
     method public abstract void onDestroy();
-    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
     method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
     method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
     method public abstract void onStopPrinterDiscovery();
@@ -42345,6 +42350,7 @@
     method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence, int);
     method protected deprecated boolean fitSystemWindows(android.graphics.Rect);
     method public android.view.View focusSearch(int);
+    method public void forceHasOverlappingRendering(boolean);
     method public void forceLayout();
     method public static int generateViewId();
     method public java.lang.CharSequence getAccessibilityClassName();
@@ -42390,6 +42396,7 @@
     method public boolean getGlobalVisibleRect(android.graphics.Rect, android.graphics.Point);
     method public final boolean getGlobalVisibleRect(android.graphics.Rect);
     method public android.os.Handler getHandler();
+    method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
     method public void getHitRect(android.graphics.Rect);
     method public int getHorizontalFadingEdgeLength();
@@ -48290,9 +48297,15 @@
     method public void collapseActionView();
     method public void dismissPopupMenus();
     method public int getContentInsetEnd();
+    method public int getContentInsetEndWithActions();
     method public int getContentInsetLeft();
     method public int getContentInsetRight();
     method public int getContentInsetStart();
+    method public int getContentInsetStartWithNavigation();
+    method public int getCurrentContentInsetEnd();
+    method public int getCurrentContentInsetLeft();
+    method public int getCurrentContentInsetRight();
+    method public int getCurrentContentInsetStart();
     method public android.graphics.drawable.Drawable getLogo();
     method public java.lang.CharSequence getLogoDescription();
     method public android.view.Menu getMenu();
@@ -48311,6 +48324,8 @@
     method public void inflateMenu(int);
     method public boolean isOverflowMenuShowing();
     method protected void onLayout(boolean, int, int, int, int);
+    method public void setContentInsetEndWithActions(int);
+    method public void setContentInsetStartWithNavigation(int);
     method public void setContentInsetsAbsolute(int, int);
     method public void setContentInsetsRelative(int, int);
     method public void setLogo(int);
@@ -57333,6 +57348,7 @@
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public boolean removeIf(java.util.function.Predicate<? super E>);
+    method public void replaceAll(java.util.function.UnaryOperator<E>);
     method public int size();
     method public void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
@@ -57970,6 +57986,7 @@
     method public java.lang.Object clone();
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public void forEach(java.util.function.BiConsumer<? super K, ? super V>);
+    method public boolean replace(K, V, V);
   }
 
   public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set {
@@ -57990,6 +58007,9 @@
     ctor public Hashtable(java.util.Map<? extends K, ? extends V>);
     method public synchronized void clear();
     method public synchronized java.lang.Object clone();
+    method public synchronized V compute(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
+    method public synchronized V computeIfAbsent(K, java.util.function.Function<? super K, ? extends V>);
+    method public synchronized V computeIfPresent(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
     method public synchronized boolean contains(java.lang.Object);
     method public synchronized boolean containsKey(java.lang.Object);
     method public boolean containsValue(java.lang.Object);
@@ -57997,13 +58017,19 @@
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public synchronized void forEach(java.util.function.BiConsumer<? super K, ? super V>);
     method public synchronized V get(java.lang.Object);
+    method public synchronized V getOrDefault(java.lang.Object, V);
     method public synchronized boolean isEmpty();
     method public java.util.Set<K> keySet();
     method public synchronized java.util.Enumeration<K> keys();
+    method public synchronized V merge(K, V, java.util.function.BiFunction<? super V, ? super V, ? extends V>);
     method public synchronized V put(K, V);
     method public synchronized void putAll(java.util.Map<? extends K, ? extends V>);
+    method public synchronized V putIfAbsent(K, V);
     method protected void rehash();
     method public synchronized V remove(java.lang.Object);
+    method public synchronized boolean remove(java.lang.Object, java.lang.Object);
+    method public synchronized boolean replace(K, V, V);
+    method public synchronized V replace(K, V);
     method public synchronized int size();
     method public java.util.Collection<V> values();
   }
@@ -58148,9 +58174,11 @@
     method public abstract boolean remove(java.lang.Object);
     method public abstract E remove(int);
     method public abstract boolean removeAll(java.util.Collection<?>);
+    method public default void replaceAll(java.util.function.UnaryOperator<E>);
     method public abstract boolean retainAll(java.util.Collection<?>);
     method public abstract E set(int, E);
     method public abstract int size();
+    method public default void sort(java.util.Comparator<? super E>);
     method public abstract java.util.List<E> subList(int, int);
     method public abstract java.lang.Object[] toArray();
     method public abstract T[] toArray(T[]);
@@ -59065,9 +59093,11 @@
     method public synchronized boolean removeElement(java.lang.Object);
     method public synchronized void removeElementAt(int);
     method public synchronized boolean removeIf(java.util.function.Predicate<? super E>);
+    method public synchronized void replaceAll(java.util.function.UnaryOperator<E>);
     method public synchronized void setElementAt(E, int);
     method public synchronized void setSize(int);
     method public synchronized int size();
+    method public synchronized void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
     method public synchronized void trimToSize();
     field protected int capacityIncrement;
@@ -59533,6 +59563,7 @@
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
+    method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
diff --git a/api/system-current.txt b/api/system-current.txt
index a3ed210..bd77046 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -511,9 +511,11 @@
     field public static final int contentAuthority = 16843408; // 0x1010290
     field public static final int contentDescription = 16843379; // 0x1010273
     field public static final int contentInsetEnd = 16843860; // 0x1010454
+    field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
     field public static final int contentInsetLeft = 16843861; // 0x1010455
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
+    field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
     field public static final int contextClickable = 16844007; // 0x10104e7
     field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
     field public static final int controlX1 = 16843772; // 0x10103fc
@@ -671,6 +673,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceHasOverlappingRendering = 16844068; // 0x1010524
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -5992,7 +5995,7 @@
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
     method public java.lang.String getDeviceOwner();
-    method public java.lang.String getDeviceOwnerLockScreenInfo();
+    method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.lang.String getDeviceOwnerNameOnAnyUser();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6036,7 +6039,7 @@
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
-    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6071,7 +6074,7 @@
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
-    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+    method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -10396,6 +10399,7 @@
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
     method public java.lang.String getPackageName();
+    method public java.lang.String getText();
     method public java.lang.String getTitle();
     method public int getWeight();
     method public boolean hasIconFile();
@@ -10423,6 +10427,7 @@
     method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
     method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setWeight(int);
   }
@@ -26814,7 +26819,9 @@
     method public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
     method public boolean getScanResults();
     method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
+    method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
     method public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
+    method public void startScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource);
     method public void startTrackingBssids(android.net.wifi.WifiScanner.BssidInfo[], int, android.net.wifi.WifiScanner.BssidListener);
     method public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
     method public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
@@ -32682,7 +32689,7 @@
     method public final boolean isDestroyed();
     method public final boolean isPrinterDiscoveryStarted();
     method public abstract void onDestroy();
-    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
     method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
     method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
     method public abstract void onStopPrinterDiscovery();
@@ -45071,6 +45078,7 @@
     method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence, int);
     method protected deprecated boolean fitSystemWindows(android.graphics.Rect);
     method public android.view.View focusSearch(int);
+    method public void forceHasOverlappingRendering(boolean);
     method public void forceLayout();
     method public static int generateViewId();
     method public java.lang.CharSequence getAccessibilityClassName();
@@ -45116,6 +45124,7 @@
     method public boolean getGlobalVisibleRect(android.graphics.Rect, android.graphics.Point);
     method public final boolean getGlobalVisibleRect(android.graphics.Rect);
     method public android.os.Handler getHandler();
+    method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
     method public void getHitRect(android.graphics.Rect);
     method public int getHorizontalFadingEdgeLength();
@@ -48675,7 +48684,6 @@
   public final class WebViewFactory {
     ctor public WebViewFactory();
     method public static android.content.pm.PackageInfo getLoadedPackageInfo();
-    method public static java.lang.String getWebViewPackageName();
     method public static int loadWebViewNativeLibraryFromPackage(java.lang.String, java.lang.ClassLoader);
     method public static void prepareWebViewInZygote();
     field public static final java.lang.String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY = "persist.sys.webview.vmsize";
@@ -51355,9 +51363,15 @@
     method public void collapseActionView();
     method public void dismissPopupMenus();
     method public int getContentInsetEnd();
+    method public int getContentInsetEndWithActions();
     method public int getContentInsetLeft();
     method public int getContentInsetRight();
     method public int getContentInsetStart();
+    method public int getContentInsetStartWithNavigation();
+    method public int getCurrentContentInsetEnd();
+    method public int getCurrentContentInsetLeft();
+    method public int getCurrentContentInsetRight();
+    method public int getCurrentContentInsetStart();
     method public android.graphics.drawable.Drawable getLogo();
     method public java.lang.CharSequence getLogoDescription();
     method public android.view.Menu getMenu();
@@ -51376,6 +51390,8 @@
     method public void inflateMenu(int);
     method public boolean isOverflowMenuShowing();
     method protected void onLayout(boolean, int, int, int, int);
+    method public void setContentInsetEndWithActions(int);
+    method public void setContentInsetStartWithNavigation(int);
     method public void setContentInsetsAbsolute(int, int);
     method public void setContentInsetsRelative(int, int);
     method public void setLogo(int);
@@ -60398,6 +60414,7 @@
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public boolean removeIf(java.util.function.Predicate<? super E>);
+    method public void replaceAll(java.util.function.UnaryOperator<E>);
     method public int size();
     method public void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
@@ -61035,6 +61052,7 @@
     method public java.lang.Object clone();
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public void forEach(java.util.function.BiConsumer<? super K, ? super V>);
+    method public boolean replace(K, V, V);
   }
 
   public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set {
@@ -61055,6 +61073,9 @@
     ctor public Hashtable(java.util.Map<? extends K, ? extends V>);
     method public synchronized void clear();
     method public synchronized java.lang.Object clone();
+    method public synchronized V compute(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
+    method public synchronized V computeIfAbsent(K, java.util.function.Function<? super K, ? extends V>);
+    method public synchronized V computeIfPresent(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
     method public synchronized boolean contains(java.lang.Object);
     method public synchronized boolean containsKey(java.lang.Object);
     method public boolean containsValue(java.lang.Object);
@@ -61062,13 +61083,19 @@
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public synchronized void forEach(java.util.function.BiConsumer<? super K, ? super V>);
     method public synchronized V get(java.lang.Object);
+    method public synchronized V getOrDefault(java.lang.Object, V);
     method public synchronized boolean isEmpty();
     method public java.util.Set<K> keySet();
     method public synchronized java.util.Enumeration<K> keys();
+    method public synchronized V merge(K, V, java.util.function.BiFunction<? super V, ? super V, ? extends V>);
     method public synchronized V put(K, V);
     method public synchronized void putAll(java.util.Map<? extends K, ? extends V>);
+    method public synchronized V putIfAbsent(K, V);
     method protected void rehash();
     method public synchronized V remove(java.lang.Object);
+    method public synchronized boolean remove(java.lang.Object, java.lang.Object);
+    method public synchronized boolean replace(K, V, V);
+    method public synchronized V replace(K, V);
     method public synchronized int size();
     method public java.util.Collection<V> values();
   }
@@ -61213,9 +61240,11 @@
     method public abstract boolean remove(java.lang.Object);
     method public abstract E remove(int);
     method public abstract boolean removeAll(java.util.Collection<?>);
+    method public default void replaceAll(java.util.function.UnaryOperator<E>);
     method public abstract boolean retainAll(java.util.Collection<?>);
     method public abstract E set(int, E);
     method public abstract int size();
+    method public default void sort(java.util.Comparator<? super E>);
     method public abstract java.util.List<E> subList(int, int);
     method public abstract java.lang.Object[] toArray();
     method public abstract T[] toArray(T[]);
@@ -62130,9 +62159,11 @@
     method public synchronized boolean removeElement(java.lang.Object);
     method public synchronized void removeElementAt(int);
     method public synchronized boolean removeIf(java.util.function.Predicate<? super E>);
+    method public synchronized void replaceAll(java.util.function.UnaryOperator<E>);
     method public synchronized void setElementAt(E, int);
     method public synchronized void setSize(int);
     method public synchronized int size();
+    method public synchronized void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
     method public synchronized void trimToSize();
     field protected int capacityIncrement;
@@ -62598,6 +62629,7 @@
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
+    method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
diff --git a/api/test-current.txt b/api/test-current.txt
index 6a31de4..8a49756 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -416,9 +416,11 @@
     field public static final int contentAuthority = 16843408; // 0x1010290
     field public static final int contentDescription = 16843379; // 0x1010273
     field public static final int contentInsetEnd = 16843860; // 0x1010454
+    field public static final int contentInsetEndWithActions = 16844070; // 0x1010526
     field public static final int contentInsetLeft = 16843861; // 0x1010455
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
+    field public static final int contentInsetStartWithNavigation = 16844069; // 0x1010525
     field public static final int contextClickable = 16844007; // 0x10104e7
     field public static final int contextPopupMenuStyle = 16844034; // 0x1010502
     field public static final int controlX1 = 16843772; // 0x10103fc
@@ -576,6 +578,7 @@
     field public static final int fontFamily = 16843692; // 0x10103ac
     field public static final int fontFeatureSettings = 16843959; // 0x10104b7
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
+    field public static final int forceHasOverlappingRendering = 16844068; // 0x1010524
     field public static final int foreground = 16843017; // 0x1010109
     field public static final int foregroundGravity = 16843264; // 0x1010200
     field public static final int foregroundTint = 16843885; // 0x101046d
@@ -5856,7 +5859,7 @@
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
-    method public java.lang.String getDeviceOwnerLockScreenInfo();
+    method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
     method public java.lang.String getLongSupportMessage(android.content.ComponentName);
@@ -5894,7 +5897,7 @@
     method public boolean hasGrantedPolicy(android.content.ComponentName, int);
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
-    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -5927,7 +5930,7 @@
     method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
-    method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
+    method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
@@ -10008,6 +10011,7 @@
     method public android.content.Intent getIntent();
     method public long getLastChangedTimestamp();
     method public java.lang.String getPackageName();
+    method public java.lang.String getText();
     method public java.lang.String getTitle();
     method public int getWeight();
     method public boolean hasIconFile();
@@ -10035,6 +10039,7 @@
     method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
     method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
+    method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
     method public android.content.pm.ShortcutInfo.Builder setWeight(int);
   }
@@ -30436,7 +30441,7 @@
     method public final boolean isDestroyed();
     method public final boolean isPrinterDiscoveryStarted();
     method public abstract void onDestroy();
-    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
+    method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.os.CancellationSignal, android.printservice.CustomPrinterIconCallback);
     method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
     method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
     method public abstract void onStopPrinterDiscovery();
@@ -42419,6 +42424,7 @@
     method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence, int);
     method protected deprecated boolean fitSystemWindows(android.graphics.Rect);
     method public android.view.View focusSearch(int);
+    method public void forceHasOverlappingRendering(boolean);
     method public void forceLayout();
     method public static int generateViewId();
     method public java.lang.CharSequence getAccessibilityClassName();
@@ -42464,6 +42470,7 @@
     method public boolean getGlobalVisibleRect(android.graphics.Rect, android.graphics.Point);
     method public final boolean getGlobalVisibleRect(android.graphics.Rect);
     method public android.os.Handler getHandler();
+    method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
     method public void getHitRect(android.graphics.Rect);
     method public int getHorizontalFadingEdgeLength();
@@ -48364,9 +48371,15 @@
     method public void collapseActionView();
     method public void dismissPopupMenus();
     method public int getContentInsetEnd();
+    method public int getContentInsetEndWithActions();
     method public int getContentInsetLeft();
     method public int getContentInsetRight();
     method public int getContentInsetStart();
+    method public int getContentInsetStartWithNavigation();
+    method public int getCurrentContentInsetEnd();
+    method public int getCurrentContentInsetLeft();
+    method public int getCurrentContentInsetRight();
+    method public int getCurrentContentInsetStart();
     method public android.graphics.drawable.Drawable getLogo();
     method public java.lang.CharSequence getLogoDescription();
     method public android.view.Menu getMenu();
@@ -48385,6 +48398,8 @@
     method public void inflateMenu(int);
     method public boolean isOverflowMenuShowing();
     method protected void onLayout(boolean, int, int, int, int);
+    method public void setContentInsetEndWithActions(int);
+    method public void setContentInsetStartWithNavigation(int);
     method public void setContentInsetsAbsolute(int, int);
     method public void setContentInsetsRelative(int, int);
     method public void setLogo(int);
@@ -57407,6 +57422,7 @@
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public boolean removeIf(java.util.function.Predicate<? super E>);
+    method public void replaceAll(java.util.function.UnaryOperator<E>);
     method public int size();
     method public void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
@@ -58044,6 +58060,7 @@
     method public java.lang.Object clone();
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public void forEach(java.util.function.BiConsumer<? super K, ? super V>);
+    method public boolean replace(K, V, V);
   }
 
   public class HashSet extends java.util.AbstractSet implements java.lang.Cloneable java.io.Serializable java.util.Set {
@@ -58064,6 +58081,9 @@
     ctor public Hashtable(java.util.Map<? extends K, ? extends V>);
     method public synchronized void clear();
     method public synchronized java.lang.Object clone();
+    method public synchronized V compute(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
+    method public synchronized V computeIfAbsent(K, java.util.function.Function<? super K, ? extends V>);
+    method public synchronized V computeIfPresent(K, java.util.function.BiFunction<? super K, ? super V, ? extends V>);
     method public synchronized boolean contains(java.lang.Object);
     method public synchronized boolean containsKey(java.lang.Object);
     method public boolean containsValue(java.lang.Object);
@@ -58071,13 +58091,19 @@
     method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
     method public synchronized void forEach(java.util.function.BiConsumer<? super K, ? super V>);
     method public synchronized V get(java.lang.Object);
+    method public synchronized V getOrDefault(java.lang.Object, V);
     method public synchronized boolean isEmpty();
     method public java.util.Set<K> keySet();
     method public synchronized java.util.Enumeration<K> keys();
+    method public synchronized V merge(K, V, java.util.function.BiFunction<? super V, ? super V, ? extends V>);
     method public synchronized V put(K, V);
     method public synchronized void putAll(java.util.Map<? extends K, ? extends V>);
+    method public synchronized V putIfAbsent(K, V);
     method protected void rehash();
     method public synchronized V remove(java.lang.Object);
+    method public synchronized boolean remove(java.lang.Object, java.lang.Object);
+    method public synchronized boolean replace(K, V, V);
+    method public synchronized V replace(K, V);
     method public synchronized int size();
     method public java.util.Collection<V> values();
   }
@@ -58222,9 +58248,11 @@
     method public abstract boolean remove(java.lang.Object);
     method public abstract E remove(int);
     method public abstract boolean removeAll(java.util.Collection<?>);
+    method public default void replaceAll(java.util.function.UnaryOperator<E>);
     method public abstract boolean retainAll(java.util.Collection<?>);
     method public abstract E set(int, E);
     method public abstract int size();
+    method public default void sort(java.util.Comparator<? super E>);
     method public abstract java.util.List<E> subList(int, int);
     method public abstract java.lang.Object[] toArray();
     method public abstract T[] toArray(T[]);
@@ -59139,9 +59167,11 @@
     method public synchronized boolean removeElement(java.lang.Object);
     method public synchronized void removeElementAt(int);
     method public synchronized boolean removeIf(java.util.function.Predicate<? super E>);
+    method public synchronized void replaceAll(java.util.function.UnaryOperator<E>);
     method public synchronized void setElementAt(E, int);
     method public synchronized void setSize(int);
     method public synchronized int size();
+    method public synchronized void sort(java.util.Comparator<? super E>);
     method public java.util.Spliterator<E> spliterator();
     method public synchronized void trimToSize();
     field protected int capacityIncrement;
@@ -59607,6 +59637,7 @@
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
+    method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
     method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 887932a..0d387e6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5920,6 +5920,9 @@
      * @return true if this is the topmost, non-finishing activity in its task.
      */
     private boolean isTopOfTask() {
+        if (mToken == null || mWindow == null || !mWindowAdded) {
+            return false;
+        }
         try {
             return ActivityManagerNative.getDefault().isTopOfTask(mToken);
         } catch (RemoteException e) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dbc6c84..9cee815 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5077,26 +5077,6 @@
          */
         TimeZone.setDefault(null);
 
-        synchronized (mResourcesManager) {
-            /*
-             * Initialize the default locales in this process for the reasons we set the time zone.
-             *
-             * We do this through ResourcesManager, since we need to do locale negotiation.
-             */
-            mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
-
-            /*
-             * Update the system configuration since its preloaded and might not
-             * reflect configuration changes. The configuration object passed
-             * in AppBindData can be safely assumed to be up to date
-             */
-            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
-            mCurDefaultDisplayDpi = data.config.densityDpi;
-
-            // This calls mResourcesManager so keep it within the synchronized block.
-            applyCompatConfiguration(mCurDefaultDisplayDpi);
-        }
-
         data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
 
         /**
@@ -5221,6 +5201,26 @@
         }
 
         final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
+        synchronized (mResourcesManager) {
+            /*
+             * Initialize the default locales in this process for the reasons we set the time zone.
+             *
+             * We do this through ResourcesManager, since we need to do locale negotiation.
+             */
+            mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
+
+            /*
+             * Update the system configuration since its preloaded and might not
+             * reflect configuration changes. The configuration object passed
+             * in AppBindData can be safely assumed to be up to date
+             */
+            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+            mCurDefaultDisplayDpi = data.config.densityDpi;
+
+            // This calls mResourcesManager so keep it within the synchronized block.
+            applyCompatConfiguration(mCurDefaultDisplayDpi);
+        }
+
         if (!Process.isIsolated() && !"android".equals(appContext.getPackageName())) {
             // This cache location probably points at credential-encrypted
             // storage which may not be accessible yet; assign it anyway instead
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 54d813d..1a31332 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -66,7 +66,7 @@
                 }
             };
 
-    private String[] mSystemLocales = {};
+    private String[] mSystemLocales = null;
     private final HashSet<String> mNonSystemLocales = new HashSet<>();
     private boolean mHasNonSystemLocales = false;
 
@@ -431,7 +431,7 @@
         final boolean findSystemLocales;
         final boolean hasNonSystemLocales;
         synchronized (this) {
-            findSystemLocales = (mSystemLocales.length == 0);
+            findSystemLocales = (mSystemLocales == null || mSystemLocales.length == 0);
             hasNonSystemLocales = mHasNonSystemLocales;
 
             if (DEBUG) {
@@ -483,7 +483,7 @@
                 LocaleList.isPseudoLocalesOnly(nonSystemLocales);
 
         synchronized (this) {
-            if (mSystemLocales.length == 0) {
+            if (mSystemLocales == null || mSystemLocales.length == 0) {
                 mSystemLocales = systemLocales;
             }
             mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
@@ -552,6 +552,9 @@
     }
 
     /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
+        if (mSystemLocales == null) {
+            throw new RuntimeException("ResourcesManager is not ready to negotiate locales.");
+        }
         final int bestLocale;
         if (mHasNonSystemLocales) {
             bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
@@ -649,4 +652,4 @@
 
         return changes != 0;
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e7427bf..36c82e5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -71,6 +71,7 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -2782,7 +2783,7 @@
      * compromised, certificates it had already installed will be protected.
      *
      * <p>If the installer must have access to the credentials, call
-     * {@link #installKeyPair(ComponentName, PrivateKey, Certificate, String, boolean)} instead.
+     * {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, boolean)} instead.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *            {@code null} if calling from a delegated certificate installer.
@@ -2796,13 +2797,14 @@
      */
     public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
             @NonNull Certificate cert, @NonNull String alias) {
-        return installKeyPair(admin, privKey, cert, alias, false);
+        return installKeyPair(admin, privKey, new Certificate[] {cert}, alias, false);
     }
 
     /**
      * Called by a device or profile owner, or delegated certificate installer, to install a
-     * certificate and corresponding private key. All apps within the profile will be able to access
-     * the certificate and use the private key, given direct user approval.
+     * certificate chain and corresponding private key for the leaf certificate. All apps within the
+     * profile will be able to access the certificate chain and use the private key, given direct
+     * user approval.
      *
      * <p>The caller of this API may grant itself access to the certificate and private key
      * immediately, without user approval. It is a best practice not to request this unless strictly
@@ -2811,7 +2813,9 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if calling from a delegated certificate installer.
      * @param privKey The private key to install.
-     * @param cert The certificate to install.
+     * @param certs The certificate chain to install. The chain should start with the leaf
+     *        certificate and include the chain of trust in order. This will be returned by
+     *        {@link android.security.KeyChain#getCertificateChain}.
      * @param alias The private key alias under which to install the certificate. If a certificate
      *        with that alias already exists, it will be overwritten.
      * @param requestAccess {@code true} to request that the calling app be granted access to the
@@ -2820,14 +2824,20 @@
      * @return {@code true} if the keys were installed, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
+     * @see android.security.KeyChain#getCertificateChain
      */
     public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
-            @NonNull Certificate cert, @NonNull String alias, boolean requestAccess) {
+            @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
         try {
-            final byte[] pemCert = Credentials.convertToPem(cert);
+            final byte[] pemCert = Credentials.convertToPem(certs[0]);
+            byte[] pemChain = null;
+            if (certs.length > 1) {
+                pemChain = Credentials.convertToPem(Arrays.copyOfRange(certs, 1, certs.length));
+            }
             final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
                     .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
-            return mService.installKeyPair(admin, pkcs8Key, pemCert, alias, requestAccess);
+            return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias,
+                    requestAccess);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -3736,24 +3746,22 @@
      *
      * @param admin The name of the admin component to check.
      * @param info Device owner information which will be displayed instead of the user owner info.
-     * @return Whether the device owner information has been set.
      * @throws SecurityException if {@code admin} is not a device owner.
      */
-    public boolean setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, String info) {
+    public void setDeviceOwnerLockScreenInfo(@NonNull ComponentName admin, CharSequence info) {
         if (mService != null) {
             try {
-                return mService.setDeviceOwnerLockScreenInfo(admin, info);
+                mService.setDeviceOwnerLockScreenInfo(admin, info);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
         }
-        return false;
     }
 
     /**
      * @return The device owner information. If it is not set returns {@code null}.
      */
-    public String getDeviceOwnerLockScreenInfo() {
+    public CharSequence getDeviceOwnerLockScreenInfo() {
         if (mService != null) {
             try {
                 return mService.getDeviceOwnerLockScreenInfo();
@@ -5919,7 +5927,7 @@
     /**
      * Called by a profile owner of a managed profile to set the color used for customization. This
      * color is used as background color of the confirm credentials screen for that user. The
-     * default color is {@link android.graphics.Color#GRAY}.
+     * default color is teal (#00796B).
      * <p>
      * The confirm credentials screen can be created using
      * {@link android.app.KeyguardManager#createConfirmDeviceCredentialIntent}.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index aed220d..8be52d8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -135,8 +135,8 @@
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
 
-    boolean setDeviceOwnerLockScreenInfo(in ComponentName who, String deviceOwnerInfo);
-    String getDeviceOwnerLockScreenInfo();
+    void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
+    CharSequence getDeviceOwnerLockScreenInfo();
 
     String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
     boolean getPackageSuspended(in ComponentName admin, String packageName);
@@ -146,7 +146,7 @@
     void enforceCanManageCaCerts(in ComponentName admin);
 
     boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
-            String alias, boolean requestAccess);
+            in byte[] certChainBuffer, String alias, boolean requestAccess);
     boolean removeKeyPair(in ComponentName who, String alias);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
 
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 68442ea..b8a40dc 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -422,7 +422,7 @@
                     try {
                         mAuthRetry = true;
                         mService.writeDescriptor(mClientIf, address, handle,
-                            descriptor.getCharacteristic().getWriteType(),
+                            BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
                             AUTHENTICATION_MITM, descriptor.getValue());
                         return;
                     } catch (RemoteException e) {
@@ -545,7 +545,6 @@
     /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, int instanceId) {
         for(BluetoothGattService svc : mServices) {
             for(BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
-                Log.w(TAG, "getCharacteristicById() comparing " + charac.getInstanceId() + " and " + instanceId);
                 if (charac.getInstanceId() == instanceId)
                     return charac;
             }
@@ -944,7 +943,8 @@
 
         try {
             mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
-                characteristic.getWriteType(), AUTHENTICATION_NONE, descriptor.getValue());
+                BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT, AUTHENTICATION_NONE,
+                descriptor.getValue());
         } catch (RemoteException e) {
             Log.e(TAG,"",e);
             mDeviceBusy = false;
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 736e55d..eab4c6f 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,6 @@
         "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
 
     private IBluetoothPbapClient mService;
-    private BluetoothDevice mDevice;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -173,7 +172,6 @@
         }
         if (mService != null && isEnabled() && isValidDevice(device)) {
             try {
-                mDevice = device;
                 return mService.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -193,13 +191,13 @@
      * @return false on error,
      *               true otherwise
      */
-    public boolean disconnect() {
+    public boolean disconnect(BluetoothDevice device) {
         if (DBG) {
-            log("disconnect(" + mDevice + ")");
+            log("disconnect(" + device + ")" + new Exception() );
         }
-        if (mService != null && isEnabled() && isValidDevice(mDevice)) {
+        if (mService != null && isEnabled() && isValidDevice(device)) {
             try {
-                mService.disconnect(mDevice);
+                mService.disconnect(device);
                 return true;
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -328,4 +326,66 @@
        }
        return false;
     }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     *  Priority can be one of {@link #PRIORITY_ON} or
+     * {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     */
+    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, 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}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     */
+    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, Log.getStackTraceString(new Throwable()));
+                return PRIORITY_OFF;
+            }
+        }
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return PRIORITY_OFF;
+    }
 }
diff --git a/core/java/android/bluetooth/IBluetoothPbapClient.aidl b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
index b26ea29..6d4c5a6 100644
--- a/core/java/android/bluetooth/IBluetoothPbapClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
@@ -29,4 +29,6 @@
     List<BluetoothDevice> getConnectedDevices();
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
+    boolean setPriority(in BluetoothDevice device, int priority);
+    int getPriority(in BluetoothDevice device);
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 66e0ada..10259be 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1581,14 +1581,6 @@
             = "android.intent.extra.UNINSTALL_ALL_USERS";
 
     /**
-     * Specified when the uninstall confirmation dialog is not required to be shown.
-     * Use with {@link #ACTION_UNINSTALL_PACKAGE}
-     * @hide
-     */
-    public static final String EXTRA_SKIP_UNINSTALL_CONFIRMATION =
-            "android.intent.extra.SKIP_UNINSTALL_CONFIRMATION";
-
-    /**
      * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
      * describing the last run version of the platform that was setup.
      * @hide
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index e3fb161..6fce36b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -316,6 +316,12 @@
     int getApplicationEnabledSetting(in String packageName, int userId);
 
     /**
+     * Logs process start information (including APK hash) to the security log.
+     */
+    void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, String apkFile,
+            int pid);
+
+    /**
      * As per {@link android.content.pm.PackageManager#flushPackageRestrictionsAsUser}.
      */
     void flushPackageRestrictionsAsUser(in int userId);
@@ -431,10 +437,9 @@
     void performFstrimIfNeeded();
 
     /**
-     * Ask the package manager to extract packages if needed, to save
-     * the VM unzipping the APK in memory during launch.
+     * Ask the package manager to update packages if needed.
      */
-    void extractPackagesIfNeeded();
+    void updatePackagesIfNeeded();
 
     /**
      * Notify the package manager that a package is going to be used.
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 8f9dcfc..31d377b 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -47,4 +47,8 @@
     int getIconMaxDimensions(String packageName, int userId);
 
     void resetThrottling(); // system only API for developer opsions
+
+    byte[] getBackupPayload(int user);
+
+    void applyRestore(in byte[] payload, int user);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index ae75e3f..fbe35a65 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -34,18 +34,20 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+// TODO Enhance javadoc
 /**
- * TODO Enhance javadoc
  *
- * Represents a shortcut form an application.
+ * Represents a shortcut from an application.
  *
- * Notes...
- * - If an {@link Icon} is of a resource, then we'll just persist the package name and resource ID.
+ * <p>Notes about icons:
+ * <ul>
+ *     <li>If an {@link Icon} is a resource, the system keeps the package name and the resource ID.
+ *     Otherwise, the bitmap is fetched when it's registered to ShortcutManager,
+ *     then shrunk if necessary, and persisted.
+ *     <li>The system disallows byte[] icons, because they can easily go over the binder size limit.
+ * </ul>
  *
- *   Otherwise, the bitmap will be fetched when it's registered to ShortcutManager, then *shrunk*
- *   if necessary, and persisted.
- *
- *   We will disallow byte[] icons, because they can easily go over binder size limit.
+ * @see {@link ShortcutManager}.
  */
 public class ShortcutInfo implements Parcelable {
     /* @hide */
@@ -118,6 +120,9 @@
     @NonNull
     private String mTitle;
 
+    @Nullable
+    private String mText;
+
     /**
      * Intent *with extras removed*.
      */
@@ -157,6 +162,7 @@
         mActivityComponent = b.mActivityComponent;
         mIcon = b.mIcon;
         mTitle = b.mTitle;
+        mText = b.mText;
         mIntent = b.mIntent;
         if (mIntent != null) {
             final Bundle intentExtras = mIntent.getExtras();
@@ -176,6 +182,7 @@
      * @hide
      */
     public void enforceMandatoryFields() {
+        Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
         Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
         Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
     }
@@ -195,16 +202,17 @@
             if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
                 mIcon = source.mIcon;
                 mBitmapPath = source.mBitmapPath;
+                mIconResourceId = source.mIconResourceId;
             }
 
             mTitle = source.mTitle;
+            mText = source.mText;
             if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
                 mIntent = source.mIntent;
                 mIntentPersistableExtras = source.mIntentPersistableExtras;
             }
             mWeight = source.mWeight;
             mExtras = source.mExtras;
-            mIconResourceId = source.mIconResourceId;
         } else {
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
@@ -244,6 +252,9 @@
         if (source.mTitle != null) {
             mTitle = source.mTitle;
         }
+        if (source.mText != null) {
+            mText = source.mText;
+        }
         if (source.mIntent != null) {
             mIntent = source.mIntent;
             mIntentPersistableExtras = source.mIntentPersistableExtras;
@@ -305,6 +316,8 @@
 
         private String mTitle;
 
+        private String mText;
+
         private Intent mIntent;
 
         private int mWeight;
@@ -360,6 +373,9 @@
 
         /**
          * Sets the title of a shortcut.  This is a mandatory field.
+         *
+         * <p>This field is intended for a concise description of a shortcut displayed under
+         * an icon.  The recommend max length is 10 characters.
          */
         @NonNull
         public Builder setTitle(@NonNull String title) {
@@ -368,6 +384,18 @@
         }
 
         /**
+         * Sets the text of a shortcut.  This is an optional field.
+         *
+         * <p>This field is intended to be more descriptive than the shortcut title.
+         * The recommend max length is 25 characters.
+         */
+        @NonNull
+        public Builder setText(@NonNull String text) {
+            mText = Preconditions.checkStringNotEmpty(text, "text");
+            return this;
+        }
+
+        /**
          * Sets the intent of a shortcut.  This is a mandatory field.  The extras must only contain
          * persistable information.  (See {@link PersistableBundle}).
          */
@@ -457,6 +485,14 @@
     }
 
     /**
+     * Return the shortcut text.
+     */
+    @Nullable
+    public String getText() {
+        return mText;
+    }
+
+    /**
      * Return the intent.
      *
      * <p>All shortcuts must have an intent, but this method will return null when
@@ -630,6 +666,7 @@
         mActivityComponent = source.readParcelable(cl);
         mIcon = source.readParcelable(cl);
         mTitle = source.readString();
+        mText = source.readString();
         mIntent = source.readParcelable(cl);
         mIntentPersistableExtras = source.readParcelable(cl);
         mWeight = source.readInt();
@@ -647,6 +684,7 @@
         dest.writeParcelable(mActivityComponent, flags);
         dest.writeParcelable(mIcon, flags);
         dest.writeString(mTitle);
+        dest.writeString(mText);
         dest.writeParcelable(mIntent, flags);
         dest.writeParcelable(mIntentPersistableExtras, flags);
         dest.writeInt(mWeight);
@@ -708,6 +746,9 @@
         sb.append(", title=");
         sb.append(secure ? "***" : mTitle);
 
+        sb.append(", text=");
+        sb.append(secure ? "***" : mText);
+
         sb.append(", icon=");
         sb.append(mIcon);
 
@@ -744,7 +785,8 @@
 
     /** @hide */
     public ShortcutInfo(String id, String packageName, ComponentName activityComponent,
-            Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras,
+            Icon icon, String title, String text, Intent intent,
+            PersistableBundle intentPersistableExtras,
             int weight, PersistableBundle extras, long lastChangedTimestamp,
             int flags, int iconResId, String bitmapPath) {
         mId = id;
@@ -752,6 +794,7 @@
         mActivityComponent = activityComponent;
         mIcon = icon;
         mTitle = title;
+        mText = text;
         mIntent = intent;
         mIntentPersistableExtras = intentPersistableExtras;
         mWeight = weight;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index b247f65..e4a98b5 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -19,15 +19,13 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
+// TODO Enhance javadoc
 /**
- * TODO Enhance javadoc
- *
  * {@link ShortcutManager} manages shortcuts created by applications.
  *
  * <h3>Dynamic shortcuts and pinned shortcuts</h3>
@@ -66,15 +64,18 @@
  * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
  * which happens at a certain time every day.
  *
- * <p>An applications can use {@link #getRateLimitResetTime()} to get the next reset time.
+ * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time.
+ *
+ * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the
+ * internal rate-limiting counter.  Automated tests can use the following ADB shell command to
+ * achieve the same effect:</p>
+ * <pre>adb shell cmd shortcut reset-throttling</pre>
  *
  * <h3>Backup and Restore</h3>
  *
  * Shortcuts will be backed up and restored across devices.  This means all information, including
  * IDs, must be meaningful on a different device.
  *
- * TODO: Define a Broadcast to let apps update shortcuts on a restored device.
- *
  * <h3>APIs for launcher</h3>
  *
  * Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 4255582..d57f2e6e 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -40,28 +40,37 @@
     }
 
     public abstract List<ShortcutInfo>
-            getShortcuts(@NonNull String callingPackage, long changedSince,
+            getShortcuts(int launcherUserId,
+            @NonNull String callingPackage, long changedSince,
             @Nullable String packageName, @Nullable ComponentName componentName,
             @ShortcutQuery.QueryFlags int flags,
             int userId);
 
     public abstract List<ShortcutInfo>
-            getShortcutInfo(@NonNull String callingPackage,
+            getShortcutInfo(int launcherUserId, @NonNull String callingPackage,
             @NonNull String packageName, @Nullable List<String> ids, int userId);
 
-    public abstract void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
+
+    public abstract boolean
+            isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
+            @NonNull String packageName, @NonNull String id, int userId);
+
+    public abstract void pinShortcuts(int launcherUserId,
+            @NonNull String callingPackage, @NonNull String packageName,
             @NonNull List<String> shortcutIds, int userId);
 
-    public abstract Intent createShortcutIntent(@NonNull String callingPackage,
+    public abstract Intent createShortcutIntent(int launcherUserId, @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
     public abstract void addListener(@NonNull ShortcutChangeListener listener);
 
-    public abstract int getShortcutIconResId(@NonNull String callingPackage,
+    public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
             @NonNull ShortcutInfo shortcut, int userId);
 
-    public abstract ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage,
+    public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
+            @NonNull String callingPackage,
             @NonNull ShortcutInfo shortcut, int userId);
 
-    public abstract boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId);
+    public abstract boolean hasShortcutHostPermission(int launcherUserId,
+            @NonNull String callingPackage);
 }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index b75e653..7d903bd 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -31,12 +31,15 @@
 import android.util.SparseIntArray;
 import dalvik.system.CloseGuard;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+
 /**
  * Sensor manager implementation that communicates with the built-in
  * system sensors.
@@ -55,7 +58,9 @@
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
     private static final Object sLock = new Object();
-    private static boolean sSensorModuleInitialized = false;
+    @GuardedBy("sLock")
+    private static boolean sNativeClassInited = false;
+    @GuardedBy("sLock")
     private static InjectEventQueue sInjectEventQueue = null;
 
     private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
@@ -84,8 +89,8 @@
     /** {@hide} */
     public SystemSensorManager(Context context, Looper mainLooper) {
         synchronized(sLock) {
-            if (!sSensorModuleInitialized) {
-                sSensorModuleInitialized = true;
+            if (!sNativeClassInited) {
+                sNativeClassInited = true;
                 nativeClassInit();
             }
         }
diff --git a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
rename to core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
index 163f7e40..2239a25 100644
--- a/services/net/java/android/net/metrics/CaptivePortalCheckResultEvent.java
+++ b/core/java/android/net/metrics/CaptivePortalCheckResultEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class CaptivePortalCheckResultEvent extends IpConnectivityEvent implements Parcelable {
     public static final String TAG = "CaptivePortalCheckResultEvent";
 
diff --git a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
rename to core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
index d0cc120..00808c1 100644
--- a/services/net/java/android/net/metrics/CaptivePortalStateChangeEvent.java
+++ b/core/java/android/net/metrics/CaptivePortalStateChangeEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class CaptivePortalStateChangeEvent extends IpConnectivityEvent implements Parcelable {
     public static final String TAG = "CaptivePortalStateChangeEvent";
 
diff --git a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
rename to core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
index 92b376c..c6fcb2d 100644
--- a/services/net/java/android/net/metrics/ConnectivityServiceChangeEvent.java
+++ b/core/java/android/net/metrics/ConnectivityServiceChangeEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class ConnectivityServiceChangeEvent extends IpConnectivityEvent implements Parcelable {
     public static final String TAG = "ConnectivityServiceChangeEvent";
 
diff --git a/services/net/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/DhcpClientEvent.java
rename to core/java/android/net/metrics/DhcpClientEvent.java
index 2c24034..7b44664 100644
--- a/services/net/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class DhcpClientEvent extends IpConnectivityEvent implements Parcelable {
     public static final String TAG = "DhcpClientEvent";
 
diff --git a/services/net/java/android/net/metrics/IpConnectivityEvent.java b/core/java/android/net/metrics/IpConnectivityEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpConnectivityEvent.java
rename to core/java/android/net/metrics/IpConnectivityEvent.java
index f277bd0..ec42890 100644
--- a/services/net/java/android/net/metrics/IpConnectivityEvent.java
+++ b/core/java/android/net/metrics/IpConnectivityEvent.java
@@ -20,6 +20,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class IpConnectivityEvent implements Parcelable {
     // IPRM = IpReachabilityMonitor
     // DHCP = DhcpClient
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
rename to core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
index a8c18d6..e71b0be 100644
--- a/services/net/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityMonitorMessageEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class IpReachabilityMonitorMessageEvent extends IpConnectivityEvent
     implements Parcelable {
     public static final String TAG = "IpReachabilityMonitorMessageEvent";
diff --git a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
similarity index 98%
rename from services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
rename to core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
index 172cbf8..182b778 100644
--- a/services/net/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityMonitorProbeEvent.java
@@ -19,6 +19,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * {@hide}
+ */
 public class IpReachabilityMonitorProbeEvent extends IpConnectivityEvent
     implements Parcelable {
     public static final String TAG = "IpReachabilityMonitorProbeEvent";
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 57c7718..2941283 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -32,6 +32,9 @@
      */
     public static final PageRange ALL_PAGES = new PageRange(0, Integer.MAX_VALUE);
 
+    /** @hide */
+    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
+
     private final int mStart;
     private final int mEnd;
 
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index cd5a903..7b9533d 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.pm.ParceledListSlice;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.print.PrinterCapabilitiesInfo;
 import android.print.PrinterId;
@@ -412,11 +413,13 @@
      * service.
      *
      * @param printerId The printer to icon belongs to.
+     * @param cancellationSignal Signal used to cancel the request
      * @param callback Callback for returning the icon to the print spooler.
      *
      * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
      */
     public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+            @NonNull CancellationSignal cancellationSignal,
             @NonNull CustomPrinterIconCallback callback) {
     }
 
@@ -533,7 +536,7 @@
         if (!mIsDestroyed && mObserver != null) {
             CustomPrinterIconCallback callback = new CustomPrinterIconCallback(printerId,
                     mObserver);
-            onRequestCustomPrinterIcon(printerId, callback);
+            onRequestCustomPrinterIcon(printerId, new CancellationSignal(), callback);
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f7e0e03..ebecfdb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6089,7 +6089,6 @@
             MOUNT_UMS_AUTOSTART,
             MOUNT_UMS_PROMPT,
             MOUNT_UMS_NOTIFY_ENABLED,
-            UI_NIGHT_MODE,
             SLEEP_TIMEOUT,
             DOUBLE_TAP_TO_WAKE,
             WAKE_GESTURE_ENABLED,
@@ -7680,6 +7679,9 @@
                 BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
         /** {@hide} */
         public static final String
+                BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_";
+        /** {@hide} */
+        public static final String
                 BLUETOOTH_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
 
         /**
@@ -7835,6 +7837,14 @@
         }
 
         /**
+         * Get the key that retrieves a bluetooth pbap client priority.
+         * @hide
+         */
+        public static final String getBluetoothPbapClientPriorityKey(String address) {
+            return BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+        }
+
+        /**
          * Get the key that retrieves a bluetooth map priority.
          * @hide
          */
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 085613f..356804e 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -152,6 +152,9 @@
         {"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
         {"en-VI", "en-US"}, // English (Virgin Islands)
 
+        // All English locales other than those falling back to en-US are mapped to en-GB.
+        {"en", "en-GB"},
+
         // For German, we're assuming the 1996 (and later) orthography by default.
         {"de", "de-1996"},
         // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
@@ -160,6 +163,9 @@
         // Norwegian is very probably Norwegian Bokmål.
         {"no", "nb"},
 
+        // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
+        {"mn", "mn-Cyrl"}, // Mongolian
+
         // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
         // Data is from CLDR's likelySubtags.xml.
         // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
@@ -182,15 +188,36 @@
 
         // TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
         String[] availableLanguages = {
+            "as",
+            "bn",
+            "cy",
+            "da",
             "de-1901", "de-1996", "de-CH-1901",
-            "en-US",
+            "en-GB", "en-US",
             "es",
+            "et",
+            "eu",
+            "fr",
+            "ga",
+            "gu",
+            "hi",
+            "hr",
             "hu",
             "hy",
+            "kn",
+            "ml",
+            "mn-Cyrl",
+            "mr",
             "nb",
             "nn",
+            "or",
+            "pa",
             "pt",
-            "und-Ethi"
+            "sl",
+            "ta",
+            "te",
+            "tk",
+            "und-Ethi",
         };
         for (int i = 0; i < availableLanguages.length; i++) {
             String languageTag = availableLanguages[i];
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a1e2e94..8e1609c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -116,14 +116,11 @@
      *  @param top The new top position
      *  @param right The new right position
      *  @param bottom The new bottom position
-     *  @param requestedWidth The new requested width
-     *  @param requestedHeight The new requested height
      *  @param deferTransactionUntilFrame Frame number from our parent (attached) to
      *  defer this action until.
      *  @param outFrame Rect in which is placed the new position/size on screen.
      */
     void repositionChild(IWindow childWindow, int left, int top, int right, int bottom,
-            int requestedWidth, int requestedHeight,
             long deferTransactionUntilFrame, out Rect outFrame);
 
     /*
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 477ffd9..8a8fb43 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -490,7 +490,7 @@
                               | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                               | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                               ;
-                if (!creating && !force && !mUpdateWindowNeeded) {
+                if (!creating && !force && !mUpdateWindowNeeded && !sizeChanged) {
                     mLayout.privateFlags |=
                             WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
                 } else {
@@ -584,18 +584,6 @@
 
                     mSurface.transferFrom(mNewSurface);
                     if (visible && mSurface.isValid()) {
-                        // We set SCALING_MODE_NO_SCALE_CROP to allow the WindowManager
-                        // to update our Surface crop without requiring a new buffer from
-                        // us. In the default mode of SCALING_MODE_FREEZE, surface geometry
-                        // state (which includes crop) is only applied when a buffer
-                        // with appropriate geometry is available. During drag resize
-                        // it is quite frequent that a matching buffer will not be available
-                        // (because we are constantly being resized and have fallen behind).
-                        // However in such situations the WindowManager still needs to be able
-                        // to update our crop to ensure we stay within the bounds of the containing
-                        // window.
-                        mSurface.setScalingMode(Surface.SCALING_MODE_NO_SCALE_CROP);
-
                         if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                             mSurfaceCreated = true;
                             mIsCreating = true;
@@ -666,7 +654,6 @@
                             mLocation[0], mLocation[1]));
                     mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
                             mLocation[0], mLocation[1],
-                            mWindowSpaceWidth, mWindowSpaceHeight,
                             -1, mWinFrame);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Exception from relayout", ex);
@@ -703,7 +690,6 @@
             }
             // Just using mRTLastReportedPosition as a dummy rect here
             session.repositionChild(window, left, top, right, bottom,
-                    mWindowSpaceWidth, mWindowSpaceHeight,
                     frameNumber,
                     mRTLastReportedPosition);
             // Now overwrite mRTLastReportedPosition with our values
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 83c6e9e..6811aed 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2429,7 +2429,11 @@
      *                     1             PFLAG3_SCROLL_INDICATOR_START
      *                    1              PFLAG3_SCROLL_INDICATOR_END
      *                   1               PFLAG3_ASSIST_BLOCKED
-     *            1111111                PFLAG3_POINTER_ICON_MASK
+     *                  1                PFLAG3_POINTER_ICON_NULL
+     *                 1                 PFLAG3_POINTER_ICON_VALUE_START
+     *           11111111                PFLAG3_POINTER_ICON_MASK
+     *          1                        PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE
+     *         1                         PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED
      * |-------|-------|-------|-------|
      */
 
@@ -2518,8 +2522,6 @@
      */
     static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
 
-    /* End of masks for mPrivateFlags3 */
-
     static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
 
     static final int SCROLL_INDICATORS_NONE = 0x0000;
@@ -2651,6 +2653,23 @@
     private static final int PFLAG3_POINTER_ICON_VALUE_START = 2 << PFLAG3_POINTER_ICON_LSHIFT;
 
     /**
+     * Whether this view has rendered elements that overlap (see {@link
+     * #hasOverlappingRendering()}, {@link #forceHasOverlappingRendering(boolean)}, and
+     * {@link #getHasOverlappingRendering()} ). The value in this bit is only valid when
+     * PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED has been set. Otherwise, the value is
+     * determined by whatever {@link #hasOverlappingRendering()} returns.
+     */
+    private static final int PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE = 0x800000;
+
+    /**
+     * Whether {@link #forceHasOverlappingRendering(boolean)} has been called. When true, value
+     * in PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE is valid.
+     */
+    private static final int PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED = 0x1000000;
+
+    /* End of masks for mPrivateFlags3 */
+
+    /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
      *
@@ -4516,6 +4535,12 @@
                         }
                     }
                     break;
+                case R.styleable.View_forceHasOverlappingRendering:
+                    if (a.peekValue(attr) != null) {
+                        forceHasOverlappingRendering(a.getBoolean(attr, true));
+                    }
+                    break;
+
             }
         }
 
@@ -12116,6 +12141,42 @@
     }
 
     /**
+     * Sets the behavior for overlapping rendering for this view (see {@link
+     * #hasOverlappingRendering()} for more details on this behavior). Calling this method
+     * is an alternative to overriding {@link #hasOverlappingRendering()} in a subclass,
+     * providing the value which is then used internally. That is, when {@link
+     * #forceHasOverlappingRendering(boolean)} is called, the value of {@link
+     * #hasOverlappingRendering()} is ignored and the value passed into this method is used
+     * instead.
+     *
+     * @param hasOverlappingRendering The value for overlapping rendering to be used internally
+     * instead of that returned by {@link #hasOverlappingRendering()}.
+     *
+     * @attr ref android.R.styleable#View_forceHasOverlappingRendering
+     */
+    public void forceHasOverlappingRendering(boolean hasOverlappingRendering) {
+        mPrivateFlags3 |= PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED;
+        if (hasOverlappingRendering) {
+            mPrivateFlags3 |= PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+        } else {
+            mPrivateFlags3 &= ~PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE;
+        }
+    }
+
+    /**
+     * Returns the value for overlapping rendering that is used internally. This is either
+     * the value passed into {@link #forceHasOverlappingRendering(boolean)}, if called, or
+     * the return value of {@link #hasOverlappingRendering()}, otherwise.
+     *
+     * @return The value for overlapping rendering being used internally.
+     */
+    public final boolean getHasOverlappingRendering() {
+        return (mPrivateFlags3 & PFLAG3_HAS_OVERLAPPING_RENDERING_FORCED) != 0 ?
+                (mPrivateFlags3 & PFLAG3_OVERLAPPING_RENDERING_FORCED_VALUE) != 0 :
+                hasOverlappingRendering();
+    }
+
+    /**
      * Returns whether this View has content which overlaps.
      *
      * <p>This function, intended to be overridden by specific View types, is an optimization when
@@ -12131,6 +12192,9 @@
      * necessitates that a View return true if it uses the methods internally without passing the
      * {@link Canvas#CLIP_TO_LAYER_SAVE_FLAG}.</p>
      *
+     * <p><strong>Note:</strong> The return value of this method is ignored if {@link
+     * #forceHasOverlappingRendering(boolean)} has been called on this view.</p>
+     *
      * @return true if the content in this view might overlap, false otherwise.
      */
     @ViewDebug.ExportedProperty(category = "drawing")
@@ -16566,7 +16630,7 @@
      */
     void setDisplayListProperties(RenderNode renderNode) {
         if (renderNode != null) {
-            renderNode.setHasOverlappingRendering(hasOverlappingRendering());
+            renderNode.setHasOverlappingRendering(getHasOverlappingRendering());
             renderNode.setClipToBounds(mParent instanceof ViewGroup
                     && ((ViewGroup) mParent).getClipChildren());
 
@@ -16855,9 +16919,14 @@
             } else {
                 // use layer paint to draw the bitmap, merging the two alphas, but also restore
                 int layerPaintAlpha = mLayerPaint != null ? mLayerPaint.getAlpha() : 255;
-                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
+                if (mLayerPaint == null && alpha < 1) {
+                    mLayerPaint = new Paint();
+                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
+                }
                 canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
-                mLayerPaint.setAlpha(layerPaintAlpha);
+                if (mLayerPaint != null) {
+                    mLayerPaint.setAlpha(layerPaintAlpha);
+                }
             }
         }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 816d9c4..f695873 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -6749,7 +6749,8 @@
     @Override
     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
             int dxUnconsumed, int dyUnconsumed) {
-        // Do nothing
+        // Re-dispatch up the tree by default
+        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
     }
 
     /**
@@ -6757,7 +6758,8 @@
      */
     @Override
     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
-        // Do nothing
+        // Re-dispatch up the tree by default
+        dispatchNestedPreScroll(dx, dy, consumed, null);
     }
 
     /**
@@ -6765,7 +6767,8 @@
      */
     @Override
     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
-        return false;
+        // Re-dispatch up the tree by default
+        return dispatchNestedFling(velocityX, velocityY, consumed);
     }
 
     /**
@@ -6773,7 +6776,8 @@
      */
     @Override
     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
-        return false;
+        // Re-dispatch up the tree by default
+        return dispatchNestedPreFling(velocityX, velocityY);
     }
 
     /**
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index f18b7ac..c5b8849 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -1113,13 +1113,6 @@
             if (mListener != null) {
                 mListener.onAnimationEnd(animation);
             }
-            if (mAnimatorOnEndMap != null) {
-                Runnable r = mAnimatorOnEndMap.get(animation);
-                if (r != null) {
-                    r.run();
-                }
-                mAnimatorOnEndMap.remove(animation);
-            }
             if (mAnimatorCleanupMap != null) {
                 Runnable r = mAnimatorCleanupMap.get(animation);
                 if (r != null) {
@@ -1127,6 +1120,13 @@
                 }
                 mAnimatorCleanupMap.remove(animation);
             }
+            if (mAnimatorOnEndMap != null) {
+                Runnable r = mAnimatorOnEndMap.get(animation);
+                if (r != null) {
+                    r.run();
+                }
+                mAnimatorOnEndMap.remove(animation);
+            }
             mAnimatorMap.remove(animation);
         }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fdf6979..5d4ee874 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -85,6 +85,7 @@
 import android.widget.Scroller;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.policy.PhoneFallbackEventHandler;
@@ -154,7 +155,12 @@
 
     static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
 
-    final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList();
+    /**
+     * This list must only be modified by the main thread, so a lock is only needed when changing
+     * the list or when accessing the list from a non-main thread.
+     */
+    @GuardedBy("mWindowCallbacks")
+    final ArrayList<WindowCallbacks> mWindowCallbacks = new ArrayList<>();
     final Context mContext;
     final IWindowSession mWindowSession;
     final Display mDisplay;
@@ -2120,7 +2126,7 @@
 
         boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
 
-        if (!cancelDraw) {
+        if (!cancelDraw && !newSurface) {
             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
                     mPendingTransitions.get(i).startChangingAnimations();
@@ -2169,7 +2175,6 @@
             }
         }
     }
-
     private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
         Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
         try {
@@ -2437,10 +2442,8 @@
     @Override
     public void onHardwarePostDraw(DisplayListCanvas canvas) {
         drawAccessibilityFocusedDrawableIfNeeded(canvas);
-        synchronized (mWindowCallbacks) {
-            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                mWindowCallbacks.get(i).onPostDraw(canvas);
-            }
+        for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+            mWindowCallbacks.get(i).onPostDraw(canvas);
         }
     }
 
@@ -7092,11 +7095,9 @@
             Rect stableInsets, int resizeMode) {
         if (!mDragResizing) {
             mDragResizing = true;
-            synchronized (mWindowCallbacks) {
-                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                    mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds, fullscreen,
-                            systemInsets, stableInsets, resizeMode);
-                }
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds, fullscreen,
+                        systemInsets, stableInsets, resizeMode);
             }
             mFullRedrawNeeded = true;
         }
@@ -7108,10 +7109,8 @@
     private void endDragResizing() {
         if (mDragResizing) {
             mDragResizing = false;
-            synchronized (mWindowCallbacks) {
-                for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                    mWindowCallbacks.get(i).onWindowDragResizeEnd();
-                }
+            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+                mWindowCallbacks.get(i).onWindowDragResizeEnd();
             }
             mFullRedrawNeeded = true;
         }
@@ -7119,13 +7118,11 @@
 
     private boolean updateContentDrawBounds() {
         boolean updated = false;
-        synchronized (mWindowCallbacks) {
-            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                updated |= mWindowCallbacks.get(i).onContentDrawn(
-                        mWindowAttributes.surfaceInsets.left,
-                        mWindowAttributes.surfaceInsets.top,
-                        mWidth, mHeight);
-            }
+        for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+            updated |= mWindowCallbacks.get(i).onContentDrawn(
+                    mWindowAttributes.surfaceInsets.left,
+                    mWindowAttributes.surfaceInsets.top,
+                    mWidth, mHeight);
         }
         return updated | (mDragResizing && mReportNextDraw);
     }
@@ -7134,10 +7131,8 @@
         if (mReportNextDraw) {
             mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
         }
-        synchronized (mWindowCallbacks) {
-            for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
-                mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
-            }
+        for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
+            mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
         }
     }
 
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 929fdac..750931a 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -91,6 +91,7 @@
         mWindowDecorInsetsConsumed = src.mWindowDecorInsetsConsumed;
         mStableInsetsConsumed = src.mStableInsetsConsumed;
         mIsRound = src.mIsRound;
+        mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
     }
 
     /** @hide */
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index c22d60d..3ad730b 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -157,6 +158,15 @@
     public abstract void setMagnificationSpec(MagnificationSpec spec);
 
     /**
+     * Obtains the magnified and available regions.
+     *
+     * @param outMagnified the currently magnified region
+     * @param outAvailable the region available for magnification
+     */
+    public abstract void getMagnificationRegions(@NonNull Region outMagnified,
+            @NonNull Region outAvailable);
+
+    /**
      * Gets the magnification and translation applied to a window given its token.
      * Not all windows are magnified and the window manager policy determines which
      * windows are magnified. The returned result also takes into account the compat
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index bdaf291..1482111 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2926,8 +2926,10 @@
         mInputType = other.mInputType;
         mLiveRegion = other.mLiveRegion;
         mDrawingOrderInParent = other.mDrawingOrderInParent;
-        if (other.mExtras != null && !other.mExtras.isEmpty()) {
-            getExtras().putAll(other.mExtras);
+        if (other.mExtras != null) {
+            mExtras = new Bundle(other.mExtras);
+        } else {
+            mExtras = null;
         }
         mRangeInfo = (other.mRangeInfo != null)
                 ? RangeInfo.obtain(other.mRangeInfo) : null;
@@ -3006,7 +3008,9 @@
         mDrawingOrderInParent = parcel.readInt();
 
         if (parcel.readInt() == 1) {
-            getExtras().putAll(parcel.readBundle());
+            mExtras = parcel.readBundle();
+        } else {
+            mExtras = null;
         }
 
         if (parcel.readInt() == 1) {
@@ -3073,9 +3077,7 @@
         mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
         mInputType = InputType.TYPE_NULL;
         mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
-        if (mExtras != null) {
-            mExtras.clear();
-        }
+        mExtras = null;
         if (mRangeInfo != null) {
             mRangeInfo.recycle();
             mRangeInfo = null;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index f1bf890..d884f19 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -128,11 +128,6 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
-    // TODO (gsennton) remove when committing webview xts test change
-    public static String getWebViewPackageName() {
-        return null;
-    }
-
     /**
      * @hide
      */
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index f16fdd6..00f368e 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -325,16 +325,24 @@
 
         if (getChildCount() > 0) {
             final View child = getChildAt(0);
-            int width = getMeasuredWidth();
-            if (child.getMeasuredWidth() < width) {
-                final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int widthPadding;
+            final int heightPadding;
+            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+            if (targetSdkVersion >= Build.VERSION_CODES.M) {
+                widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
+                heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
+            } else {
+                widthPadding = mPaddingLeft + mPaddingRight;
+                heightPadding = mPaddingTop + mPaddingBottom;
+            }
 
-                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
-                        + mPaddingBottom, lp.height);
-                width -= mPaddingLeft;
-                width -= mPaddingRight;
-                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-
+            int desiredWidth = getMeasuredWidth() - widthPadding;
+            if (child.getMeasuredWidth() < desiredWidth) {
+                final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        desiredWidth, MeasureSpec.EXACTLY);
+                final int childHeightMeasureSpec = getChildMeasureSpec(
+                        heightMeasureSpec, heightPadding, lp.height);
                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
             }
         }
@@ -1235,17 +1243,17 @@
     }
 
     @Override
-    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+    protected void measureChild(View child, int parentWidthMeasureSpec,
+            int parentHeightMeasureSpec) {
         ViewGroup.LayoutParams lp = child.getLayoutParams();
 
-        int childWidthMeasureSpec;
-        int childHeightMeasureSpec;
+        final int horizontalPadding = mPaddingLeft + mPaddingRight;
+        final int childWidthMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
+                Math.max(0, MeasureSpec.getSize(parentWidthMeasureSpec) - horizontalPadding),
+                MeasureSpec.UNSPECIFIED);
 
-        childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
-                + mPaddingBottom, lp.height);
-
-        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                mPaddingTop + mPaddingBottom, lp.height);
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
 
@@ -1257,8 +1265,11 @@
         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                         + heightUsed, lp.height);
-        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+        final int usedTotal = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin +
+                widthUsed;
+        final int childWidthMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
+                Math.max(0, MeasureSpec.getSize(parentWidthMeasureSpec) - usedTotal),
+                MeasureSpec.UNSPECIFIED);
 
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 18687c9..92631da 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -173,9 +173,6 @@
     private int mHeight = LayoutParams.WRAP_CONTENT;
     private int mLastHeight;
 
-    private int mPopupWidth;
-    private int mPopupHeight;
-
     private float mElevation;
 
     private Drawable mBackground;
@@ -1298,8 +1295,6 @@
 
         mPopupViewInitialLayoutDirectionInherited =
                 (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
-        mPopupWidth = p.width;
-        mPopupHeight = p.height;
     }
 
     /**
@@ -2006,7 +2001,7 @@
      * @param height the new height, must be >= 0 or -1 to ignore
      */
     public void update(View anchor, int width, int height) {
-        update(anchor, false, 0, 0, true, width, height);
+        update(anchor, false, 0, 0, width, height);
     }
 
     /**
@@ -2026,11 +2021,11 @@
      * @param height the new height, must be >= 0 or -1 to ignore
      */
     public void update(View anchor, int xoff, int yoff, int width, int height) {
-        update(anchor, true, xoff, yoff, true, width, height);
+        update(anchor, true, xoff, yoff, width, height);
     }
 
     private void update(View anchor, boolean updateLocation, int xoff, int yoff,
-            boolean updateDimension, int width, int height) {
+            int width, int height) {
 
         if (!isShowing() || mContentView == null) {
             return;
@@ -2055,13 +2050,13 @@
         final int oldX = p.x;
         final int oldY = p.y;
 
-        if (updateDimension) {
-            if (width == -1) {
-                width = mPopupWidth;
-            }
-            if (height == -1) {
-                height = mPopupHeight;
-            }
+        // If an explicit width/height has not specified, use the most recent
+        // explicitly specified value (either from setWidth/Height or update).
+        if (width == -1) {
+            width = mWidth;
+        }
+        if (height == -1) {
+            height = mHeight;
         }
 
         final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3f7a07b..0555cd4 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -350,24 +350,24 @@
 
         if (getChildCount() > 0) {
             final View child = getChildAt(0);
-            final int height = getMeasuredHeight();
-            if (child.getMeasuredHeight() < height) {
-                final int widthPadding;
-                final int heightPadding;
-                final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
-                if (targetSdkVersion >= VERSION_CODES.M) {
-                    widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
-                    heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
-                } else {
-                    widthPadding = mPaddingLeft + mPaddingRight;
-                    heightPadding = mPaddingTop + mPaddingBottom;
-                }
+            final int widthPadding;
+            final int heightPadding;
+            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (targetSdkVersion >= VERSION_CODES.M) {
+                widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
+                heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
+            } else {
+                widthPadding = mPaddingLeft + mPaddingRight;
+                heightPadding = mPaddingTop + mPaddingBottom;
+            }
 
+            final int desiredHeight = getMeasuredHeight() - heightPadding;
+            if (child.getMeasuredHeight() < desiredHeight) {
                 final int childWidthMeasureSpec = getChildMeasureSpec(
                         widthMeasureSpec, widthPadding, lp.width);
                 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        height - heightPadding, MeasureSpec.EXACTLY);
+                        desiredHeight, MeasureSpec.EXACTLY);
                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
             }
         }
@@ -1268,9 +1268,10 @@
 
         childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
                 + mPaddingRight, lp.width);
-
+        final int verticalPadding = mPaddingTop + mPaddingBottom;
         childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
-                MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
+                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
+                MeasureSpec.UNSPECIFIED);
 
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
@@ -1283,8 +1284,11 @@
         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                         + widthUsed, lp.width);
+        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
+                heightUsed;
         final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
-                MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
+                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
+                MeasureSpec.UNSPECIFIED);
 
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 43cf5a1..ee716df 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -16,6 +16,9 @@
 
 package android.widget;
 
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -44,14 +47,13 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
-import com.android.internal.R;
-import com.android.internal.widget.ExploreByTouchHelper;
-
 import java.text.NumberFormat;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Locale;
 
+import libcore.icu.LocaleData;
+
 /**
  * A calendar-like view displaying a specified month and the appropriate selectable day numbers
  * within the specified month.
@@ -66,7 +68,6 @@
     private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
 
     private static final String MONTH_YEAR_FORMAT = "MMMMy";
-    private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
 
     private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0;
 
@@ -80,6 +81,7 @@
     private final Paint mDayHighlightPaint = new Paint();
     private final Paint mDayHighlightSelectorPaint = new Paint();
 
+    /** Array of single-character weekday labels ordered by column index. */
     private final String[] mDayOfWeekLabels = new String[7];
 
     private final Calendar mCalendar;
@@ -120,7 +122,7 @@
      */
     private int mToday = DEFAULT_SELECTED_DAY;
 
-    /** The first day of the week (ex. Calendar.SUNDAY). */
+    /** The first day of the week (ex. Calendar.SUNDAY) indexed from one. */
     private int mWeekStart = DEFAULT_WEEK_START;
 
     /** The number of days (ex. 28) in the current month. */
@@ -199,13 +201,11 @@
             Log.d(LOG_TAG, "mWeekStart => " + mWeekStart);
         }
 
-        final Calendar calendar = Calendar.getInstance(mLocale);
-        calendar.setFirstDayOfWeek(mWeekStart);
-
-        final SimpleDateFormat formatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, mLocale);
-        for (int i = 0; i < 7; i++) {
-            calendar.set(Calendar.DAY_OF_WEEK, i);
-            mDayOfWeekLabels[i] = formatter.format(calendar.getTime());
+        // Use tiny (e.g. single-character) weekday names from ICU. The indices
+        // for this list correspond to Calendar days, e.g. SUNDAY is index 1.
+        final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames;
+        for (int i = 0; i < DAYS_IN_WEEK; i++) {
+            mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1];
         }
 
         if (DEBUG_WRONG_DATE) {
@@ -662,8 +662,7 @@
                 colCenterRtl = colCenter;
             }
 
-            final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
-            final String label = mDayOfWeekLabels[dayOfWeek];
+            final String label = mDayOfWeekLabels[col];
             canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
         }
     }
@@ -813,6 +812,11 @@
      */
     void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
             int enabledDayEnd) {
+        if (DEBUG_WRONG_DATE) {
+            Log.d(LOG_TAG, "setMonthParams(" + selectedDay + ", " + month + ", " + year + ", "
+                    + weekStart + ", " + enabledDayStart + ", " + enabledDayEnd + ")");
+        }
+
         mActivatedDay = selectedDay;
 
         if (isValidMonth(month)) {
@@ -849,6 +853,14 @@
         mTouchHelper.invalidateRoot();
 
         updateMonthYearLabel();
+
+        if (DEBUG_WRONG_DATE) {
+            Log.d(LOG_TAG, "mMonth = " + mMonth);
+            Log.d(LOG_TAG, "mDayOfWeekStart = " + mDayOfWeekStart);
+            Log.d(LOG_TAG, "mWeekStart = " + mWeekStart);
+            Log.d(LOG_TAG, "mDaysInMonth = " + mDaysInMonth);
+            Log.d(LOG_TAG, "mToday = " + mToday);
+        }
     }
 
     private static int getDaysInMonth(int month, int year) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3195097..4a68d3c 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
 import android.R;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
@@ -3115,10 +3116,15 @@
     }
 
     /**
+     * Returns the font feature settings. The format is the same as the CSS
+     * font-feature-settings attribute:
+     * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
+     *     http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
+     *
      * @return the currently set font feature settings.  Default is null.
      *
      * @see #setFontFeatureSettings(String)
-     * @see Paint#setFontFeatureSettings
+     * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
      */
     @Nullable
     public String getFontFeatureSettings() {
@@ -3182,13 +3188,15 @@
     }
 
     /**
-     * Sets font feature settings.  The format is the same as the CSS
+     * Sets font feature settings. The format is the same as the CSS
      * font-feature-settings attribute:
-     * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
+     * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
+     *     http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
      *
      * @param fontFeatureSettings font feature settings represented as CSS compatible string
+     *
      * @see #getFontFeatureSettings()
-     * @see Paint#getFontFeatureSettings
+     * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
      *
      * @attr ref android.R.styleable#TextView_fontFeatureSettings
      */
@@ -3403,17 +3411,10 @@
     }
 
     /**
-     * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
-     * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
-     * following is true:
-     * <ul>
-     * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
-     * {@link #setText} or {@link #append}.
-     * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
-     * </ul>
-     *
-     * <p>This function does not have an immediate effect, movement method will be set only after a
-     * call to {@link #setText} or {@link #append}. The default is true.</p>
+     * Sets whether the movement method will automatically be set to
+     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+     * set to nonzero and links are detected in {@link #setText}.
+     * The default is true.
      *
      * @attr ref android.R.styleable#TextView_linksClickable
      */
@@ -3423,14 +3424,10 @@
     }
 
     /**
-     * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
-     * after {@link #setText} or {@link #append} is called.
-     *
-     * See {@link #setLinksClickable} for details.
-     *
-     * <p>The default is true.</p>
-     *
-     * @see #setLinksClickable
+     * Returns whether the movement method will automatically be set to
+     * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
+     * set to nonzero and links are detected in {@link #setText}.
+     * The default is true.
      *
      * @attr ref android.R.styleable#TextView_linksClickable
      */
@@ -4024,19 +4021,13 @@
 
         ((Editable) mText).append(text, start, end);
 
-        boolean hasClickableSpans = false;
         if (mAutoLinkMask != 0) {
-            hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
-        } else if (mLinksClickable && text instanceof Spanned) {
-            ClickableSpan[] clickableSpans =
-                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
-            hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
-        }
-
-        // Do not change the movement method for text that supports text selection as it
-        // would prevent an arbitrary cursor displacement.
-        if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
-            setMovementMethod(LinkMovementMethod.getInstance());
+            boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+            // Do not change the movement method for text that support text selection as it
+            // would prevent an arbitrary cursor displacement.
+            if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
+                setMovementMethod(LinkMovementMethod.getInstance());
+            }
         }
     }
 
@@ -4389,7 +4380,6 @@
             text = TextUtils.stringOrSpannedString(text);
         }
 
-        boolean hasClickableSpans = false;
         if (mAutoLinkMask != 0) {
             Spannable s2;
 
@@ -4399,32 +4389,22 @@
                 s2 = mSpannableFactory.newSpannable(text);
             }
 
-            hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
-            if (hasClickableSpans) {
+            if (Linkify.addLinks(s2, mAutoLinkMask)) {
                 text = s2;
-            }
-        } else if (mLinksClickable && text instanceof Spanned) {
-            ClickableSpan[] clickableSpans =
-                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
-            hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
-            if (hasClickableSpans && !(text instanceof Spannable)) {
-                text = mSpannableFactory.newSpannable(text);
-            }
-        }
+                type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
 
-        if (hasClickableSpans) {
-            type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
-            /*
-             * We must go ahead and set the text before changing the
-             * movement method, because setMovementMethod() may call
-             * setText() again to try to upgrade the buffer type.
-             */
-            mText = text;
+                /*
+                 * We must go ahead and set the text before changing the
+                 * movement method, because setMovementMethod() may call
+                 * setText() again to try to upgrade the buffer type.
+                 */
+                mText = text;
 
-            // Do not change the movement method for text that supports text selection as it
-            // would prevent an arbitrary cursor displacement.
-            if (mLinksClickable && !textCanBeSelected()) {
-                setMovementMethod(LinkMovementMethod.getInstance());
+                // Do not change the movement method for text that support text selection as it
+                // would prevent an arbitrary cursor displacement.
+                if (mLinksClickable && !textCanBeSelected()) {
+                    setMovementMethod(LinkMovementMethod.getInstance());
+                }
             }
         }
 
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 06daf61..5b0a90a 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -106,6 +106,8 @@
  * @attr ref android.R.styleable#Toolbar_contentInsetLeft
  * @attr ref android.R.styleable#Toolbar_contentInsetRight
  * @attr ref android.R.styleable#Toolbar_contentInsetStart
+ * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+ * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
  * @attr ref android.R.styleable#Toolbar_gravity
  * @attr ref android.R.styleable#Toolbar_logo
  * @attr ref android.R.styleable#Toolbar_logoDescription
@@ -159,6 +161,8 @@
     private int mTitleMarginBottom;
 
     private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
+    private int mContentInsetStartWithNavigation;
+    private int mContentInsetEndWithActions;
 
     private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
 
@@ -272,6 +276,11 @@
             mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
         }
 
+        mContentInsetStartWithNavigation = a.getDimensionPixelOffset(
+                R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED);
+        mContentInsetEndWithActions = a.getDimensionPixelOffset(
+                R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED);
+
         mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
         mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
 
@@ -1055,7 +1064,7 @@
     }
 
     /**
-     * Set the content insets for this toolbar relative to layout direction.
+     * Sets the content insets for this toolbar relative to layout direction.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1069,13 +1078,15 @@
      * @see #getContentInsetEnd()
      * @see #getContentInsetLeft()
      * @see #getContentInsetRight()
+     * @attr ref android.R.styleable#Toolbar_contentInsetEnd
+     * @attr ref android.R.styleable#Toolbar_contentInsetStart
      */
     public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
         mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
     }
 
     /**
-     * Get the starting content inset for this toolbar.
+     * Gets the starting content inset for this toolbar.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1088,13 +1099,14 @@
      * @see #getContentInsetEnd()
      * @see #getContentInsetLeft()
      * @see #getContentInsetRight()
+     * @attr ref android.R.styleable#Toolbar_contentInsetStart
      */
     public int getContentInsetStart() {
         return mContentInsets.getStart();
     }
 
     /**
-     * Get the ending content inset for this toolbar.
+     * Gets the ending content inset for this toolbar.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1107,13 +1119,14 @@
      * @see #getContentInsetStart()
      * @see #getContentInsetLeft()
      * @see #getContentInsetRight()
+     * @attr ref android.R.styleable#Toolbar_contentInsetEnd
      */
     public int getContentInsetEnd() {
         return mContentInsets.getEnd();
     }
 
     /**
-     * Set the content insets for this toolbar.
+     * Sets the content insets for this toolbar.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1127,13 +1140,15 @@
      * @see #getContentInsetEnd()
      * @see #getContentInsetLeft()
      * @see #getContentInsetRight()
+     * @attr ref android.R.styleable#Toolbar_contentInsetLeft
+     * @attr ref android.R.styleable#Toolbar_contentInsetRight
      */
     public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
         mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
     }
 
     /**
-     * Get the left content inset for this toolbar.
+     * Gets the left content inset for this toolbar.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1146,13 +1161,14 @@
      * @see #getContentInsetStart()
      * @see #getContentInsetEnd()
      * @see #getContentInsetRight()
+     * @attr ref android.R.styleable#Toolbar_contentInsetLeft
      */
     public int getContentInsetLeft() {
         return mContentInsets.getLeft();
     }
 
     /**
-     * Get the right content inset for this toolbar.
+     * Gets the right content inset for this toolbar.
      *
      * <p>The content inset affects the valid area for Toolbar content other than
      * the navigation button and menu. Insets define the minimum margin for these components
@@ -1165,11 +1181,160 @@
      * @see #getContentInsetStart()
      * @see #getContentInsetEnd()
      * @see #getContentInsetLeft()
+     * @attr ref android.R.styleable#Toolbar_contentInsetRight
      */
     public int getContentInsetRight() {
         return mContentInsets.getRight();
     }
 
+    /**
+     * Gets the start content inset to use when a navigation button is present.
+     *
+     * <p>Different content insets are often called for when additional buttons are present
+     * in the toolbar, as well as at different toolbar sizes. The larger value of
+     * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+     *
+     * @return the start content inset used when a navigation icon has been set in pixels
+     *
+     * @see #setContentInsetStartWithNavigation(int)
+     * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+     */
+    public int getContentInsetStartWithNavigation() {
+        return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED
+                ? mContentInsetStartWithNavigation
+                : getContentInsetStart();
+    }
+
+    /**
+     * Sets the start content inset to use when a navigation button is present.
+     *
+     * <p>Different content insets are often called for when additional buttons are present
+     * in the toolbar, as well as at different toolbar sizes. The larger value of
+     * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+     *
+     * @param insetStartWithNavigation the inset to use when a navigation icon has been set
+     *                                 in pixels
+     *
+     * @see #getContentInsetStartWithNavigation()
+     * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation
+     */
+    public void setContentInsetStartWithNavigation(int insetStartWithNavigation) {
+        if (insetStartWithNavigation < 0) {
+            insetStartWithNavigation = RtlSpacingHelper.UNDEFINED;
+        }
+        if (insetStartWithNavigation != mContentInsetStartWithNavigation) {
+            mContentInsetStartWithNavigation = insetStartWithNavigation;
+            if (getNavigationIcon() != null) {
+                requestLayout();
+            }
+        }
+    }
+
+    /**
+     * Gets the end content inset to use when action buttons are present.
+     *
+     * <p>Different content insets are often called for when additional buttons are present
+     * in the toolbar, as well as at different toolbar sizes. The larger value of
+     * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+     *
+     * @return the end content inset used when a menu has been set in pixels
+     *
+     * @see #setContentInsetEndWithActions(int)
+     * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
+     */
+    public int getContentInsetEndWithActions() {
+        return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED
+                ? mContentInsetEndWithActions
+                : getContentInsetEnd();
+    }
+
+    /**
+     * Sets the start content inset to use when action buttons are present.
+     *
+     * <p>Different content insets are often called for when additional buttons are present
+     * in the toolbar, as well as at different toolbar sizes. The larger value of
+     * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+     *
+     * @param insetEndWithActions the inset to use when a menu has been set in pixels
+     *
+     * @see #setContentInsetEndWithActions(int)
+     * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions
+     */
+    public void setContentInsetEndWithActions(int insetEndWithActions) {
+        if (insetEndWithActions < 0) {
+            insetEndWithActions = RtlSpacingHelper.UNDEFINED;
+        }
+        if (insetEndWithActions != mContentInsetEndWithActions) {
+            mContentInsetEndWithActions = insetEndWithActions;
+            if (getNavigationIcon() != null) {
+                requestLayout();
+            }
+        }
+    }
+
+    /**
+     * Gets the content inset that will be used on the starting side of the bar in the current
+     * toolbar configuration.
+     *
+     * @return the current content inset start in pixels
+     *
+     * @see #getContentInsetStartWithNavigation()
+     */
+    public int getCurrentContentInsetStart() {
+        return getNavigationIcon() != null
+                ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))
+                : getContentInsetStart();
+    }
+
+    /**
+     * Gets the content inset that will be used on the ending side of the bar in the current
+     * toolbar configuration.
+     *
+     * @return the current content inset end in pixels
+     *
+     * @see #getContentInsetEndWithActions()
+     */
+    public int getCurrentContentInsetEnd() {
+        boolean hasActions = false;
+        if (mMenuView != null) {
+            final MenuBuilder mb = mMenuView.peekMenu();
+            hasActions = mb != null && mb.hasVisibleItems();
+        }
+        return hasActions
+                ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0))
+                : getContentInsetEnd();
+    }
+
+    /**
+     * Gets the content inset that will be used on the left side of the bar in the current
+     * toolbar configuration.
+     *
+     * @return the current content inset left in pixels
+     *
+     * @see #getContentInsetStartWithNavigation()
+     * @see #getContentInsetEndWithActions()
+     */
+    public int getCurrentContentInsetLeft() {
+        return isLayoutRtl()
+                ? getCurrentContentInsetEnd()
+                : getCurrentContentInsetStart();
+    }
+
+    /**
+     * Gets the content inset that will be used on the right side of the bar in the current
+     * toolbar configuration.
+     *
+     * @return the current content inset right in pixels
+     *
+     * @see #getContentInsetStartWithNavigation()
+     * @see #getContentInsetEndWithActions()
+     */
+    public int getCurrentContentInsetRight() {
+        return isLayoutRtl()
+                ? getCurrentContentInsetStart()
+                : getCurrentContentInsetEnd();
+    }
+
     private void ensureNavButtonView() {
         if (mNavButtonView == null) {
             mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
@@ -1406,7 +1571,7 @@
             childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
         }
 
-        final int contentInsetStart = getContentInsetStart();
+        final int contentInsetStart = getCurrentContentInsetStart();
         width += Math.max(contentInsetStart, navWidth);
         collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
 
@@ -1420,7 +1585,7 @@
             childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
         }
 
-        final int contentInsetEnd = getContentInsetEnd();
+        final int contentInsetEnd = getCurrentContentInsetEnd();
         width += Math.max(contentInsetEnd, menuWidth);
         collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
 
@@ -1543,10 +1708,12 @@
             }
         }
 
-        collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
-        collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
-        left = Math.max(left, getContentInsetLeft());
-        right = Math.min(right, width - paddingRight - getContentInsetRight());
+        final int contentInsetLeft = getCurrentContentInsetLeft();
+        final int contentInsetRight = getCurrentContentInsetRight();
+        collapsingMargins[0] = Math.max(0, contentInsetLeft - left);
+        collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right));
+        left = Math.max(left, contentInsetLeft);
+        right = Math.min(right, width - paddingRight - contentInsetRight);
 
         if (shouldLayout(mExpandedActionView)) {
             if (isRtl) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 5726de1..0e02ed6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -393,12 +393,7 @@
         final DisplayResolveInfo dri = mAdapter.getOtherProfile();
         if (dri != null) {
             mProfileView.setVisibility(View.VISIBLE);
-            final ImageView icon = (ImageView) mProfileView.findViewById(R.id.icon);
-            final TextView text = (TextView) mProfileView.findViewById(R.id.text1);
-            if (!dri.hasDisplayIcon()) {
-                new LoadIconIntoViewTask(dri, icon).execute();
-            }
-            icon.setImageDrawable(dri.getDisplayIcon());
+            final TextView text = (TextView) mProfileView.findViewById(R.id.profile_button);
             text.setText(dri.getDisplayLabel());
         } else {
             mProfileView.setVisibility(View.GONE);
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 10027b6..5e8f4a2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -27,7 +27,6 @@
 import android.content.pm.PackageInfo;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.os.SELinux;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructStat;
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 9904893..ea0fbda 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -183,6 +183,7 @@
     private boolean mLastHasBottomStableInset = false;
     private boolean mLastHasRightStableInset = false;
     private int mLastWindowFlags = 0;
+    private boolean mLastShouldAlwaysConsumeNavBar = false;
 
     private int mRootScrollY = 0;
 
@@ -208,6 +209,7 @@
     private Drawable mResizingBackgroundDrawable;
     private Drawable mCaptionBackgroundDrawable;
     private Drawable mUserCaptionBackgroundDrawable;
+    private Drawable mOriginalBackgroundDrawable;
 
     private float mAvailableWidth;
 
@@ -887,6 +889,11 @@
                 mBackgroundPadding.setEmpty();
             }
             drawableChanged();
+
+            // Make sure we don't reset to the old drawable when finishing resizing.
+            if (mResizeMode != RESIZE_MODE_INVALID) {
+                mOriginalBackgroundDrawable = null;
+            }
         }
     }
 
@@ -996,6 +1003,7 @@
                 boolean hasRightStableInset = insets.getStableInsetRight() != 0;
                 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
                 mLastHasRightStableInset = hasRightStableInset;
+                mLastShouldAlwaysConsumeNavBar = insets.shouldAlwaysConsumeNavBar();
             }
 
             boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
@@ -1016,12 +1024,11 @@
         // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
         // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
         // explicitly asked for it.
-
         boolean consumingNavBar =
                 (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
-                || (insets != null && insets.shouldAlwaysConsumeNavBar());
+                || mLastShouldAlwaysConsumeNavBar;
 
         // If we didn't request fullscreen layout, but we still got it because of the
         // mForceWindowDrawsStatusBarBackground flag, also consume top inset.
@@ -1949,6 +1956,9 @@
             updateElevation();
 
             updateColorViews(null /* insets */, false);
+
+            mOriginalBackgroundDrawable = getBackground();
+            setBackgroundDrawable(null);
         }
         mResizeMode = resizeMode;
         getViewRootImpl().requestInvalidateRootRenderNode();
@@ -1960,6 +1970,10 @@
         updateColorViews(null /* insets */, false);
         mResizeMode = RESIZE_MODE_INVALID;
         getViewRootImpl().requestInvalidateRootRenderNode();
+        if (mOriginalBackgroundDrawable != null) {
+            setBackgroundDrawable(mOriginalBackgroundDrawable);
+            mOriginalBackgroundDrawable = null;
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
index 526e2ae..9e2cbdb 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -45,7 +45,8 @@
     private static float[] createLUT(TimeInterpolator interpolator, long duration) {
         long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
         int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
-        int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+        // We need 2 frame values as the minimal.
+        int numAnimFrames = Math.max(2, (int) Math.ceil(((double) duration) / animIntervalMs));
         float values[] = new float[numAnimFrames];
         float lastFrame = numAnimFrames - 1;
         for (int i = 0; i < numAnimFrames; i++) {
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index bd97e5d..4738f5e 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -346,16 +346,6 @@
             }
             return false;
         }
-
-        @Override
-        protected boolean onForwardingStopped() {
-            final ShowableListMenu popup = getPopup();
-            if (popup != null) {
-                popup.dismiss();
-                return true;
-            }
-            return false;
-        }
     }
 
     public static abstract class PopupCallback {
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 31b2f96..df57639 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -65,7 +65,6 @@
 
     private final Context mContext;
     private final Resources mResources;
-    private final boolean mShowCascadingMenus;
 
     /**
      * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
@@ -188,9 +187,6 @@
     public MenuBuilder(Context context) {
         mContext = context;
         mResources = context.getResources();
-        mShowCascadingMenus = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus);
-        
         mItems = new ArrayList<MenuItemImpl>();
         
         mVisibleItems = new ArrayList<MenuItemImpl>();
@@ -915,10 +911,6 @@
                 close(true /* closeAllMenus */);
             }
         } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
-            if (!mShowCascadingMenus) {
-                close(false /* closeAllMenus */);
-            }
-
             if (!itemImpl.hasSubMenu()) {
                 itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
             }
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index 2cb224e..8ced36f 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -240,7 +240,10 @@
             mTreeObserver = null;
         }
         mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener);
-        mOnDismissListener.onDismiss();
+
+        if (mOnDismissListener != null) {
+            mOnDismissListener.onDismiss();
+        }
     }
 
     @Override
@@ -265,6 +268,13 @@
             subPopup.setPresenterCallback(mPresenterCallback);
             subPopup.setForceShowIcon(mAdapter.getForceShowIcon());
 
+            // Pass responsibility for handling onDismiss to the submenu.
+            subPopup.setOnDismissListener(mOnDismissListener);
+            mOnDismissListener = null;
+
+            // Close this menu popup to make room for the submenu popup.
+            dismiss();
+
             // Show the new sub-menu popup at the same location as this popup.
             if (subPopup.tryShow(mXOffset, mYOffset)) {
                 if (mPresenterCallback != null) {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index e39bb1c..90f407f 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -36,15 +36,17 @@
 
 #include "core_jni_helpers.h"
 
-static struct {
+namespace {
+
+using namespace android;
+
+struct {
     jclass clazz;
     jmethodID dispatchSensorEvent;
     jmethodID dispatchFlushCompleteEvent;
     jmethodID dispatchAdditionalInfoEvent;
 } gBaseEventQueueClassInfo;
 
-namespace android {
-
 struct SensorOffsets
 {
     jclass      clazz;
@@ -72,9 +74,9 @@
 } gListOffsets;
 
 /*
- * The method below are not thread-safe and not intended to be
+ * nativeClassInit is not inteneded to be thread-safe. It should be called before other native...
+ * functions (except nativeCreate).
  */
-
 static void
 nativeClassInit (JNIEnv *_env, jclass _this)
 {
@@ -494,9 +496,7 @@
             (void*)nativeInjectSensorData },
 };
 
-}; // namespace android
-
-using namespace android;
+} //unnamed namespace
 
 int register_android_hardware_SensorManager(JNIEnv *env)
 {
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 41726fb..d12c8ba 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -25,63 +25,48 @@
         android:maxCollapsedHeightSmall="56dp"
         android:id="@id/contentPanel">
 
-    <LinearLayout
+    <RelativeLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alwaysShow="true"
             android:elevation="8dp"
             android:paddingStart="16dp"
             android:background="@color/white" >
+        <TextView android:id="@+id/profile_button"
+                  android:layout_width="wrap_content"
+                  android:layout_height="48dp"
+                  android:layout_marginEnd="8dp"
+                  android:paddingStart="8dp"
+                  android:paddingEnd="8dp"
+                  android:visibility="gone"
+                  style="?attr/borderlessButtonStyle"
+                  android:textAppearance="?attr/textAppearanceButton"
+                  android:textColor="@color/material_deep_teal_500"
+                  android:gravity="center_vertical"
+                  android:layout_alignParentTop="true"
+                  android:layout_alignParentRight="true"
+                  android:singleLine="true"/>
         <ImageView android:id="@+id/title_icon"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
-                   android:layout_gravity="start|center_vertical"
                    android:layout_marginEnd="16dp"
                    android:visibility="gone"
-                   android:scaleType="fitCenter" />
+                   android:scaleType="fitCenter"
+                   android:layout_below="@id/profile_button"
+                   android:layout_alignParentLeft="true"
+                   />
         <TextView android:id="@+id/title"
-                  android:layout_width="0dp"
                   android:layout_height="wrap_content"
-                  android:layout_weight="1"
+                  android:layout_width="wrap_content"
                   android:textAppearance="?attr/textAppearanceMedium"
                   android:textSize="14sp"
                   android:gravity="start|center_vertical"
                   android:paddingEnd="?attr/dialogPreferredPadding"
                   android:paddingTop="12dp"
-                  android:paddingBottom="12dp" />
-        <LinearLayout android:id="@+id/profile_button"
-                      android:layout_width="wrap_content"
-                      android:layout_height="48dp"
-                      android:layout_marginTop="4dp"
-                      android:layout_marginEnd="4dp"
-                      android:paddingStart="8dp"
-                      android:paddingEnd="8dp"
-                      android:paddingTop="4dp"
-                      android:paddingBottom="4dp"
-                      android:focusable="true"
-                      android:visibility="gone"
-                      style="?attr/borderlessButtonStyle">
-            <ImageView android:id="@+id/icon"
-                       android:layout_width="24dp"
-                       android:layout_height="24dp"
-                       android:layout_gravity="start|center_vertical"
-                       android:layout_marginStart="4dp"
-                       android:layout_marginEnd="16dp"
-                       android:layout_marginTop="12dp"
-                       android:layout_marginBottom="12dp"
-                       android:scaleType="fitCenter" />
-            <TextView android:id="@id/text1"
-                      android:layout_width="wrap_content"
-                      android:layout_height="wrap_content"
-                      android:layout_gravity="start|center_vertical"
-                      android:layout_marginEnd="16dp"
-                      android:textAppearance="?attr/textAppearanceButton"
-                      android:textColor="?attr/textColorPrimary"
-                      android:minLines="1"
-                      android:maxLines="1"
-                      android:ellipsize="marquee" />
-        </LinearLayout>
-    </LinearLayout>
+                  android:paddingBottom="12dp"
+                  android:layout_below="@id/profile_button"
+                  android:layout_toRightOf="@id/title_icon"/>
+    </RelativeLayout>
 
     <ListView
             android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 00c25e6..4b8640c 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -25,56 +25,39 @@
     android:maxCollapsedHeightSmall="56dp"
     android:id="@id/contentPanel">
 
-    <LinearLayout
+    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
         android:elevation="8dp"
         android:background="@color/white" >
+        <TextView android:id="@+id/profile_button"
+                  android:layout_width="wrap_content"
+                  android:layout_height="48dp"
+                  android:layout_marginEnd="8dp"
+                  android:paddingStart="8dp"
+                  android:paddingEnd="8dp"
+                  android:visibility="gone"
+                  style="?attr/borderlessButtonStyle"
+                  android:textAppearance="?attr/textAppearanceButton"
+                  android:textColor="@color/material_deep_teal_500"
+                  android:gravity="center_vertical"
+                  android:layout_alignParentTop="true"
+                  android:layout_alignParentRight="true"
+                  android:singleLine="true"/>
         <TextView android:id="@+id/title"
-                  android:layout_width="0dp"
+                  android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
-                  android:layout_weight="1"
                   android:minHeight="56dp"
                   android:textAppearance="?attr/textAppearanceMedium"
                   android:gravity="start|center_vertical"
                   android:paddingStart="?attr/dialogPreferredPadding"
                   android:paddingEnd="?attr/dialogPreferredPadding"
                   android:paddingTop="8dp"
+                  android:layout_below="@id/profile_button"
+                  android:layout_alignParentLeft="true"
                   android:paddingBottom="8dp" />
-        <LinearLayout android:id="@+id/profile_button"
-                      android:layout_width="wrap_content"
-                      android:layout_height="48dp"
-                      android:layout_marginTop="4dp"
-                      android:layout_marginEnd="4dp"
-                      android:paddingStart="8dp"
-                      android:paddingEnd="8dp"
-                      android:paddingTop="4dp"
-                      android:paddingBottom="4dp"
-                      android:focusable="true"
-                      android:visibility="gone"
-                      style="?attr/borderlessButtonStyle">
-            <ImageView android:id="@+id/icon"
-                       android:layout_width="24dp"
-                       android:layout_height="24dp"
-                       android:layout_gravity="start|center_vertical"
-                       android:layout_marginStart="4dp"
-                       android:layout_marginEnd="16dp"
-                       android:layout_marginTop="12dp"
-                       android:layout_marginBottom="12dp"
-                       android:scaleType="fitCenter" />
-            <TextView android:id="@id/text1"
-                      android:layout_width="wrap_content"
-                      android:layout_height="wrap_content"
-                      android:layout_gravity="start|center_vertical"
-                      android:layout_marginEnd="16dp"
-                      android:textAppearance="?attr/textAppearanceButton"
-                      android:textColor="?attr/textColorPrimary"
-                      android:minLines="1"
-                      android:maxLines="1"
-                      android:ellipsize="marquee" />
-        </LinearLayout>
-    </LinearLayout>
+    </RelativeLayout>
 
     <ListView
         android:layout_width="match_parent"
diff --git a/core/res/res/values-sw600dp/dimens_material.xml b/core/res/res/values-sw600dp/dimens_material.xml
index 3bbb352..1ec5c0f 100644
--- a/core/res/res/values-sw600dp/dimens_material.xml
+++ b/core/res/res/values-sw600dp/dimens_material.xml
@@ -23,6 +23,8 @@
     <dimen name="action_bar_default_height_material">64dp</dimen>
     <!-- Default content inset of an action bar. -->
     <dimen name="action_bar_content_inset_material">24dp</dimen>
+    <!-- Default content inset of an action bar with navigation present. -->
+    <dimen name="action_bar_content_inset_with_nav">80dp</dimen>
 
     <!-- Default start padding of an action bar. -->
     <dimen name="action_bar_default_padding_start_material">8dp</dimen>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a52c4e5..4429001 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2846,6 +2846,11 @@
             <!-- Pointer icon of a hand sign while grabbing something. -->
             <enum name="grabbing" value="1021" />
         </attr>
+
+        <!-- Whether this view has elements that may overlap when drawn. See
+             {@link android.view.View#forceHasOverlappingRendering(boolean)}. -->
+        <attr name="forceHasOverlappingRendering" format="boolean" />
+
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -4379,8 +4384,7 @@
         <attr name="autoLink" />
         <!-- If set to false, keeps the movement method from being set
              to the link movement method even if autoLink causes links
-             to be found or the input text contains a
-             {@link android.text.style.ClickableSpan ClickableSpan}. -->
+             to be found. -->
         <attr name="linksClickable" format="boolean" />
         <!-- If set, specifies that this TextView has a numeric input method.
              The default is false.
@@ -7639,6 +7643,12 @@
         <!-- Minimum inset for content views within a bar. Navigation buttons and
              menu views are excepted. Only valid for some themes and configurations. -->
         <attr name="contentInsetRight" format="dimension" />
+        <!-- Minimum inset for content views within a bar when a navigation button
+             is present, such as the Up button. Only valid for some themes and configurations. -->
+        <attr name="contentInsetStartWithNavigation" format="dimension" />
+        <!-- Minimum inset for content views within a bar when actions from a menu
+             are present. Only valid for some themes and configurations. -->
+        <attr name="contentInsetEndWithActions" format="dimension" />
         <!-- Elevation for the action bar itself -->
         <attr name="elevation" />
         <!-- Reference to a theme that should be used to inflate popups
@@ -8006,6 +8016,8 @@
         <attr name="contentInsetEnd" />
         <attr name="contentInsetLeft" />
         <attr name="contentInsetRight" />
+        <attr name="contentInsetStartWithNavigation" />
+        <attr name="contentInsetEndWithActions" />
         <attr name="maxButtonHeight" format="dimension" />
         <attr name="navigationButtonStyle" format="reference" />
         <attr name="buttonGravity">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6ecaa1f..892b3d5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2497,4 +2497,8 @@
     <!-- True if the device supports at least one form of multi-window.
          E.g. freeform, split-screen, picture-in-picture. -->
     <bool name="config_supportsMultiWindow">true</bool>
+
+    <!-- True if the device requires AppWidgetService even if it does not have
+         the PackageManager.FEATURE_APP_WIDGETS feature -->
+    <bool name="config_enableAppWidgetService">false</bool>
 </resources>
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 2fe4f66..ad2b335d 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -41,6 +41,8 @@
     <dimen name="action_bar_default_padding_end_material">0dp</dimen>
     <!-- Default content inset of an action bar. -->
     <dimen name="action_bar_content_inset_material">16dp</dimen>
+    <!-- Default content inset of an action bar when a navigation button is present. -->
+    <dimen name="action_bar_content_inset_with_nav">72dp</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="action_bar_icon_vertical_padding_material">16dp</dimen>
     <!-- Top margin for action bar subtitles -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7fd5456..0839187 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2710,6 +2710,9 @@
     <public type="attr" name="popupEnterTransition" />
     <public type="attr" name="popupExitTransition" />
     <public type="attr" name="minimalHeight" />
+    <public type="attr" name="forceHasOverlappingRendering" />
+    <public type="attr" name="contentInsetStartWithNavigation" />
+    <public type="attr" name="contentInsetEndWithActions" />
 
     <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
     <public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e94a91d..b878b00 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -563,11 +563,11 @@
     <!-- Label for the Android system components when they are shown to the user. -->
     <string name="android_system_label">Android System</string>
 
-    <!-- Label for the user owner in the intent forwarding app. [CHAR LIMIT=15] -->
-    <string name="user_owner_label">Personal</string>
+    <!-- Label for the user owner in the intent forwarding app. -->
+    <string name="user_owner_label">Switch to Personal</string>
 
-    <!-- Label for a corporate profile in the intent forwarding app. [CHAR LIMIT=15] -->
-    <string name="managed_profile_label">Work</string>
+    <!-- Label for a corporate profile in the intent forwarding app. -->
+    <string name="managed_profile_label">Switch to Work</string>
 
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_contacts">Contacts</string>
@@ -2953,10 +2953,11 @@
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
     <string name="hardware">Show virtual keyboard</string>
 
-    <!-- Title of the notification to prompt the user to select a keyboard layout. -->
-    <string name="select_keyboard_layout_notification_title">Select keyboard layout</string>
-    <!-- Message of the notification to prompt the user to select a keyboard layout. -->
-    <string name="select_keyboard_layout_notification_message">Touch to select a keyboard layout.</string>
+    <!-- Title of the notification to prompt the user to configure physical keyboard settings. -->
+    <string name="select_keyboard_layout_notification_title">Configure physical keyboard</string>
+    <!-- Message of the notification to prompt the user to configure physical keyboard settings
+         where the user can associate language with physical keyboard layout. -->
+    <string name="select_keyboard_layout_notification_message">Tap to select language and layout</string>
 
     <string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
     <string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 86b9f1d..790dcfa 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1234,6 +1234,7 @@
         <item name="collapseIcon">?attr/homeAsUpIndicator</item>
         <item name="collapseContentDescription">@string/toolbar_collapse_description</item>
         <item name="contentInsetStart">16dp</item>
+        <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
         <item name="touchscreenBlocksFocus">true</item>
     </style>
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index e636bc0..2420c1a 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -311,7 +311,7 @@
     <style name="TextAppearance.Material.Widget.PopupMenu.Header">
         <item name="fontFamily">@string/font_family_title_material</item>
         <item name="textSize">@dimen/text_size_menu_header_material</item>
-        <item name="textColor">?attr/textColorSecondary</item>
+        <item name="textColor">?attr/colorAccent</item>
     </style>
 
     <style name="TextAppearance.Material.Widget.DropDownHint" parent="TextAppearance.Material.Menu" />
@@ -944,6 +944,7 @@
         <item name="homeLayout">@layout/action_bar_home_material</item>
         <item name="gravity">center_vertical</item>
         <item name="contentInsetStart">@dimen/action_bar_content_inset_material</item>
+        <item name="contentInsetStartWithNavigation">@dimen/action_bar_content_inset_with_nav</item>
         <item name="contentInsetEnd">@dimen/action_bar_content_inset_material</item>
         <item name="elevation">@dimen/action_bar_elevation_material</item>
         <item name="popupTheme">?attr/actionBarPopupTheme</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6526571..694e934 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -307,6 +307,7 @@
   <java-symbol type="bool" name="config_supportsMultiWindow" />
   <java-symbol type="bool" name="config_guestUserEphemeral" />
   <java-symbol type="bool" name="config_localDisplaysMirrorContent" />
+  <java-symbol type="bool" name="config_enableAppWidgetService" />
   <java-symbol type="string" name="config_defaultPictureInPictureBounds" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
   <java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
diff --git a/core/tests/coretests/src/android/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java
index c9bc8aa..d56a405 100644
--- a/core/tests/coretests/src/android/print/BasePrintTest.java
+++ b/core/tests/coretests/src/android/print/BasePrintTest.java
@@ -28,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.print.PrintAttributes;
@@ -281,7 +282,8 @@
         }
         if (onRequestCustomPrinterIcon != null) {
             doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
-                    any(PrinterId.class), any(CustomPrinterIconCallback.class));
+                    any(PrinterId.class), any(CancellationSignal.class),
+                    any(CustomPrinterIconCallback.class));
         }
         if (onStopPrinterStateTracking != null) {
             doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
diff --git a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java b/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
index 26b7cae..be002e2 100644
--- a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
+++ b/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
@@ -16,6 +16,7 @@
 
 package android.print.mockservice;
 
+import android.os.CancellationSignal;
 import android.print.PrinterId;
 import android.printservice.CustomPrinterIconCallback;
 
@@ -42,7 +43,7 @@
     public abstract void onStartPrinterStateTracking(PrinterId printerId);
 
     public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
-            CustomPrinterIconCallback callback);
+            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
 
     public abstract void onStopPrinterStateTracking(PrinterId printerId);
 
diff --git a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
index 04683f2..e132d79 100644
--- a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
+++ b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
@@ -16,6 +16,7 @@
 
 package android.print.mockservice;
 
+import android.os.CancellationSignal;
 import android.print.PrinterId;
 import android.printservice.CustomPrinterIconCallback;
 import android.printservice.PrintService;
@@ -70,9 +71,9 @@
 
     @Override
     public void onRequestCustomPrinterIcon(PrinterId printerId,
-            CustomPrinterIconCallback callback) {
+            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback) {
         if (mCallbacks != null) {
-            mCallbacks.onRequestCustomPrinterIcon(printerId, callback);
+            mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
         }
     }
 
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index a37abf1..59ffd56 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -42,6 +43,7 @@
     }
 
     @SmallTest
+    @Suppress
     public void testTextAppearanceInSuggestionsPopup() {
         final Activity activity = getActivity();
 
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index 3fbc16a..923b829 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -44,7 +44,6 @@
 import android.support.test.espresso.Espresso;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.view.MotionEvent;
 import android.widget.espresso.ContextMenuUtils;
 
@@ -98,7 +97,6 @@
     }
 
     @SmallTest
-    @Suppress
     public void testContextMenu() throws Exception {
         final String text = "abc def ghi.";
         onView(withId(R.id.textview)).perform(click());
diff --git a/docs/docs-redirect-index.html b/docs/docs-redirect-index.html
index dc0bc72..ea5f186 100644
--- a/docs/docs-redirect-index.html
+++ b/docs/docs-redirect-index.html
@@ -1,8 +1,8 @@
-<html>

-<head>

-<meta http-equiv="refresh" content="0;url=framework/index.html">

-</head>

-<body>

-<a href="framework/index.html">click here if you are not redirected</a>

-</body>

-</html>

+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=framework/index.html">
+</head>
+<body>
+<a href="framework/index.html">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/docs-redirect.html b/docs/docs-redirect.html
index 4e0743e..d83564c 100644
--- a/docs/docs-redirect.html
+++ b/docs/docs-redirect.html
@@ -1,8 +1,8 @@
-<html>

-<head>

-<meta http-equiv="refresh" content="0;url=docs/offline.html">

-</head>

-<body>

-<a href="docs/offline.html">click here if you are not redirected</a>

-</body>

-</html>

+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=docs/offline.html">
+</head>
+<body>
+<a href="docs/offline.html">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/docs-samples-redirect.html b/docs/docs-samples-redirect.html
index 4e549e6..abefe6c 100644
--- a/docs/docs-samples-redirect.html
+++ b/docs/docs-samples-redirect.html
@@ -1,8 +1,8 @@
-<html>

-<head>

-<meta http-equiv="refresh" content="0;url=../../samples/">

-</head>

-<body>

-<a href="../../samples/">click here if you are not redirected</a>

-</body>

-</html>

+<html>
+<head>
+<meta http-equiv="refresh" content="0;url=../../samples/">
+</head>
+<body>
+<a href="../../samples/">click here if you are not redirected</a>
+</body>
+</html>
diff --git a/docs/html/guide/topics/renderscript/reference/overview.jd b/docs/html/guide/topics/renderscript/reference/overview.jd
index 5e824ee..017a713 100644
--- a/docs/html/guide/topics/renderscript/reference/overview.jd
+++ b/docs/html/guide/topics/renderscript/reference/overview.jd
@@ -611,7 +611,7 @@
   </tr>
 </tbody></table>
 <h2>Conversion Functions</h2>
-<p> The functions below convert from a numerical vector type to another, of from one color
+<p> The functions below convert from a numerical vector type to another, or from one color
 representation to another.
 </p>
 <table class='jd-sumtable'><tbody>
diff --git a/docs/html/guide/topics/renderscript/reference/rs_convert.jd b/docs/html/guide/topics/renderscript/reference/rs_convert.jd
index d4bf77fa..89fd040 100644
--- a/docs/html/guide/topics/renderscript/reference/rs_convert.jd
+++ b/docs/html/guide/topics/renderscript/reference/rs_convert.jd
@@ -4,7 +4,7 @@
 
 <div class='renderscript'>
 <h2>Overview</h2>
-<p> The functions below convert from a numerical vector type to another, of from one color
+<p> The functions below convert from a numerical vector type to another, or from one color
 representation to another.
 </p>
 <h2>Summary</h2>
diff --git a/docs/html/guide/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index 0a96a15..e60ca9f 100644
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -1,484 +1,484 @@
-page.title=Localizing with Resources

-parent.title=Application Resources

-page.tags="localizing","localization","resources", "formats", "l10n"

-parent.link=index.html

-@jd:body

-

-<div id="qv-wrapper">

-    <div id="qv">

-

-<h2>Quickview</h2>

-

-<ul>

-  <li>Use resource sets to create a localized app.</li>

-  <li>Android loads the correct resource set for the user's language and locale.</li>

-  <li>If localized resources are not available, Android loads your default resources.</li>

-</ul>

-

-<h2>In this document</h2>

-<ol>

-  <li><a href="#resource-switching">Overview: Resource-Switching in Android</a></li>

-<li><a href="#using-framework">Using Resources for Localization</a></li>

-<li><a href="#strategies">Localization Tips</a></li>

-<li><a href="#testing">Testing Localized Applications</a></li>

-</ol>

-

-<h2>See also</h2>

-  <ol>

-    <li><a href="{@docRoot}distribute/tools/localization-checklist.html">Localization Checklist</a></li>

-    <li><a href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></li>

-    <li><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Layouts</a></li>

-    <li><a href="{@docRoot}reference/android/app/Activity.html#ActivityLifecycle">Activity Lifecycle</a></li>

-</ol>

-</div>

-</div>

-

-<p>Android will run on many  devices in many  regions. To reach the most users,

-your application should handle text, audio files, numbers, currency, and

-graphics in ways appropriate to the locales where your application will be used.

-</p>

-

-<p>This document describes best practices for localizing Android

-applications. The principles apply whether you are developing your application  

-using ADT with Eclipse, Ant-based tools, or any other IDE. </p>

-

-<p>You should already have a working knowledge of Java and be  familiar with

-Android resource loading, the declaration of user interface elements in XML,

-development considerations such as Activity lifecycle, and general principles of

-internationalization and localization. </p>

-

-<p>It is good practice to use the Android resource framework to separate the

-localized aspects of your application as much as possible from the core Java

-functionality:</p>

-

-<ul>

-  <li>You can put most or all of the <em>contents</em> of your application's

-user interface into resource files, as described in this document and in <a

-href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a>.</li>

-  <li>The <em>behavior</em> of the user interface, on the other hand, is driven

-by your Java code. 

-    For example, if users input data that needs to be formatted or sorted

-differently depending on locale, then you would use Java to handle the data

-programmatically. This document does not cover how to  localize your Java code.

-</li>

-</ul>

-

-<p>For a short guide to localizing strings in your app, see the training lesson, <a

-href="{@docRoot}training/basics/supporting-devices/languages.html">Supporting Different Languages</a>. </p>

-

-

-<h2 id="resource-switching">Overview: Resource-Switching in Android</h2>

-

-<p>Resources are text strings, layouts, sounds, graphics, and any other static

-data that your  Android application  needs. An application can include multiple

-sets of resources, each customized for a different device configuration. When a

-user runs the application,  Android    automatically selects and loads the 

-resources that best match the device.</p>

-

-<p>(This document focuses on localization and locale. For a complete description

-of resource-switching and all the types of configurations that you can

-specify &#8212; screen orientation, touchscreen type, and so on &#8212; see <a

-href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing

-Alternative Resources</a>.)</p>

-

-<table border="0" cellspacing="0" cellpadding="0">

-  <tr border="0">

-    <td width="180" style="border: 0pt none ;"><p class="special-note">

-    <strong>When you write your application:</strong>

-    <br><br>

-    You create a set of default resources, plus alternatives to be used in

-    different locales.</p></td>

-    <td style="border: 0pt none; padding:0">

-    <p style="border:0; padding:0"><img src="../../../images/resources/right-arrow.png" alt="right-arrow" 

-    width="51" height="17"></p></td>

-    <td width="180" style="border: 0pt none ;"><p class="special-note">

-    <strong>When a user runs your application:</strong>

-    <br><br>The Android system selects which resources to load, based on the

-    device's locale.</p></td>

-  </tr>

-</table>

-

-<p>When you write your application, you create default and alternative resources

-for your application to use. To create  resources, you place files within

-specially named subdirectories of the project's <code>res/</code> directory.

-</p>

-

-

-

-<h3 id="defaults-r-important">Why Default Resources Are Important</h3>

-

-<p>Whenever the application runs in a locale for which you have not provided

-locale-specific text,  Android will load the default strings from

-<code>res/values/strings.xml</code>. If this default  file is absent, or if it 

-is missing a string that your application needs, then your application will not run 

-and will show an error. 

-The example below illustrates what can happen when the default text file is incomplete. </p>

-

-<p><em>Example:</em>

-<p>An application's Java code refers to just two strings, <code>text_a</code> and 

-	<code>text_b</code>. This application includes a localized resource file 

-	(<code>res/values-en/strings.xml</code>) that defines <code>text_a</code> and 

-	<code>text_b</code> in English. This application also includes a default 

-	resource file (<code>res/values/strings.xml</code>) that includes a

-definition for <code>text_a</code>, but not for <code>text_b</code>:

-<ul>

-  <li>This application might compile without a problem. An IDE such as Eclipse 

-  	will not highlight any errors if a resource is missing.</li>

-  <li>When this application is launched on a device with locale set to English, 

-  	the application  might run without a problem, because 

-  	<code>res/values-en/strings.xml</code> contains both of the needed text 

-  	strings.</li>

-  <li>However, <strong>the user  will see an error message and a Force Close 

-  	button</strong> when this application is launched on a device set to a 

-  	language other than English. The application will not load.</li>

-</ul>

-

-

-<p>To prevent this situation, make sure that a <code>res/values/strings.xml</code> 

-	file exists and that it defines every needed string. The situation applies to 

-	all types of resources, not just strings: You 

-	need to create a  set of default resource files containing all 

-	the resources that your application calls upon &#8212; layouts, drawables, 

-	animations, etc. For information about testing, see <a href="#test-for-default">

-	Testing for Default Resources</a>.</p>

-

-<h2 id="using-framework">Using Resources for Localization</h2>

-

-<h3 id="creating-defaults">How to Create Default Resources</h3>

-

-<p>Put the application's default text in

-a file with the following location and name:</p>

-<p><code>&nbsp;&nbsp;&nbsp;&nbsp;res/values/strings.xml</code> (required directory)</p>

-

-<p>The text strings in <code>res/values/strings.xml</code> should  use the

-default language, which is the language that you expect most of your application's users to

-speak.  </p>

-

-<p>The default resource set must also include any default drawables and layouts, 

-	and can include other types of resources such as animations. 

-<br>

-  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/drawable/</code>(required directory holding at least

-  one graphic file, for the application's icon on Google Play)<br>

-  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/layout/</code> (required directory holding an XML

-  file that defines the default layout)<br>

-  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/anim/</code> (required if you have any 

-  <code>res/anim-<em>&lt;qualifiers&gt;</em></code> folders)<br>

-  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/xml/</code> (required if you have any 

-  <code>res/xml-<em>&lt;qualifiers&gt;</em></code> folders)<br>

-  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/raw/</code> (required if you have any 

-  <code>res/raw-<em>&lt;qualifiers&gt;</em></code> folders)

-</p>

-

-<p class="note"><strong>Tip:</strong> In your code, examine each reference to 

-	an Android resource. Make sure that a default resource is defined for each

-	one. Also make sure that the default string file is complete: A <em>

-	localized</em> string file can contain a subset of the strings, but the 

-	<em>default</em> string file must contain them all. 

-</p>

-

-<h3 id="creating-alternatives">How to Create Alternative Resources</h3>

-

-<p>A large part of localizing an application is providing alternative text for

-different languages. In some cases you will also provide alternative graphics,

-sounds, layouts, and other locale-specific resources. </p>

-

-<p>An application can specify many <code>res/<em>&lt;qualifiers&gt;</em>/</code>

-directories, each with different qualifiers. To create an alternative resource for

-a different locale, you use a qualifier that specifies a language or a 

-language-region combination. (The name of a resource directory must conform 

-to the naming scheme described in 

-<a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing

-Alternative Resources</a>,

-or else it will not compile.)</p>

-

-<p><em>Example:</em></p>

-

-<p>Suppose that your application's default language is English. Suppose also

-that you want to localize all the text in your application to French, and most

-of the text in your application (everything except the application's title) to

-Japanese. In this case, you could create three alternative <code>strings.xml</code>

-files, each stored in a locale-specific resource directory:</p>

-

-<ol>

-  <li><code>res/values/strings.xml</code><br>

-    Contains  English text for all  the strings that the application uses,

-including text for a string named <code>title</code>.</li>

-  <li><code>res/values-fr/strings.xml</code><br>

-    Contain French text for all  the strings, including <code>title</code>.</li>

-  <li><code>res/values-ja/strings.xml</code><br>

-    Contain Japanese text for all  the strings <em>except</em>

-<code>title</code>.<br>

-  <code></code></li>

-</ol>

-

-<p>If your Java code refers to <code>R.string.title</code>,  here is what will

-happen at runtime:</p>

-

-<ul>

-  <li>If the device is set to any language other than French, Android will load

-<code>title</code> from the <code>res/values/strings.xml</code> file.</li>

-  <li>If the device is set to French, Android will load <code>title</code> from

-the <code>res/values-fr/strings.xml</code> file.</li>

-</ul>

-

-<p>Notice that if the device is set to Japanese, Android will look for

-<code>title</code> in the <code>res/values-ja/strings.xml</code> file. But

-because no such string is included in that file, Android will fall back to the

-default, and will load  <code>title</code> in English from the

-<code>res/values/strings.xml</code> file.  </p>

-

-<h3 id="resource-precedence">Which Resources Take Precedence?</h3>

-

-<p> If multiple resource files match a device's configuration, Android follows a

-set of rules in deciding which file to use. Among the qualifiers that can be

-specified in a resource directory name, <strong>locale almost always takes

-precedence</strong>. </p>

-<p><em>Example:</em></p>

-

-<p>Assume that an application  includes a default set of graphics and two other

-sets of graphics, each optimized for a different device setup:</p>

-

-<ul>

-  <li><code>res/drawable/</code><br>

-    Contains

-  default graphics.</li>

-  <li><code>res/drawable-small-land-stylus/</code><br>

-  Contains  graphics optimized for use with a device that expects input from a 

-  stylus and has a QVGA low-density screen in landscape orientation.</li>

-  <li><code>res/drawable-ja/</code> <br>

-  Contains  graphics optimized for use with Japanese.</li>

-</ul>

-

-<p>If the application runs on a device that is configured to use Japanese,

-Android will load graphics from  <code>res/drawable-ja/</code>, even if the

-device happens to be one that expects input from a stylus and has a QVGA 

-low-density screen in landscape orientation.</p>

-

-<p class="note"><strong>Exception:</strong> The only qualifiers that take

-precedence over locale in the selection process are MCC and MNC (mobile country

-code and mobile network code). </p>

-

-<p><em>Example:</em></p>

-

-<p>Assume that you have the following situation:</p>

-

-<ul>

-  <li>The application code calls for <code>R.string.text_a</code></li>

-  <li>Two relevant resource files are available:

-    <ul>

-      <li><code>res/values-mcc404/strings.xml</code>, which includes

-<code>text_a</code> in the application's default language, in this case

-English.</li>

-      <li><code>res/values-hi/strings.xml</code>, which includes

-<code>text_a</code> in Hindi.</li>

-    </ul>

-  </li>

-  <li>The application is running on a device that has the following

-configuration:

-    <ul>

-      <li>The SIM card is connected to a mobile network in India (MCC 404).</li>

-      <li>The language is set to Hindi (<code>hi</code>).</li>

-    </ul>

-  </li>

-</ul>

-

-<p>Android will load <code>text_a</code> from

-<code>res/values-mcc404/strings.xml</code> (in English), even if the device is

-configured for Hindi. That is because in the resource-selection process, Android

-will prefer an MCC match over a language match. </p>

-

-<p>The selection process is not always as straightforward as these examples

-suggest. Please read  <a

-href="{@docRoot}guide/topics/resources/providing-resources.html#BestMatch">How Android Finds

-the Best-matching Resource</a> for a more nuanced description of the

-process. All the qualifiers are described and listed in order of

-precedence in <a

-href="{@docRoot}guide/topics/resources/providing-resources.html#table2">Table 2 of Providing

-Alternative Resources</a>.</p>

-

-<h3 id="referring-to-resources">Referring to Resources in Java</h3>

-

-<p>In your application's Java code, you refer to  resources using the syntax

-<code>R.<em>resource_type</em>.<em>resource_name</em></code> or

-<code>android.R.<em>resource_type</em>.<em>resource_name</em></code><em>.</em>

-For more about this, see <a

-href="{@docRoot}guide/topics/resources/accessing-resources.html">Accessing Resources</a>.</p>

-

-<h2 id="checklist">Localization Checklist</h2>

-

-<p>For a complete overview of the process of localizing and distributing an Android application,

-see the <a href="{@docRoot}distribute/tools/localization-checklist.html">Localization

-Checklist</a> document.</p>

-

-<h2 id="strategies">Localization Tips</h2>

-

-<h4 id="failing2">Design your application  to work in any locale</h4>

-

-<p>You cannot assume anything about the device on which a user will

-run your application. The device might have hardware that you were not

-anticipating, or it might be set to a locale that you did not plan for or that 

-you cannot test. Design your application so that it will function normally or fail gracefully no 

-matter what device it runs on.</p>

-

-<p class="note"><strong>Important:</strong> Make sure that your application

-includes a full set of default resources.</p> <p>Make sure to include

-<code>res/drawable/</code> and a <code>res/values/</code> folders (without any

-additional modifiers in the folder names) that contain all the images and text

-that your application will need. </p>

-

-<p>If an application is missing even one default resource, it will not run on a 

-	device that is set to an unsupported locale. For example, the 

-	<code>res/values/strings.xml</code> default file might lack one string that 

-	the application needs: When the application runs in an unsupported locale and 

-	attempts to load <code>res/values/strings.xml</code>, the user will see an 

-	error message and a Force Close button. An IDE such as Eclipse will not 

-	highlight this kind of error, and you will not see the problem when you 

-	test the application on a device or emulator that is set to a supported locale.</p>

-

-<p>For more information, see <a href="#test-for-default">Testing for Default Resources</a>.</p>

-

-<h4>Design a flexible layout</h4>

-

-<p> If you need to rearrange your layout to fit a certain language (for example

-German with its long words), you can create an alternative layout for that

-language (for example <code>res/layout-de/main.xml</code>). However, doing this

-can make your application harder to maintain.  It is better to create a single

-layout that is more flexible.</p>

-

-<p>Another typical situation is a language that requires something different in

-its layout. For example, you might have a contact form that should include  two

-name fields when the application runs in Japanese, but three name fields when

-the application  runs in some other language. You could handle this in either of

-two ways:</p>

-

-<ul>

-  <li>Create  one  layout with a field that you can programmatically enable or

-disable, based on the language, or</li>

-  <li>Have the main layout include another layout that  includes the changeable

-field. The second layout can have different configurations for different

-languages.</li>

-</ul>

-

-<h4>Avoid creating more resource files and text strings than you need</h4>

-

-<p>You probably do not need to create a locale-specific

-alternative for every resource in your application. For example, the layout

-defined in the <code>res/layout/main.xml</code> file might work in any locale,

-in which case there would be no need to create any alternative layout files.

-</p>

-

-<p>Also, you might not need to create alternative text for every

-string. For example, assume the following:</p>

-

-<ul>

-  <li>Your application's default language is American

-English. Every string that the application uses is defined, using American

-English spellings, in <code>res/values/strings.xml</code>. </li>

-

-  <li>For  a few important phrases, you want to provide

-British English spelling. You want these alternative strings to be used when your

-application runs on a device in the United Kingdom. </li>

-</ul>

-

-<p>To do this, you could create a small file called

-<code>res/values-en-rGB/strings.xml</code> that includes only the strings that

-should be different when the application  runs in the U.K. For all the rest of

-the strings, the application will fall back to the defaults and use what is

-defined in <code>res/values/strings.xml</code>.</p>

-

-<h4>Use the Android Context object for manual locale lookup</h4>

-

-<p>You can look up the locale using the {@link android.content.Context} object

-that Android makes available:</p>

-

-<pre>String locale = context.getResources().getConfiguration().locale.getDisplayName();</pre>

-

-<h2 id="testing">Testing Localized Applications</h2>

-

-<h3 id="device">Testing on a Device</h3>

-<p>Keep in mind that the device you are testing may be significantly different from 

-	the devices available to consumers in other geographies. The locales available 

-	on your device may differ from those available on other devices. Also, the 

-	resolution and density of the device screen may differ, which could affect 

-	the display of strings and drawables in your UI.</p>

-

-<p>To change the locale or language on a device, use the Settings application.</p>

-

-<h3 id="emulator">Testing on an Emulator</h3>

-

-<p>For details about using the emulator, see See <a

-href="{@docRoot}tools/help/emulator.html">Android Emulator</a>.</p>

-<h4>Creating and using a custom locale</h4>

-

-<p>A &quot;custom&quot; locale is a language/region combination that the Android

-system image does not explicitly support. (For a list of supported locales in

-Android platforms see the Version Notes in the <a

-href="{@docRoot}sdk/index.html">SDK</a> tab). You can test

-how your application will run in a custom locale by creating a custom locale in

-the emulator. There are two ways to do this:</p>

-

-<ul>

-  <li>Use the Custom Locale application, which is accessible from the

-Application tab. (After you create a custom locale, switch to it by 

-pressing and holding the locale name.)</li>

-  <li>Change to a custom locale from the adb shell, as described below.</li>

-</ul>

-

-<p>When you set the emulator to a locale that is not available in the Android

-system image, the system itself will display in its default language. Your

-application, however, should localize properly.</p>

-

-<h4>Changing the emulator locale from the adb shell</h4>

-

-<p>To change the locale in the emulator by using the adb shell. </p>

-

-<ol>

-  <li>Pick the locale you want to test and determine its BCP-47 language tag, for

-example, Canadian French would be <code>fr-CA</code>.<br>

-  </li>

-  <li>Launch an emulator.</li>

-  <li>From a command-line shell on the host computer, run the following

-command:<br>

-    <code>adb shell</code><br>

-  or if you have a device attached, specify that you want the emulator by adding

-the <code>-e</code> option:<br>

-  <code>adb -e shell</code></li>

-  <li>At  the  adb shell prompt (<code>#</code>), run this command: <br>

-    <code>setprop persist.sys.locale [<em>BCP-47 language tag</em>];stop;sleep 5;start <br>

-    </code>Replace bracketed sections with the  appropriate codes from Step

-1.</li>

-</ol>

-

-<p>For instance, to test in Canadian French:</p>

-

-<p><code>setprop persist.sys.locale fr-CA;stop;sleep 5;start </code></p>

-

-<p>This will cause the emulator  to restart. (It will look like a full reboot,

-but it is not.) Once the Home screen appears again, re-launch your application (for

-example, click the Run icon in Eclipse), and the application will launch with

-the new locale. </p>

-

-<h3 id="test-for-default">Testing for Default Resources</h3>

-<p>Here's how to test whether an application includes every string resource that it needs:  </p>

-<ol><li>Set the emulator or device to a language that your application does not 

-	support. For example, if the application has French strings in 

-	<code>res/values-fr/</code> but does not have any Spanish strings in 

-	<code>res/values-es/</code>, then set the emulator's locale to Spanish. 

-	(You can use the Custom Locale application to set the emulator to an 

-	unsupported locale.)</li>

-	<li>Run the application.</li>  

-<li>If the application shows an error message and a Force Close button, it might 

-	be looking for a string that is not available. Make sure that your 

-	<code>res/values/strings.xml</code> file includes a definition for 

-	every string that the application uses.</li>

-</ol> 

-</p> 

-

-<p>If the test is successful, repeat it for other types of 

-	configurations. For example, if the application has a layout file called 

-	<code>res/layout-land/main.xml</code> but does not contain a file called 

-	<code>res/layout-port/main.xml</code>, then set the emulator or device to 

-	portrait orientation and see if the application will run. 

-

-

-

+page.title=Localizing with Resources
+parent.title=Application Resources
+page.tags="localizing","localization","resources", "formats", "l10n"
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+    <div id="qv">
+
+<h2>Quickview</h2>
+
+<ul>
+  <li>Use resource sets to create a localized app.</li>
+  <li>Android loads the correct resource set for the user's language and locale.</li>
+  <li>If localized resources are not available, Android loads your default resources.</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+  <li><a href="#resource-switching">Overview: Resource-Switching in Android</a></li>
+<li><a href="#using-framework">Using Resources for Localization</a></li>
+<li><a href="#strategies">Localization Tips</a></li>
+<li><a href="#testing">Testing Localized Applications</a></li>
+</ol>
+
+<h2>See also</h2>
+  <ol>
+    <li><a href="{@docRoot}distribute/tools/localization-checklist.html">Localization Checklist</a></li>
+    <li><a href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></li>
+    <li><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Layouts</a></li>
+    <li><a href="{@docRoot}reference/android/app/Activity.html#ActivityLifecycle">Activity Lifecycle</a></li>
+</ol>
+</div>
+</div>
+
+<p>Android will run on many  devices in many  regions. To reach the most users,
+your application should handle text, audio files, numbers, currency, and
+graphics in ways appropriate to the locales where your application will be used.
+</p>
+
+<p>This document describes best practices for localizing Android
+applications. The principles apply whether you are developing your application  
+using ADT with Eclipse, Ant-based tools, or any other IDE. </p>
+
+<p>You should already have a working knowledge of Java and be  familiar with
+Android resource loading, the declaration of user interface elements in XML,
+development considerations such as Activity lifecycle, and general principles of
+internationalization and localization. </p>
+
+<p>It is good practice to use the Android resource framework to separate the
+localized aspects of your application as much as possible from the core Java
+functionality:</p>
+
+<ul>
+  <li>You can put most or all of the <em>contents</em> of your application's
+user interface into resource files, as described in this document and in <a
+href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a>.</li>
+  <li>The <em>behavior</em> of the user interface, on the other hand, is driven
+by your Java code. 
+    For example, if users input data that needs to be formatted or sorted
+differently depending on locale, then you would use Java to handle the data
+programmatically. This document does not cover how to  localize your Java code.
+</li>
+</ul>
+
+<p>For a short guide to localizing strings in your app, see the training lesson, <a
+href="{@docRoot}training/basics/supporting-devices/languages.html">Supporting Different Languages</a>. </p>
+
+
+<h2 id="resource-switching">Overview: Resource-Switching in Android</h2>
+
+<p>Resources are text strings, layouts, sounds, graphics, and any other static
+data that your  Android application  needs. An application can include multiple
+sets of resources, each customized for a different device configuration. When a
+user runs the application,  Android    automatically selects and loads the 
+resources that best match the device.</p>
+
+<p>(This document focuses on localization and locale. For a complete description
+of resource-switching and all the types of configurations that you can
+specify &#8212; screen orientation, touchscreen type, and so on &#8212; see <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
+Alternative Resources</a>.)</p>
+
+<table border="0" cellspacing="0" cellpadding="0">
+  <tr border="0">
+    <td width="180" style="border: 0pt none ;"><p class="special-note">
+    <strong>When you write your application:</strong>
+    <br><br>
+    You create a set of default resources, plus alternatives to be used in
+    different locales.</p></td>
+    <td style="border: 0pt none; padding:0">
+    <p style="border:0; padding:0"><img src="../../../images/resources/right-arrow.png" alt="right-arrow" 
+    width="51" height="17"></p></td>
+    <td width="180" style="border: 0pt none ;"><p class="special-note">
+    <strong>When a user runs your application:</strong>
+    <br><br>The Android system selects which resources to load, based on the
+    device's locale.</p></td>
+  </tr>
+</table>
+
+<p>When you write your application, you create default and alternative resources
+for your application to use. To create  resources, you place files within
+specially named subdirectories of the project's <code>res/</code> directory.
+</p>
+
+
+
+<h3 id="defaults-r-important">Why Default Resources Are Important</h3>
+
+<p>Whenever the application runs in a locale for which you have not provided
+locale-specific text,  Android will load the default strings from
+<code>res/values/strings.xml</code>. If this default  file is absent, or if it 
+is missing a string that your application needs, then your application will not run 
+and will show an error. 
+The example below illustrates what can happen when the default text file is incomplete. </p>
+
+<p><em>Example:</em>
+<p>An application's Java code refers to just two strings, <code>text_a</code> and 
+	<code>text_b</code>. This application includes a localized resource file 
+	(<code>res/values-en/strings.xml</code>) that defines <code>text_a</code> and 
+	<code>text_b</code> in English. This application also includes a default 
+	resource file (<code>res/values/strings.xml</code>) that includes a
+definition for <code>text_a</code>, but not for <code>text_b</code>:
+<ul>
+  <li>This application might compile without a problem. An IDE such as Eclipse 
+  	will not highlight any errors if a resource is missing.</li>
+  <li>When this application is launched on a device with locale set to English, 
+  	the application  might run without a problem, because 
+  	<code>res/values-en/strings.xml</code> contains both of the needed text 
+  	strings.</li>
+  <li>However, <strong>the user  will see an error message and a Force Close 
+  	button</strong> when this application is launched on a device set to a 
+  	language other than English. The application will not load.</li>
+</ul>
+
+
+<p>To prevent this situation, make sure that a <code>res/values/strings.xml</code> 
+	file exists and that it defines every needed string. The situation applies to 
+	all types of resources, not just strings: You 
+	need to create a  set of default resource files containing all 
+	the resources that your application calls upon &#8212; layouts, drawables, 
+	animations, etc. For information about testing, see <a href="#test-for-default">
+	Testing for Default Resources</a>.</p>
+
+<h2 id="using-framework">Using Resources for Localization</h2>
+
+<h3 id="creating-defaults">How to Create Default Resources</h3>
+
+<p>Put the application's default text in
+a file with the following location and name:</p>
+<p><code>&nbsp;&nbsp;&nbsp;&nbsp;res/values/strings.xml</code> (required directory)</p>
+
+<p>The text strings in <code>res/values/strings.xml</code> should  use the
+default language, which is the language that you expect most of your application's users to
+speak.  </p>
+
+<p>The default resource set must also include any default drawables and layouts, 
+	and can include other types of resources such as animations. 
+<br>
+  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/drawable/</code>(required directory holding at least
+  one graphic file, for the application's icon on Google Play)<br>
+  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/layout/</code> (required directory holding an XML
+  file that defines the default layout)<br>
+  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/anim/</code> (required if you have any 
+  <code>res/anim-<em>&lt;qualifiers&gt;</em></code> folders)<br>
+  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/xml/</code> (required if you have any 
+  <code>res/xml-<em>&lt;qualifiers&gt;</em></code> folders)<br>
+  <code>&nbsp;&nbsp;&nbsp;&nbsp;res/raw/</code> (required if you have any 
+  <code>res/raw-<em>&lt;qualifiers&gt;</em></code> folders)
+</p>
+
+<p class="note"><strong>Tip:</strong> In your code, examine each reference to 
+	an Android resource. Make sure that a default resource is defined for each
+	one. Also make sure that the default string file is complete: A <em>
+	localized</em> string file can contain a subset of the strings, but the 
+	<em>default</em> string file must contain them all. 
+</p>
+
+<h3 id="creating-alternatives">How to Create Alternative Resources</h3>
+
+<p>A large part of localizing an application is providing alternative text for
+different languages. In some cases you will also provide alternative graphics,
+sounds, layouts, and other locale-specific resources. </p>
+
+<p>An application can specify many <code>res/<em>&lt;qualifiers&gt;</em>/</code>
+directories, each with different qualifiers. To create an alternative resource for
+a different locale, you use a qualifier that specifies a language or a 
+language-region combination. (The name of a resource directory must conform 
+to the naming scheme described in 
+<a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">Providing
+Alternative Resources</a>,
+or else it will not compile.)</p>
+
+<p><em>Example:</em></p>
+
+<p>Suppose that your application's default language is English. Suppose also
+that you want to localize all the text in your application to French, and most
+of the text in your application (everything except the application's title) to
+Japanese. In this case, you could create three alternative <code>strings.xml</code>
+files, each stored in a locale-specific resource directory:</p>
+
+<ol>
+  <li><code>res/values/strings.xml</code><br>
+    Contains  English text for all  the strings that the application uses,
+including text for a string named <code>title</code>.</li>
+  <li><code>res/values-fr/strings.xml</code><br>
+    Contain French text for all  the strings, including <code>title</code>.</li>
+  <li><code>res/values-ja/strings.xml</code><br>
+    Contain Japanese text for all  the strings <em>except</em>
+<code>title</code>.<br>
+  <code></code></li>
+</ol>
+
+<p>If your Java code refers to <code>R.string.title</code>,  here is what will
+happen at runtime:</p>
+
+<ul>
+  <li>If the device is set to any language other than French, Android will load
+<code>title</code> from the <code>res/values/strings.xml</code> file.</li>
+  <li>If the device is set to French, Android will load <code>title</code> from
+the <code>res/values-fr/strings.xml</code> file.</li>
+</ul>
+
+<p>Notice that if the device is set to Japanese, Android will look for
+<code>title</code> in the <code>res/values-ja/strings.xml</code> file. But
+because no such string is included in that file, Android will fall back to the
+default, and will load  <code>title</code> in English from the
+<code>res/values/strings.xml</code> file.  </p>
+
+<h3 id="resource-precedence">Which Resources Take Precedence?</h3>
+
+<p> If multiple resource files match a device's configuration, Android follows a
+set of rules in deciding which file to use. Among the qualifiers that can be
+specified in a resource directory name, <strong>locale almost always takes
+precedence</strong>. </p>
+<p><em>Example:</em></p>
+
+<p>Assume that an application  includes a default set of graphics and two other
+sets of graphics, each optimized for a different device setup:</p>
+
+<ul>
+  <li><code>res/drawable/</code><br>
+    Contains
+  default graphics.</li>
+  <li><code>res/drawable-small-land-stylus/</code><br>
+  Contains  graphics optimized for use with a device that expects input from a 
+  stylus and has a QVGA low-density screen in landscape orientation.</li>
+  <li><code>res/drawable-ja/</code> <br>
+  Contains  graphics optimized for use with Japanese.</li>
+</ul>
+
+<p>If the application runs on a device that is configured to use Japanese,
+Android will load graphics from  <code>res/drawable-ja/</code>, even if the
+device happens to be one that expects input from a stylus and has a QVGA 
+low-density screen in landscape orientation.</p>
+
+<p class="note"><strong>Exception:</strong> The only qualifiers that take
+precedence over locale in the selection process are MCC and MNC (mobile country
+code and mobile network code). </p>
+
+<p><em>Example:</em></p>
+
+<p>Assume that you have the following situation:</p>
+
+<ul>
+  <li>The application code calls for <code>R.string.text_a</code></li>
+  <li>Two relevant resource files are available:
+    <ul>
+      <li><code>res/values-mcc404/strings.xml</code>, which includes
+<code>text_a</code> in the application's default language, in this case
+English.</li>
+      <li><code>res/values-hi/strings.xml</code>, which includes
+<code>text_a</code> in Hindi.</li>
+    </ul>
+  </li>
+  <li>The application is running on a device that has the following
+configuration:
+    <ul>
+      <li>The SIM card is connected to a mobile network in India (MCC 404).</li>
+      <li>The language is set to Hindi (<code>hi</code>).</li>
+    </ul>
+  </li>
+</ul>
+
+<p>Android will load <code>text_a</code> from
+<code>res/values-mcc404/strings.xml</code> (in English), even if the device is
+configured for Hindi. That is because in the resource-selection process, Android
+will prefer an MCC match over a language match. </p>
+
+<p>The selection process is not always as straightforward as these examples
+suggest. Please read  <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#BestMatch">How Android Finds
+the Best-matching Resource</a> for a more nuanced description of the
+process. All the qualifiers are described and listed in order of
+precedence in <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#table2">Table 2 of Providing
+Alternative Resources</a>.</p>
+
+<h3 id="referring-to-resources">Referring to Resources in Java</h3>
+
+<p>In your application's Java code, you refer to  resources using the syntax
+<code>R.<em>resource_type</em>.<em>resource_name</em></code> or
+<code>android.R.<em>resource_type</em>.<em>resource_name</em></code><em>.</em>
+For more about this, see <a
+href="{@docRoot}guide/topics/resources/accessing-resources.html">Accessing Resources</a>.</p>
+
+<h2 id="checklist">Localization Checklist</h2>
+
+<p>For a complete overview of the process of localizing and distributing an Android application,
+see the <a href="{@docRoot}distribute/tools/localization-checklist.html">Localization
+Checklist</a> document.</p>
+
+<h2 id="strategies">Localization Tips</h2>
+
+<h4 id="failing2">Design your application  to work in any locale</h4>
+
+<p>You cannot assume anything about the device on which a user will
+run your application. The device might have hardware that you were not
+anticipating, or it might be set to a locale that you did not plan for or that 
+you cannot test. Design your application so that it will function normally or fail gracefully no 
+matter what device it runs on.</p>
+
+<p class="note"><strong>Important:</strong> Make sure that your application
+includes a full set of default resources.</p> <p>Make sure to include
+<code>res/drawable/</code> and a <code>res/values/</code> folders (without any
+additional modifiers in the folder names) that contain all the images and text
+that your application will need. </p>
+
+<p>If an application is missing even one default resource, it will not run on a 
+	device that is set to an unsupported locale. For example, the 
+	<code>res/values/strings.xml</code> default file might lack one string that 
+	the application needs: When the application runs in an unsupported locale and 
+	attempts to load <code>res/values/strings.xml</code>, the user will see an 
+	error message and a Force Close button. An IDE such as Eclipse will not 
+	highlight this kind of error, and you will not see the problem when you 
+	test the application on a device or emulator that is set to a supported locale.</p>
+
+<p>For more information, see <a href="#test-for-default">Testing for Default Resources</a>.</p>
+
+<h4>Design a flexible layout</h4>
+
+<p> If you need to rearrange your layout to fit a certain language (for example
+German with its long words), you can create an alternative layout for that
+language (for example <code>res/layout-de/main.xml</code>). However, doing this
+can make your application harder to maintain.  It is better to create a single
+layout that is more flexible.</p>
+
+<p>Another typical situation is a language that requires something different in
+its layout. For example, you might have a contact form that should include  two
+name fields when the application runs in Japanese, but three name fields when
+the application  runs in some other language. You could handle this in either of
+two ways:</p>
+
+<ul>
+  <li>Create  one  layout with a field that you can programmatically enable or
+disable, based on the language, or</li>
+  <li>Have the main layout include another layout that  includes the changeable
+field. The second layout can have different configurations for different
+languages.</li>
+</ul>
+
+<h4>Avoid creating more resource files and text strings than you need</h4>
+
+<p>You probably do not need to create a locale-specific
+alternative for every resource in your application. For example, the layout
+defined in the <code>res/layout/main.xml</code> file might work in any locale,
+in which case there would be no need to create any alternative layout files.
+</p>
+
+<p>Also, you might not need to create alternative text for every
+string. For example, assume the following:</p>
+
+<ul>
+  <li>Your application's default language is American
+English. Every string that the application uses is defined, using American
+English spellings, in <code>res/values/strings.xml</code>. </li>
+
+  <li>For  a few important phrases, you want to provide
+British English spelling. You want these alternative strings to be used when your
+application runs on a device in the United Kingdom. </li>
+</ul>
+
+<p>To do this, you could create a small file called
+<code>res/values-en-rGB/strings.xml</code> that includes only the strings that
+should be different when the application  runs in the U.K. For all the rest of
+the strings, the application will fall back to the defaults and use what is
+defined in <code>res/values/strings.xml</code>.</p>
+
+<h4>Use the Android Context object for manual locale lookup</h4>
+
+<p>You can look up the locale using the {@link android.content.Context} object
+that Android makes available:</p>
+
+<pre>String locale = context.getResources().getConfiguration().locale.getDisplayName();</pre>
+
+<h2 id="testing">Testing Localized Applications</h2>
+
+<h3 id="device">Testing on a Device</h3>
+<p>Keep in mind that the device you are testing may be significantly different from 
+	the devices available to consumers in other geographies. The locales available 
+	on your device may differ from those available on other devices. Also, the 
+	resolution and density of the device screen may differ, which could affect 
+	the display of strings and drawables in your UI.</p>
+
+<p>To change the locale or language on a device, use the Settings application.</p>
+
+<h3 id="emulator">Testing on an Emulator</h3>
+
+<p>For details about using the emulator, see See <a
+href="{@docRoot}tools/help/emulator.html">Android Emulator</a>.</p>
+<h4>Creating and using a custom locale</h4>
+
+<p>A &quot;custom&quot; locale is a language/region combination that the Android
+system image does not explicitly support. (For a list of supported locales in
+Android platforms see the Version Notes in the <a
+href="{@docRoot}sdk/index.html">SDK</a> tab). You can test
+how your application will run in a custom locale by creating a custom locale in
+the emulator. There are two ways to do this:</p>
+
+<ul>
+  <li>Use the Custom Locale application, which is accessible from the
+Application tab. (After you create a custom locale, switch to it by 
+pressing and holding the locale name.)</li>
+  <li>Change to a custom locale from the adb shell, as described below.</li>
+</ul>
+
+<p>When you set the emulator to a locale that is not available in the Android
+system image, the system itself will display in its default language. Your
+application, however, should localize properly.</p>
+
+<h4>Changing the emulator locale from the adb shell</h4>
+
+<p>To change the locale in the emulator by using the adb shell. </p>
+
+<ol>
+  <li>Pick the locale you want to test and determine its BCP-47 language tag, for
+example, Canadian French would be <code>fr-CA</code>.<br>
+  </li>
+  <li>Launch an emulator.</li>
+  <li>From a command-line shell on the host computer, run the following
+command:<br>
+    <code>adb shell</code><br>
+  or if you have a device attached, specify that you want the emulator by adding
+the <code>-e</code> option:<br>
+  <code>adb -e shell</code></li>
+  <li>At  the  adb shell prompt (<code>#</code>), run this command: <br>
+    <code>setprop persist.sys.locale [<em>BCP-47 language tag</em>];stop;sleep 5;start <br>
+    </code>Replace bracketed sections with the  appropriate codes from Step
+1.</li>
+</ol>
+
+<p>For instance, to test in Canadian French:</p>
+
+<p><code>setprop persist.sys.locale fr-CA;stop;sleep 5;start </code></p>
+
+<p>This will cause the emulator  to restart. (It will look like a full reboot,
+but it is not.) Once the Home screen appears again, re-launch your application (for
+example, click the Run icon in Eclipse), and the application will launch with
+the new locale. </p>
+
+<h3 id="test-for-default">Testing for Default Resources</h3>
+<p>Here's how to test whether an application includes every string resource that it needs:  </p>
+<ol><li>Set the emulator or device to a language that your application does not 
+	support. For example, if the application has French strings in 
+	<code>res/values-fr/</code> but does not have any Spanish strings in 
+	<code>res/values-es/</code>, then set the emulator's locale to Spanish. 
+	(You can use the Custom Locale application to set the emulator to an 
+	unsupported locale.)</li>
+	<li>Run the application.</li>  
+<li>If the application shows an error message and a Force Close button, it might 
+	be looking for a string that is not available. Make sure that your 
+	<code>res/values/strings.xml</code> file includes a definition for 
+	every string that the application uses.</li>
+</ol> 
+</p> 
+
+<p>If the test is successful, repeat it for other types of 
+	configurations. For example, if the application has a layout file called 
+	<code>res/layout-land/main.xml</code> but does not contain a file called 
+	<code>res/layout-port/main.xml</code>, then set the emulator or device to 
+	portrait orientation and see if the application will run. 
+
+
+
diff --git a/docs/overview-ext.html b/docs/overview-ext.html
index 5720245..f80d629 100644
--- a/docs/overview-ext.html
+++ b/docs/overview-ext.html
@@ -1,8 +1,8 @@
-<body>

-Some random libraries that we've imported:

-<ul>

-    <li>GData</li>

-    <li>Apache commons HTTPClient</li>

-    <li>A sax parser</li>

-</ul>

-</body>

+<body>
+Some random libraries that we've imported:
+<ul>
+    <li>GData</li>
+    <li>Apache commons HTTPClient</li>
+    <li>A sax parser</li>
+</ul>
+</body>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 8f7c6a62..7871aa8 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -179,10 +179,10 @@
         int tag = 0;
         String tagStr = parser.getAttributeValue(null, "tag");
         if (tagStr != null && TAG_PATTERN.matcher(tagStr).matches()) {
-            tag = tagStr.charAt(0) << 24 +
-                  tagStr.charAt(1) << 16 +
-                  tagStr.charAt(2) <<  8 +
-                  tagStr.charAt(3);
+            tag = (tagStr.charAt(0) << 24) +
+                  (tagStr.charAt(1) << 16) +
+                  (tagStr.charAt(2) <<  8) +
+                  (tagStr.charAt(3)      );
         } else {
             throw new XmlPullParserException("Invalid tag attribute value.", parser, null);
         }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 291fdc4..0dae796 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1494,9 +1494,14 @@
     }
 
     /**
-     * Get font feature settings.  Default is null.
+     * Returns the font feature settings. The format is the same as the CSS
+     * font-feature-settings attribute:
+     * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
+     *     http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
      *
-     * @return the paint's currently set font feature settings.
+     * @return the paint's currently set font feature settings. Default is null.
+     *
+     * @see #setFontFeatureSettings(String)
      */
     public String getFontFeatureSettings() {
         return mFontFeatureSettings;
@@ -1506,7 +1511,10 @@
      * Set font feature settings.
      *
      * The format is the same as the CSS font-feature-settings attribute:
-     * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
+     * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
+     *     http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
+     *
+     * @see #getFontFeatureSettings()
      *
      * @param settings the font feature settings string to use, may be null.
      */
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index cfcb4e0..deea972 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -27,12 +27,13 @@
     // APIs used by KeyChain
     String requestPrivateKey(String alias);
     byte[] getCertificate(String alias);
+    byte[] getCaCertificates(String alias);
 
     // APIs used by CertInstaller
     void installCaCertificate(in byte[] caCertificate);
 
     // APIs used by DevicePolicyManager
-    boolean installKeyPair(in byte[] privateKey, in byte[] userCert, String alias);
+    boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
     boolean removeKeyPair(String alias);
 
     // APIs used by Settings
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 0886487..cce58c2 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -42,6 +42,8 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.BlockingQueue;
@@ -389,7 +391,12 @@
 
     /**
      * Returns the {@code X509Certificate} chain for the requested
-     * alias, or null if no there is no result.
+     * alias, or null if there is no result.
+     * <p>
+     * <strong>Note:</strong> If a certificate chain was explicitly specified when the alias was
+     * installed, this method will return that chain. If only the client certificate was specified
+     * at the installation time, this method will try to build a certificate chain using all
+     * available trust anchors (preinstalled and user-added).
      *
      * <p> This method may block while waiting for a connection to another process, and must never
      * be called from the main thread.
@@ -413,11 +420,31 @@
             if (certificateBytes == null) {
                 return null;
             }
-
-            TrustedCertificateStore store = new TrustedCertificateStore();
-            List<X509Certificate> chain = store
-                    .getCertificateChain(toCertificate(certificateBytes));
-            return chain.toArray(new X509Certificate[chain.size()]);
+            X509Certificate leafCert = toCertificate(certificateBytes);
+            final byte[] certChainBytes = keyChainService.getCaCertificates(alias);
+            // If the keypair is installed with a certificate chain by either
+            // DevicePolicyManager.installKeyPair or CertInstaller, return that chain.
+            if (certChainBytes != null && certChainBytes.length != 0) {
+                Collection<X509Certificate> chain = toCertificates(certChainBytes);
+                ArrayList<X509Certificate> fullChain = new ArrayList<>(chain.size() + 1);
+                fullChain.add(leafCert);
+                fullChain.addAll(chain);
+                return fullChain.toArray(new X509Certificate[fullChain.size()]);
+            } else {
+                // If there isn't a certificate chain, either due to a pre-existing keypair
+                // installed before N, or no chain is explicitly installed under the new logic,
+                // fall back to old behavior of constructing the chain from trusted credentials.
+                //
+                // This logic exists to maintain old behaviour for already installed keypair, at
+                // the cost of potentially returning extra certificate chain for new clients who
+                // explicitly installed only the client certificate without a chain. The latter
+                // case is actually no different from pre-N behaviour of getCertificateChain(),
+                // in that sense this change introduces no regression. Besides the returned chain
+                // is still valid so the consumer of the chain should have no problem verifying it.
+                TrustedCertificateStore store = new TrustedCertificateStore();
+                List<X509Certificate> chain = store.getCertificateChain(leafCert);
+                return chain.toArray(new X509Certificate[chain.size()]);
+            }
         } catch (CertificateException e) {
             throw new KeyChainException(e);
         } catch (RemoteException e) {
@@ -486,6 +513,21 @@
         }
     }
 
+    /** @hide */
+    @NonNull
+    public static Collection<X509Certificate> toCertificates(@NonNull byte[] bytes) {
+        if (bytes == null) {
+            throw new IllegalArgumentException("bytes == null");
+        }
+        try {
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            return (Collection<X509Certificate>) certFactory.generateCertificates(
+                    new ByteArrayInputStream(bytes));
+        } catch (CertificateException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     /**
      * @hide for reuse by CertInstaller and Settings.
      * @see KeyChain#bind
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 01f9cde..1f17851 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -322,6 +322,7 @@
     $(hwui_test_common_src_files) \
     tests/microbench/main.cpp \
     tests/microbench/DisplayListCanvasBench.cpp \
+    tests/microbench/FontBench.cpp \
     tests/microbench/LinearAllocatorBench.cpp \
     tests/microbench/PathParserBench.cpp \
     tests/microbench/ShadowBench.cpp \
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 85903654..b70d586 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -108,5 +108,63 @@
     clippedBounds.doIntersect(clipRect->rect);
 }
 
+BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+            allocator, snapshot, recordedOp, false);
+    if (bakedState->computedState.clippedBounds.isEmpty()) {
+        // bounds are empty, so op is rejected
+        allocator.rewindIfLastAlloc(bakedState);
+        return nullptr;
+    }
+    return bakedState;
+}
+
+BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+}
+
+BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+    bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
+            ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
+            : true;
+
+    BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+           allocator, snapshot, recordedOp, expandForStroke);
+    if (bakedState->computedState.clippedBounds.isEmpty()) {
+        // bounds are empty, so op is rejected
+        // NOTE: this won't succeed if a clip was allocated
+        allocator.rewindIfLastAlloc(bakedState);
+        return nullptr;
+    }
+    return bakedState;
+}
+
+BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
+        Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+    if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+
+    // clip isn't empty, so construct the op
+    return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
+}
+
+BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
+        const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
+    return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
+}
+
+void BakedOpState::setupOpacity(const SkPaint* paint) {
+    computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
+            && computedState.clipState->mode == ClipMode::Rectangle
+            && MathUtils::areEqual(alpha, 1.0f)
+            && !roundRectClipState
+            && PaintUtils::isOpaquePaint(paint);
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 4e3cb8a..e1441fc 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -93,6 +93,7 @@
     Rect clippedBounds;
     int clipSideFlags = 0;
     const SkPath* localProjectionPathMask = nullptr;
+    bool opaqueOverClippedBounds = false;
 };
 
 /**
@@ -103,23 +104,10 @@
 class BakedOpState {
 public:
     static BakedOpState* tryConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
-                allocator, snapshot, recordedOp, false);
-        if (bakedState->computedState.clippedBounds.isEmpty()) {
-            // bounds are empty, so op is rejected
-            allocator.rewindIfLastAlloc(bakedState);
-            return nullptr;
-        }
-        return bakedState;
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp);
 
     static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp);
 
     enum class StrokeBehavior {
         // stroking is forced, regardless of style on paint (such as for lines)
@@ -129,35 +117,16 @@
     };
 
     static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
-                ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
-                : true;
-
-        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
-                allocator, snapshot, recordedOp, expandForStroke);
-        if (bakedState->computedState.clippedBounds.isEmpty()) {
-            // bounds are empty, so op is rejected
-            // NOTE: this won't succeed if a clip was allocated
-            allocator.rewindIfLastAlloc(bakedState);
-            return nullptr;
-        }
-        return bakedState;
-    }
+            Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior);
 
     static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
-            Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
-        if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-
-        // clip isn't empty, so construct the op
-        return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
-    }
+            Snapshot& snapshot, const ShadowOp* shadowOpPtr);
 
     static BakedOpState* directConstruct(LinearAllocator& allocator,
-            const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
-        return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
-    }
+            const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
+
+    // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
+    void setupOpacity(const SkPaint* paint);
 
     // computed state:
     ResolvedRenderState computedState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index b1314fe..9c46c00 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -37,7 +37,8 @@
         const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches& caches)
         : mCanvasState(*this)
         , mCaches(caches)
-        , mLightRadius(lightGeometry.radius) {
+        , mLightRadius(lightGeometry.radius)
+        , mDrawFbo0(!nodes.empty()) {
     ATRACE_NAME("prepare drawing commands");
 
     mLayerBuilders.reserve(layers.entries().size());
@@ -481,12 +482,17 @@
  * Defers an unmergeable, strokeable op, accounting correctly
  * for paint's style on the bounds being computed.
  */
-const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
         BakedOpState::StrokeBehavior strokeBehavior) {
     // Note: here we account for stroke when baking the op
     BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
             mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
     if (!bakedState) return nullptr; // quick rejected
+
+    if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
+        bakedState->setupOpacity(op.paint);
+    }
+
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
     return bakedState;
 }
@@ -516,6 +522,7 @@
 void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
     BakedOpState* bakedState = tryBakeOpState(op);
     if (!bakedState) return; // quick rejected
+    bakedState->setupOpacity(op.paint);
 
     // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
     // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 0b7a606..e418227 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -137,12 +137,14 @@
         }
 
         GL_CHECKPOINT(MODERATE);
-        const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
-        renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
-        GL_CHECKPOINT(MODERATE);
-        fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
-        GL_CHECKPOINT(MODERATE);
-        renderer.endFrame(fbo0.repaintRect);
+        if (CC_LIKELY(mDrawFbo0)) {
+            const LayerBuilder& fbo0 = *(mLayerBuilders[0]);
+            renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
+            GL_CHECKPOINT(MODERATE);
+            fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
+            GL_CHECKPOINT(MODERATE);
+            renderer.endFrame(fbo0.repaintRect);
+        }
     }
 
     void dump() const {
@@ -201,7 +203,7 @@
         return mAllocator.create<SkPath>();
     }
 
-    const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+    BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
             BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
 
     /**
@@ -239,6 +241,8 @@
 
     // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
     LinearAllocator mAllocator;
+
+    const bool mDrawFbo0;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 2246cf9c..47d0108 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -15,6 +15,8 @@
  */
 #include "JankTracker.h"
 
+#include "Properties.h"
+
 #include <algorithm>
 #include <cutils/ashmem.h>
 #include <cutils/log.h>
@@ -77,6 +79,11 @@
 // If a frame is > this, start counting in increments of 4ms
 static const uint32_t kBucket4msIntervals = 48;
 
+// For testing purposes to try and eliminate test infra overhead we will
+// consider any unknown delay of frame start as part of the test infrastructure
+// and filter it out of the frame profile data
+static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
+
 // This will be called every frame, performance sensitive
 // Uses bit twiddling to avoid branching while achieving the packing desired
 static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) {
@@ -242,9 +249,9 @@
     mData->totalFrameCount++;
     // Fast-path for jank-free frames
     int64_t totalDuration =
-            frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync];
+            frame[FrameInfoIndex::FrameCompleted] - frame[sFrameStart];
     uint32_t framebucket = frameCountIndexForFrameTime(
-            totalDuration, mData->frameCounts.size());
+            totalDuration, mData->frameCounts.size() - 1);
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
         mData->frameCounts[framebucket]++;
@@ -280,6 +287,9 @@
 }
 
 void JankTracker::dumpData(const ProfileData* data, int fd) {
+    if (sFrameStart != FrameInfoIndex::IntendedVsync) {
+        dprintf(fd, "\nNote: Data has been filtered!");
+    }
     dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
     dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
     dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
@@ -305,6 +315,9 @@
     mData->totalFrameCount = 0;
     mData->jankFrameCount = 0;
     mData->statStartTime = systemTime(CLOCK_MONOTONIC);
+    sFrameStart = Properties::filterOutTestOverhead
+            ? FrameInfoIndex::HandleInputStart
+            : FrameInfoIndex::IntendedVsync;
 }
 
 uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index e6a95ff..eea11bf 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -236,6 +236,21 @@
     mClearRects.push_back(rect);
 }
 
+void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
+    if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
+        // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
+        // and issue them together in one draw.
+        flushLayerClears(allocator);
+
+        if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
+                && bakedState->computedState.opaqueOverClippedBounds
+                && bakedState->computedState.clippedBounds.contains(repaintRect))) {
+            // discard all deferred drawing ops, since new one will occlude them
+            clear();
+        }
+    }
+}
+
 void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
     if (CC_UNLIKELY(!mClearRects.empty())) {
         const int vertCount = mClearRects.size() * 4;
@@ -270,11 +285,7 @@
 
 void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
         BakedOpState* op, batchid_t batchId) {
-    if (batchId != OpBatchType::CopyToLayer) {
-        // if first op after one or more unclipped saveLayers, flush the layer clears
-        flushLayerClears(allocator);
-    }
-
+    onDeferOp(allocator, op);
     OpBatch* targetBatch = mBatchLookup[batchId];
 
     size_t insertBatchIndex = mBatches.size();
@@ -295,10 +306,7 @@
 
 void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
         BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
-    if (batchId != OpBatchType::CopyToLayer) {
-        // if first op after one or more unclipped saveLayers, flush the layer clears
-        flushLayerClears(allocator);
-    }
+    onDeferOp(allocator, op);
     MergingOpBatch* targetBatch = nullptr;
 
     // Try to merge with any existing batch with same mergeId
@@ -348,6 +356,14 @@
     }
 }
 
+void LayerBuilder::clear() {
+    mBatches.clear();
+    for (int i = 0; i < OpBatchType::Count; i++) {
+        mBatchLookup[i] = nullptr;
+        mMergingBatchLookup[i].clear();
+    }
+}
+
 void LayerBuilder::dump() const {
     ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
             this, width, height, offscreenBuffer, beginLayerOp,
diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h
index 4a7ca2d..4de432c 100644
--- a/libs/hwui/LayerBuilder.h
+++ b/libs/hwui/LayerBuilder.h
@@ -100,9 +100,7 @@
         return mBatches.empty();
     }
 
-    void clear() {
-        mBatches.clear();
-    }
+    void clear();
 
     void dump() const;
 
@@ -117,6 +115,7 @@
     // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
     std::vector<BakedOpState*> activeUnclippedSaveLayers;
 private:
+    void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
     void flushLayerClears(LinearAllocator& allocator);
 
     std::vector<BatchBase*> mBatches;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index bbd8c72..6f68c2b 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -66,6 +66,8 @@
 
 bool Properties::waitForGpuCompletion = false;
 
+bool Properties::filterOutTestOverhead = false;
+
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
 
@@ -156,6 +158,8 @@
     textureCacheFlushRate = std::max(0.0f, std::min(1.0f,
             property_get_float(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, DEFAULT_TEXTURE_CACHE_FLUSH_RATE)));
 
+    filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
+
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
             || (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 249b5b0..171873d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -151,6 +151,8 @@
  */
 #define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates"
 
+#define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Runtime configuration properties
 ///////////////////////////////////////////////////////////////////////////////
@@ -294,6 +296,10 @@
     // Should be used only by test apps
     static bool waitForGpuCompletion;
 
+    // Should only be set by automated tests to try and filter out
+    // any overhead they add
+    static bool filterOutTestOverhead;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 9a825fd..8e04c87 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -356,8 +356,6 @@
 }
 
 void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) {
-    ATRACE_NAME("Precache Glyphs");
-
     if (numGlyphs == 0 || glyphs == nullptr) {
         return;
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eee5278..6933b2f 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -86,10 +86,12 @@
     freePrefetechedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
+#if !HWUI_NEW_OPS
     if (mCanvas) {
         delete mCanvas;
         mCanvas = nullptr;
     }
+#endif
 }
 
 void CanvasContext::setSurface(Surface* surface) {
@@ -587,9 +589,11 @@
 
 void CanvasContext::buildLayer(RenderNode* node) {
     ATRACE_CALL();
-    if (!mEglManager.hasEglContext() || !mCanvas) {
-        return;
-    }
+    if (!mEglManager.hasEglContext()) return;
+#if !HWUI_NEW_OPS
+    if (!mCanvas) return;
+#endif
+
     // buildLayer() will leave the tree in an unknown state, so we must stop drawing
     stopDrawing();
 
@@ -609,7 +613,15 @@
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
 #if HWUI_NEW_OPS
-    // TODO: support buildLayer
+    static const std::vector< sp<RenderNode> > emptyNodeList;
+    auto& caches = Caches::getInstance();
+    FrameBuilder frameBuilder(mLayerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
+            emptyNodeList, mLightGeometry, mContentDrawBounds, caches);
+    mLayerUpdateQueue.clear();
+    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+            mOpaque, mLightInfo);
+    LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
+    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
 #else
     mCanvas->markLayersAsBuildLayers();
     mCanvas->flushLayerUpdates();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 6706c30..6d0889e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -196,10 +196,11 @@
     RingBuffer<SwapHistory, 3> mSwapHistory;
 
     bool mOpaque;
-    OpenGLRenderer* mCanvas = nullptr;
 #if HWUI_NEW_OPS
     BakedOpRenderer::LightInfo mLightInfo;
     FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
+#else
+    OpenGLRenderer* mCanvas = nullptr;
 #endif
 
     bool mHaveNewSurface = false;
diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp
new file mode 100644
index 0000000..df3d041
--- /dev/null
+++ b/libs/hwui/tests/microbench/FontBench.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/benchmark.h>
+
+#include "GammaFontRenderer.h"
+#include "tests/common/TestUtils.h"
+
+#include <SkPaint.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+void BM_FontRenderer_precache_cachehits(benchmark::State& state) {
+    TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) {
+        SkPaint paint;
+        paint.setTextSize(20);
+        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+        GammaFontRenderer gammaFontRenderer;
+        FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
+        fontRenderer.setFont(&paint, SkMatrix::I());
+
+        std::vector<glyph_t> glyphs;
+        std::vector<float> positions;
+        float totalAdvance;
+        uirenderer::Rect bounds;
+        TestUtils::layoutTextUnscaled(paint, "This is a test",
+                &glyphs, &positions, &totalAdvance, &bounds);
+
+        fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+
+        while (state.KeepRunning()) {
+            fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
+        }
+    });
+}
+BENCHMARK(BM_FontRenderer_precache_cachehits);
diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
index 9daf633..0aef620 100644
--- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp
+++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp
@@ -113,15 +113,17 @@
 };
 
 void BM_FrameBuilder_defer_scene(benchmark::State& state) {
-    const char* sceneName = *(SCENES.begin() + state.range_x());
-    state.SetLabel(sceneName);
-    auto nodes = getSyncedSceneNodes(sceneName);
-    while (state.KeepRunning()) {
-        FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
-                SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
-                nodes, sLightGeometry, Caches::getInstance());
-        benchmark::DoNotOptimize(&frameBuilder);
-    }
+    TestUtils::runOnRenderThread([&state](RenderThread& thread) {
+        const char* sceneName = *(SCENES.begin() + state.range_x());
+        state.SetLabel(sceneName);
+        auto nodes = getSyncedSceneNodes(sceneName);
+        while (state.KeepRunning()) {
+            FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
+                    SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
+                    nodes, sLightGeometry, Caches::getInstance());
+            benchmark::DoNotOptimize(&frameBuilder);
+        }
+    });
 }
 BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1);
 
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e97aaa6..7dfafb9 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -30,6 +30,7 @@
 namespace uirenderer {
 
 const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const std::vector< sp<RenderNode> > sEmptyNodeList;
 const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
 
 
@@ -216,6 +217,123 @@
             << "Expect number of ops = 2 * loop count";
 }
 
+RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) {
+    class EmptyNoFbo0TestRenderer : public TestRendererBase {
+    public:
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            ADD_FAILURE() << "Primary frame draw not expected in this test";
+        }
+        void endFrame(const Rect& repaintRect) override {
+            ADD_FAILURE() << "Primary frame draw not expected in this test";
+        }
+    };
+
+    // Pass empty node list, so no work is enqueued for Fbo0
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            sEmptyNodeList, sLightGeometry, Caches::getInstance());
+    EmptyNoFbo0TestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+}
+
+RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) {
+    class EmptyWithFbo0TestRenderer : public TestRendererBase {
+    public:
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void endFrame(const Rect& repaintRect) override {
+            EXPECT_EQ(1, mIndex++);
+        }
+    };
+    auto node = TestUtils::createNode(10, 10, 110, 110,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        // no drawn content
+    });
+    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+
+    // Draw, but pass empty node list, so no work is done for primary frame
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedNodeList, sLightGeometry, Caches::getInstance());
+    EmptyWithFbo0TestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced,"
+            " but fbo0 update lifecycle should still be observed";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+    class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(mIndex++, 0) << "Should be one rect";
+            EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
+                    << "Last rect should occlude others.";
+        }
+    };
+    auto node = TestUtils::createNode(0, 0, 200, 200,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 200, 200, SkPaint());
+        canvas.drawRect(0, 0, 200, 200, SkPaint());
+        canvas.drawRect(10, 10, 190, 190, SkPaint());
+    });
+
+    // Damage (and therefore clip) is same as last draw, subset of renderable area.
+    // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
+    SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190);
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200,
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
+            << "Recording must not have rejected ops, in order for this test to be valid";
+
+    AvoidOverdrawRectsTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+    static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
+            SkColorType::kRGB_565_SkColorType);
+    static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
+            SkColorType::kAlpha_8_SkColorType);
+    class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
+    public:
+        void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+            EXPECT_LT(mIndex++, 2) << "Should be two bitmaps";
+            switch(mIndex++) {
+            case 0:
+                EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+                break;
+            case 1:
+                EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+                break;
+            default:
+                ADD_FAILURE() << "Only two ops expected.";
+            }
+        }
+    };
+
+    auto node = TestUtils::createNode(0, 0, 50, 50,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        canvas.drawRect(0, 0, 50, 50, SkPaint());
+        canvas.drawRect(0, 0, 50, 50, SkPaint());
+        canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+
+        // only the below draws should remain, since they're
+        canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
+        canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+    });
+
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50,
+            TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+    EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
+            << "Recording must not have rejected ops, in order for this test to be valid";
+
+    AvoidOverdrawBitmapsTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly one op";
+}
+
 RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
     class ClippedMergingTestRenderer : public TestRendererBase {
     public:
@@ -1078,6 +1196,64 @@
     *(parent->getLayerHandle()) = nullptr;
 }
 
+
+RENDERTHREAD_TEST(FrameBuilder, buildLayer) {
+    class BuildLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+            EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
+        }
+        void onColorOp(const ColorOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+
+            EXPECT_TRUE(state.computedState.transform.isIdentity())
+                    << "Transform should be reset within layer";
+
+            EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
+                    << "Damage rect should be used to clip layer content";
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+            ADD_FAILURE() << "Primary frame draw not expected in this test";
+        }
+        void endFrame(const Rect& repaintRect) override {
+            ADD_FAILURE() << "Primary frame draw not expected in this test";
+        }
+    };
+
+    auto node = TestUtils::createNode(10, 10, 110, 110,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
+        canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode);
+    });
+    OffscreenBuffer** layerHandle = node->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *layerHandle = &layer;
+
+    auto syncedNodeList = TestUtils::createSyncedNodeList(node);
+
+    // only enqueue partial damage
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+    // Draw, but pass empty node list, so no work is done for primary frame
+    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
+            sEmptyNodeList, sLightGeometry, Caches::getInstance());
+    BuildLayerTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
 static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index db53713..4faab9a 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -67,6 +67,21 @@
                 && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
     }
 
+    static bool isOpaquePaint(const SkPaint* paint) {
+        if (!paint) return true; // default (paintless) behavior is SrcOver, black
+
+        if (paint->getAlpha() != 0xFF
+                || PaintUtils::isBlendedShader(paint->getShader())
+                || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) {
+            return false;
+        }
+
+        // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
+        SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+        return mode == SkXfermode::Mode::kSrcOver_Mode
+                || mode == SkXfermode::Mode::kSrc_Mode;
+    }
+
     static bool isBlendedShader(const SkShader* shader) {
         if (shader == nullptr) {
             return false;
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 9c509d6..d76feec 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -121,7 +121,7 @@
      * @param satIndex the index of the satellite in the list.
      */
     public float getElevationDegrees(int satIndex) {
-        return 0f;
+        return mElevations[satIndex];
     }
 
     /**
diff --git a/location/java/android/location/INetInitiatedListener.aidl b/location/java/android/location/INetInitiatedListener.aidl
index f2f5a32..fc64dd6 100644
--- a/location/java/android/location/INetInitiatedListener.aidl
+++ b/location/java/android/location/INetInitiatedListener.aidl
@@ -1,26 +1,26 @@
-/*

-**

-** Copyright 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.location;

-

-/**

- * {@hide}

- */

-interface INetInitiatedListener

-{

-    boolean sendNiResponse(int notifId, int userResponse);

-}

+/*
+**
+** Copyright 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.location;
+
+/**
+ * {@hide}
+ */
+interface INetInitiatedListener
+{
+    boolean sendNiResponse(int notifId, int userResponse);
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index d9caf03..b6ff41e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1120,7 +1120,7 @@
      * <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
+     * @throws IllegalStateException if track is not initialized.
      */
     public int getBufferSizeInFrames() {
         return native_get_buffer_size_frames();
@@ -1147,7 +1147,7 @@
      * @param bufferSizeInFrames requested buffer size
      * @return the actual buffer size in frames or an error code,
      *    {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION}
-     * @throws IllegalStateException
+     * @throws IllegalStateException if track is not initialized.
      */
     public int setBufferSizeInFrames(int bufferSizeInFrames) {
         if (mDataLoadMode == MODE_STATIC || mState == STATE_UNINITIALIZED) {
@@ -1176,7 +1176,7 @@
      *  <p> See also {@link AudioManager#getProperty(String)} for key
      *  {@link AudioManager#PROPERTY_OUTPUT_FRAMES_PER_BUFFER}.
      *  @return maximum size in frames of the <code>AudioTrack</code> buffer.
-     *  @throws IllegalStateException
+     *  @throws IllegalStateException if track is not initialized.
      */
     public int getBufferCapacityInFrames() {
         return native_get_buffer_capacity_frames();
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 4bf0852..cd2d51d 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -693,7 +693,7 @@
      */
     public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
         if (fileDescriptor == null) {
-            throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
+            throw new IllegalArgumentException("fileDescriptor cannot be null");
         }
         mAssetInputStream = null;
         mFilename = null;
@@ -705,7 +705,7 @@
             try {
                 fileDescriptor = Os.dup(fileDescriptor);
             } catch (ErrnoException e) {
-                e.rethrowAsIOException();
+                throw e.rethrowAsIOException();
             }
         } else {
             mSeekableFileDescriptor = null;
@@ -811,6 +811,9 @@
      */
     public void setAttribute(String tag, String value) {
         for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+            if (i == IFD_THUMBNAIL_HINT && !mHasThumbnail) {
+                continue;
+            }
             if (sExifTagMapsForWriting[i].containsKey(tag)) {
                 mAttributes[i].put(tag, value);
             }
@@ -818,6 +821,35 @@
     }
 
     /**
+     * Update the values of the tags in the tag groups if any value for the tag already was stored.
+     *
+     * @param tag the name of the tag.
+     * @param value the value of the tag.
+     * @return Returns {@code true} if updating is placed.
+     */
+    private boolean updateAttribute(String tag, String value) {
+        boolean updated = false;
+        for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+            if (mAttributes[i].containsKey(tag)) {
+                mAttributes[i].put(tag, value);
+                updated = true;
+            }
+        }
+        return updated;
+    }
+
+    /**
+     * Remove any values of the specified tag.
+     *
+     * @param tag the name of the tag.
+     */
+    private void removeAttribute(String tag) {
+        for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
+            mAttributes[i].remove(tag);
+        }
+    }
+
+    /**
      * This function decides which parser to read the image data according to the given input stream
      * type and the content of the input stream. In each case, it reads the first three bytes to
      * determine whether the image data format is JPEG or not.
@@ -853,7 +885,7 @@
         } catch (IOException e) {
             // Ignore exceptions in order to keep the compatibility with the old versions of
             // ExifInterface.
-            Log.w(TAG, "Invalid JPEG: ExifInterface got an unsupported image format file"
+            Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file"
                     + "(ExifInterface supports JPEG and some RAW image formats only) "
                     + "or a corrupted JPEG file to ExifInterface.", e);
         } finally {
@@ -882,27 +914,22 @@
         // Mark for disabling the save feature.
         mIsRaw = true;
 
-        for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
-            String attrName = (String) entry.getKey();
-
-            switch (attrName) {
-                case TAG_HAS_THUMBNAIL:
-                    mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
-                    break;
-                case TAG_THUMBNAIL_OFFSET:
-                    mThumbnailOffset = Integer.parseInt((String) entry.getValue());
-                    break;
-                case TAG_THUMBNAIL_LENGTH:
-                    mThumbnailLength = Integer.parseInt((String) entry.getValue());
-                    break;
-                case TAG_THUMBNAIL_DATA:
-                    mThumbnailBytes = (byte[]) entry.getValue();
-                    break;
-                default:
-                    setAttribute(attrName, (String) entry.getValue());
-                    break;
-            }
+        String value = (String) map.remove(TAG_HAS_THUMBNAIL);
+        mHasThumbnail = value != null && value.equalsIgnoreCase("true");
+        value = (String) map.remove(TAG_THUMBNAIL_OFFSET);
+        if (value != null) {
+            mThumbnailOffset = Integer.parseInt(value);
         }
+        value = (String) map.remove(TAG_THUMBNAIL_LENGTH);
+        if (value != null) {
+            mThumbnailLength = Integer.parseInt(value);
+        }
+        mThumbnailBytes = (byte[]) map.remove(TAG_THUMBNAIL_DATA);
+
+        for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
+            setAttribute((String) entry.getKey(), (String) entry.getValue());
+        }
+
         return true;
     }
 
@@ -928,7 +955,7 @@
     /**
      * Save the tag data into the original image file. This is expensive because it involves
      * copying all the data from one file to another and deleting the old file and renaming the
-     * other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write
+     * other. It's best to use {@link #setAttribute(String,String)} to set all attributes to write
      * and make a single call rather than multiple calls for each attribute.
      */
     public void saveAttributes() throws IOException {
@@ -963,7 +990,7 @@
                 Streams.copy(in, out);
             }
         } catch (ErrnoException e) {
-            e.rethrowAsIOException();
+            throw e.rethrowAsIOException();
         } finally {
             IoUtils.closeQuietly(in);
             IoUtils.closeQuietly(out);
@@ -982,7 +1009,7 @@
             }
             saveJpegAttributes(in, out);
         } catch (ErrnoException e) {
-            e.rethrowAsIOException();
+            throw e.rethrowAsIOException();
         } finally {
             IoUtils.closeQuietly(in);
             IoUtils.closeQuietly(out);
@@ -1276,7 +1303,8 @@
                         throw new IOException("Invalid exif");
                     }
                     length = 0;
-                    setAttribute("UserComment", new String(bytes, Charset.forName("US-ASCII")));
+                    mAttributes[IFD_EXIF_HINT].put(TAG_USER_COMMENT,
+                            new String(bytes, Charset.forName("US-ASCII")));
                     break;
                 }
 
@@ -1296,9 +1324,10 @@
                     if (dataInputStream.skipBytes(1) != 1) {
                         throw new IOException("Invalid SOFx");
                     }
-                    setAttribute("ImageLength",
+                    mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH,
                             String.valueOf(dataInputStream.readUnsignedShort()));
-                    setAttribute("ImageWidth", String.valueOf(dataInputStream.readUnsignedShort()));
+                    mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH,
+                            String.valueOf(dataInputStream.readUnsignedShort()));
                     length -= 5;
                     break;
                 }
@@ -1521,31 +1550,31 @@
         convertToInt(TAG_GPS_ALTITUDE_REF);
         convertToRational(TAG_GPS_LONGITUDE);
         convertToRational(TAG_GPS_LATITUDE);
-        convertToTimetamp(TAG_GPS_TIMESTAMP);
+        convertToTimestamp(TAG_GPS_TIMESTAMP);
 
         // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
-        String valueOfDateTimeOriginal = getAttribute("DateTimeOriginal");
+        String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
         if (valueOfDateTimeOriginal != null) {
-            setAttribute(TAG_DATETIME, valueOfDateTimeOriginal);
+            mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME, valueOfDateTimeOriginal);
         }
 
         // Add the default value.
         if (getAttribute(TAG_IMAGE_WIDTH) == null) {
-            setAttribute(TAG_IMAGE_WIDTH, "0");
+            mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, "0");
         }
         if (getAttribute(TAG_IMAGE_LENGTH) == null) {
-            setAttribute(TAG_IMAGE_LENGTH, "0");
+            mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, "0");
         }
         if (getAttribute(TAG_ORIENTATION) == null) {
-            setAttribute(TAG_ORIENTATION, "0");
+            mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION, "0");
         }
         if (getAttribute(TAG_LIGHT_SOURCE) == null) {
-            setAttribute(TAG_LIGHT_SOURCE, "0");
+            mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE, "0");
         }
     }
 
     // Converts the tag value to timestamp; Otherwise deletes the given tag.
-    private void convertToTimetamp(String tagName) {
+    private void convertToTimestamp(String tagName) {
         String entryValue = getAttribute(tagName);
         if (entryValue == null) return;
         int dataFormat = getDataFormatOfExifEntryValue(entryValue);
@@ -1566,9 +1595,9 @@
                 int value = numerator / denominator;
                 stringBuilder.append(String.format("%02d", value));
             }
-            setAttribute(tagName, stringBuilder.toString());
+            updateAttribute(tagName, stringBuilder.toString());
         } else if (dataFormat != IFD_FORMAT_STRING) {
-            setAttribute(tagName, null);
+            removeAttribute(tagName);
         }
     }
 
@@ -1595,14 +1624,14 @@
                     }
                     stringBuilder.append((double) numerator / denominator);
                 }
-                setAttribute(tagName, stringBuilder.toString());
+                updateAttribute(tagName, stringBuilder.toString());
                 break;
             }
             case IFD_FORMAT_DOUBLE:
                 // Keep it as is.
                 break;
             default:
-                setAttribute(tagName, null);
+                removeAttribute(tagName);
                 break;
         }
     }
@@ -1624,14 +1653,14 @@
                     double doubleValue = Double.parseDouble(component);
                     stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000);
                 }
-                setAttribute(tagName, stringBuilder.toString());
+                updateAttribute(tagName, stringBuilder.toString());
                 break;
             }
             case IFD_FORMAT_SRATIONAL:
                 // Keep it as is.
                 break;
             default:
-                setAttribute(tagName, null);
+                removeAttribute(tagName);
                 break;
         }
     }
@@ -1642,7 +1671,7 @@
         if (entryValue == null) return;
         int dataFormat = getDataFormatOfExifEntryValue(entryValue);
         if (dataFormat != IFD_FORMAT_SLONG) {
-            setAttribute(tagName, null);
+            removeAttribute(tagName);
         }
     }
 
@@ -1758,7 +1787,7 @@
                 String entryValue = readExifEntryValue(
                         dataInputStream, dataFormat, numberOfComponents);
                 if (entryValue != null) {
-                    setAttribute(tagName, entryValue);
+                    mAttributes[hint].put(tagName, entryValue);
                 }
             } else {
                 StringBuilder entryValueBuilder = new StringBuilder();
@@ -1769,7 +1798,7 @@
                     entryValueBuilder.append(readExifEntryValue(
                             dataInputStream, dataFormat, numberOfComponents));
                 }
-                setAttribute(tagName, entryValueBuilder.toString());
+                mAttributes[hint].put(tagName, entryValueBuilder.toString());
             }
 
             if (dataInputStream.peek() != nextEntryOffset) {
@@ -1886,11 +1915,11 @@
 
         // Remove IFD pointer tags (we'll re-add it later.)
         for (ExifTag tag : IFD_POINTER_TAGS) {
-            setAttribute(tag.name, null);
+            removeAttribute(tag.name);
         }
         // Remove old thumbnail data
-        setAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name, null);
-        setAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, null);
+        removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
+        removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
 
         // Remove null value tags.
         for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index b63df6f..d2dc440 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -554,6 +554,10 @@
                     if (bufidx >= 0) {
                         size_t bufsize;
                         uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
+                        if (buf == nullptr) {
+                            ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
+                            break;
+                        }
                         int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
                         ALOGV("read %d", sampleSize);
                         if (sampleSize < 0) {
@@ -563,10 +567,16 @@
                         }
                         int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
 
-                        AMediaCodec_queueInputBuffer(codec, bufidx,
+                        media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
                                 0 /* offset */, sampleSize, presentationTimeUs,
                                 sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-                        AMediaExtractor_advance(ex);
+                        if (mstatus != AMEDIA_OK) {
+                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                            ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
+                                    (int)mstatus);
+                            break;
+                        }
+                        (void)AMediaExtractor_advance(ex);
                     }
                 }
 
@@ -581,6 +591,10 @@
                     ALOGV("got decoded buffer size %d", info.size);
 
                     uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
+                    if (buf == nullptr) {
+                        ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
+                        break;
+                    }
                     size_t dataSize = info.size;
                     if (dataSize > available) {
                         dataSize = available;
@@ -589,7 +603,14 @@
                     writePos += dataSize;
                     written += dataSize;
                     available -= dataSize;
-                    AMediaCodec_releaseOutputBuffer(codec, status, false /* render */);
+                    media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+                            codec, status, false /* render */);
+                    if (mstatus != AMEDIA_OK) {
+                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                        ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
+                                (int)mstatus);
+                        break;
+                    }
                     if (available == 0) {
                         // there might be more data, but there's no space for it
                         sawOutputEOS = true;
@@ -610,21 +631,21 @@
                 }
             }
 
-            AMediaCodec_stop(codec);
-            AMediaCodec_delete(codec);
-            AMediaExtractor_delete(ex);
+            (void)AMediaCodec_stop(codec);
+            (void)AMediaCodec_delete(codec);
+            (void)AMediaExtractor_delete(ex);
             if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
                     !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
-                AMediaFormat_delete(format);
+                (void)AMediaFormat_delete(format);
                 return UNKNOWN_ERROR;
             }
-            AMediaFormat_delete(format);
+            (void)AMediaFormat_delete(format);
             *memsize = written;
             return OK;
         }
-        AMediaFormat_delete(format);
+        (void)AMediaFormat_delete(format);
     }
-    AMediaExtractor_delete(ex);
+    (void)AMediaExtractor_delete(ex);
     return UNKNOWN_ERROR;
 }
 
diff --git a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
index d556ad3..3c5dd37 100644
--- a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
+++ b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
@@ -103,7 +103,7 @@
         <item>600</item>
         <item>800</item>
         <item>1</item>
-        <item />
+        <item>0</item>
     </array>
     <array name="volantis_jpg">
         <item>false</item>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index 6207f7d..dde9bda 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -45,7 +45,7 @@
     private static final String TAG = ExifInterface.class.getSimpleName();
     private static final boolean VERBOSE = false;  // lots of logging
 
-    private static final double DIFFERENCE_TOLERANCE = .005;
+    private static final double DIFFERENCE_TOLERANCE = .001;
 
     // List of files.
     private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
@@ -111,11 +111,11 @@
         public final String gpsLongitudeRef;
         public final String gpsProcessingMethod;
         public final String gpsTimestamp;
-        public final String imageLength;
-        public final String imageWidth;
+        public final int imageLength;
+        public final int imageWidth;
         public final String iso;
-        public final String whiteBalance;
-        public final String orientation;
+        public final int orientation;
+        public final int whiteBalance;
 
         private static String getString(TypedArray typedArray, int index) {
             String stringValue = typedArray.getString(index);
@@ -137,7 +137,7 @@
             longitude = typedArray.getFloat(5, 0f);
             altitude = typedArray.getFloat(6, 0f);
 
-            // Read values.
+            // Reads values.
             make = getString(typedArray, 7);
             model = getString(typedArray, 8);
             aperture = typedArray.getFloat(9, 0f);
@@ -154,11 +154,11 @@
             gpsLongitudeRef = getString(typedArray, 20);
             gpsProcessingMethod = getString(typedArray, 21);
             gpsTimestamp = getString(typedArray, 22);
-            imageLength = getString(typedArray, 23);
-            imageWidth = getString(typedArray, 24);
+            imageLength = typedArray.getInt(23, 0);
+            imageWidth = typedArray.getInt(24, 0);
             iso = getString(typedArray, 25);
-            orientation = getString(typedArray, 26);
-            whiteBalance = getString(typedArray, 27);
+            orientation = typedArray.getInt(26, 0);
+            whiteBalance = typedArray.getInt(27, 0);
 
             typedArray.recycle();
         }
@@ -208,11 +208,13 @@
                             + bitmap.getHeight());
                 }
             } else {
-                Log.e(TAG, fileName + " Corrupted image (no thumbnail)");
+                Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
+                        + "A thumbnail is expected.");
             }
         } else {
             if (exifInterface.getThumbnail() != null) {
-                Log.e(TAG, fileName + " Corrupted image (a thumbnail exists)");
+                Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
+                        + "No thumbnail is expected.");
             } else {
                 Log.v(TAG, fileName + " No thumbnail");
             }
@@ -226,28 +228,27 @@
             Log.v(TAG, fileName + " Latitude = " + latLong[0]);
             Log.v(TAG, fileName + " Longitude = " + latLong[1]);
         } else {
-            Log.v(TAG, fileName + "No latlong data");
+            Log.v(TAG, fileName + " No latlong data");
         }
 
         // Prints values.
         for (String tagKey : EXIF_TAGS) {
             String tagValue = exifInterface.getAttribute(tagKey);
-            Log.v(TAG, fileName + "Key{" + tagKey + "} = '" + tagValue + "'");
+            Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
         }
     }
 
-    private void compareFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
-        String stringValue = exifInterface.getAttribute(tag);
-        float floatValue = 0f;
-
-        if (stringValue != null) {
-            floatValue = Float.parseFloat(stringValue);
-        }
-
-        assertEquals(expectedValue, floatValue, DIFFERENCE_TOLERANCE);
+    private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
+        int intValue = exifInterface.getAttributeInt(tag, 0);
+        assertEquals(expectedValue, intValue);
     }
 
-    private void compareStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
+    private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
+        double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
+        assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
+    }
+
+    private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
         String stringValue = exifInterface.getAttribute(tag);
         if (stringValue != null) {
             stringValue = stringValue.trim();
@@ -257,7 +258,10 @@
     }
 
     private void compareWithExpectedValue(ExifInterface exifInterface,
-            ExpectedValue expectedValue) {
+            ExpectedValue expectedValue, String verboseTag) {
+        if (VERBOSE) {
+            printExifTagsAndValues(verboseTag, exifInterface);
+        }
         // Checks a thumbnail image.
         assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
         if (expectedValue.hasThumbnail) {
@@ -282,78 +286,144 @@
         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
 
         // Checks values.
-        compareStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
-        compareStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
-        compareFloatTag(exifInterface, ExifInterface.TAG_APERTURE, expectedValue.aperture);
-        compareStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
-        compareFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
-        compareFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
-        compareStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
+        assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
+        assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
+        assertFloatTag(exifInterface, ExifInterface.TAG_APERTURE, expectedValue.aperture);
+        assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime);
+        assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
+        assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
+        assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
                 expectedValue.gpsAltitudeRef);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP,
-                expectedValue.gpsDatestamp);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
                 expectedValue.gpsLatitudeRef);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE,
-                expectedValue.gpsLongitude);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
                 expectedValue.gpsLongitudeRef);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
                 expectedValue.gpsProcessingMethod);
-        compareStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP,
-                expectedValue.gpsTimestamp);
-        compareStringTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
-        compareStringTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
-        compareStringTag(exifInterface, ExifInterface.TAG_ISO, expectedValue.iso);
-        compareStringTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
-        compareStringTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE,
-                expectedValue.whiteBalance);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
+        assertStringTag(exifInterface, ExifInterface.TAG_ISO, expectedValue.iso);
+        assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
+        assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
     }
 
     private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
             throws IOException {
-        // Created via path.
+        String verboseTag = imageFile.getName();
+
+        // Creates via path.
         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        if (VERBOSE) {
-            printExifTagsAndValues(imageFile.getName(), exifInterface);
-        }
-        compareWithExpectedValue(exifInterface, expectedValue);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
 
-        // Created from an asset file.
-        InputStream in = mContext.getAssets().open(imageFile.getName());
-        exifInterface = new ExifInterface(in);
-        if (VERBOSE) {
-            printExifTagsAndValues(imageFile.getName(), exifInterface);
-        }
-        compareWithExpectedValue(exifInterface, expectedValue);
-
-        // Created via InputStream.
-        in = null;
+        // Creates from an asset file.
+        InputStream in = null;
         try {
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            in = mContext.getAssets().open(imageFile.getName());
             exifInterface = new ExifInterface(in);
-            if (VERBOSE) {
-                printExifTagsAndValues(imageFile.getName(), exifInterface);
-            }
-            compareWithExpectedValue(exifInterface, expectedValue);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
         } finally {
             IoUtils.closeQuietly(in);
         }
 
-        // Created via FileDescriptor.
+        // Creates via InputStream.
+        in = null;
         try {
-            FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
-            exifInterface = new ExifInterface(fd);
-            if (VERBOSE) {
-                printExifTagsAndValues(imageFile.getName(), exifInterface);
-            }
-            compareWithExpectedValue(exifInterface, expectedValue);
-        } catch (ErrnoException e) {
-            e.rethrowAsIOException();
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            exifInterface = new ExifInterface(in);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        } finally {
+            IoUtils.closeQuietly(in);
         }
+
+        // Creates via FileDescriptor.
+        FileDescriptor fd = null;
+        try {
+            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+    }
+
+    private void testSaveAttributes_withFileName(File imageFile, ExpectedValue expectedValue)
+            throws IOException {
+        String verboseTag = imageFile.getName();
+
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+        // Test for modifying one attribute.
+        String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+        // Restore the backup value.
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+    }
+
+    private void testSaveAttributes_withFileDescriptor(File imageFile, ExpectedValue expectedValue)
+            throws IOException {
+        String verboseTag = imageFile.getName();
+
+        FileDescriptor fd = null;
+        try {
+            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
+            ExifInterface exifInterface = new ExifInterface(fd);
+            exifInterface.saveAttributes();
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+            // Test for modifying one attribute.
+            String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+            exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+            exifInterface.saveAttributes();
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+            // Restore the backup value.
+            exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+            exifInterface.saveAttributes();
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+    }
+
+    private void testSaveAttributes_withInputStream(File imageFile, ExpectedValue expectedValue)
+            throws IOException {
+        InputStream in = null;
+        try {
+            in = getContext().getAssets().open(imageFile.getName());
+            ExifInterface exifInterface = new ExifInterface(in);
+            exifInterface.saveAttributes();
+        } catch (UnsupportedOperationException e) {
+            // Expected. saveAttributes is not supported with an ExifInterface object which was
+            // created with InputStream.
+            return;
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+        fail("Should not reach here!");
     }
 
     private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
@@ -366,30 +436,9 @@
         testExifInterfaceCommon(imageFile, expectedValue);
 
         // Test for saving attributes.
-        ExifInterface exifInterface;
-        try {
-            FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
-            exifInterface = new ExifInterface(fd);
-            exifInterface.saveAttributes();
-            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
-            exifInterface = new ExifInterface(fd);
-            if (VERBOSE) {
-                printExifTagsAndValues(fileName, exifInterface);
-            }
-            compareWithExpectedValue(exifInterface, expectedValue);
-        } catch (ErrnoException e) {
-            e.rethrowAsIOException();
-        }
-
-        // Test for modifying one attribute.
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        if (VERBOSE) {
-            printExifTagsAndValues(fileName, exifInterface);
-        }
-        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+        testSaveAttributes_withFileName(imageFile, expectedValue);
+        testSaveAttributes_withFileDescriptor(imageFile, expectedValue);
+        testSaveAttributes_withInputStream(imageFile, expectedValue);
     }
 
     private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
diff --git a/packages/DocumentsUI/perf-tests/Android.mk b/packages/DocumentsUI/perf-tests/Android.mk
index 11c163b..5ebf85f 100644
--- a/packages/DocumentsUI/perf-tests/Android.mk
+++ b/packages/DocumentsUI/perf-tests/Android.mk
@@ -2,8 +2,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-#LOCAL_SDK_VERSION := current
 
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-java-files-under, ../tests/src/com/android/documentsui/bots) \
     ../tests/src/com/android/documentsui/ActivityTest.java \
@@ -11,7 +11,7 @@
     ../tests/src/com/android/documentsui/StubProvider.java
 
 LOCAL_JAVA_LIBRARIES := android-support-v4 android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator ub-janktesthelper
 
 LOCAL_PACKAGE_NAME := DocumentsUIPerfTests
 LOCAL_INSTRUMENTATION_FOR := DocumentsUI
diff --git a/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg b/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg
new file mode 100644
index 0000000..dd2da3e
--- /dev/null
+++ b/packages/DocumentsUI/perf-tests/res/raw/earth_small.jpg
Binary files differ
diff --git a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java
new file mode 100644
index 0000000..cb2d904
--- /dev/null
+++ b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/FilesJankPerfTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.StressProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StressProvider.STRESS_ROOT_0_ID;
+import static com.android.documentsui.StressProvider.STRESS_ROOT_2_ID;
+
+import android.app.Activity;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import android.content.Intent;
+import android.content.Context;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.JankTestBase;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.uiautomator.UiScrollable;
+import android.util.Log;
+
+import com.android.documentsui.FilesActivity;
+import com.android.documentsui.bots.RootsListBot;
+import com.android.documentsui.bots.DirectoryListBot;
+
+@LargeTest
+public class FilesJankPerfTest extends JankTestBase {
+    private static final String DOCUMENTSUI_PACKAGE = "com.android.documentsui";
+    private static final int MAX_FLINGS = 10;
+    private static final int BOT_TIMEOUT = 5000;
+
+    private RootsListBot mRootsListBot;
+    private DirectoryListBot mDirListBot;
+    private Activity mActivity = null;
+
+    public void setUpInLoop() {
+        final UiDevice device = UiDevice.getInstance(getInstrumentation());
+        final Context context = getInstrumentation().getTargetContext();
+        mRootsListBot = new RootsListBot(device, context, BOT_TIMEOUT);
+        mDirListBot = new DirectoryListBot(device, context, BOT_TIMEOUT);
+
+        final Intent intent = new Intent(context, FilesActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mActivity = getInstrumentation().startActivitySync(intent);
+    }
+
+    public void tearDownInLoop() {
+        if (mActivity != null) {
+            mActivity.finish();
+            mActivity = null;
+        }
+    }
+
+    public void setupAndOpenInLoop() throws Exception {
+        setUpInLoop();
+        openRoot();
+    }
+
+    public void openRoot() throws Exception {
+        mRootsListBot.openRoot(STRESS_ROOT_2_ID);
+    }
+
+    @JankTest(expectedFrames=0, beforeLoop="setUpInLoop", afterLoop="tearDownInLoop")
+    @GfxMonitor(processName=DOCUMENTSUI_PACKAGE)
+    public void testOpenRootJankPerformance() throws Exception {
+        openRoot();
+        getInstrumentation().waitForIdleSync();
+    }
+
+    @JankTest(expectedFrames=0, beforeLoop="setupAndOpenInLoop", afterLoop="tearDownInLoop")
+    @GfxMonitor(processName=DOCUMENTSUI_PACKAGE)
+    public void testFlingJankPerformance() throws Exception {
+        new UiScrollable(mDirListBot.findDocumentsList().getSelector()).flingToEnd(MAX_FLINGS);
+        getInstrumentation().waitForIdleSync();
+    }
+}
diff --git a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
index 1bc802a..f9b06f8 100644
--- a/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
+++ b/packages/DocumentsUI/perf-tests/src/com/android/documentsui/StressProvider.java
@@ -18,9 +18,11 @@
 
 import android.content.Context;
 import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.MatrixCursor.RowBuilder;
 import android.database.MatrixCursor;
+import android.graphics.Point;
 import android.os.CancellationSignal;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -31,8 +33,11 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 /**
@@ -46,11 +51,21 @@
     // Empty root.
     public static final String STRESS_ROOT_0_ID = "STRESS_ROOT_0";
 
-    // Root with thousands of items.
+    // Root with thousands of directories.
     public static final String STRESS_ROOT_1_ID = "STRESS_ROOT_1";
 
+    // Root with hundreds of files.
+    public static final String STRESS_ROOT_2_ID = "STRESS_ROOT_2";
+
     private static final String STRESS_ROOT_0_DOC_ID = "STRESS_ROOT_0_DOC";
     private static final String STRESS_ROOT_1_DOC_ID = "STRESS_ROOT_1_DOC";
+    private static final String STRESS_ROOT_2_DOC_ID = "STRESS_ROOT_2_DOC";
+
+    private static final int STRESS_ROOT_1_ITEMS = 10000;
+    private static final int STRESS_ROOT_2_ITEMS = 300;
+
+    private static final String MIME_TYPE_IMAGE = "image/jpeg";
+    private static final long REFERENCE_TIMESTAMP = 1459159369359L;
 
     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
@@ -62,7 +77,12 @@
     };
 
     private String mAuthority = DEFAULT_AUTHORITY;
-    private ArrayList<String> mIds = new ArrayList<>();
+
+    // Map from a root document id to children document ids.
+    private Map<String, ArrayList<StubDocument>> mChildDocuments = new HashMap<>();
+
+    private Map<String, StubDocument> mDocuments = new HashMap<>();
+    private Map<String, StubRoot> mRoots = new HashMap<>();
 
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
@@ -72,20 +92,48 @@
 
     @Override
     public boolean onCreate() {
-        mIds = new ArrayList();
-        for (int i = 0; i < 10000; i++) {
-            mIds.add(createRandomId(i));
+        StubDocument document;
+
+        ArrayList<StubDocument> children = new ArrayList<StubDocument>();
+        mChildDocuments.put(STRESS_ROOT_1_DOC_ID, children);
+        for (int i = 0; i < STRESS_ROOT_1_ITEMS; i++) {
+            document = StubDocument.createDirectory(i);
+            mDocuments.put(document.id, document);
+            children.add(document);
         }
-        mIds.add(STRESS_ROOT_0_DOC_ID);
-        mIds.add(STRESS_ROOT_1_DOC_ID);
+
+        children = new ArrayList<StubDocument>();
+        mChildDocuments.put(STRESS_ROOT_2_DOC_ID, children);
+        for (int i = 0; i < STRESS_ROOT_2_ITEMS; i++) {
+            try {
+                document = StubDocument.createFile(
+                        getContext(), MIME_TYPE_IMAGE,
+                        com.android.documentsui.perftests.R.raw.earth_small,
+                        STRESS_ROOT_1_ITEMS + i);
+            } catch (IOException e) {
+                return false;
+            }
+            mDocuments.put(document.id, document);
+            children.add(document);
+        }
+
+        mRoots.put(STRESS_ROOT_0_ID, new StubRoot(STRESS_ROOT_0_ID, STRESS_ROOT_0_DOC_ID));
+        mRoots.put(STRESS_ROOT_1_ID, new StubRoot(STRESS_ROOT_1_ID, STRESS_ROOT_1_DOC_ID));
+        mRoots.put(STRESS_ROOT_2_ID, new StubRoot(STRESS_ROOT_2_ID, STRESS_ROOT_2_DOC_ID));
+
+        mDocuments.put(STRESS_ROOT_0_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_0_DOC_ID));
+        mDocuments.put(STRESS_ROOT_1_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_1_DOC_ID));
+        mDocuments.put(STRESS_ROOT_2_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_2_DOC_ID));
+
         return true;
     }
 
     @Override
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(DEFAULT_ROOT_PROJECTION);
-        includeRoot(result, STRESS_ROOT_0_ID, STRESS_ROOT_0_DOC_ID);
-        includeRoot(result, STRESS_ROOT_1_ID, STRESS_ROOT_1_DOC_ID);
+        for (StubRoot root : mRoots.values()) {
+            includeRoot(result, root);
+        }
         return result;
     }
 
@@ -93,57 +141,125 @@
     public Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
-        includeDocument(result, documentId);
+        final StubDocument document = mDocuments.get(documentId);
+        includeDocument(result, document);
         return result;
     }
 
     @Override
-    public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
+    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+            String sortOrder)
             throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
-        if (STRESS_ROOT_1_DOC_ID.equals(parentDocumentId)) {
-            for (String id : mIds) {
-                includeDocument(result, id);
+        final ArrayList<StubDocument> childDocuments = mChildDocuments.get(parentDocumentId);
+        if (childDocuments != null) {
+            for (StubDocument document : childDocuments) {
+                includeDocument(result, document);
             }
         }
         return result;
     }
 
     @Override
-    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+    public AssetFileDescriptor openDocumentThumbnail(String docId, Point sizeHint,
+            CancellationSignal signal)
+            throws FileNotFoundException {
+        final StubDocument document = mDocuments.get(docId);
+        return getContext().getResources().openRawResourceFd(document.thumbnail);
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(String docId, String mode,
+            CancellationSignal signal)
             throws FileNotFoundException {
         throw new UnsupportedOperationException();
     }
 
-    private void includeRoot(MatrixCursor result, String rootId, String docId) {
+    private void includeRoot(MatrixCursor result, StubRoot root) {
         final RowBuilder row = result.newRow();
-        row.add(Root.COLUMN_ROOT_ID, rootId);
+        row.add(Root.COLUMN_ROOT_ID, root.id);
         row.add(Root.COLUMN_FLAGS, 0);
-        row.add(Root.COLUMN_TITLE, rootId);
-        row.add(Root.COLUMN_DOCUMENT_ID, docId);
+        row.add(Root.COLUMN_TITLE, root.id);
+        row.add(Root.COLUMN_DOCUMENT_ID, root.documentId);
     }
 
-    private void includeDocument(MatrixCursor result, String id) {
+    private void includeDocument(MatrixCursor result, StubDocument document) {
         final RowBuilder row = result.newRow();
-        row.add(Document.COLUMN_DOCUMENT_ID, id);
-        row.add(Document.COLUMN_DISPLAY_NAME, id);
-        row.add(Document.COLUMN_SIZE, 0);
-        row.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
-        row.add(Document.COLUMN_FLAGS, 0);
-        row.add(Document.COLUMN_LAST_MODIFIED, null);
+        row.add(Document.COLUMN_DOCUMENT_ID, document.id);
+        row.add(Document.COLUMN_DISPLAY_NAME, document.id);
+        row.add(Document.COLUMN_SIZE, document.size);
+        row.add(Document.COLUMN_MIME_TYPE, document.mimeType);
+        row.add(Document.COLUMN_FLAGS,
+                document.thumbnail != -1 ? Document.FLAG_SUPPORTS_THUMBNAIL : 0);
+        row.add(Document.COLUMN_LAST_MODIFIED, document.lastModified);
     }
 
-    private static String getDocumentIdForFile(File file) {
+    private static String getStubDocumentIdForFile(File file) {
         return file.getAbsolutePath();
     }
 
-    private String createRandomId(int index) {
-        final Random random = new Random(index);
-        final StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < 20; i++) {
-            builder.append((char) (random.nextInt(96) + 32));
+    private static class StubDocument {
+        final String mimeType;
+        final String id;
+        final int size;
+        final long lastModified;
+        final int thumbnail;
+
+        private StubDocument(String mimeType, String id, int size, long lastModified,
+                int thumbnail) {
+            this.mimeType = mimeType;
+            this.id = id;
+            this.size = size;
+            this.lastModified = lastModified;
+            this.thumbnail = thumbnail;
         }
-        builder.append(index);  // Append a number to guarantee uniqueness.
-        return builder.toString();
+
+        public static StubDocument createDirectory(int index) {
+            return new StubDocument(
+                    DocumentsContract.Document.MIME_TYPE_DIR, createRandomId(index), 0,
+                    createRandomTime(index), -1);
+        }
+
+        public static StubDocument createDirectory(String id) {
+            return new StubDocument(DocumentsContract.Document.MIME_TYPE_DIR, id, 0, 0, -1);
+        }
+
+        public static StubDocument createFile(Context context, String mimeType, int thumbnail,
+                int index) throws IOException {
+            return new StubDocument(
+                    mimeType, createRandomId(index), createRandomSize(index),
+                    createRandomTime(index), thumbnail);
+        }
+
+        private static String createRandomId(int index) {
+            final Random random = new Random(index);
+            final StringBuilder builder = new StringBuilder();
+            for (int i = 0; i < 20; i++) {
+                builder.append((char) (random.nextInt(96) + 32));
+            }
+            builder.append(index);  // Append a number to guarantee uniqueness.
+            return builder.toString();
+        }
+
+        private static int createRandomSize(int index) {
+            final Random random = new Random(index);
+            return random.nextInt(1024 * 1024 * 100);  // Up to 100 MB.
+        }
+
+        private static long createRandomTime(int index) {
+            final Random random = new Random(index);
+            // Up to 30 days backwards from REFERENCE_TIMESTAMP.
+            return REFERENCE_TIMESTAMP - random.nextLong() % 1000L * 60 * 60 * 24 * 30;
+        }
+    }
+
+    private static class StubRoot {
+        final String id;
+        final String documentId;
+
+        public StubRoot(String id, String documentId) {
+            this.id = id;
+            this.documentId = documentId;
+        }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 2dbb730..7a4099a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -18,6 +18,7 @@
 
 import static com.android.documentsui.Shared.DEBUG;
 
+import android.annotation.IntDef;
 import android.app.Activity;
 import android.content.Context;
 import android.support.v4.app.ActionBarDrawerToggle;
@@ -27,16 +28,44 @@
 import android.view.View;
 import android.widget.Toolbar;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A facade over the various pieces comprising "roots fragment in a Drawer".
  *
  * @see DrawerController#create(DrawerLayout)
  */
 abstract class DrawerController implements DrawerListener {
-
     public static final String TAG = "DrawerController";
 
+    // Drawer opening triggered by tapping the navigation icon
+    public static final int OPENED_HAMBURGER = 0;
+    // Drawer opening triggered by swiping right from the edge of the screen
+    public static final int OPENED_SWIPE = 1;
+    // Mostly programmatically forced drawer opening
+    public static final int OPENED_OTHER = 2;
+
+    @IntDef(flag = true, value = {
+            OPENED_HAMBURGER,
+            OPENED_SWIPE,
+            OPENED_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Trigger {}
+
+    /**
+     * Toggles the drawer and sets the OPENED_OTHER as the action that causes opening the drawer.
+     * @param open
+     */
     abstract void setOpen(boolean open);
+
+    /**
+     * Toggles the drawer.
+     * @param open
+     * @param trigger Indicates what action caused opening the drawer. It is ignored for closing.
+     */
+    abstract void setOpen(boolean open, @Trigger int trigger);
     abstract boolean isPresent();
     abstract boolean isOpen();
     abstract void setTitle(String title);
@@ -92,11 +121,11 @@
      * Runtime controller that manages a real drawer.
      */
     private static final class RuntimeDrawerController extends DrawerController {
-
         private final ActionBarDrawerToggle mToggle;
         private DrawerLayout mLayout;
         private View mDrawer;
         private Toolbar mToolbar;
+        private @Trigger int mTrigger = OPENED_OTHER;
 
         public RuntimeDrawerController(
                 DrawerLayout layout, View drawer, ActionBarDrawerToggle toggle,
@@ -113,8 +142,14 @@
 
         @Override
         void setOpen(boolean open) {
+            setOpen(open, OPENED_OTHER);
+        }
+
+        @Override
+        void setOpen(boolean open, @Trigger int trigger) {
             if (open) {
                 mLayout.openDrawer(mDrawer);
+                mTrigger = trigger;
             } else {
                 mLayout.closeDrawer(mDrawer);
             }
@@ -148,16 +183,21 @@
         @Override
         public void onDrawerOpened(View drawerView) {
             mToggle.onDrawerOpened(drawerView);
+            Metrics.logDrawerOpened(mToolbar.getContext(), mTrigger);
         }
 
         @Override
         public void onDrawerClosed(View drawerView) {
             mToggle.onDrawerClosed(drawerView);
+            mTrigger = OPENED_OTHER;
         }
 
         @Override
         public void onDrawerStateChanged(int newState) {
             mToggle.onDrawerStateChanged(newState);
+            if (newState == DrawerLayout.STATE_DRAGGING) {
+                mTrigger = OPENED_SWIPE;
+            }
         }
     }
 
@@ -169,6 +209,8 @@
         @Override
         void setOpen(boolean open) {}
 
+        @Override
+        void setOpen(boolean open, @Trigger int trigger) {}
 
         @Override
         boolean isOpen() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index e6b22e6..a477636 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -30,6 +30,7 @@
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import com.android.documentsui.State.ActionType;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
 import com.android.documentsui.services.FileOperationService;
@@ -65,6 +66,7 @@
     private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
     private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";
     private static final String COUNT_STARTUP_MS = "docsui_startup_ms";
+    private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened";
 
     // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
     // root that is not explicitly recognized by the Metrics code (see {@link
@@ -226,6 +228,21 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Provider {}
 
+    // Codes representing different actions to open the drawer. They are used for bucketing stats in
+    // the COUNT_DRAWER_OPENED histogram.
+    // Do not change or rearrange these values, that will break historical data. Only add to the
+    // list.
+    // Do not use negative numbers or zero; clearcut only handles positive integers.
+    private static final int DRAWER_OPENED_HAMBURGER = 1;
+    private static final int DRAWER_OPENED_SWIPE = 2;
+
+    @IntDef(flag = true, value = {
+            DRAWER_OPENED_HAMBURGER,
+            DRAWER_OPENED_SWIPE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DrawerTrigger {}
+
     /**
      * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
      *
@@ -286,6 +303,20 @@
     }
 
     /**
+     * Logs a drawer opened event. Call this when the user opens drawer by swipe or by clicking the
+     * hamburger icon.
+     * @param context
+     * @param trigger type of action that opened the drawer
+     */
+    public static void logDrawerOpened(Context context, @DrawerController.Trigger int trigger) {
+        if (trigger == DrawerController.OPENED_HAMBURGER) {
+            logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_HAMBURGER);
+        } else if (trigger == DrawerController.OPENED_SWIPE) {
+            logHistogram(context, COUNT_DRAWER_OPENED, DRAWER_OPENED_SWIPE);
+        }
+    }
+
+    /**
      * Logs file operation stats. Call this when a file operation has completed. The given
      * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
      * provider to another vs copying within a given provider).  No PII is logged.
@@ -502,7 +533,7 @@
      * @param name The name of the histogram.
      * @param bucket The bucket to increment.
      */
-    private static void logHistogram(Context context, String name, int bucket) {
+    private static void logHistogram(Context context, String name, @ActionType int bucket) {
         if (DEBUG) Log.d(TAG, name + ": " + bucket);
         MetricsLogger.histogram(context, name, bucket);
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
index 30c1020..f6fe47b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
@@ -91,7 +91,7 @@
 
     private void onNavigationIconClicked() {
         if (mDrawer.isPresent()) {
-            mDrawer.setOpen(true);
+            mDrawer.setOpen(true, DrawerController.OPENED_HAMBURGER);
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 35da8cc..8b4f40e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -17,6 +17,7 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.State.ACTION_OPEN_TREE;
 
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -117,7 +118,7 @@
 
                 Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
 
-                mAdapter = new RootsAdapter(context, result, handlerAppIntent);
+                mAdapter = new RootsAdapter(context, result, handlerAppIntent, state);
                 mList.setAdapter(mAdapter);
 
                 onCurrentRootChanged();
@@ -308,8 +309,8 @@
          * @param handlerAppIntent When not null, apps capable of handling the original
          *     intent will be included in list of roots (in special section at bottom).
          */
-        public RootsAdapter(
-                Context context, Collection<RootInfo> roots, @Nullable Intent handlerAppIntent) {
+        public RootsAdapter(Context context, Collection<RootInfo> roots,
+                @Nullable Intent handlerAppIntent, State state) {
             super(context, 0);
 
             final List<RootItem> libraries = new ArrayList<>();
@@ -320,7 +321,8 @@
 
                 if (root.isHome() && Shared.isHomeRootHidden(context)) {
                     continue;
-                } else if (root.isAdvanced() && Shared.areAdvancedRootsHidden(context)) {
+                } else if (root.isAdvanced()
+                        && Shared.areAdvancedRootsHidden(context, state)) {
                     continue;
                 } else if (root.isLibrary()) {
                     if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index d21afee..655359a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -22,6 +22,11 @@
 import android.text.format.DateUtils;
 import android.text.format.Time;
 import android.view.WindowManager;
+
+import com.android.documentsui.State.ActionType;
+
+import static com.android.documentsui.State.ACTION_OPEN_TREE;
+
 import android.app.AlertDialog;
 
 import java.text.Collator;
@@ -179,8 +184,9 @@
     /*
      * Indicates if the advanced roots should be hidden.
      */
-    public static boolean areAdvancedRootsHidden(Context context) {
-        return context.getResources().getBoolean(R.bool.advanced_roots_hidden);
+    public static boolean areAdvancedRootsHidden(Context context, State state) {
+        return context.getResources().getBoolean(R.bool.advanced_roots_hidden)
+                && state.action != ACTION_OPEN_TREE;
     }
 
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 16b7660..534a483 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -43,10 +43,19 @@
 
     private static final String TAG = "State";
 
+    @IntDef(flag = true, value = {
+            ACTION_BROWSE,
+            ACTION_PICK_COPY_DESTINATION,
+            ACTION_OPEN,
+            ACTION_CREATE,
+            ACTION_GET_CONTENT,
+            ACTION_OPEN_TREE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActionType {}
     // File manager and related private picking activity.
     public static final int ACTION_BROWSE = 1;
     public static final int ACTION_PICK_COPY_DESTINATION = 2;
-
     // All public picking activities
     public static final int ACTION_OPEN = 3;
     public static final int ACTION_CREATE = 4;
@@ -69,7 +78,7 @@
     public static final int SORT_ORDER_LAST_MODIFIED = 2;
     public static final int SORT_ORDER_SIZE = 3;
 
-    public int action;
+    public @ActionType int action;
     public String[] acceptMimes;
 
     /** Derived from local preferences */
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4acf85e..73ce0e1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1084,7 +1084,7 @@
         }
 
         // Make all items draggable.
-        view.setOnLongClickListener(mDragHelper);
+        view.setOnLongClickListener(onLongClickListener);
     }
 
     private View.OnDragListener mOnDragListener = new View.OnDragListener() {
@@ -1387,9 +1387,10 @@
         }
     }
 
-    private DragStartHelper mDragHelper = new DragStartHelper(null) {
+    private DragStartHelper.OnDragStartListener mOnDragStartListener =
+            new DragStartHelper.OnDragStartListener() {
         @Override
-        protected boolean onDragStart(View v) {
+        public boolean onDragStart(View v, DragStartHelper helper) {
             if (isSelected(getModelId(v))) {
                 List<DocumentInfo> docs = getDraggableDocuments(v);
                 if (docs.isEmpty()) {
@@ -1409,6 +1410,15 @@
         }
     };
 
+    private DragStartHelper mDragHelper = new DragStartHelper(null, mOnDragStartListener);
+
+    private View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
+        @Override
+        public boolean onLongClick(View v) {
+            return mDragHelper.onLongClick(v);
+        }
+    };
+
     // Previously we listened to events with one class, only to bounce them forward
     // to GestureDetector. We're still doing that here, but with a single class
     // that reduces overall complexity in our glue code.
@@ -1439,7 +1449,7 @@
 
             // Detect drag events. When a drag is detected, intercept the rest of the gesture.
             View itemView = rv.findChildViewUnder(e.getX(), e.getY());
-            if (itemView != null && mDragHelper.handleTouch(itemView,  e)) {
+            if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
                 return true;
             }
             // Forward unhandled events to the GestureDetector.
@@ -1451,7 +1461,7 @@
         @Override
         public void onTouchEvent(RecyclerView rv, MotionEvent e) {
             View itemView = rv.findChildViewUnder(e.getX(), e.getY());
-            mDragHelper.handleTouch(itemView,  e);
+            mDragHelper.onTouch(itemView,  e);
             // Note: even though this event is being handled as part of a drag gesture, continue
             // forwarding to the GestureDetector. The detector needs to see the entire cluster of
             // events in order to properly interpret gestures.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index 9ed2abf..f10af43 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -45,6 +45,8 @@
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.webkit.MimeTypeMap;
@@ -451,7 +453,7 @@
         ParcelFileDescriptor srcFile = null;
         ParcelFileDescriptor dstFile = null;
         InputStream in = null;
-        OutputStream out = null;
+        ParcelFileDescriptor.AutoCloseOutputStream out = null;
         boolean success = false;
 
         try {
@@ -502,6 +504,8 @@
                     makeCopyProgress(len);
                 }
 
+                // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush.
+                IoUtils.close(dstFile.getFileDescriptor());
                 srcFile.checkError();
             } catch (IOException e) {
                 throw new ResourceException(
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
index da9f9c3..7c1e2196 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/DirectoryListBot.java
@@ -167,7 +167,7 @@
         return true;
     }
 
-    private UiObject findDocumentsList() {
+    public UiObject findDocumentsList() {
         return findObject(
                 "com.android.documentsui:id/container_directory",
                 DIR_LIST_ID);
diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
index 1d4ed1d..eb96015 100644
--- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
+++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp
@@ -52,6 +52,8 @@
 static jmethodID app_fuse_get_file_size;
 static jmethodID app_fuse_read_object_bytes;
 static jmethodID app_fuse_write_object_bytes;
+static jmethodID app_fuse_flush_file_handle;
+static jmethodID app_fuse_close_file_handle;
 static jfieldID app_fuse_buffer;
 
 // NOTE:
@@ -307,7 +309,8 @@
         const uint32_t size = in->size;
         const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in);
         uint32_t written_size;
-        const int result = write_object_bytes(it->second, offset, size, buffer, &written_size);
+        const int result = write_object_bytes(
+                in->fh, it->second, offset, size, buffer, &written_size);
         if (result < 0) {
             return result;
         }
@@ -320,13 +323,13 @@
                             const fuse_release_in* in,
                             FuseResponse<void>* /* out */) {
         handles_.erase(in->fh);
-        return 0;
+        return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh));
     }
 
     int handle_fuse_flush(const fuse_in_header& /* header */,
-                          const void* /* in */,
+                          const fuse_flush_in* in,
                           FuseResponse<void>* /* out */) {
-        return 0;
+        return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
     }
 
     template <typename T, typename S>
@@ -382,8 +385,10 @@
         return read_size;
     }
 
-    int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer,
-                           uint32_t* written_size) {
+    int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size,
+                           const void* buffer, uint32_t* written_size) {
+        static_assert(sizeof(uint64_t) <= sizeof(jlong),
+                      "jlong must be able to express any uint64_t values");
         ScopedLocalRef<jbyteArray> array(
                 env_,
                 static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
@@ -394,15 +399,28 @@
             }
             memcpy(bytes.get(), buffer, size);
         }
-        *written_size = env_->CallIntMethod(
-                self_, app_fuse_write_object_bytes, inode, offset, size, array.get());
-        if (env_->ExceptionCheck()) {
-            env_->ExceptionClear();
-            return -EIO;
+        const int result = env_->CallIntMethod(
+                self_,
+                app_fuse_write_object_bytes,
+                file_handle_to_jlong(handle),
+                inode,
+                offset,
+                size,
+                array.get());
+        if (result < 0) {
+            return result;
         }
+        *written_size = result;
         return 0;
     }
 
+    static jlong file_handle_to_jlong(uint64_t handle) {
+        static_assert(
+                sizeof(uint64_t) <= sizeof(jlong),
+                "jlong must be able to express any uint64_t values");
+        return static_cast<jlong>(handle);
+    }
+
     static void fuse_reply(int fd, int unique, int reply_code, void* reply_data,
                            size_t reply_size) {
         // Don't send any data for error case.
@@ -511,15 +529,21 @@
         return -1;
     }
 
-    app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B");
-    if (app_fuse_buffer == nullptr) {
-        ALOGE("Can't find mBuffer");
+    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I");
+    if (app_fuse_write_object_bytes == nullptr) {
+        ALOGE("Can't find writeObjectBytes");
         return -1;
     }
 
-    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I");
-    if (app_fuse_write_object_bytes == nullptr) {
-        ALOGE("Can't find getWriteObjectBytes");
+    app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I");
+    if (app_fuse_flush_file_handle == nullptr) {
+        ALOGE("Can't find flushFileHandle");
+        return -1;
+    }
+
+    app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I");
+    if (app_fuse_close_file_handle == nullptr) {
+        ALOGE("Can't find closeFileHandle");
         return -1;
     }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
index 777dc60..88858a8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
@@ -20,6 +20,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.storage.StorageManager;
+import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.util.Log;
 import com.android.internal.annotations.VisibleForTesting;
@@ -34,6 +35,8 @@
         System.loadLibrary("appfuse_jni");
     }
 
+    private static final boolean DEBUG = false;
+
     /**
      * Max read amount specified at the FUSE kernel implementation.
      * The value is copied from sdcard.c.
@@ -94,7 +97,8 @@
     public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException {
         Preconditions.checkArgument(
                 mode == ParcelFileDescriptor.MODE_READ_ONLY ||
-                mode == ParcelFileDescriptor.MODE_WRITE_ONLY);
+                mode == (ParcelFileDescriptor.MODE_WRITE_ONLY |
+                         ParcelFileDescriptor.MODE_TRUNCATE));
         return ParcelFileDescriptor.open(new File(
                 getMountPoint(),
                 Integer.toString(i)),
@@ -127,6 +131,7 @@
 
         /**
          * Handles writing bytes for the give inode.
+         * @param fileHandle
          * @param inode
          * @param offset Offset for file bytes.
          * @param size Size for file bytes.
@@ -134,7 +139,23 @@
          * @return Number of read bytes. Must not be negative.
          * @throws IOException
          */
-        int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException;
+        int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
+                throws IOException, ErrnoException;
+
+        /**
+         * Flushes bytes for file handle.
+         * @param fileHandle
+         * @throws IOException
+         * @throws ErrnoException
+         */
+        void flushFileHandle(long fileHandle) throws IOException, ErrnoException;
+
+        /**
+         * Closes file handle.
+         * @param fileHandle
+         * @throws IOException
+         */
+        void closeFileHandle(long fileHandle) throws IOException, ErrnoException;
     }
 
     @UsedByNative("com_android_mtp_AppFuse.cpp")
@@ -142,10 +163,8 @@
     private long getFileSize(int inode) {
         try {
             return mCallback.getFileSize(inode);
-        } catch (FileNotFoundException e) {
-            return -OsConstants.ENOENT;
-        } catch (UnsupportedOperationException e) {
-            return -OsConstants.ENOTSUP;
+        } catch (Exception error) {
+            return -getErrnoFromException(error);
         }
     }
 
@@ -159,20 +178,62 @@
             // It's OK to share the same mBuffer among requests because the requests are processed
             // by AppFuseMessageThread sequentially.
             return mCallback.readObjectBytes(inode, offset, size, mBuffer);
-        } catch (IOException e) {
-            return -OsConstants.EIO;
-        } catch (UnsupportedOperationException e) {
-            return -OsConstants.ENOTSUP;
+        } catch (Exception error) {
+            return -getErrnoFromException(error);
         }
     }
 
     @UsedByNative("com_android_mtp_AppFuse.cpp")
     @WorkerThread
-    private /* unsgined */ int writeObjectBytes(int inode,
+    private /* unsgined */ int writeObjectBytes(long fileHandler,
+                                                int inode,
                                                 /* unsigned */ long offset,
                                                 /* unsigned */ int size,
-                                                byte[] bytes) throws IOException {
-        return mCallback.writeObjectBytes(inode, offset, size, bytes);
+                                                byte[] bytes) {
+        try {
+            return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes);
+        } catch (Exception error) {
+            return -getErrnoFromException(error);
+        }
+    }
+
+    @UsedByNative("com_android_mtp_AppFuse.cpp")
+    @WorkerThread
+    private int flushFileHandle(long fileHandle) {
+        try {
+            mCallback.flushFileHandle(fileHandle);
+            return 0;
+        } catch (Exception error) {
+            return -getErrnoFromException(error);
+        }
+    }
+
+    @UsedByNative("com_android_mtp_AppFuse.cpp")
+    @WorkerThread
+    private int closeFileHandle(long fileHandle) {
+        try {
+            mCallback.closeFileHandle(fileHandle);
+            return 0;
+        } catch (Exception error) {
+            return -getErrnoFromException(error);
+        }
+    }
+
+    private static int getErrnoFromException(Exception error) {
+        if (DEBUG) {
+            Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error);
+        }
+        if (error instanceof FileNotFoundException) {
+            return OsConstants.ENOENT;
+        } else if (error instanceof IOException) {
+            return OsConstants.EIO;
+        } else if (error instanceof UnsupportedOperationException) {
+            return OsConstants.ENOTSUP;
+        } else if (error instanceof IllegalArgumentException) {
+            return OsConstants.EINVAL;
+        } else {
+            return OsConstants.EIO;
+        }
     }
 
     private native boolean native_start_app_fuse_loop(int fd);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 9f64046ce..50781bf 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -17,6 +17,7 @@
 package com.android.mtp;
 
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.UriPermission;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
@@ -38,11 +39,16 @@
 import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.HashMap;
@@ -82,6 +88,7 @@
     private MtpDatabase mDatabase;
     private AppFuse mAppFuse;
     private ServiceIntentSender mIntentSender;
+    private Context mContext;
 
     /**
      * Provides singleton instance to MtpDocumentsService.
@@ -93,6 +100,7 @@
     @Override
     public boolean onCreate() {
         sSingleton = this;
+        mContext = getContext();
         mResources = getContext().getResources();
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
@@ -137,12 +145,14 @@
 
     @VisibleForTesting
     boolean onCreateForTesting(
+            Context context,
             Resources resources,
             MtpManager mtpManager,
             ContentResolver resolver,
             MtpDatabase database,
             StorageManager storageManager,
             ServiceIntentSender intentSender) {
+        mContext = context;
         mResources = resources;
         mMtpManager = mtpManager;
         mResolver = resolver;
@@ -232,43 +242,43 @@
         try {
             openDevice(identifier.mDeviceId);
             final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
-            switch (mode) {
-                case "r":
-                    long fileSize;
-                    try {
-                        fileSize = getFileSize(documentId);
-                    } catch (UnsupportedOperationException exception) {
-                        fileSize = -1;
-                    }
-                    // MTP getPartialObject operation does not support files that are larger than
-                    // 4GB. Fallback to non-seekable file descriptor.
-                    if (MtpDeviceRecord.isPartialReadSupported(
-                            device.operationsSupported, fileSize)) {
-                        return mAppFuse.openFile(
-                                Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY);
-                    } else {
-                        return getPipeManager(identifier).readDocument(mMtpManager, identifier);
-                    }
-                case "w":
-                    // TODO: Clear the parent document loader task (if exists) and call notify
-                    // when writing is completed.
-                    if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
-                        return getPipeManager(identifier).writeDocument(
-                                getContext(), mMtpManager, identifier, device.operationsSupported);
-                    } else {
-                        throw new UnsupportedOperationException(
-                                "The device does not support writing operation.");
-                    }
-                case "rw":
-                    // TODO: Add support for "rw" mode.
+            // Turn off MODE_CREATE because openDocument does not allow to create new files.
+            final int modeFlag =
+                    ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE;
+            if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) {
+                long fileSize;
+                try {
+                    fileSize = getFileSize(documentId);
+                } catch (UnsupportedOperationException exception) {
+                    fileSize = -1;
+                }
+                if (MtpDeviceRecord.isPartialReadSupported(
+                        device.operationsSupported, fileSize)) {
+                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+                } else {
+                    // If getPartialObject{|64} are not supported for the device, returns
+                    // non-seekable pipe FD instead.
+                    return getPipeManager(identifier).readDocument(mMtpManager, identifier);
+                }
+            } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) {
+                // TODO: Clear the parent document loader task (if exists) and call notify
+                // when writing is completed.
+                if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
+                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+                } else {
                     throw new UnsupportedOperationException(
-                            "The provider does not support 'rw' mode.");
-                default:
-                    throw new IllegalArgumentException("Unknown mode for openDocument: " + mode);
+                            "The device does not support writing operation.");
+                }
+            } else {
+                // TODO: Add support for "rw" mode.
+                throw new UnsupportedOperationException("The provider does not support 'rw' mode.");
             }
+        } catch (FileNotFoundException | RuntimeException error) {
+            Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
+            throw error;
         } catch (IOException error) {
             Log.e(MtpDocumentsProvider.TAG, "openDocument", error);
-            throw new FileNotFoundException(error.getMessage());
+            throw new IllegalStateException(error);
         }
     }
 
@@ -595,6 +605,13 @@
     }
 
     private class AppFuseCallback implements AppFuse.Callback {
+        private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
+
+        @Override
+        public long getFileSize(int inode) throws FileNotFoundException {
+            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+        }
+
         @Override
         public long readObjectBytes(
                 int inode, long offset, long size, byte[] buffer) throws IOException {
@@ -617,15 +634,43 @@
         }
 
         @Override
-        public long getFileSize(int inode) throws FileNotFoundException {
-            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+        public int writeObjectBytes(
+                long fileHandle, int inode, long offset, int size, byte[] bytes)
+                throws IOException, ErrnoException {
+            final MtpFileWriter writer;
+            if (mWriters.containsKey(fileHandle)) {
+                writer = mWriters.get(fileHandle);
+            } else {
+                writer = new MtpFileWriter(mContext, String.valueOf(inode));
+                mWriters.put(fileHandle, writer);
+            }
+            return writer.write(offset, size, bytes);
         }
 
         @Override
-        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
-                throws IOException {
-            // TODO: Implement it.
-            throw new IOException();
+        public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
+            final MtpFileWriter writer = mWriters.get(fileHandle);
+            if (writer == null) {
+                // File handle for reading.
+                return;
+            }
+            final MtpDeviceRecord device = getDeviceToolkit(
+                    mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
+            writer.flush(mMtpManager, mDatabase, device.operationsSupported);
+        }
+
+        @Override
+        public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
+            final MtpFileWriter writer = mWriters.get(fileHandle);
+            if (writer == null) {
+                // File handle for reading.
+                return;
+            }
+            try {
+                writer.close();
+            } finally {
+                mWriters.remove(fileHandle);
+            }
         }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java
new file mode 100644
index 0000000..3e1bedc
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import android.content.Context;
+import android.mtp.MtpObjectInfo;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
+
+class MtpFileWriter implements AutoCloseable {
+    final ParcelFileDescriptor mCacheFd;
+    final String mDocumentId;
+    boolean mDirty;
+
+    MtpFileWriter(Context context, String documentId) throws IOException {
+        mDocumentId = documentId;
+        mDirty = false;
+        final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir());
+        mCacheFd = ParcelFileDescriptor.open(
+                tempFile,
+                ParcelFileDescriptor.MODE_READ_WRITE |
+                ParcelFileDescriptor.MODE_TRUNCATE |
+                ParcelFileDescriptor.MODE_CREATE);
+        tempFile.delete();
+    }
+
+    String getDocumentId() {
+        return mDocumentId;
+    }
+
+    int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException {
+        Preconditions.checkArgumentNonnegative(offset, "offset");
+        Preconditions.checkArgumentNonnegative(size, "size");
+        Preconditions.checkArgument(size <= bytes.length);
+        if (size == 0) {
+            return 0;
+        }
+        mDirty = true;
+        Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET);
+        return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size);
+    }
+
+    void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported)
+            throws IOException, ErrnoException {
+        // Skip unnecessary flush.
+        if (!mDirty) {
+            return;
+        }
+
+        // Get the placeholder object info.
+        final Identifier identifier = database.createIdentifier(mDocumentId);
+        final MtpObjectInfo placeholderObjectInfo =
+                manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle);
+
+        // Delete the target object info if it already exists (as a placeholder).
+        manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
+
+        // Create the target object info with a correct file size and upload the file.
+        final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END);
+        final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo)
+                .setCompressedSize(size)
+                .build();
+
+        Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+        final int newObjectHandle = manager.createDocument(
+                identifier.mDeviceId, targetObjectInfo, mCacheFd);
+
+        final MtpObjectInfo newObjectInfo = manager.getObjectInfo(
+                identifier.mDeviceId, newObjectHandle);
+        final Identifier parentIdentifier =
+                database.getParentIdentifier(identifier.mDocumentId);
+        database.updateObject(
+                identifier.mDocumentId,
+                identifier.mDeviceId,
+                parentIdentifier.mDocumentId,
+                operationsSupported,
+                newObjectInfo,
+                size);
+
+        mDirty = false;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mCacheFd.close();
+    }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index 1520f3b..795bbc1 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -16,13 +16,9 @@
 
 package com.android.mtp;
 
-import android.content.Context;
-import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -52,15 +48,6 @@
         return task.getReadingFileDescriptor();
     }
 
-    ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier,
-                                       int[] operationsSupported)
-            throws IOException {
-        final Task task = new WriteDocumentTask(
-                context, model, identifier, operationsSupported, mDatabase);
-        mExecutor.execute(task);
-        return task.getWritingFileDescriptor();
-    }
-
     ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException {
         final Task task = new GetThumbnailTask(model, identifier);
         mExecutor.execute(task);
@@ -81,10 +68,6 @@
         ParcelFileDescriptor getReadingFileDescriptor() {
             return mDescriptors[0];
         }
-
-        ParcelFileDescriptor getWritingFileDescriptor() {
-            return mDescriptors[1];
-        }
     }
 
     private static class ImportFileTask extends Task {
@@ -108,85 +91,6 @@
         }
     }
 
-    private static class WriteDocumentTask extends Task {
-        private final Context mContext;
-        private final MtpDatabase mDatabase;
-        private final int[] mOperationsSupported;
-
-        WriteDocumentTask(Context context,
-                          MtpManager model,
-                          Identifier identifier,
-                          int[] supportedOperations,
-                          MtpDatabase database)
-                throws IOException {
-            super(model, identifier);
-            mContext = context;
-            mDatabase = database;
-            mOperationsSupported = supportedOperations;
-        }
-
-        @Override
-        public void run() {
-            File tempFile = null;
-            try {
-                // Obtain a temporary file and copy the data to it.
-                tempFile = File.createTempFile("mtp", "tmp", mContext.getCacheDir());
-                try (
-                    final FileOutputStream tempOutputStream =
-                            new ParcelFileDescriptor.AutoCloseOutputStream(
-                                    ParcelFileDescriptor.open(
-                                            tempFile, ParcelFileDescriptor.MODE_WRITE_ONLY));
-                    final ParcelFileDescriptor.AutoCloseInputStream inputStream =
-                            new ParcelFileDescriptor.AutoCloseInputStream(mDescriptors[0])
-                ) {
-                    final byte[] buffer = new byte[32 * 1024];
-                    int bytes;
-                    while ((bytes = inputStream.read(buffer)) != -1) {
-                        mDescriptors[0].checkError();
-                        tempOutputStream.write(buffer, 0, bytes);
-                    }
-                    tempOutputStream.flush();
-                }
-
-                // Get the placeholder object info.
-                final MtpObjectInfo placeholderObjectInfo =
-                        mManager.getObjectInfo(mIdentifier.mDeviceId, mIdentifier.mObjectHandle);
-
-                // Delete the target object info if it already exists (as a placeholder).
-                mManager.deleteDocument(mIdentifier.mDeviceId, mIdentifier.mObjectHandle);
-
-                // Create the target object info with a correct file size and upload the file.
-                final MtpObjectInfo targetObjectInfo =
-                        new MtpObjectInfo.Builder(placeholderObjectInfo)
-                                .setCompressedSize(tempFile.length())
-                                .build();
-                final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open(
-                        tempFile, ParcelFileDescriptor.MODE_READ_ONLY);
-                final int newObjectHandle = mManager.createDocument(
-                        mIdentifier.mDeviceId, targetObjectInfo, tempInputDescriptor);
-
-                final MtpObjectInfo newObjectInfo = mManager.getObjectInfo(
-                        mIdentifier.mDeviceId, newObjectHandle);
-                final Identifier parentIdentifier =
-                        mDatabase.getParentIdentifier(mIdentifier.mDocumentId);
-                mDatabase.updateObject(
-                        mIdentifier.mDocumentId,
-                        mIdentifier.mDeviceId,
-                        parentIdentifier.mDocumentId,
-                        mOperationsSupported,
-                        newObjectInfo,
-                        tempFile.length());
-            } catch (IOException error) {
-                Log.w(MtpDocumentsProvider.TAG,
-                        "Failed to send a file because of: " + error.getMessage());
-            } finally {
-                if (tempFile != null) {
-                    tempFile.delete();
-                }
-            }
-        }
-    }
-
     private static class GetThumbnailTask extends Task {
         GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException {
             super(model, identifier);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
index 3b92506..e421de7 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java
@@ -23,6 +23,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import libcore.io.IoUtils;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -143,7 +145,8 @@
                     }
 
                     @Override
-                    public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) {
+                    public int writeObjectBytes(
+                            long fileHandle, int inode, long offset, int size, byte[] bytes) {
                         for (int i = 0; i < size; i++) {
                             resultBytes[(int)(offset + i)] = bytes[i];
                         }
@@ -152,7 +155,7 @@
                 });
         appFuse.mount(storageManager);
         final ParcelFileDescriptor fd = appFuse.openFile(
-                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
         try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
             stream.write('a');
@@ -182,7 +185,7 @@
                 });
         appFuse.mount(storageManager);
         final ParcelFileDescriptor fd = appFuse.openFile(
-                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY);
+                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
         try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
             stream.write('a');
@@ -192,6 +195,46 @@
         appFuse.close();
     }
 
+    public void testWriteFile_flushError() throws IOException {
+        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
+        final int INODE = 10;
+        final AppFuse appFuse = new AppFuse(
+                "test",
+                new TestCallback() {
+                    @Override
+                    public long getFileSize(int inode) throws FileNotFoundException {
+                        if (inode != INODE) {
+                            throw new FileNotFoundException();
+                        }
+                        return 5;
+                    }
+
+                    @Override
+                    public int writeObjectBytes(
+                            long fileHandle, int inode, long offset, int size, byte[] bytes) {
+                        return size;
+                    }
+
+                    @Override
+                    public void flushFileHandle(long fileHandle) throws IOException {
+                        throw new IOException();
+                    }
+                });
+        appFuse.mount(storageManager);
+        final ParcelFileDescriptor fd = appFuse.openFile(
+                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+            stream.write('a');
+            try {
+                IoUtils.close(fd.getFileDescriptor());
+                fail();
+            } catch (IOException e) {
+            }
+        }
+        appFuse.close();
+    }
+
     private static class TestCallback implements AppFuse.Callback {
         @Override
         public long getFileSize(int inode) throws FileNotFoundException {
@@ -205,9 +248,15 @@
         }
 
         @Override
-        public int writeObjectBytes(int inode, long offset, int size, byte[] bytes)
+        public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
                 throws IOException {
             throw new IOException();
         }
+
+        @Override
+        public void flushFileHandle(long fileHandle) throws IOException {}
+
+        @Override
+        public void closeFileHandle(long fileHandle) {}
     }
 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 0de761c..9ed15c8 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -21,6 +21,7 @@
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
@@ -533,6 +534,30 @@
         }
     }
 
+    public void testOpenDocument_writing() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] {
+                new MtpRoot(0, 0, "Storage", 0, 0, "")
+        });
+        final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
+        {
+            final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
+            try (ParcelFileDescriptor.AutoCloseOutputStream stream =
+                    new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
+                stream.write("Hello".getBytes());
+            }
+        }
+        {
+            final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
+            try (ParcelFileDescriptor.AutoCloseInputStream stream =
+                    new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+                final byte[] bytes = new byte[5];
+                stream.read(bytes);
+                assertTrue(Arrays.equals("Hello".getBytes(), bytes));
+            }
+        }
+    }
+
     public void testBusyDevice() throws Exception {
         mMtpManager = new TestMtpManager(getContext()) {
             @Override
@@ -740,6 +765,7 @@
         mProvider = new MtpDocumentsProvider();
         final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
         assertTrue(mProvider.onCreateForTesting(
+                getContext(),
                 mResources,
                 mMtpManager,
                 mResolver,
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 8611797..53dc3db 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -16,10 +16,7 @@
 
 package com.android.mtp;
 
-import android.database.Cursor;
-import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -66,64 +63,6 @@
         assertDescriptorError(descriptor);
     }
 
-    public void testWriteDocument_basic() throws Exception {
-        TestUtil.addTestDevice(mDatabase);
-        TestUtil.addTestStorage(mDatabase, "1");
-
-        final MtpObjectInfo info =
-                new MtpObjectInfo.Builder().setObjectHandle(1).setName("note.txt").build();
-        mDatabase.getMapper().startAddingDocuments("2");
-        mDatabase.getMapper().putChildDocuments(
-                0, "2", TestUtil.OPERATIONS_SUPPORTED,
-                new MtpObjectInfo[] { info },
-                new long[] { 0L });
-        mDatabase.getMapper().stopAddingDocuments("2");
-        // Create a placeholder file which should be replaced by a real file later.
-        mtpManager.setObjectInfo(0, info);
-
-        // Upload testing bytes.
-        final ParcelFileDescriptor descriptor = mPipeManager.writeDocument(
-                getContext(),
-                mtpManager,
-                new Identifier(0, 0, 1, "2", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT),
-                TestUtil.OPERATIONS_SUPPORTED);
-        final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
-                new ParcelFileDescriptor.AutoCloseOutputStream(descriptor);
-        outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length);
-        outputStream.close();
-        mExecutor.shutdown();
-        assertTrue(mExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS));
-
-        // Check if the placeholder file is removed.
-        try {
-            mtpManager.getObjectInfo(0, 1);
-            fail();  // The placeholder file has not been deleted.
-        } catch (IOException e) {
-            // Expected error, as the file is gone.
-        }
-
-        // Confirm that the target file is created.
-        final MtpObjectInfo targetDocument = mtpManager.getObjectInfo(
-                0, TestMtpManager.CREATED_DOCUMENT_HANDLE);
-        assertTrue(targetDocument != null);
-
-        // Confirm the object handle is updated.
-        try (final Cursor cursor = mDatabase.queryDocument(
-                "2", new String[] { MtpDatabaseConstants.COLUMN_OBJECT_HANDLE })) {
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(TestMtpManager.CREATED_DOCUMENT_HANDLE, cursor.getInt(0));
-        }
-
-        // Verify uploaded bytes.
-        final byte[] uploadedBytes = mtpManager.getImportFileBytes(
-                0, TestMtpManager.CREATED_DOCUMENT_HANDLE);
-        assertEquals(HELLO_BYTES.length, uploadedBytes.length);
-        for (int i = 0; i < HELLO_BYTES.length; i++) {
-            assertEquals(HELLO_BYTES[i], uploadedBytes[i]);
-        }
-    }
-
     public void testReadThumbnail_basic() throws Exception {
         mtpManager.setThumbnail(0, 1, HELLO_BYTES);
         final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail(
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 2d3935b..99145b7b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -178,6 +178,8 @@
         }
         if (mState == STATE_FAILED) {
             Log.w(LOG_TAG, "Failed before start.");
+        } else if (mState == STATE_DESTROYED) {
+            Log.w(LOG_TAG, "Destroyed before start.");
         } else {
             if (mState != STATE_INITIAL) {
                 throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
@@ -267,7 +269,7 @@
         }
         if (mState != STATE_STARTED && mState != STATE_UPDATED
                 && mState != STATE_FAILED && mState != STATE_CANCELING
-                && mState != STATE_CANCELED) {
+                && mState != STATE_CANCELED && mState != STATE_DESTROYED) {
             throw new IllegalStateException("Cannot finish in state:"
                     + stateToString(mState));
         }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 3b5513a..7935440 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -267,9 +267,12 @@
 
         // The contract is that if we already have a valid,
         // result the we have to deliver it immediately.
-        if (!mPrinters.isEmpty()) {
-            deliverResult(new ArrayList<>(mPrinters));
-        }
+        (new Handler(Looper.getMainLooper())).post(new Runnable() {
+            @Override public void run() {
+                deliverResult(new ArrayList<>(mPrinters));
+            }
+        });
+
         // Always load the data to ensure discovery period is
         // started and to make sure obsolete printers are updated.
         onForceLoad();
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index eebb60c..c1a3f86 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -206,6 +206,7 @@
             int documentPageCount, MediaSize mediaSize, Margins minMargins) {
         boolean documentChanged = false;
         boolean updatePreviewAreaAndPageSize = false;
+        boolean clearSelectedPages = false;
 
         // If the app does not tell how many pages are in the document we cannot
         // optimize and ask for all pages whose count we get from the renderer.
@@ -225,30 +226,41 @@
             }
         }
 
-        if (!Arrays.equals(mSelectedPages, selectedPages)) {
-            mSelectedPages = selectedPages;
-            mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
-                    mSelectedPages, documentPageCount);
-            setConfirmedPages(mSelectedPages, documentPageCount);
-            updatePreviewAreaAndPageSize = true;
-            documentChanged = true;
-        }
-
         if (mDocumentPageCount != documentPageCount) {
             mDocumentPageCount = documentPageCount;
             documentChanged = true;
+            clearSelectedPages = true;
         }
 
         if (mMediaSize == null || !mMediaSize.equals(mediaSize)) {
             mMediaSize = mediaSize;
             updatePreviewAreaAndPageSize = true;
             documentChanged = true;
+
+            clearSelectedPages = true;
         }
 
         if (mMinMargins == null || !mMinMargins.equals(minMargins)) {
             mMinMargins = minMargins;
             updatePreviewAreaAndPageSize = true;
             documentChanged = true;
+
+            clearSelectedPages = true;
+        }
+
+        if (clearSelectedPages) {
+            mSelectedPages = PageRange.ALL_PAGES_ARRAY;
+            mSelectedPageCount = documentPageCount;
+            setConfirmedPages(mSelectedPages, documentPageCount);
+            updatePreviewAreaAndPageSize = true;
+            documentChanged = true;
+        } else if (!Arrays.equals(mSelectedPages, selectedPages)) {
+            mSelectedPages = selectedPages;
+            mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
+                    mSelectedPages, documentPageCount);
+            setConfirmedPages(mSelectedPages, documentPageCount);
+            updatePreviewAreaAndPageSize = true;
+            documentChanged = true;
         }
 
         // If *all pages* is selected we need to convert that to absolute
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index a24d664..66c1dbe 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -176,8 +176,6 @@
             "[\\s]*[0-9]+[\\-]?[\\s]*[0-9]*[\\s]*?(([,])"
                     + "[\\s]*[0-9]+[\\s]*[\\-]?[\\s]*[0-9]*[\\s]*|[\\s]*)+");
 
-    public static final PageRange[] ALL_PAGES_ARRAY = new PageRange[]{PageRange.ALL_PAGES};
-
     private boolean mIsOptionsUiBound = false;
 
     private final PrinterAvailabilityDetector mPrinterAvailabilityDetector =
@@ -597,14 +595,6 @@
 
     @Override
     public void onOptionsClosed() {
-        PageRange[] selectedPages = computeSelectedPages();
-        if (!Arrays.equals(mSelectedPages, selectedPages)) {
-            mSelectedPages = selectedPages;
-
-            // Update preview.
-            updatePrintPreviewController(false);
-        }
-
         // Make sure the IME is not on the way of preview as
         // the user may have used it to type copies or range.
         InputMethodManager imm = getSystemService(InputMethodManager.class);
@@ -950,7 +940,7 @@
         mSelectedPages = selectedPages;
         mPrintJob.setPages(selectedPages);
 
-        if (Arrays.equals(selectedPages, ALL_PAGES_ARRAY)) {
+        if (Arrays.equals(selectedPages, PageRange.ALL_PAGES_ARRAY)) {
             if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
                 mRangeOptionsSpinner.setSelection(0);
                 mPageRangeEditText.setText("");
@@ -1049,7 +1039,22 @@
         }
     }
 
+    /**
+     * Clear the selected page range and update the preview if needed.
+     */
+    private void clearPageRanges() {
+        mRangeOptionsSpinner.setSelection(0);
+        mPageRangeEditText.setError(null);
+        mPageRangeEditText.setText("");
+        mSelectedPages = PageRange.ALL_PAGES_ARRAY;
+
+        if (!Arrays.equals(mSelectedPages, mPrintPreviewController.getSelectedPages())) {
+            updatePrintPreviewController(false);
+        }
+    }
+
     private void updatePrintAttributesFromCapabilities(PrinterCapabilitiesInfo capabilities) {
+        boolean clearRanges = false;
         PrintAttributes defaults = capabilities.getDefaults();
 
         // Sort the media sizes based on the current locale.
@@ -1061,6 +1066,7 @@
         // Media size.
         MediaSize currMediaSize = attributes.getMediaSize();
         if (currMediaSize == null) {
+            clearRanges = true;
             attributes.setMediaSize(defaults.getMediaSize());
         } else {
             MediaSize newMediaSize = null;
@@ -1079,6 +1085,7 @@
             }
             // If we did not find the current media size fall back to default.
             if (newMediaSize == null) {
+                clearRanges = true;
                 newMediaSize = defaults.getMediaSize();
             }
 
@@ -1110,7 +1117,14 @@
         }
 
         // Margins.
+        if (!Objects.equals(attributes.getMinMargins(), defaults.getMinMargins())) {
+            clearRanges = true;
+        }
         attributes.setMinMargins(defaults.getMinMargins());
+
+        if (clearRanges) {
+            clearPageRanges();
+        }
     }
 
     private boolean updateDocument(boolean clearLastError) {
@@ -1262,6 +1276,7 @@
         // Page range
         mPageRangeTitle = (TextView) findViewById(R.id.page_range_title);
         mPageRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+        mPageRangeEditText.setVisibility(View.INVISIBLE);
         mPageRangeEditText.setOnFocusChangeListener(mSelectAllOnFocusListener);
         mPageRangeEditText.addTextChangedListener(new RangeTextWatcher());
 
@@ -1753,36 +1768,38 @@
         // Range options
         PrintDocumentInfo info = mPrintedDocument.getDocumentInfo().info;
         final int pageCount = getAdjustedPageCount(info);
-        if (info != null && pageCount > 0) {
-            if (pageCount == 1) {
-                mRangeOptionsSpinner.setEnabled(false);
-            } else {
-                mRangeOptionsSpinner.setEnabled(true);
-                if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
-                    if (!mPageRangeEditText.isEnabled()) {
-                        mPageRangeEditText.setEnabled(true);
-                        mPageRangeEditText.setVisibility(View.VISIBLE);
-                        mPageRangeTitle.setVisibility(View.VISIBLE);
-                        mPageRangeEditText.requestFocus();
-                        InputMethodManager imm = (InputMethodManager)
-                                getSystemService(Context.INPUT_METHOD_SERVICE);
-                        imm.showSoftInput(mPageRangeEditText, 0);
-                    }
+        if (pageCount > 0) {
+            if (info != null) {
+                if (pageCount == 1) {
+                    mRangeOptionsSpinner.setEnabled(false);
                 } else {
-                    mPageRangeEditText.setEnabled(false);
-                    mPageRangeEditText.setVisibility(View.INVISIBLE);
-                    mPageRangeTitle.setVisibility(View.INVISIBLE);
+                    mRangeOptionsSpinner.setEnabled(true);
+                    if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+                        if (!mPageRangeEditText.isEnabled()) {
+                            mPageRangeEditText.setEnabled(true);
+                            mPageRangeEditText.setVisibility(View.VISIBLE);
+                            mPageRangeTitle.setVisibility(View.VISIBLE);
+                            mPageRangeEditText.requestFocus();
+                            InputMethodManager imm = (InputMethodManager)
+                                    getSystemService(Context.INPUT_METHOD_SERVICE);
+                            imm.showSoftInput(mPageRangeEditText, 0);
+                        }
+                    } else {
+                        mPageRangeEditText.setEnabled(false);
+                        mPageRangeEditText.setVisibility(View.INVISIBLE);
+                        mPageRangeTitle.setVisibility(View.INVISIBLE);
+                    }
                 }
+            } else {
+                if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+                    mRangeOptionsSpinner.setSelection(0);
+                    mPageRangeEditText.setText("");
+                }
+                mRangeOptionsSpinner.setEnabled(false);
+                mPageRangeEditText.setEnabled(false);
+                mPageRangeEditText.setVisibility(View.INVISIBLE);
+                mPageRangeTitle.setVisibility(View.INVISIBLE);
             }
-        } else {
-            if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
-                mRangeOptionsSpinner.setSelection(0);
-                mPageRangeEditText.setText("");
-            }
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPageRangeEditText.setVisibility(View.INVISIBLE);
-            mPageRangeTitle.setVisibility(View.INVISIBLE);
         }
 
         final int newPageCount = getAdjustedPageCount(info);
@@ -1933,7 +1950,7 @@
             return PageRangeUtils.normalize(pageRangesArray);
         }
 
-        return ALL_PAGES_ARRAY;
+        return PageRange.ALL_PAGES_ARRAY;
     }
 
     private int getAdjustedPageCount(PrintDocumentInfo info) {
@@ -2607,6 +2624,8 @@
     private final class MyOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
         @Override
         public void onItemSelected(AdapterView<?> spinner, View view, int position, long id) {
+            boolean clearRanges = false;
+
             if (spinner == mDestinationSpinner) {
                 if (position == AdapterView.INVALID_POSITION) {
                     return;
@@ -2653,10 +2672,17 @@
             } else if (spinner == mMediaSizeSpinner) {
                 SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position);
                 PrintAttributes attributes = mPrintJob.getAttributes();
+
+                MediaSize newMediaSize;
                 if (mOrientationSpinner.getSelectedItemPosition() == 0) {
-                    attributes.setMediaSize(mediaItem.value.asPortrait());
+                    newMediaSize = mediaItem.value.asPortrait();
                 } else {
-                    attributes.setMediaSize(mediaItem.value.asLandscape());
+                    newMediaSize = mediaItem.value.asLandscape();
+                }
+
+                if (newMediaSize != attributes.getMediaSize()) {
+                    clearRanges = true;
+                    attributes.setMediaSize(newMediaSize);
                 }
             } else if (spinner == mColorModeSpinner) {
                 SpinnerItem<Integer> colorModeItem = mColorModeSpinnerAdapter.getItem(position);
@@ -2668,25 +2694,35 @@
                 SpinnerItem<Integer> orientationItem = mOrientationSpinnerAdapter.getItem(position);
                 PrintAttributes attributes = mPrintJob.getAttributes();
                 if (mMediaSizeSpinner.getSelectedItem() != null) {
-                    if (orientationItem.value == ORIENTATION_PORTRAIT) {
-                        attributes.copyFrom(attributes.asPortrait());
-                    } else {
-                        attributes.copyFrom(attributes.asLandscape());
+                    boolean isPortrait = attributes.isPortrait();
+
+                    if (isPortrait != (orientationItem.value == ORIENTATION_PORTRAIT)) {
+                        clearRanges = true;
+                        if (orientationItem.value == ORIENTATION_PORTRAIT) {
+                            attributes.copyFrom(attributes.asPortrait());
+                        } else {
+                            attributes.copyFrom(attributes.asLandscape());
+                        }
                     }
                 }
             } else if (spinner == mRangeOptionsSpinner) {
                 if (mRangeOptionsSpinner.getSelectedItemPosition() == 0) {
+                    clearRanges = true;
                     mPageRangeEditText.setText("");
                 } else if (TextUtils.isEmpty(mPageRangeEditText.getText())) {
                     mPageRangeEditText.setError("");
                 }
             }
 
-            if (canUpdateDocument()) {
-                updateDocument(false);
+            if (clearRanges) {
+                clearPageRanges();
             }
 
             updateOptionsUi();
+
+            if (canUpdateDocument()) {
+                updateDocument(false);
+            }
         }
 
         @Override
@@ -2702,6 +2738,16 @@
             if (!TextUtils.isEmpty(editText.getText())) {
                 editText.setSelection(editText.getText().length());
             }
+
+            if (view == mPageRangeEditText && !hasFocus) {
+                PageRange[] selectedPages = computeSelectedPages();
+                if (selectedPages != null && !Arrays.equals(mSelectedPages, selectedPages)) {
+                    mSelectedPages = selectedPages;
+
+                    // Update preview.
+                    updatePrintPreviewController(false);
+                }
+            }
         }
     }
 
@@ -2719,19 +2765,22 @@
         @Override
         public void afterTextChanged(Editable editable) {
             final boolean hadErrors = hasErrors();
-
             String text = editable.toString();
 
             if (TextUtils.isEmpty(text)) {
-                mPageRangeEditText.setError("");
-                updateOptionsUi();
+                if (mPageRangeEditText.getError() == null) {
+                    mPageRangeEditText.setError("");
+                    updateOptionsUi();
+                }
                 return;
             }
 
             String escapedText = PATTERN_ESCAPE_SPECIAL_CHARS.matcher(text).replaceAll("////");
             if (!PATTERN_PAGE_RANGE.matcher(escapedText).matches()) {
-                mPageRangeEditText.setError("");
-                updateOptionsUi();
+                if (mPageRangeEditText.getError() == null) {
+                    mPageRangeEditText.setError("");
+                    updateOptionsUi();
+                }
                 return;
             }
 
@@ -2747,8 +2796,10 @@
                 }
                 final int pageIndex = Integer.parseInt(numericString);
                 if (pageIndex < 1 || pageIndex > pageCount) {
-                    mPageRangeEditText.setError("");
-                    updateOptionsUi();
+                    if (mPageRangeEditText.getError() == null) {
+                        mPageRangeEditText.setError("");
+                        updateOptionsUi();
+                    }
                     return;
                 }
             }
@@ -2757,13 +2808,14 @@
             // greater than the to page. When computing the requested pages
             // we just swap them if necessary.
 
-            mPageRangeEditText.setError(null);
-            mPrintButton.setEnabled(true);
-            updateOptionsUi();
-
-            if (hadErrors && !hasErrors()) {
+            if (mPageRangeEditText.getError() != null) {
+                mPageRangeEditText.setError(null);
                 updateOptionsUi();
             }
+
+            if (hadErrors && canUpdateDocument()) {
+                updateDocument(false);
+            }
         }
     }
 
@@ -2783,8 +2835,10 @@
             final boolean hadErrors = hasErrors();
 
             if (editable.length() == 0) {
-                mCopiesEditText.setError("");
-                updateOptionsUi();
+                if (mCopiesEditText.getError() == null) {
+                    mCopiesEditText.setError("");
+                    updateOptionsUi();
+                }
                 return;
             }
 
@@ -2796,16 +2850,19 @@
             }
 
             if (copies < MIN_COPIES) {
-                mCopiesEditText.setError("");
-                updateOptionsUi();
+                if (mCopiesEditText.getError() == null) {
+                    mCopiesEditText.setError("");
+                    updateOptionsUi();
+                }
                 return;
             }
 
             mPrintJob.setCopies(copies);
 
-            mCopiesEditText.setError(null);
-
-            updateOptionsUi();
+            if (mCopiesEditText.getError() != null) {
+                mCopiesEditText.setError(null);
+                updateOptionsUi();
+            }
 
             if (hadErrors && canUpdateDocument()) {
                 updateDocument(false);
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
new file mode 100755
index 0000000..299a5b7
--- /dev/null
+++ b/packages/SettingsLib/res/values/config.xml
@@ -0,0 +1,22 @@
+<?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.
+*/
+-->
+<resources>
+    <!-- Configuration for automotive -->
+    <bool name="enable_pbap_pce_profile">false</bool>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 6226b23..6052ccd 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -24,12 +24,14 @@
 import android.bluetooth.BluetoothMap;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
 import android.os.ParcelUuid;
 import android.util.Log;
+import com.android.settingslib.R;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -82,7 +84,9 @@
     private final HidProfile mHidProfile;
     private OppProfile mOppProfile;
     private final PanProfile mPanProfile;
+    private PbapClientProfile mPbapClientProfile;
     private final PbapServerProfile mPbapProfile;
+    private final boolean mUsePbapPce;
 
     /**
      * Mapping from profile name, e.g. "HEADSET" to profile object.
@@ -99,6 +103,7 @@
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mEventManager = eventManager;
+        mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
         // pass this reference to adapter and event manager (circular dependency)
         mLocalAdapter.setProfileManager(this);
         mEventManager.setProfileManager(this);
@@ -205,9 +210,24 @@
         } else if (mOppProfile != null) {
             Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
         }
+
+        //PBAP Client
+        if (mUsePbapPce) {
+            if (mPbapClientProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
+                mPbapClientProfile = new PbapClientProfile(mContext, mLocalAdapter, mDeviceManager,
+                        this);
+                addProfile(mPbapClientProfile, PbapClientProfile.NAME,
+                        BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mPbapClientProfile != null) {
+            Log.w(TAG,
+                "Warning: PBAP Client profile was previously added but the UUID is now missing.");
+        }
+
         mEventManager.registerProfileIntentReceiver();
 
-        // There is no local SDP record for HID and Settings app doesn't control PBAP
+        // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
     }
 
     private final Collection<ServiceListener> mServiceListeners =
@@ -351,6 +371,10 @@
         }
     }
 
+    public PbapClientProfile getPbapClientProfile() {
+        return mPbapClientProfile;
+    }
+
     public PbapServerProfile getPbapProfile(){
         return mPbapProfile;
     }
@@ -430,6 +454,12 @@
             removedProfiles.remove(mMapProfile);
             mMapProfile.setPreferred(device, true);
         }
-    }
 
+        if (mUsePbapPce) {
+            profiles.add(mPbapClientProfile);
+            removedProfiles.remove(mPbapClientProfile);
+            profiles.remove(mPbapProfile);
+            removedProfiles.add(mPbapProfile);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
new file mode 100755
index 0000000..aa95be2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -0,0 +1,233 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothPbapClient;
+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.Collection;
+import java.util.List;
+
+final class PbapClientProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PbapClientProfile";
+    private static boolean V = false;
+
+    private BluetoothPbapClient mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final ParcelUuid[] SRC_UUIDS = {
+        BluetoothUuid.PBAP_PSE,
+    };
+
+    static final String NAME = "PbapClient";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 6;
+
+    // These callbacks run on the main thread.
+    private final class PbapClientServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) {
+                Log.d(TAG,"Bluetooth service connected");
+            }
+            mService = (BluetoothPbapClient) proxy;
+            // We just bound to the service, so refresh the UI for any connected PBAP 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, "PbapClientProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(PbapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady = true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) {
+                Log.d(TAG,"Bluetooth service disconnected");
+            }
+            mIsProfileReady = false;
+        }
+    }
+
+    private void refreshProfiles() {
+        Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy();
+        for (CachedBluetoothDevice device : cachedDevices) {
+            device.onUuidChanged();
+        }
+    }
+
+    public boolean pbapClientExists() {
+        return (mService != null);
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    PbapClientProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new PbapClientServiceListener(),
+                BluetoothProfile.PBAP_CLIENT);
+    }
+
+    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 (V) {
+            Log.d(TAG,"PBAPClientProfile got connect request");
+        }
+        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;
+                }
+            }
+            mService.disconnect(device);
+        }
+        Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
+
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (V) {
+            Log.d(TAG,"PBAPClientProfile got disconnect request");
+        }
+        if (mService == null) {
+            return false;
+        }
+        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);
+        }
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        // we need to have same string in UI as the server side.
+        return R.string.bluetooth_profile_pbap;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap_summary;
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) {
+            Log.d(TAG, "finalize()");
+        }
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
+                    BluetoothProfile.PBAP_CLIENT,mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PBAP Client proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 64910fd..ff70190 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -155,8 +155,10 @@
             mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
             updateDrawer();
         } else {
-            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
-            mDrawerLayout = null;
+            if (mDrawerLayout != null) {
+                mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+                mDrawerLayout = null;
+            }
         }
     }
 
@@ -174,8 +176,11 @@
 
     @Override
     public void setContentView(@LayoutRes int layoutResID) {
-        LayoutInflater.from(this).inflate(layoutResID,
-                (ViewGroup) findViewById(R.id.content_frame));
+        final ViewGroup parent = (ViewGroup) findViewById(R.id.content_frame);
+        if (parent != null) {
+            parent.removeAllViews();
+        }
+        LayoutInflater.from(this).inflate(layoutResID, parent);
     }
 
     @Override
diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
index 814aa8c..49759c5 100644
--- a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
+++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
@@ -56,7 +56,7 @@
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
         final RowBuilder row = result.newRow();
         row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
-        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED);
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
         row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
         row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
         row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
index 6cb8470..381fb16 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
@@ -16,7 +16,7 @@
   ~ limitations under the License
   -->
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:layout_width="match_parent"
+          android:layout_width="wrap_content"
           android:layout_height="match_parent"
           android:textSize="14sp"
           android:paddingStart="24dp"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index cb861ec..b88846b 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -26,9 +26,6 @@
     android:clipChildren="false"
     android:clipToPadding="false"
     android:baselineAligned="false"
-    android:background="@drawable/quick_header_bg"
-    android:clickable="true"
-    android:focusable="true"
     >
 
     <LinearLayout
@@ -83,6 +80,10 @@
             android:id="@+id/expand_indicator"
             android:layout_width="48dp"
             android:layout_height="48dp"
+            android:clipToPadding="false"
+            android:clickable="true"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackgroundBorderless"
             android:padding="12dp" />
 
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/recents_stack_action_button.xml b/packages/SystemUI/res/layout/recents_stack_action_button.xml
index 8783261..2b2ce4e 100644
--- a/packages/SystemUI/res/layout/recents_stack_action_button.xml
+++ b/packages/SystemUI/res/layout/recents_stack_action_button.xml
@@ -27,8 +27,6 @@
     android:textSize="14sp"
     android:textColor="#FFFFFF"
     android:textAllCaps="true"
-    android:drawableStart="@drawable/ic_history"
-    android:drawablePadding="6dp"
     android:shadowColor="#99000000"
     android:shadowDx="0"
     android:shadowDy="2"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 622ae71..fd051b1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -153,6 +153,9 @@
     <!-- The animation duration for animating the removal of a task view. -->
     <integer name="recents_animate_task_view_remove_duration">175</integer>
 
+    <!-- The base animation duration for animating the removal of all task views. -->
+    <integer name="recents_animate_task_views_remove_all_duration">300</integer>
+
     <!-- The animation duration for scrolling the stack to a particular item. -->
     <integer name="recents_animate_task_stack_scroll_duration">200</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d1faa4a..c094da9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -574,10 +574,12 @@
     <dimen name="recents_layout_bottom_margin">16dp</dimen>
     <dimen name="recents_layout_side_margin_phone">16dp</dimen>
     <dimen name="recents_layout_side_margin_tablet">48dp</dimen>
+    <dimen name="recents_layout_side_margin_tablet_docked">16dp</dimen>
     <dimen name="recents_layout_side_margin_tablet_xlarge">64dp</dimen>
+    <dimen name="recents_layout_side_margin_tablet_xlarge_docked">16dp</dimen>
 
     <!-- The height between the top margin and the top of the focused task. -->
-    <dimen name="recents_layout_top_peek_size">56dp</dimen>
+    <dimen name="recents_layout_top_peek_size">48dp</dimen>
     <!-- The height between the bottom margin and the top of task in front of the focused task. -->
     <dimen name="recents_layout_bottom_peek_size">56dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index 4845425..087f61e 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -452,7 +452,7 @@
         boolean pctOpaque = false;
         float pctX = 0, pctY = 0;
         String pctText = null;
-        if (!mPluggedIn && level > mCriticalLevel && mShowPercent) {
+        if (!mPluggedIn && !mPowerSaveEnabled && level > mCriticalLevel && mShowPercent) {
             mTextPaint.setColor(getColorForLevel(level));
             mTextPaint.setTextSize(height *
                     (SINGLE_DIGIT_PERCENT ? 0.75f
@@ -480,7 +480,7 @@
         mShapePath.op(mClipPath, Path.Op.INTERSECT);
         c.drawPath(mShapePath, mBatteryPaint);
 
-        if (!mPluggedIn) {
+        if (!mPluggedIn && !mPowerSaveEnabled) {
             if (level <= mCriticalLevel) {
                 // draw the warning text
                 final float x = mWidth * 0.5f;
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 5e33a9f..17f4dab 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -17,6 +17,7 @@
 package com.android.systemui;
 
 import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
@@ -32,6 +33,7 @@
     public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
     public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
     public static final Interpolator LINEAR = new LinearInterpolator();
+    public static final Interpolator ACCELERATE = new AccelerateInterpolator();
     public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
     public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index fd43aa0..115c9d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,13 +1,13 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import com.android.internal.widget.PagerAdapter;
-import com.android.internal.widget.ViewPager;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanel.TileRecord;
@@ -37,7 +37,8 @@
             public void onPageSelected(int position) {
                 if (mPageIndicator == null) return;
                 if (mPageListener != null) {
-                    mPageListener.onPageChanged(position == 0);
+                    mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
+                            : position == 0);
                 }
             }
 
@@ -47,7 +48,8 @@
                 if (mPageIndicator == null) return;
                 mPageIndicator.setLocation(position + positionOffset);
                 if (mPageListener != null) {
-                    mPageListener.onPageChanged(position == 0 && positionOffsetPixels == 0);
+                    mPageListener.onPageChanged(positionOffsetPixels == 0 &&
+                            (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
                 }
             }
 
@@ -59,6 +61,21 @@
     }
 
     @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        setAdapter(mAdapter);
+        setCurrentItem(0, false);
+    }
+
+    @Override
+    public void setCurrentItem(int item, boolean smoothScroll) {
+        if (isLayoutRtl()) {
+            item = mPages.size() - 1 - item;
+        }
+        super.setCurrentItem(item, smoothScroll);
+    }
+
+    @Override
     public boolean hasOverlappingRendering() {
         return false;
     }
@@ -128,6 +145,7 @@
             mNumPages = index + 1;
             mPageIndicator.setNumPages(mNumPages);
             mAdapter.notifyDataSetChanged();
+            setCurrentItem(0, false);
         }
     }
 
@@ -206,6 +224,9 @@
 
         public Object instantiateItem(ViewGroup container, int position) {
             if (DEBUG) Log.d(TAG, "Instantiating " + position);
+            if (isLayoutRtl()) {
+                position = mPages.size() - 1 - position;
+            }
             ViewGroup view = mPages.get(position);
             container.addView(view);
             return view;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 0b6eaba..58d7c81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -64,6 +64,7 @@
     private boolean mAllowFancy;
     private boolean mFullRows;
     private int mNumQuickTiles;
+    private float mLastPosition;
 
     public QSAnimator(QSContainer container, QuickQSPanel quickPanel, QSPanel panel) {
         mQsContainer = container;
@@ -80,6 +81,10 @@
         }
     }
 
+    public void onRtlChanged() {
+        updateAnimators();
+    }
+
     public void setOnKeyguard(boolean onKeyguard) {
         mOnKeyguard = onKeyguard;
         if (mOnKeyguard) {
@@ -246,7 +251,7 @@
 
     private void getRelativePositionInt(int[] loc1, View view, View parent) {
         if(view == parent || view == null) return;
-        loc1[0] += view.getLeft();
+        loc1[0] += view.getX();
         loc1[1] += view.getTop();
         getRelativePositionInt(loc1, (View) view.getParent(), parent);
     }
@@ -256,6 +261,7 @@
         if (mOnKeyguard) {
             return;
         }
+        mLastPosition = position;
         if (mOnFirstPage && mAllowFancy) {
             mQuickQsPanel.setAlpha(1);
             mFirstPageAnimator.setPosition(position);
@@ -296,7 +302,7 @@
     private void clearAnimationState() {
         final int N = mAllViews.size();
         mQuickQsPanel.setAlpha(0);
-        mQuickQsPanel.setVisibility(View.INVISIBLE);
+        mQuickQsPanel.setVisibility(View.VISIBLE);
         for (int i = 0; i < N; i++) {
             View v = mAllViews.get(i);
             v.setAlpha(1);
@@ -312,7 +318,7 @@
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
             int oldTop, int oldRight, int oldBottom) {
-        updateAnimators();
+        mQsPanel.post(mUpdateAnimators);
     }
 
     @Override
@@ -334,6 +340,7 @@
         @Override
         public void run() {
             updateAnimators();
+            setPosition(mLastPosition);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index 43ebe04..5d06aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -43,6 +44,7 @@
     private static final boolean DEBUG = false;
 
     private final Point mSizePoint = new Point();
+    private final Rect mQsBounds = new Rect();
 
     private int mHeightOverride = -1;
     private QSPanel mQSPanel;
@@ -76,6 +78,12 @@
         mQSCustomizer.setQsContainer(this);
     }
 
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        mQSAnimator.onRtlChanged();
+    }
+
     public void setHost(QSTileHost qsh) {
         mQSPanel.setHost(qsh, mQSCustomizer);
         mHeader.setQSPanel(mQSPanel);
@@ -226,6 +234,12 @@
         mQSDetail.setFullyExpanded(expansion == 1);
         mQSAnimator.setPosition(expansion);
         updateBottom();
+
+        // Set bounds on the QS panel so it doesn't run over the header.
+        mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
+        mQsBounds.right = mQSPanel.getWidth();
+        mQsBounds.bottom = mQSPanel.getHeight();
+        mQSPanel.setClipBounds(mQsBounds);
     }
 
     public void animateHeaderSlidingIn(long delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 899b0ef..a05818a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -119,12 +119,18 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS);
+        if (mHost != null) {
+            setTiles(mHost.getTiles());
+        }
     }
 
     @Override
     protected void onDetachedFromWindow() {
         TunerService.get(mContext).removeTunable(this);
         mHost.removeCallback(this);
+        for (TileRecord record : mRecords) {
+            record.tile.removeCallbacks();
+        }
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 1659888..8777a91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -24,11 +24,15 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.qs.QSTile.State;
 import com.android.systemui.qs.external.TileServices;
@@ -81,6 +85,8 @@
     abstract protected void handleClick();
     abstract protected void handleUpdateState(TState state, Object arg);
 
+    private UserManager mUserManager;
+
     /**
      * Declare the category of this tile.
      *
@@ -93,6 +99,7 @@
         mHost = host;
         mContext = host.getContext();
         mHandler = new H(host.getLooper());
+        mUserManager = UserManager.get(mContext);
     }
 
     public String getTileSpec() {
@@ -212,6 +219,7 @@
     }
 
     protected void handleLongClick() {
+        MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
         mHost.startActivityDismissingKeyguard(getLongClickIntent());
     }
 
@@ -282,12 +290,11 @@
     }
 
     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
-        EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
-                userRestriction, ActivityManager.getCurrentUser());
-        if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
-                userRestriction, ActivityManager.getCurrentUser())) {
+        UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+        if (mUserManager.hasUserRestriction(userRestriction, user)
+                && !mUserManager.hasBaseUserRestriction(userRestriction, user)) {
             state.disabledByPolicy = true;
-            state.enforcedAdmin = admin;
+            state.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
         } else {
             state.disabledByPolicy = false;
             state.enforcedAdmin = null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index aeca840..83ac45c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -32,6 +32,8 @@
 import android.widget.LinearLayout;
 import android.widget.Toolbar;
 import android.widget.Toolbar.OnMenuItemClickListener;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSDetailClipper;
@@ -116,6 +118,7 @@
 
     public void show(int x, int y) {
         if (!isShown) {
+            MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
             isShown = true;
             setTileSpecs();
             setVisibility(View.VISIBLE);
@@ -127,6 +130,7 @@
 
     public void hide(int x, int y) {
         if (isShown) {
+            MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
             isShown = false;
             mToolbar.dismissPopupMenus();
             setCustomizing(false);
@@ -149,6 +153,7 @@
     public boolean onMenuItemClick(MenuItem item) {
         switch (item.getItemId()) {
             case MENU_RESET:
+                MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_QS_EDIT_RESET);
                 reset();
                 break;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 353c6b6..4c13451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -16,6 +16,7 @@
 
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Canvas;
@@ -36,12 +37,15 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSIconView;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.customize.TileAdapter.Holder;
 import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
 import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
+import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
@@ -304,12 +308,24 @@
         notifyItemMoved(from, to);
         CharSequence announcement;
         if (to >= mDividerIndex) {
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
+                    strip(mTiles.get(to)));
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
+                    from);
             announcement = mContext.getString(R.string.accessibility_qs_edit_tile_removed,
                     fromLabel);
         } else if (from >= mDividerIndex) {
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
+                    strip(mTiles.get(to)));
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
+                    to);
             announcement = mContext.getString(R.string.accessibility_qs_edit_tile_added,
                     fromLabel, (to + 1));
         } else {
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE_SPEC,
+                    strip(mTiles.get(to)));
+            MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE,
+                    to);
             announcement = mContext.getString(R.string.accessibility_qs_edit_tile_moved,
                     fromLabel, (to + 1));
         }
@@ -317,6 +333,15 @@
         return true;
     }
 
+    private String strip(TileInfo tileInfo) {
+        String spec = tileInfo.spec;
+        if (spec.startsWith(CustomTile.PREFIX)) {
+            ComponentName component = CustomTile.getComponentFromSpec(spec);
+            return component.getPackageName();
+        }
+        return spec;
+    }
+
     private <T> void move(int from, int to, List<T> list) {
         list.add(from > to ? to : to + 1, list.get(from));
         list.remove(from > to ? from + 1 : from);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index fe7ac71..d7777d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -90,6 +90,7 @@
 
     private RecentsPackageMonitor mPackageMonitor;
     private long mLastTabKeyEventTime;
+    private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
     private boolean mFinishedOnStartup;
     private boolean mIgnoreAltTabRelease;
     private boolean mIsVisible;
@@ -266,6 +267,7 @@
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 
+        mLastOrientation = getResources().getConfiguration().orientation;
         mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
         mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
             @Override
@@ -274,6 +276,9 @@
             }
         });
 
+        // Set the window background
+        getWindow().setBackgroundDrawable(mRecentsView.getBackgroundScrim());
+
         // Create the home intent runnable
         mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
         mHomeIntent.addCategory(Intent.CATEGORY_HOME);
@@ -338,7 +343,7 @@
         mRecentsView.updateStack(stack);
 
         // Update the nav bar scrim, but defer the animation until the enter-window event
-        boolean animateNavBarScrim = !launchState.launchedWhileDocking;
+        boolean animateNavBarScrim = !launchState.launchedViaDockGesture;
         updateNavBarScrim(animateNavBarScrim, null);
 
         // If this is a new instance relaunched by AM, without going through the normal mechanisms,
@@ -415,13 +420,18 @@
         // Update the nav bar for the current orientation
         updateNavBarScrim(false /* animateNavBarScrim */, AnimationProps.IMMEDIATE);
 
-        EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */));
+        // Notify of the config change
+        int newOrientation = getResources().getConfiguration().orientation;
+        EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
+                (mLastOrientation != newOrientation)));
+        mLastOrientation = newOrientation;
     }
 
     @Override
     public void onMultiWindowChanged(boolean inMultiWindow) {
         super.onMultiWindowChanged(inMultiWindow);
-        EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */));
+        EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
+                false /* fromOrientationChange */));
 
         if (mRecentsView != null) {
             // Reload the task stack completely
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 77f7739..7161053 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -29,10 +29,9 @@
 
     public boolean launchedWithAltTab;
     public boolean launchedFromApp;
-    public boolean launchedFromAppDocked;
     public boolean launchedFromHome;
     public boolean launchedViaDragGesture;
-    public boolean launchedWhileDocking;
+    public boolean launchedViaDockGesture;
     public int launchedToTaskId;
     public int launchedNumVisibleTasks;
     public int launchedNumVisibleThumbnails;
@@ -40,18 +39,10 @@
     public void reset() {
         launchedFromHome = false;
         launchedFromApp = false;
-        launchedFromAppDocked = false;
         launchedToTaskId = -1;
         launchedWithAltTab = false;
         launchedViaDragGesture = false;
-        launchedWhileDocking = false;
-    }
-
-    /** Called when the configuration has changed, and we want to reset any configuration specific
-     * members. */
-    public void updateOnConfigurationChange() {
-        launchedViaDragGesture = false;
-        launchedWhileDocking = false;
+        launchedViaDockGesture = false;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 40613f0..73c6e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -80,12 +80,4 @@
     public RecentsActivityLaunchState getLaunchState() {
         return mLaunchState;
     }
-
-    /**
-     * Called when the configuration has changed, and we want to reset any configuration specific
-     * members.
-     */
-    public void updateOnConfigurationChange() {
-        mLaunchState.updateOnConfigurationChange();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 043510e..1715356 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -36,7 +36,7 @@
         // Enables the task affiliations
         public static final boolean EnableAffiliatedTaskGroups = false;
         // TODO: To be repurposed
-        public static final boolean EnableStackActionButton = false;
+        public static final boolean EnableStackActionButton = true;
         // Overrides the Tuner flags and enables the timeout
         private static final boolean EnableFastToggleTimeout = false;
         // Overrides the Tuner flags and enables the paging via the Recents button
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index c52d17f..43d627d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -43,7 +43,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.EventBus.Event;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.ForcedResizableEvent;
@@ -208,7 +207,6 @@
     public void onConfigurationChanged() {
         reloadHeaderBarLayout();
         updateHeaderBarLayout(null /* stack */);
-        Recents.getConfiguration().updateOnConfigurationChange();
     }
 
     /**
@@ -665,13 +663,18 @@
             @Override
             public void run() {
                 final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mThumbnailTransitionBitmapCache = transitionBitmap;
-                        mThumbnailTransitionBitmapCacheKey = toTask;
-                    }
-                });
+                if (transitionBitmap != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mThumbnailTransitionBitmapCache = transitionBitmap;
+                            mThumbnailTransitionBitmapCacheKey = toTask;
+                        }
+                    });
+                } else {
+                    Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " +
+                            toTransform);
+                }
             }
         });
     }
@@ -776,7 +779,7 @@
         // Get the transform for the running task
         stackView.updateLayoutAlgorithm(true /* boundScroll */);
         stackView.updateToInitialState(true /* scrollToInitialState */);
-        mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
+        stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
         return mTmpTransform;
     }
@@ -852,13 +855,12 @@
         // Update the launch state
         launchState.launchedFromHome = false;
         launchState.launchedFromApp = mLaunchedWhileDocking;
+        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
         launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
-        launchState.launchedFromAppDocked = mLaunchedWhileDocking;
         launchState.launchedWithAltTab = mTriggeredFromAltTab;
         launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
         launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
         launchState.launchedViaDragGesture = mDraggingInRecents;
-        launchState.launchedWhileDocking = mLaunchedWhileDocking;
 
         if (!animate) {
             startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
index c234c34..8be9ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
@@ -24,8 +24,10 @@
 public class ConfigurationChangedEvent extends EventBus.AnimatedEvent {
 
     public final boolean fromMultiWindow;
+    public final boolean fromOrientationChange;
 
-    public ConfigurationChangedEvent(boolean fromMultiWindow) {
+    public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange) {
         this.fromMultiWindow = fromMultiWindow;
+        this.fromOrientationChange = fromOrientationChange;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
index 863f40b..f8b59c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ResetBackgroundScrimEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
@@ -17,10 +17,11 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.views.TaskView;
 
 /**
- * This is sent to reset the background scrim back to the initial state.
+ * This event is sent to request that all the {@link TaskView}s are dismissed.
  */
-public class ResetBackgroundScrimEvent extends EventBus.Event {
+public class DismissAllTaskViewsEvent extends EventBus.AnimatedEvent {
     // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
index 1165f4e..1f8c644 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.views.TaskView;
 
 /**
@@ -26,10 +25,8 @@
 public class DismissTaskViewEvent extends EventBus.AnimatedEvent {
 
     public final TaskView taskView;
-    public final Task task;
 
-    public DismissTaskViewEvent(TaskView taskView, Task task) {
+    public DismissTaskViewEvent(TaskView taskView) {
         this.taskView = taskView;
-        this.task = task;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java
deleted file mode 100644
index fdd4c67..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateBackgroundScrimEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to request an update to the background scrim.
- */
-public class UpdateBackgroundScrimEvent extends EventBus.Event {
-
-    public final float alpha;
-
-    public UpdateBackgroundScrimEvent(float alpha) {
-        this.alpha = alpha;
-    }
-}
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 5a2507d..df3f56c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -222,6 +222,11 @@
             Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture);
 
         /**
+         * Notifies when all tasks have been removed from the stack.
+         */
+        void onStackTasksRemoved(TaskStack stack);
+
+        /**
          * Notifies when tasks in the stack have been updated.
          */
         void onStackTasksUpdated(TaskStack stack);
@@ -510,6 +515,22 @@
     }
 
     /**
+     * Removes all tasks from the stack.
+     */
+    public void removeAllTasks() {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task t = tasks.get(i);
+            removeTaskImpl(mStackTaskList, t);
+            mRawTaskList.remove(t);
+        }
+        if (mCb != null) {
+            // Notify that all tasks have been removed
+            mCb.onStackTasksRemoved(this);
+        }
+    }
+
+    /**
      * Sets a few tasks in one go, without calling any callbacks.
      *
      * @param tasks the new set of tasks to replace the current set.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 3d0e75a..5eb9fda 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -158,6 +158,11 @@
     }
 
     @Override
+    public void onStackTasksRemoved(TaskStack stack) {
+        // Do nothing
+    }
+
+    @Override
     public void onStackTasksUpdated(TaskStack stack) {
         // Do nothing
     }
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 9dc3fb1..9eec2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -28,11 +28,13 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
 
@@ -252,12 +254,13 @@
     /**
      * Composes the transition spec when docking a task, which includes a full task bitmap.
      */
-    public List<AppTransitionAnimationSpec> composeDockAnimationSpec(
-            TaskView taskView, Rect transform) {
-        TaskViewTransform viewTransform = new TaskViewTransform();
-        viewTransform.fillIn(taskView);
-        return Collections.singletonList(new AppTransitionAnimationSpec(taskView.getTask().key.id,
-                RecentsTransitionHelper.composeTaskBitmap(taskView, viewTransform), transform));
+    public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
+            Rect bounds) {
+        mTmpTransform.fillIn(taskView);
+        Task task = taskView.getTask();
+        Bitmap thumbnail = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
+        return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, thumbnail,
+                bounds));
     }
 
     /**
@@ -336,18 +339,27 @@
         float scale = transform.scale;
         int fromWidth = (int) (transform.rect.width() * scale);
         int fromHeight = (int) (transform.rect.height() * scale);
-        Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight,
-                Bitmap.Config.ARGB_8888);
+        if (fromWidth == 0 || fromHeight == 0) {
+            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+                    " at transform: " + transform);
 
-        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-            b.eraseColor(0xFFff0000);
+            Bitmap b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            b.eraseColor(Color.TRANSPARENT);
+            return b;
         } else {
-            Canvas c = new Canvas(b);
-            c.scale(scale, scale);
-            taskView.draw(c);
-            c.setBitmap(null);
+            Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight,
+                    Bitmap.Config.ARGB_8888);
+
+            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+                b.eraseColor(0xFFff0000);
+            } else {
+                Canvas c = new Canvas(b);
+                c.scale(scale, scale);
+                taskView.draw(c);
+                c.setBitmap(null);
+            }
+            return b.createAshmemBitmap();
         }
-        return b.createAshmemBitmap();
     }
 
     private static Bitmap composeHeaderBitmap(TaskView taskView,
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 a1ba493..da2f721 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -58,10 +58,11 @@
 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
-import com.android.systemui.recents.events.ui.ResetBackgroundScrimEvent;
-import com.android.systemui.recents.events.ui.UpdateBackgroundScrimEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -87,6 +88,9 @@
     private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
     private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
 
+    private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 150;
+    private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
+
     private TaskStack mStack;
     private TaskStackView mTaskStackView;
     private TextView mStackActionButton;
@@ -99,7 +103,8 @@
     private Rect mSystemInsets = new Rect();
     private int mDividerSize;
 
-    private ColorDrawable mBackgroundScrim = new ColorDrawable(Color.BLACK);
+    private Drawable mBackgroundScrim = new ColorDrawable(
+            Color.argb((int) (DEFAULT_SCRIM_ALPHA * 255), 0, 0, 0)).mutate();
     private Animator mBackgroundScrimAnimator;
 
     private RecentsTransitionHelper mTransitionHelper;
@@ -135,10 +140,11 @@
                     R.dimen.recents_task_view_rounded_corners_radius);
             mStackActionButton = (TextView) inflater.inflate(R.layout.recents_stack_action_button,
                     this, false);
+            mStackActionButton.forceHasOverlappingRendering(false);
             mStackActionButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    // TODO: To be implemented
+                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
                 }
             });
             addView(mStackActionButton);
@@ -152,8 +158,6 @@
         }
         mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
         addView(mEmptyView);
-
-        setBackground(mBackgroundScrim);
     }
 
     /**
@@ -179,14 +183,14 @@
 
         if (isResumingFromVisible) {
             // If we are already visible, then restore the background scrim
-            animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
+            animateBackgroundScrim(1f, DEFAULT_UPDATE_SCRIM_DURATION);
         } else {
             // If we are already occluded by the app, then set the final background scrim alpha now.
             // Otherwise, defer until the enter animation completes to animate the scrim alpha with
             // the tasks for the home animation.
-            if (launchState.launchedWhileDocking || launchState.launchedFromApp
+            if (launchState.launchedViaDockGesture || launchState.launchedFromApp
                     || isTaskStackEmpty) {
-                mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255));
+                mBackgroundScrim.setAlpha(255);
             } else {
                 mBackgroundScrim.setAlpha(0);
             }
@@ -215,6 +219,13 @@
         return mStack;
     }
 
+    /*
+     * Returns the window background scrim.
+     */
+    public Drawable getBackgroundScrim() {
+        return mBackgroundScrim;
+    }
+
     /**
      * Returns whether the last task launched was in the freeform stack or not.
      */
@@ -564,19 +575,23 @@
 
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        if (!launchState.launchedWhileDocking && !launchState.launchedFromApp
+        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
                 && mStack.getTaskCount() > 0) {
-            animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
+            animateBackgroundScrim(1f,
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
     }
 
-    public final void onBusEvent(UpdateBackgroundScrimEvent event) {
-        animateBackgroundScrim(event.alpha, DEFAULT_UPDATE_SCRIM_DURATION);
+    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
     }
 
-    public final void onBusEvent(ResetBackgroundScrimEvent event) {
-        animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, DEFAULT_UPDATE_SCRIM_DURATION);
+    public final void onBusEvent(DismissAllTaskViewsEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (!ssp.hasDockedTask()) {
+            // Animate the background away only if we are dismissing Recents to home
+            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
+        }
     }
 
     public final void onBusEvent(ShowStackActionButtonEvent event) {
@@ -584,7 +599,7 @@
             return;
         }
 
-        showStackActionButton(150, event.translate);
+        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
     }
 
     public final void onBusEvent(HideStackActionButtonEvent event) {
@@ -592,7 +607,7 @@
             return;
         }
 
-        hideStackActionButton(100, true /* translate */);
+        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
     }
 
     /**
@@ -623,7 +638,6 @@
                             .alpha(1f)
                             .setDuration(duration)
                             .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                            .withLayer()
                             .start();
                 }
             });
@@ -669,7 +683,6 @@
                             postAnimationTrigger.decrement();
                         }
                     })
-                    .withLayer()
                     .start();
             postAnimationTrigger.increment();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 9c8189a..13ad9a5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -24,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -107,4 +108,14 @@
             animateNavBarScrimVisibility(false, animation);
         }
     }
+
+    public final void onBusEvent(DismissAllTaskViewsEvent event) {
+        if (mHasNavBarScrim) {
+            AnimationProps animation = new AnimationProps()
+                    .setDuration(AnimationProps.BOUNDS,
+                            TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION)
+                    .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
+            animateNavBarScrimVisibility(false, animation);
+        }
+    }
 }
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 1c7d609..1c433d8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -20,7 +20,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.RectF;
 import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -32,6 +31,7 @@
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -152,30 +152,26 @@
 
             if (hideTask) {
                 tv.setVisibility(View.INVISIBLE);
-            } else if (launchState.launchedFromApp && !launchState.launchedWhileDocking) {
+            } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
                     tv.onPrepareLaunchTargetForEnterAnimation();
                 } else if (currentTaskOccludesLaunchTarget) {
                     // Move the task view slightly lower so we can animate it in
-                    RectF bounds = new RectF(mTmpTransform.rect);
-                    bounds.offset(0, taskViewAffiliateGroupEnterOffset);
+                    mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
+                    mTmpTransform.alpha = 0f;
+                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
+                            AnimationProps.IMMEDIATE);
                     tv.setClipViewInStack(false);
-                    tv.setAlpha(0f);
-                    tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top,
-                            (int) bounds.right, (int) bounds.bottom);
                 }
             } else if (launchState.launchedFromHome) {
                 // Move the task view off screen (below) so we can animate it in
-                RectF bounds = new RectF(mTmpTransform.rect);
-                bounds.offset(0, offscreenYOffset);
-                tv.setAlpha(0f);
-                tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right,
-                        (int) bounds.bottom);
-            } else if (launchState.launchedWhileDocking) {
-                RectF bounds = new RectF(mTmpTransform.rect);
-                bounds.offset(0, launchedWhileDockingOffset);
-                tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right,
-                        (int) bounds.bottom);
+                mTmpTransform.rect.offset(0, offscreenYOffset);
+                mTmpTransform.alpha = 0f;
+                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
+            } else if (launchState.launchedViaDockGesture) {
+                mTmpTransform.rect.offset(0, launchedWhileDockingOffset);
+                mTmpTransform.alpha = 0f;
+                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
             }
         }
     }
@@ -223,7 +219,7 @@
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
 
-            if (launchState.launchedFromApp && !launchState.launchedWhileDocking) {
+            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
                     tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
                             taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
@@ -262,7 +258,7 @@
                 if (i == taskViewCount - 1) {
                     tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled);
                 }
-            } else if (launchState.launchedWhileDocking) {
+            } else if (launchState.launchedViaDockGesture) {
                 // Animate the tasks up
                 AnimationProps taskAnimation = new AnimationProps()
                         .setDuration(AnimationProps.BOUNDS, (int) (ENTER_WHILE_DOCKING_DURATION +
@@ -282,7 +278,6 @@
     public void startExitToHomeAnimation(boolean animated,
             ReferenceCountedTrigger postAnimationTrigger) {
         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
         TaskStack stack = mStackView.getStack();
 
         // Break early if there are no tasks
@@ -318,8 +313,7 @@
                 taskAnimation = AnimationProps.IMMEDIATE;
             }
 
-            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
+            mTmpTransform.fillIn(tv);
             mTmpTransform.alpha = 0f;
             mTmpTransform.rect.offset(0, offscreenYOffset);
             mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
@@ -333,8 +327,6 @@
     public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested,
             final ReferenceCountedTrigger postAnimationTrigger) {
         Resources res = mStackView.getResources();
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
 
         int taskViewExitToAppDuration = res.getInteger(
                 R.integer.recents_task_exit_to_app_duration);
@@ -367,8 +359,7 @@
                         postAnimationTrigger.decrementOnAnimationEnd());
                 postAnimationTrigger.increment();
 
-                stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                        null);
+                mTmpTransform.fillIn(tv);
                 mTmpTransform.alpha = 0f;
                 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
                 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
@@ -379,16 +370,14 @@
     /**
      * Starts the delete animation for the specified {@link TaskView}.
      */
-    public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView,
+    public void startDeleteTaskAnimation(final TaskView deleteTaskView,
             final ReferenceCountedTrigger postAnimationTrigger) {
         Resources res = mStackView.getResources();
         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
 
         int taskViewRemoveAnimDuration = res.getInteger(
                 R.integer.recents_animate_task_view_remove_duration);
-        int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize(
-                R.dimen.recents_task_view_remove_anim_translation_x);
+        int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left;
 
         // Disabling clipping with the stack while the view is animating away, this will get
         // restored when the task is next picked up from the view pool
@@ -404,14 +393,58 @@
         });
         postAnimationTrigger.increment();
 
-        stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform,
-                null);
+        mTmpTransform.fillIn(deleteTaskView);
         mTmpTransform.alpha = 0f;
-        mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0);
+        mTmpTransform.rect.offset(offscreenXOffset, 0);
         mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation);
     }
 
     /**
+     * Starts the delete animation for all the {@link TaskView}s.
+     */
+    public void startDeleteAllTasksAnimation(final List<TaskView> taskViews,
+                                             final ReferenceCountedTrigger postAnimationTrigger) {
+        Resources res = mStackView.getResources();
+        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
+
+        int taskViewRemoveAnimDuration = res.getInteger(
+                R.integer.recents_animate_task_views_remove_all_duration);
+        int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left;
+
+        int taskViewCount = taskViews.size();
+        int startDelayMax = 125;
+
+        for (int i = taskViewCount - 1; i >= 0; i--) {
+            TaskView tv = taskViews.get(i);
+            int indexFromFront = taskViewCount - i - 1;
+            float x = Interpolators.ACCELERATE.getInterpolation((float) indexFromFront /
+                    taskViewCount);
+            int startDelay = (int) Utilities.mapRange(x, 0, startDelayMax);
+
+            // Disabling clipping with the stack while the view is animating away
+            tv.setClipViewInStack(false);
+
+            // Compose the new animation and transform and star the animation
+            AnimationProps taskAnimation = new AnimationProps(startDelay,
+                    taskViewRemoveAnimDuration, Interpolators.FAST_OUT_LINEAR_IN,
+                    new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    postAnimationTrigger.decrement();
+
+                    // Re-enable clipping with the stack (we will reuse this view)
+                    tv.setClipViewInStack(true);
+                }
+            });
+            postAnimationTrigger.increment();
+
+            mTmpTransform.fillIn(tv);
+            mTmpTransform.rect.offset(offscreenXOffset, 0);
+            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
+        }
+    }
+
+    /**
      * Starts the animation to focus the next {@link TaskView} when paging through recents.
      *
      * @return whether or not this will trigger a scroll in the stack
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 8a1727a..2508304 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -552,20 +552,22 @@
             mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
                     Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
             boolean scrollToFront = launchState.launchedFromHome ||
-                    launchState.launchedFromAppDocked;
+                    launchState.launchedViaDockGesture;
             if (scrollToFront) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
                 mInitialNormX = null;
             } else {
-                float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
-                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2)) -
-                        Math.max(0, mUnfocusedRange.getAbsoluteX(normX)));
+                // We are overriding the initial two task positions, so set the initial scroll
+                // position to match the second task (aka focused task) position
+                float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
+                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
+                        - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
 
                 // Set the initial scroll to the predefined state (which differs from the stack)
                 mInitialNormX = new float[] {
                         getNormalizedXFromUnfocusedY(mSystemInsets.bottom + mInitialBottomOffset,
                                 FROM_BOTTOM),
-                        normX
+                        initialTopNormX
                 };
             }
         }
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 9032871..5416a48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
@@ -60,8 +61,8 @@
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -71,6 +72,7 @@
 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
@@ -117,8 +119,6 @@
     private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216;
     private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32;
 
-    private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
-
     // The actions to perform when resetting to initial state,
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({INITIAL_STATE_UPDATE_NONE, INITIAL_STATE_UPDATE_ALL, INITIAL_STATE_UPDATE_LAYOUT_ONLY})
@@ -598,17 +598,14 @@
             if (tv == null) {
                 tv = mViewPool.pickUpViewFromPool(task, task);
                 if (task.isFreeformTask()) {
-                    tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE,
-                            mRequestUpdateClippingListener);
+                    updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
                 } else {
                     if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
-                        tv.updateViewPropertiesToTaskTransform(
-                                mLayoutAlgorithm.getBackOfStackTransform(),
-                                AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
+                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
+                                AnimationProps.IMMEDIATE);
                     } else {
-                        tv.updateViewPropertiesToTaskTransform(
-                                mLayoutAlgorithm.getFrontOfStackTransform(),
-                                AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
+                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
+                                AnimationProps.IMMEDIATE);
                     }
                 }
             } else {
@@ -1215,7 +1212,7 @@
                 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
         mLayoutAlgorithm.initialize(mWindowRect, mStackBounds,
                 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-        updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
+        updateLayoutAlgorithm(false /* boundScroll */, mIgnoreTasks);
 
         // 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
@@ -1225,7 +1222,7 @@
         }
 
         // Rebind all the views, including the ignore ones
-        bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET,
+        bindVisibleTaskViews(mStackScroller.getStackScroll(), mIgnoreTasks,
                 false /* ignoreTaskOverrides */);
 
         // Measure each of the TaskViews
@@ -1266,7 +1263,7 @@
         mTmpTaskViews.addAll(mViewPool.getViews());
         int taskViewCount = mTmpTaskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
-            layoutTaskView(mTmpTaskViews.get(i));
+            layoutTaskView(changed, mTmpTaskViews.get(i));
         }
 
         if (changed) {
@@ -1274,8 +1271,9 @@
                 mStackScroller.boundScroll();
             }
         }
+
         // Relayout all of the task views including the ignored ones
-        relayoutTaskViews(AnimationProps.IMMEDIATE, EMPTY_TASK_SET);
+        relayoutTaskViews(AnimationProps.IMMEDIATE, mIgnoreTasks);
         clipTaskViews();
 
         if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
@@ -1287,16 +1285,21 @@
     /**
      * Lays out a TaskView.
      */
-    private void layoutTaskView(TaskView tv) {
-        if (tv.getBackground() != null) {
-            tv.getBackground().getPadding(mTmpRect);
+    private void layoutTaskView(boolean changed, TaskView tv) {
+        if (changed) {
+            if (tv.getBackground() != null) {
+                tv.getBackground().getPadding(mTmpRect);
+            } else {
+                mTmpRect.setEmpty();
+            }
+            Rect taskRect = mStableLayoutAlgorithm.mTaskRect;
+            tv.cancelTransformAnimation();
+            tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+                    taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
         } else {
-            mTmpRect.setEmpty();
+            // If the layout has not changed, then just lay it out again in-place
+            tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
         }
-        Rect taskRect = mStableLayoutAlgorithm.mTaskRect;
-        tv.cancelTransformAnimation();
-        tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
-                taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
     }
 
     /** Handler for the first layout. */
@@ -1320,7 +1323,8 @@
         }
 
         // Update the stack action button visibility
-        if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+        if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+                mStack.getTaskCount() > 0) {
             EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
         } else {
             EventBus.getDefault().send(new HideStackActionButtonEvent());
@@ -1445,6 +1449,26 @@
     }
 
     @Override
+    public void onStackTasksRemoved(TaskStack stack) {
+        // Reset the focused task
+        resetFocusedTask(getFocusedTask());
+
+        // Return all the views to the pool
+        List<TaskView> taskViews = new ArrayList<>();
+        taskViews.addAll(getTaskViews());
+        for (int i = taskViews.size() - 1; i >= 0; i--) {
+            mViewPool.returnViewToPool(taskViews.get(i));
+        }
+
+        // Remove all the ignore tasks
+        mIgnoreTasks.clear();
+
+        // If there are no remaining tasks, then just close recents
+        EventBus.getDefault().send(new AllTaskViewsDismissedEvent(
+                R.string.recents_empty_message_dismissed_all));
+    }
+
+    @Override
     public void onStackTasksUpdated(TaskStack stack) {
         // Update the layout and immediately layout
         updateLayoutAlgorithm(false /* boundScroll */);
@@ -1509,7 +1533,7 @@
                 }
                 addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
                 measureTaskView(tv);
-                layoutTaskView(tv);
+                layoutTaskView(true /* changed */, tv);
             }
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1595,7 +1619,8 @@
 
         if (mEnterAnimationComplete) {
             if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                    curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
+                    curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+                    mStack.getTaskCount() > 0) {
                 EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
             } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
@@ -1703,14 +1728,42 @@
         }
     }
 
-    public final void onBusEvent(final DismissTaskViewEvent event) {
+    public final void onBusEvent(DismissTaskViewEvent event) {
         // For visible children, defer removing the task until after the animation
-        mAnimationHelper.startDeleteTaskAnimation(event.task, event.taskView,
-                event.getAnimationTrigger());
+        mAnimationHelper.startDeleteTaskAnimation(event.taskView, event.getAnimationTrigger());
+    }
+
+    public final void onBusEvent(final DismissAllTaskViewsEvent event) {
+        // Keep track of the tasks which will have their data removed
+        ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
+        mAnimationHelper.startDeleteAllTasksAnimation(getTaskViews(), event.getAnimationTrigger());
+        event.addPostAnimationCallback(new Runnable() {
+            @Override
+            public void run() {
+                // Announce for accessibility
+                announceForAccessibility(getContext().getString(
+                        R.string.accessibility_recents_all_items_dismissed));
+
+                // Remove all tasks and delete the task data for all tasks
+                mStack.removeAllTasks();
+                for (int i = tasks.size() - 1; i >= 0; i--) {
+                    EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
+                }
+
+                MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL);
+            }
+        });
+
     }
 
     public final void onBusEvent(TaskViewDismissedEvent event) {
-        removeTaskViewFromStack(event.taskView, event.task);
+        // Announce for accessibility
+        announceForAccessibility(getContext().getString(
+                R.string.accessibility_recents_item_dismissed, event.task.title));
+
+        // Remove the task from the stack
+        mStack.removeTask(event.task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
+                Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
 
         MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
@@ -1946,28 +1999,16 @@
             }
         }
 
-        // Trigger a new layout and scroll to the initial state
-        mInitialState = event.fromMultiWindow
-                ? INITIAL_STATE_UPDATE_ALL
-                : INITIAL_STATE_UPDATE_LAYOUT_ONLY;
+        // Trigger a new layout and update to the initial state if necessary
+        if (event.fromMultiWindow) {
+            mInitialState = INITIAL_STATE_UPDATE_ALL;
+        } else if (event.fromOrientationChange) {
+            mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
+        }
         requestLayout();
     }
 
     /**
-     * Removes the task from the stack, and updates the focus to the next task in the stack if the
-     * removed TaskView was focused.
-     */
-    private void removeTaskViewFromStack(TaskView tv, Task task) {
-        // Announce for accessibility
-        tv.announceForAccessibility(getContext().getString(
-                R.string.accessibility_recents_item_dismissed, task.title));
-
-        // Remove the task from the stack
-        mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN), false /* fromDockGesture */);
-    }
-
-    /**
      * Starts an alpha animation on the freeform workspace background.
      */
     private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
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 aed19c3..ee0de1a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -356,6 +356,11 @@
             return;
         }
 
+        // Disallow tapping above and below the stack to dismiss recents
+        if (x > mSv.mLayoutAlgorithm.mStackRect.left && x < mSv.mLayoutAlgorithm.mStackRect.right) {
+            return;
+        }
+
         // If tapping on the freeform workspace background, just launch the first freeform task
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.hasFreeformWorkspaceSupport()) {
@@ -507,13 +512,13 @@
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
         tv.setTouchEnabled(true);
+        // Remove the task view from the stack
+        EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
         // Update the scroll to the final scroll position from onBeginDrag()
         mSv.getScroller().setStackScroll(mTargetStackScroll, null);
         // Update the focus state to the final focus state
         mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
         mSv.getStackAlgorithm().clearUnfocusedTaskOverrides();
-        // Remove the task view from the stack
-        EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
         // Stop tracking this deletion animation
         mSwipeHelperAnimations.remove(v);
         // Keep track of deletions by keyboard
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 c085d80..f8ed700 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -384,7 +384,7 @@
     void dismissTask() {
         // Animate out the view and call the callback
         final TaskView tv = this;
-        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv, mTask);
+        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv);
         dismissEvent.addPostAnimationCallback(new Runnable() {
             @Override
             public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index dc76e61..b512393 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -226,4 +226,9 @@
         v.getViewBounds().setClipBottom(0);
         v.setLeftTopRightBottom(0, 0, 0, 0);
     }
+
+    @Override
+    public String toString() {
+        return "R: " + rect + " V: " + visible;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutKeysLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutKeysLayout.java
index ba3e774..6746a67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutKeysLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutKeysLayout.java
@@ -29,13 +29,16 @@
  */
 public final class KeyboardShortcutKeysLayout extends ViewGroup {
     private int mLineHeight;
+    private final Context mContext;
 
     public KeyboardShortcutKeysLayout(Context context) {
         super(context);
+        this.mContext = context;
     }
 
     public KeyboardShortcutKeysLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
+        this.mContext = context;
     }
 
     @Override
@@ -104,7 +107,9 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int childCount = getChildCount();
         int fullRowWidth = r - l;
-        int xPos = getPaddingLeft();
+        int xPos = isRTL()
+                ? fullRowWidth - getPaddingRight()
+                : getPaddingLeft();
         int yPos = getPaddingTop();
         int lastHorizontalSpacing = 0;
         // The index of the child which starts the current row.
@@ -117,18 +122,25 @@
                 int currentChildWidth = currentChild.getMeasuredWidth();
                 LayoutParams lp = (LayoutParams) currentChild.getLayoutParams();
 
-                // If the current child does not fit on this row.
-                if (xPos + currentChildWidth > fullRowWidth) {
+                boolean childDoesNotFitOnRow = isRTL()
+                        ? xPos - getPaddingLeft() - currentChildWidth < 0
+                        : xPos + currentChildWidth > fullRowWidth;
+
+                if (childDoesNotFitOnRow) {
                     // Layout all the children on this row but the current one.
                     layoutChildrenOnRow(rowStartIdx, i, fullRowWidth, xPos, yPos,
                             lastHorizontalSpacing);
                     // Update the positions for starting on the new row.
-                    xPos = getPaddingLeft();
+                    xPos = isRTL()
+                            ? fullRowWidth - getPaddingRight()
+                            : getPaddingLeft();
                     yPos += mLineHeight;
                     rowStartIdx = i;
                 }
 
-                xPos += currentChildWidth + lp.mHorizontalSpacing;
+                xPos = isRTL()
+                        ? xPos - currentChildWidth - lp.mHorizontalSpacing
+                        : xPos + currentChildWidth + lp.mHorizontalSpacing;
                 lastHorizontalSpacing = lp.mHorizontalSpacing;
             }
         }
@@ -148,21 +160,41 @@
 
     private void layoutChildrenOnRow(int startIndex, int endIndex, int fullRowWidth, int xPos,
             int yPos, int lastHorizontalSpacing) {
-        int freeSpace = fullRowWidth - xPos + lastHorizontalSpacing;
-        xPos = getPaddingLeft() + freeSpace;
+        if (!isRTL()) {
+            xPos = getPaddingLeft() + fullRowWidth - xPos + lastHorizontalSpacing;
+        }
 
         for (int j = startIndex; j < endIndex; ++j) {
             View currentChild = getChildAt(j);
+            int currentChildWidth = currentChild.getMeasuredWidth();
+            LayoutParams lp = (LayoutParams) currentChild.getLayoutParams();
+            if (isRTL() && j == startIndex) {
+                xPos = fullRowWidth - xPos - getPaddingRight() - currentChildWidth
+                        - lp.mHorizontalSpacing;
+            }
+
             currentChild.layout(
                     xPos,
                     yPos,
-                    xPos + currentChild.getMeasuredWidth(),
+                    xPos + currentChildWidth,
                     yPos + currentChild.getMeasuredHeight());
-            xPos += currentChild.getMeasuredWidth()
-                    + ((LayoutParams) currentChild.getLayoutParams()).mHorizontalSpacing;
+
+            if (isRTL()) {
+                int nextChildWidth = j < endIndex - 1
+                        ? getChildAt(j + 1).getMeasuredWidth()
+                        : 0;
+                xPos -= nextChildWidth + lp.mHorizontalSpacing;
+            } else {
+                xPos += currentChildWidth + lp.mHorizontalSpacing;
+            }
         }
     }
 
+    private boolean isRTL() {
+        return mContext.getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_RTL;
+    }
+
     public static class LayoutParams extends ViewGroup.LayoutParams {
         public final int mHorizontalSpacing;
         public final int mVerticalSpacing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index bb43899..d9bf539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -106,7 +106,7 @@
 
         String category = getPackageCategory(packageName);
         if (mFacetCategoryMap.containsKey(category)) {
-            int index = mFacetCategoryMap.get(packageName);
+            int index = mFacetCategoryMap.get(category);
             mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 12035b9..45e94f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -225,7 +225,8 @@
             public void onInflated(View v) {
                 mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container);
                 mQsContainer.setPanelView(NotificationPanelView.this);
-                mQsContainer.getHeader().setOnClickListener(NotificationPanelView.this);
+                mQsContainer.getHeader().findViewById(R.id.expand_indicator)
+                        .setOnClickListener(NotificationPanelView.this);
             }
         });
         mClockView = (TextView) findViewById(R.id.clock_view);
@@ -789,8 +790,9 @@
     }
 
     private boolean isInQsArea(float x, float y) {
-        return (x >= mQsContainer.getX() && x <= mQsContainer.getX() + mQsContainer.getWidth()) &&
-                (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+        return (x >= mQsDensityContainer.getX()
+                && x <= mQsDensityContainer.getX() + mQsDensityContainer.getWidth())
+                && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
                 || y <= mQsContainer.getY() + mQsContainer.getHeight());
     }
 
@@ -1337,7 +1339,8 @@
             return false;
         }
         View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer.getHeader();
-        boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
+        boolean onHeader = x >= mQsDensityContainer.getX()
+                && x <= mQsDensityContainer.getX() + mQsDensityContainer.getWidth()
                 && y >= header.getTop() && y <= header.getBottom();
         if (mQsExpanded) {
             return onHeader || (yDiff < 0 && isInQsArea(x, y));
@@ -1758,7 +1761,7 @@
 
     @Override
     public void onClick(View v) {
-        if (v == mQsContainer.getHeader()) {
+        if (v.getId() == R.id.expand_indicator) {
             onQsExpansionStarted();
             if (mQsExpanded) {
                 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 8f329c4..b507903 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -124,7 +124,6 @@
 
         // RenderThread is doing more harm than good when touching the header (to expand quick
         // settings), so disable it for this view
-        ((RippleDrawable) getBackground()).setForceSoftware(true);
         ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
 
         updateResources();
@@ -136,6 +135,12 @@
         updateResources();
     }
 
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        updateResources();
+    }
+
     private void updateResources() {
         FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
         FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
@@ -175,6 +180,20 @@
                 .addFloat(mMultiUserSwitch, "alpha", 0, 1)
                 .setStartDelay(QSAnimator.EXPANDED_TILE_DELAY)
                 .build();
+
+        final boolean isRtl = isLayoutRtl();
+        if (isRtl && mDateTimeGroup.getWidth() == 0) {
+            mDateTimeGroup.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    mDateTimeGroup.setPivotX(getWidth());
+                    mDateTimeGroup.removeOnLayoutChangeListener(this);
+                }
+            });
+        } else {
+            mDateTimeGroup.setPivotX(isRtl ? mDateTimeGroup.getWidth() : 0);
+        }
     }
 
     @Override
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 ab44b6a..84e785d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -654,12 +654,11 @@
     }
 
     private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
-        EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
-                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser());
-        if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
-                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) {
+        UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)
+                && !mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, user)) {
             record.isDisabledByAdmin = true;
-            record.enforcedAdmin = admin;
+            record.enforcedAdmin = EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
         } else {
             record.isDisabledByAdmin = false;
             record.enforcedAdmin = null;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
index 0e64dcd..15ad1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java
@@ -143,7 +143,9 @@
             @Override
             public void onClick(View v) {
                 mPipManager.closePip();
-                mListener.onClosed();
+                if (mListener != null) {
+                    mListener.onClosed();
+                }
             }
         });
         mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 6976c0b..e3ed92c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -86,7 +86,7 @@
             = new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
 
     private final Context mContext;
-    private final LayoutInflater mInflater;
+    protected final LayoutInflater mInflater;
     private final H mHandler = new H();
     private final ZenPrefs mPrefs;
     private final TransitionHelper mTransitionHelper = new TransitionHelper();
@@ -95,12 +95,12 @@
 
     private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
 
-    private SegmentedButtons mZenButtons;
+    protected SegmentedButtons mZenButtons;
     private View mZenIntroduction;
     private TextView mZenIntroductionMessage;
     private View mZenIntroductionConfirm;
     private TextView mZenIntroductionCustomize;
-    private LinearLayout mZenConditions;
+    protected LinearLayout mZenConditions;
     private TextView mZenAlarmWarning;
 
     private Callback mCallback;
@@ -148,10 +148,7 @@
         mTransitionHelper.dump(fd, pw, args);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
+    protected void createZenButtons() {
         mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
         mZenButtons.addButton(R.string.interruption_level_none_twoline,
                 R.string.interruption_level_none_with_warning,
@@ -163,7 +160,12 @@
                 R.string.interruption_level_priority,
                 Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         mZenButtons.setCallback(mZenButtonsCallback);
+    }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        createZenButtons();
         mZenIntroduction = findViewById(R.id.zen_introduction);
         mZenIntroductionMessage = (TextView) findViewById(R.id.zen_introduction_message);
         mSpTexts.add(mZenIntroductionMessage);
@@ -302,14 +304,18 @@
         }
     }
 
+    protected void addZenConditions(int count) {
+        for (int i = 0; i < count; i++) {
+            mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
+        }
+    }
+
     public void init(ZenModeController controller) {
         mController = controller;
         mCountdownConditionSupported = mController.isCountdownConditionSupported();
         final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0;
         final int minConditions = 1 /*forever*/ + countdownDelta;
-        for (int i = 0; i < minConditions; i++) {
-            mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
-        }
+        addZenConditions(minConditions);
         mSessionZen = getSelectedZen(-1);
         handleUpdateManualRule(mController.getManualRule());
         if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
@@ -917,7 +923,7 @@
         }
     }
 
-    private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
+    protected final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
         @Override
         public void onSelected(final Object value, boolean fromClick) {
             if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5389c804..53a9976 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
index 1d81fd4..e9b85b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTests.java
@@ -14,11 +14,13 @@
 
 package com.android.systemui.qs;
 
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.TouchAnimator.Listener;
 import org.mockito.Mockito;
 
+@SmallTest
 public class TouchAnimatorTests extends SysuiTestCase {
 
     private Listener mTouchListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
index f86c6a4..6a81659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
@@ -32,9 +32,11 @@
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 import android.util.Log;
 
+@SmallTest
 public class TileLifecycleManagerTests extends AndroidTestCase {
     public static final String TILE_UPDATE_BROADCAST = "com.android.systemui.tests.TILE_UPDATE";
     public static final String EXTRA_CALLBACK = "callback";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
index efdb50d..f24b541 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
@@ -19,10 +19,12 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.service.quicksettings.TileService;
+import android.test.suitebuilder.annotation.SmallTest;
 import com.android.systemui.SysuiTestCase;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+@SmallTest
 public class TileServiceManagerTests extends SysuiTestCase {
 
     private TileServices mTileServices;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
index 01514646b..94c98d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
@@ -17,6 +17,7 @@
 
 import android.content.ComponentName;
 import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -27,6 +28,7 @@
 
 import java.util.ArrayList;
 
+@SmallTest
 public class TileServicesTests extends SysuiTestCase {
     private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
 
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d6f1499..8e4bf24 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2017,6 +2017,69 @@
     // action pass package name of calling package.
     ACTION_SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST_BY_PACKAGE = 356;
 
+    // Logged when a user dismisses all task in overview
+    OVERVIEW_DISMISS_ALL = 357;
+
+    // Quick Settings -> Edit
+    QS_EDIT = 358;
+
+    // Quick Settings -> Edit -> Overflow -> Reset
+    ACTION_QS_EDIT_RESET = 359;
+
+    // QS -> Edit - Drag a tile out of the active tiles.
+    // The _SPEC contains either the spec of the tile or
+    // the package of the 3rd party app in the PKG field.
+    ACTION_QS_EDIT_REMOVE_SPEC = 360;
+    ACTION_QS_EDIT_REMOVE = 361;
+
+    // QS -> Edit - Drag a tile into the active tiles.
+    // The _SPEC contains either the spec of the tile or
+    // the package of the 3rd party app in the PKG field.
+    ACTION_QS_EDIT_ADD_SPEC = 362;
+    ACTION_QS_EDIT_ADD = 363;
+
+    // QS -> Edit - Drag a tile within the active tiles.
+    // The _SPEC contains either the spec of the tile or
+    // the package of the 3rd party app in the PKG field.
+    ACTION_QS_EDIT_MOVE_SPEC = 364;
+    ACTION_QS_EDIT_MOVE = 365;
+
+    // Long-press on a QS tile. Tile spec in package field.
+    ACTION_QS_LONG_PRESS = 366;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY = 367;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Magnification gesture
+    // ACTION: New magnification gesture configuration is chosen
+    //  SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 368;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Font size
+    // ACTION: New font size is chosen
+    //  SUBTYPE: 0 is small, 1 is default, 2 is large, 3 is largest
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY_FONT_SIZE = 369;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Display size
+    // ACTION: New display size is chosen
+    //  SUBTYPE: 0 is small, 1 is default, 2 is large, 3 is larger, 4 is largest
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY_DISPLAY_SIZE = 370;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings -> TalkBack
+    // ACTION: New screen reader configuration is chosen
+    //  SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER = 371;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index fc92966..81f63ae 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -142,7 +142,10 @@
             }
             return Element.DataType.FLOAT_64;
         }
-        return null;
+
+        throw new RSIllegalArgumentException("Parameter of type " + cmp.getSimpleName() +
+            "[] is not compatible with data type " + mType.mElement.mType.name() +
+            " of allocation");
     }
 
 
@@ -293,8 +296,13 @@
     }
 
     /**
-     * Enable/Disable AutoPadding for Vec3 elements.
-     * By default: Diabled.
+     * Enable/Disable AutoPadding for Vec3 Elements.
+     *
+     * <p> Vec3 Elements, such as {@link Element#U8_3} are treated as Vec4 Elements
+     * with the fourth vector element used as padding. Enabling the AutoPadding feature
+     * will automatically add/remove the padding when you copy to/from an Allocation
+     * with a Vec3 Element.
+     * <p> By default: Disabled.
      *
      * @param useAutoPadding True: enable AutoPadding; False: disable AutoPadding
      *
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 2b06780..f6f93cb 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -541,21 +541,22 @@
 
     /**
      * Class for specifying the specifics about how a kernel will be
-     * launched
+     * launched.
      *
      * This class can specify a potential range of cells on which to
      * run a kernel.  If no set is called for a dimension then this
      * class will have no impact on that dimension when the kernel
      * is executed.
      *
-     * The forEach launch will operate over the intersection of the
-     * dimensions.
+     * The forEach kernel launch will operate over the intersection of
+     * the dimensions.
      *
      * Example:
      * LaunchOptions with setX(5, 15)
      * Allocation with dimension X=10, Y=10
-     * The resulting forEach run would execute over x = 5 to 10 and
-     * y = 0 to 10.
+     * The resulting forEach run would execute over:
+     * x = 5 to 9 (inclusive) and
+     * y = 0 to 9 (inclusive).
      *
      *
      */
@@ -569,11 +570,11 @@
         private int strategy;
 
         /**
-         * Set the X range.  If the end value is set to 0 the X dimension is not
-         * clipped.
+         * Set the X range. xstartArg is the lowest coordinate of the range,
+         * and xendArg-1 is the highest coordinate of the range.
          *
          * @param xstartArg Must be >= 0
-         * @param xendArg Must be >= xstartArg
+         * @param xendArg Must be > xstartArg
          *
          * @return LaunchOptions
          */
@@ -587,11 +588,11 @@
         }
 
         /**
-         * Set the Y range.  If the end value is set to 0 the Y dimension is not
-         * clipped.
+         * Set the Y range. ystartArg is the lowest coordinate of the range,
+         * and yendArg-1 is the highest coordinate of the range.
          *
          * @param ystartArg Must be >= 0
-         * @param yendArg Must be >= ystartArg
+         * @param yendArg Must be > ystartArg
          *
          * @return LaunchOptions
          */
@@ -605,11 +606,11 @@
         }
 
         /**
-         * Set the Z range.  If the end value is set to 0 the Z dimension is not
-         * clipped.
+         * Set the Z range. zstartArg is the lowest coordinate of the range,
+         * and zendArg-1 is the highest coordinate of the range.
          *
          * @param zstartArg Must be >= 0
-         * @param zendArg Must be >= zstartArg
+         * @param zendArg Must be > zstartArg
          *
          * @return LaunchOptions
          */
diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
index 76da781..339e0e9 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve3x3.java
@@ -32,10 +32,9 @@
      * Supported elements types are {@link Element#U8}, {@link
      * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4},
      * {@link Element#F32}, {@link Element#F32_2}, {@link
-     * Element#F32_3}, and {@link Element#F32_4}
+     * Element#F32_3}, and {@link Element#F32_4}.
      *
-     * The default coefficients are.
-     *
+     * <p> The default coefficients are:
      * <code>
      * <p> [ 0,  0,  0 ]
      * <p> [ 0,  1,  0 ]
@@ -67,7 +66,7 @@
     }
 
     /**
-     * Set the input of the blur.
+     * Set the input of the 3x3 convolve.
      * Must match the element type supplied during create.
      *
      * @param ain The input allocation.
@@ -80,7 +79,7 @@
     /**
      * Set the coefficients for the convolve.
      *
-     * The convolve layout is
+     * <p> The convolve layout is:
      * <code>
      * <p> [ 0,  1,  2 ]
      * <p> [ 3,  4,  5 ]
diff --git a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
index 2d37600..a288cee 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -32,9 +32,9 @@
      * Supported elements types are {@link Element#U8}, {@link
      * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4},
      * {@link Element#F32}, {@link Element#F32_2}, {@link
-     * Element#F32_3}, and {@link Element#F32_4}
+     * Element#F32_3}, and {@link Element#F32_4}.
      *
-     * The default coefficients are.
+     * <p> The default coefficients are:
      * <code>
      * <p> [ 0,  0,  0,  0,  0  ]
      * <p> [ 0,  0,  0,  0,  0  ]
@@ -66,7 +66,7 @@
     }
 
     /**
-     * Set the input of the blur.
+     * Set the input of the 5x5 convolve.
      * Must match the element type supplied during create.
      *
      * @param ain The input allocation.
@@ -79,7 +79,7 @@
     /**
     * Set the coefficients for the convolve.
     *
-    * The convolve layout is
+    * <p> The convolve layout is:
     * <code>
     * <p> [ 0,  1,  2,  3,  4  ]
     * <p> [ 5,  6,  7,  8,  9  ]
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e3dac28..4b96e7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1774,17 +1774,22 @@
 
     private void updateSoftKeyboardShowModeLocked(UserState userState) {
         final int userId = userState.mUserId;
-        if (userId == mCurrentUserId) {
-            // Check whether any Accessibility Services are still enabled and, if not, remove flag
-            // requesting no soft keyboard
-            final boolean accessibilityRequestingNoIme = userState.mSoftKeyboardShowMode == 1;
-            if (accessibilityRequestingNoIme && !userState.isHandlingAccessibilityEvents()) {
-                // No active Accessibility Services can be requesting the soft keyboard to be hidden
+        // Only check whether we need to reset the soft keyboard mode if it is not set to the
+        // default.
+        if ((userId == mCurrentUserId) && (userState.mSoftKeyboardShowMode != 0)) {
+            // Check whether the last Accessibility Service that changed the soft keyboard mode to
+            // something other than the default is still enabled and, if not, remove flag and
+            // reset to the default soft keyboard behavior.
+            boolean serviceChangingSoftKeyboardModeIsEnabled =
+                    userState.mEnabledServices.contains(userState.mServiceChangingSoftKeyboardMode);
+
+            if (!serviceChangingSoftKeyboardModeIsEnabled) {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
                         0,
                         userState.mUserId);
                 userState.mSoftKeyboardShowMode = 0;
+                userState.mServiceChangingSoftKeyboardMode = null;
             }
 
             notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode);
@@ -2966,6 +2971,14 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
+                // Keep track of the last service to request a non-default show mode. The show mode
+                // should be restored to default should this service be disabled.
+                if (showMode == Settings.Secure.SHOW_MODE_AUTO) {
+                    userState.mServiceChangingSoftKeyboardMode = null;
+                } else {
+                    userState.mServiceChangingSoftKeyboardMode = mComponentName;
+                }
+
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
                         userState.mUserId);
@@ -4115,6 +4128,8 @@
         public final Set<ComponentName> mTouchExplorationGrantedServices =
                 new HashSet<>();
 
+        public ComponentName mServiceChangingSoftKeyboardMode;
+
         public int mLastSentClientState = -1;
 
         public int mSoftKeyboardShowMode = 0;
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index a093d92..e15b785 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -111,12 +111,18 @@
     public void register() {
         mScreenStateObserver.register();
         mWindowStateObserver.register();
+
+        // Obtain initial state.
+        mWindowStateObserver.getRegions(mMagnifiedRegion, mAvailableRegion);
+        mMagnifiedRegion.getBounds(mMagnifiedBounds);
     }
 
     /**
      * Unregisters magnification-related observers.
      */
     public void unregister() {
+        mSpecAnimationBridge.cancel();
+
         mScreenStateObserver.unregister();
         mWindowStateObserver.unregister();
     }
@@ -149,8 +155,10 @@
             final float offsetY = sentSpec.offsetY;
 
             // Compute the new center and update spec as needed.
-            final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale;
-            final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale;
+            final float centerX = (mMagnifiedBounds.width() / 2.0f
+                    + mMagnifiedBounds.left - offsetX) / scale;
+            final float centerY = (mMagnifiedBounds.height() / 2.0f
+                    + mMagnifiedBounds.top - offsetY) / scale;
             if (updateSpec) {
                 setScaleAndCenter(scale, centerX, centerY, false);
             } else {
@@ -246,7 +254,8 @@
      */
     public float getCenterX() {
         synchronized (mLock) {
-            return  (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale();
+            return  (mMagnifiedBounds.width() / 2.0f
+                    + mMagnifiedBounds.left - getOffsetX()) / getScale();
         }
     }
 
@@ -268,7 +277,8 @@
      */
     public float getCenterY() {
         synchronized (mLock) {
-            return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale();
+            return (mMagnifiedBounds.height() / 2.0f
+                    + mMagnifiedBounds.top - getOffsetY()) / getScale();
         }
     }
 
@@ -471,18 +481,25 @@
      *         otherwise
      */
     private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+        // Handle defaults.
+        if (Float.isNaN(centerX)) {
+            centerX = getCenterX();
+        }
+        if (Float.isNaN(centerY)) {
+            centerY = getCenterY();
+        }
+        if (Float.isNaN(scale)) {
+            scale = getScale();
+        }
+
+        // Ensure requested center is within the available region.
         if (!availableRegionContains(centerX, centerY)) {
             return false;
         }
 
-        boolean changed = false;
-
+        // Compute changes.
         final MagnificationSpec currSpec = mCurrentMagnificationSpec;
-
-        // Handle scale.
-        if (Float.isNaN(scale)) {
-            scale = getScale();
-        }
+        boolean changed = false;
 
         final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
         if (Float.compare(currSpec.scale, normScale) != 0) {
@@ -490,24 +507,16 @@
             changed = true;
         }
 
-        // Handle X offset.
-        if (Float.isNaN(centerX)) {
-            centerX = getCenterX();
-        }
-
-        final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale;
+        final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f
+                + mMagnifiedBounds.left - centerX * scale;
         final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
         if (Float.compare(currSpec.offsetX, offsetX) != 0) {
             currSpec.offsetX = offsetX;
             changed = true;
         }
 
-        // Handle Y offset.
-        if (Float.isNaN(centerY)) {
-            centerY = getCenterY();
-        }
-
-        final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale;
+        final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f
+                + mMagnifiedBounds.top - centerY * scale;
         final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
         if (Float.compare(currSpec.offsetY, offsetY) != 0) {
             currSpec.offsetY = offsetY;
@@ -661,6 +670,12 @@
             mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
         }
 
+        public void cancel() {
+            if (mTransformationAnimator != null && mTransformationAnimator.isRunning()) {
+                mTransformationAnimator.cancel();
+            }
+        }
+
         public void updateSentSpec(MagnificationSpec spec, boolean animate) {
             if (Thread.currentThread().getId() == mMainThreadId) {
                 // Already on the main thread, don't bother proxying.
@@ -811,9 +826,6 @@
         private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
         private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
 
-        private final Rect mTempRect = new Rect();
-        private final Rect mTempRect1 = new Rect();
-
         private final MagnificationController mController;
         private final WindowManagerInternal mWindowManager;
         private final Handler mHandler;
@@ -884,6 +896,10 @@
             mController.resetIfNeeded(true);
         }
 
+        public void getRegions(@NonNull Region outMagnified, @NonNull Region outAvailable) {
+            mWindowManager.getMagnificationRegions(outMagnified, outAvailable);
+        }
+
         private class CallbackHandler extends Handler {
             public CallbackHandler(Context context) {
                 super(context.getMainLooper());
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index b737ae2..6288b56 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1818,7 +1818,7 @@
             File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
             if (initSentinel.exists()) {
                 synchronized (mQueueLock) {
-                    mPendingInits.add(transportName);
+                    mPendingInits.add(name);
 
                     // TODO: pick a better starting time than now + 1 minute
                     long delay = 1000 * 60; // one minute, in milliseconds
@@ -2316,6 +2316,25 @@
         }
     }
 
+    // What name is this transport registered under...?
+    private String getTransportName(IBackupTransport transport) {
+        if (MORE_DEBUG) {
+            Slog.v(TAG, "Searching for transport name of " + transport);
+        }
+        synchronized (mTransports) {
+            final int N = mTransports.size();
+            for (int i = 0; i < N; i++) {
+                if (mTransports.valueAt(i).equals(transport)) {
+                    if (MORE_DEBUG) {
+                        Slog.v(TAG, "  Name found: " + mTransports.keyAt(i));
+                    }
+                    return mTransports.keyAt(i);
+                }
+            }
+        }
+        return null;
+    }
+
     // fire off a backup agent, blocking until it attaches or times out
     IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
         IBackupAgent agent = null;
@@ -2921,7 +2940,15 @@
                     if (MORE_DEBUG) Slog.d(TAG, "Server requires init; rerunning");
                     addBackupTrace("init required; rerunning");
                     try {
-                        mPendingInits.add(mTransport.transportDirName());
+                        final String name = getTransportName(mTransport);
+                        if (name != null) {
+                            mPendingInits.add(name);
+                        } else {
+                            if (DEBUG) {
+                                Slog.w(TAG, "Couldn't find name of transport " + mTransport
+                                        + " for init");
+                            }
+                        }
                     } catch (Exception e) {
                         Slog.w(TAG, "Failed to query transport name heading for init", e);
                         // swallow it and proceed; we don't rely on this
@@ -7590,77 +7617,6 @@
 
     // ----- Restore handling -----
 
-    // new style: we only store the SHA-1 hashes of each sig, not the full block
-    static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
-        if (target == null) {
-            return false;
-        }
-
-        // If the target resides on the system partition, we allow it to restore
-        // data from the like-named package in a restore set even if the signatures
-        // do not match.  (Unlike general applications, those flashed to the system
-        // partition will be signed with the device's platform certificate, so on
-        // different phones the same system app will have different signatures.)
-        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            if (MORE_DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
-            return true;
-        }
-
-        // Allow unsigned apps, but not signed on one device and unsigned on the other
-        // !!! TODO: is this the right policy?
-        Signature[] deviceSigs = target.signatures;
-        if (MORE_DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
-                + " device=" + deviceSigs);
-        if ((storedSigHashes == null || storedSigHashes.size() == 0)
-                && (deviceSigs == null || deviceSigs.length == 0)) {
-            return true;
-        }
-        if (storedSigHashes == null || deviceSigs == null) {
-            return false;
-        }
-
-        // !!! TODO: this demands that every stored signature match one
-        // that is present on device, and does not demand the converse.
-        // Is this this right policy?
-        final int nStored = storedSigHashes.size();
-        final int nDevice = deviceSigs.length;
-
-        // hash each on-device signature
-        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
-        for (int i = 0; i < nDevice; i++) {
-            deviceHashes.add(hashSignature(deviceSigs[i]));
-        }
-
-        // now ensure that each stored sig (hash) matches an on-device sig (hash)
-        for (int n = 0; n < nStored; n++) {
-            boolean match = false;
-            final byte[] storedHash = storedSigHashes.get(n);
-            for (int i = 0; i < nDevice; i++) {
-                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
-                    match = true;
-                    break;
-                }
-            }
-            // match is false when no on-device sig matched one of the stored ones
-            if (!match) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    static byte[] hashSignature(Signature sig) {
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-256");
-            digest.update(sig.toByteArray());
-            return digest.digest();
-        } catch (NoSuchAlgorithmException e) {
-            Slog.w(TAG, "No SHA-256 algorithm found!");
-        }
-        return null;
-    }
-
     // Old style: directly match the stored vs on device signature blocks
     static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
         if (target == null) {
@@ -8173,7 +8129,7 @@
             }
 
             Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
-            if (!signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
+            if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
                 Slog.w(TAG, "Signature mismatch restoring " + packageName);
                 EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                         "Signature mismatch");
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index f197c1e..09f240f 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -205,7 +205,7 @@
                         PackageManager.GET_SIGNATURES);
                 homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
                 homeVersion = homeInfo.versionCode;
-                homeSigHashes = hashSignatureArray(homeInfo.signatures);
+                homeSigHashes = BackupUtils.hashSignatureArray(homeInfo.signatures);
             } catch (NameNotFoundException e) {
                 Slog.w(TAG, "Can't access preferred home info");
                 // proceed as though there were no preferred home set
@@ -222,7 +222,7 @@
             final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
                     || !Objects.equals(home, mStoredHomeComponent)
                     || (home != null
-                        && !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
+                        && !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo));
             if (needHomeBackup) {
                 if (DEBUG) {
                     Slog.i(TAG, "Home preference changed; backing up new state " + home);
@@ -309,7 +309,7 @@
                     outputBuffer.reset();
                     outputBufferStream.writeInt(info.versionCode);
                     writeSignatureHashArray(outputBufferStream,
-                            hashSignatureArray(info.signatures));
+                            BackupUtils.hashSignatureArray(info.signatures));
 
                     if (DEBUG) {
                         Slog.v(TAG, "+ writing metadata for " + packName
@@ -432,18 +432,6 @@
         mRestoredSignatures = sigMap;
     }
 
-    private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
-        if (sigs == null) {
-            return null;
-        }
-
-        ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
-        for (Signature s : sigs) {
-            hashes.add(BackupManagerService.hashSignature(s));
-        }
-        return hashes;
-    }
-
     private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
             throws IOException {
         // the number of entries in the array
@@ -492,13 +480,8 @@
             }
 
             if (nonHashFound) {
-                ArrayList<byte[]> hashes =
-                        new ArrayList<byte[]>(sigs.size());
-                for (int i = 0; i < sigs.size(); i++) {
-                    Signature s = new Signature(sigs.get(i));
-                    hashes.add(BackupManagerService.hashSignature(s));
-                }
-                sigs = hashes;
+                // Replace with the hashes.
+                sigs = BackupUtils.hashSignatureArray(sigs);
             }
 
             return sigs;
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index e7db2a8..57ba1b9 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,7 +25,6 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -1645,7 +1644,9 @@
         int userId = UserHandle.USER_SYSTEM;
         String packageName;
         String opStr;
+        String modeStr;
         int op;
+        int mode;
         int packageUid;
 
         Shell(IAppOpsService iface, AppOpsService internal) {
@@ -1681,6 +1682,59 @@
             }
         }
 
+        int strModeToMode(String modeStr, PrintWriter err) {
+            switch (modeStr) {
+                case "allow":
+                    return AppOpsManager.MODE_ALLOWED;
+                case "deny":
+                    return AppOpsManager.MODE_ERRORED;
+                case "ignore":
+                    return AppOpsManager.MODE_IGNORED;
+                case "default":
+                    return AppOpsManager.MODE_DEFAULT;
+            }
+            try {
+                return Integer.parseInt(modeStr);
+            } catch (NumberFormatException e) {
+            }
+            err.println("Error: Mode " + modeStr + " is not valid");
+            return -1;
+        }
+
+        int parseUserOpMode(int defMode, PrintWriter err) throws RemoteException {
+            userId = UserHandle.USER_CURRENT;
+            opStr = null;
+            modeStr = null;
+            for (String argument; (argument = getNextArg()) != null;) {
+                if ("--user".equals(argument)) {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                } else {
+                    if (opStr == null) {
+                        opStr = argument;
+                    } else if (modeStr == null) {
+                        modeStr = argument;
+                        break;
+                    }
+                }
+            }
+            if (opStr == null) {
+                err.println("Error: Operation not specified.");
+                return -1;
+            }
+            op = strOpToOp(opStr, err);
+            if (op < 0) {
+                return -1;
+            }
+            if (modeStr != null) {
+                if ((mode=strModeToMode(modeStr, err)) < 0) {
+                    return -1;
+                }
+            } else {
+                mode = defMode;
+            }
+            return 0;
+        }
+
         int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException {
             userId = UserHandle.USER_CURRENT;
             packageName = null;
@@ -1742,6 +1796,8 @@
         pw.println("    Set the mode for a particular application and operation.");
         pw.println("  get [--user <USER_ID>] <PACKAGE> [<OP>]");
         pw.println("    Return the mode for a particular application and optional operation.");
+        pw.println("  query-op [--user <USER_ID>] <OP> [<MODE>]");
+        pw.println("    Print all packages that currently have the given op in the given mode.");
         pw.println("  reset [--user <USER_ID>] [<PACKAGE>]");
         pw.println("    Reset the given application or all applications to default modes.");
         pw.println("  write-settings");
@@ -1775,23 +1831,9 @@
                         return -1;
                     }
 
-                    final int mode;
-                    switch (modeStr) {
-                        case "allow":
-                            mode = AppOpsManager.MODE_ALLOWED;
-                            break;
-                        case "deny":
-                            mode = AppOpsManager.MODE_ERRORED;
-                            break;
-                        case "ignore":
-                            mode = AppOpsManager.MODE_IGNORED;
-                            break;
-                        case "default":
-                            mode = AppOpsManager.MODE_DEFAULT;
-                            break;
-                        default:
-                            err.println("Error: Mode " + modeStr + " is not valid,");
-                            return -1;
+                    final int mode = shell.strModeToMode(modeStr, err);
+                    if (mode < 0) {
+                        return -1;
                     }
 
                     shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode);
@@ -1856,6 +1898,34 @@
                     }
                     return 0;
                 }
+                case "query-op": {
+                    int res = shell.parseUserOpMode(AppOpsManager.MODE_IGNORED, err);
+                    if (res < 0) {
+                        return res;
+                    }
+                    List<AppOpsManager.PackageOps> ops = shell.mInterface.getPackagesForOps(
+                            new int[] {shell.op});
+                    if (ops == null || ops.size() <= 0) {
+                        pw.println("No operations.");
+                        return 0;
+                    }
+                    for (int i=0; i<ops.size(); i++) {
+                        final AppOpsManager.PackageOps pkg = ops.get(i);
+                        boolean hasMatch = false;
+                        final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+                        for (int j=0; j<entries.size(); j++) {
+                            AppOpsManager.OpEntry ent = entries.get(j);
+                            if (ent.getOp() == shell.op && ent.getMode() == shell.mode) {
+                                hasMatch = true;
+                                break;
+                            }
+                        }
+                        if (hasMatch) {
+                            pw.println(pkg.getPackageName());
+                        }
+                    }
+                    return 0;
+                }
                 case "reset": {
                     String packageName = null;
                     int userId = UserHandle.USER_CURRENT;
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 4749417..6023d7f 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -108,7 +108,7 @@
 
     private static final boolean COMPRESS_TIME = false;
 
-    private static final int EVENT_BUFFER_SIZE = 40;
+    private static final int EVENT_BUFFER_SIZE = 100;
 
     private AlarmManager mAlarmManager;
     private IBatteryStats mBatteryStats;
@@ -192,6 +192,7 @@
     private long mNextAlarmTime;
     private long mNextIdlePendingDelay;
     private long mNextIdleDelay;
+    private long mNextLightIdleDelay;
     private long mNextLightAlarmTime;
     private long mCurIdleBudget;
     private long mMaintenanceStartTime;
@@ -353,6 +354,8 @@
         }
     };
 
+    private boolean mMaintenanceMinCheckScheduled;
+
     private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             decActiveIdleOps();
@@ -477,7 +480,11 @@
      */
     private final class Constants extends ContentObserver {
         // Key names stored in the settings value.
+        private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT
+                = "light_after_inactive_to";
         private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
+        private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor";
+        private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT = "light_max_idle_to";
         private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
                 = "light_idle_maintenance_min_budget";
         private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
@@ -505,14 +512,35 @@
                 "sms_temp_app_whitelist_duration";
 
         /**
-         * This is the time, after becoming inactive, that we will start going
-         * in to light-weight idle mode.
+         * This is the time, after becoming inactive, that we go in to the first
+         * light-weight idle mode.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT
+         */
+        public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT;
+
+        /**
+         * This is the initial time that we will run in idle maintenance mode.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
          * @see #KEY_LIGHT_IDLE_TIMEOUT
          */
         public long LIGHT_IDLE_TIMEOUT;
 
         /**
+         * Scaling factor to apply to the light idle mode time each time we complete a cycle.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_FACTOR
+         */
+        public float LIGHT_IDLE_FACTOR;
+
+        /**
+         * This is the maximum time we will run in idle maintenence mode.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT
+         */
+        public long LIGHT_MAX_IDLE_TIMEOUT;
+
+        /**
          * This is the minimum amount of time we want to make available for maintenance mode
          * when lightly idling.  That is, we will always have at least this amount of time
          * available maintenance before timing out and cutting off maintenance mode.
@@ -716,7 +744,14 @@
                     Slog.e(TAG, "Bad device idle settings", e);
                 }
 
+                LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(
+                        KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
+                        !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
                 LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
+                        !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
+                LIGHT_IDLE_FACTOR = mParser.getFloat(KEY_LIGHT_IDLE_FACTOR,
+                        2f);
+                LIGHT_MAX_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_MAX_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
                 LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
                         KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
@@ -770,10 +805,22 @@
         void dump(PrintWriter pw) {
             pw.println("  Settings:");
 
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, pw);
+            pw.println();
+
             pw.print("    "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("=");
             TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
             pw.println();
 
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_FACTOR); pw.print("=");
+            pw.print(LIGHT_IDLE_FACTOR);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_LIGHT_MAX_IDLE_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_MAX_IDLE_TIMEOUT, pw);
+            pw.println();
+
             pw.print("    "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("=");
             TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw);
             pw.println();
@@ -1640,7 +1687,10 @@
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
             mCurIdleBudget = 0;
             mMaintenanceStartTime = 0;
-            mAlarmManager.cancel(mMaintenanceMinCheckListener);
+            if (mMaintenanceMinCheckScheduled) {
+                mAlarmManager.cancel(mMaintenanceMinCheckListener);
+                mMaintenanceMinCheckScheduled = false;
+            }
             resetIdleManagementLocked();
             resetLightIdleManagementLocked();
             addEvent(EVENT_NORMAL);
@@ -1663,7 +1713,7 @@
                 mLightState = LIGHT_STATE_INACTIVE;
                 if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
                 resetLightIdleManagementLocked();
-                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
                 EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
             }
         }
@@ -1672,6 +1722,7 @@
     void resetIdleManagementLocked() {
         mNextIdlePendingDelay = 0;
         mNextIdleDelay = 0;
+        mNextLightIdleDelay = 0;
         cancelAlarmLocked();
         cancelLocatingLocked();
         stopMonitoringMotionLocked();
@@ -1704,6 +1755,8 @@
         switch (mLightState) {
             case LIGHT_STATE_INACTIVE:
                 mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                // Reset the upcoming idle delays.
+                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                 mMaintenanceStartTime = 0;
             case LIGHT_STATE_IDLE_MAINTENANCE:
                 if (mMaintenanceStartTime != 0) {
@@ -1717,13 +1770,21 @@
                     }
                 }
                 mMaintenanceStartTime = 0;
-                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
+                scheduleLightAlarmLocked(mNextLightIdleDelay);
+                mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
+                        (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
+                if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
+                    mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
+                }
                 if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                 mLightState = LIGHT_STATE_IDLE;
                 EventLogTags.writeDeviceIdleLight(mLightState, reason);
                 addEvent(EVENT_LIGHT_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
-                mAlarmManager.cancel(mMaintenanceMinCheckListener);
+                if (mMaintenanceMinCheckScheduled) {
+                    mAlarmManager.cancel(mMaintenanceMinCheckListener);
+                    mMaintenanceMinCheckScheduled = false;
+                }
                 break;
             case LIGHT_STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
@@ -1744,6 +1805,7 @@
                 mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
                         mMaintenanceStartTime + mConstants.MIN_LIGHT_MAINTENANCE_TIME,
                         "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler);
+                mMaintenanceMinCheckScheduled = true;
                 break;
         }
     }
@@ -1820,6 +1882,7 @@
                 cancelAlarmLocked();
                 cancelLocatingLocked();
                 mAnyMotionDetector.stop();
+
             case STATE_IDLE_MAINTENANCE:
                 scheduleAlarmLocked(mNextIdleDelay, true);
                 if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
@@ -1827,6 +1890,9 @@
                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
+                if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
+                    mNextIdleDelay = mConstants.IDLE_TIMEOUT;
+                }
                 mState = STATE_IDLE;
                 if (mLightState != LIGHT_STATE_OVERRIDE) {
                     mLightState = LIGHT_STATE_OVERRIDE;
@@ -1835,7 +1901,10 @@
                 EventLogTags.writeDeviceIdle(mState, reason);
                 addEvent(EVENT_DEEP_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
-                mAlarmManager.cancel(mMaintenanceMinCheckListener);
+                if (mMaintenanceMinCheckScheduled) {
+                    mAlarmManager.cancel(mMaintenanceMinCheckListener);
+                    mMaintenanceMinCheckScheduled = false;
+                }
                 break;
             case STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
@@ -1846,6 +1915,9 @@
                 mMaintenanceStartTime = SystemClock.elapsedRealtime();
                 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
+                if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
+                    mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
+                }
                 mState = STATE_IDLE_MAINTENANCE;
                 EventLogTags.writeDeviceIdle(mState, reason);
                 addEvent(EVENT_DEEP_MAINTENANCE);
@@ -1853,6 +1925,7 @@
                 mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
                         mMaintenanceStartTime + mConstants.MIN_DEEP_MAINTENANCE_TIME,
                         "DeviceIdleController.maint-check", mMaintenanceMinCheckListener, mHandler);
+                mMaintenanceMinCheckScheduled = true;
                 break;
         }
     }
@@ -2735,6 +2808,11 @@
                 TimeUtils.formatDuration(mNextIdleDelay, pw);
                 pw.println();
             }
+            if (mNextLightIdleDelay != 0) {
+                pw.print("  mNextIdleDelay=");
+                TimeUtils.formatDuration(mNextLightIdleDelay, pw);
+                pw.println();
+            }
             if (mNextLightAlarmTime != 0) {
                 pw.print("  mNextLightAlarmTime=");
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
@@ -2750,6 +2828,10 @@
                 TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mMaintenanceMinCheckScheduled) {
+                pw.print("  mMaintenanceMinCheckScheduled=");
+                pw.println(mMaintenanceMinCheckScheduled);
+            }
             if (mJobsActive) {
                 pw.print("  mJobsActive="); pw.println(mJobsActive);
             }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index e042483..77b3111 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1715,8 +1715,9 @@
             // exists in the IME switcher dialog.  Might be OK to remove this condition once
             // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
             return true;
+        } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
+            return false;
         }
-        if ((visibility & InputMethodService.IME_VISIBLE) == 0) return false;
 
         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
         final int N = imis.size();
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 45008dc..9e2f1167 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -165,6 +165,7 @@
         public void onStart() {
             mMountService = new MountService(getContext());
             publishBinderService("mount", mMountService);
+            mMountService.start();
         }
 
         @Override
@@ -430,9 +431,13 @@
         = { "password", "default", "pattern", "pin" };
 
     private final Context mContext;
+
     private final NativeDaemonConnector mConnector;
     private final NativeDaemonConnector mCryptConnector;
 
+    private final Thread mConnectorThread;
+    private final Thread mCryptConnectorThread;
+
     private volatile boolean mSystemReady = false;
     private volatile boolean mBootCompleted = false;
     private volatile boolean mDaemonConnected = false;
@@ -1494,17 +1499,13 @@
                 null);
         mConnector.setDebug(true);
         mConnector.setWarnIfHeld(mLock);
-
-        Thread thread = new Thread(mConnector, VOLD_TAG);
-        thread.start();
+        mConnectorThread = new Thread(mConnector, VOLD_TAG);
 
         // Reuse parameters from first connector since they are tested and safe
         mCryptConnector = new NativeDaemonConnector(this, "cryptd",
                 MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
         mCryptConnector.setDebug(true);
-
-        Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG);
-        crypt_thread.start();
+        mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG);
 
         final IntentFilter userFilter = new IntentFilter();
         userFilter.addAction(Intent.ACTION_USER_ADDED);
@@ -1521,6 +1522,11 @@
         }
     }
 
+    private void start() {
+        mConnectorThread.start();
+        mCryptConnectorThread.start();
+    }
+
     private void systemReady() {
         mSystemReady = true;
         mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
diff --git a/services/core/java/com/android/server/ThermalObserver.java b/services/core/java/com/android/server/ThermalObserver.java
deleted file mode 100644
index aee28fb..0000000
--- a/services/core/java/com/android/server/ThermalObserver.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UEventObserver;
-import android.os.UserHandle;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * ThermalObserver for monitoring temperature changes.
- */
-public class ThermalObserver extends SystemService {
-    private static final String TAG = "ThermalObserver";
-
-    private static final String CALLSTATE_UEVENT_MATCH =
-            "DEVPATH=/devices/virtual/switch/thermalstate";
-
-    private static final int MSG_THERMAL_STATE_CHANGED = 0;
-
-    private static final int SWITCH_STATE_NORMAL = 0;
-    private static final int SWITCH_STATE_WARNING = 1;
-    private static final int SWITCH_STATE_EXCEEDED = 2;
-
-    private final PowerManager mPowerManager;
-    private final PowerManager.WakeLock mWakeLock;
-
-    private final Object mLock = new Object();
-    private Integer mLastState;
-
-    private final UEventObserver mThermalWarningObserver = new UEventObserver() {
-        @Override
-        public void onUEvent(UEventObserver.UEvent event) {
-            updateLocked(Integer.parseInt(event.get("SWITCH_STATE")));
-        }
-    };
-
-    private final Handler mHandler = new Handler(true /*async*/) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_THERMAL_STATE_CHANGED:
-                    handleThermalStateChange(msg.arg1);
-                    mWakeLock.release();
-                    break;
-            }
-        }
-    };
-
-    public ThermalObserver(Context context) {
-        super(context);
-        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
-        mThermalWarningObserver.startObserving(CALLSTATE_UEVENT_MATCH);
-    }
-
-    private void updateLocked(int state) {
-        Message message = new Message();
-        message.what = MSG_THERMAL_STATE_CHANGED;
-        message.arg1 = state;
-
-        mWakeLock.acquire();
-        mHandler.sendMessage(message);
-    }
-
-    private void handleThermalStateChange(int state) {
-        synchronized (mLock) {
-            mLastState = state;
-            Intent intent = new Intent(Intent.ACTION_THERMAL_EVENT);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-
-            final int thermalState;
-
-            switch (state) {
-                case SWITCH_STATE_WARNING:
-                    thermalState = Intent.EXTRA_THERMAL_STATE_WARNING;
-                    break;
-                case SWITCH_STATE_EXCEEDED:
-                    thermalState = Intent.EXTRA_THERMAL_STATE_EXCEEDED;
-                    break;
-                case SWITCH_STATE_NORMAL:
-                default:
-                    thermalState = Intent.EXTRA_THERMAL_STATE_NORMAL;
-                    break;
-            }
-
-            intent.putExtra(Intent.EXTRA_THERMAL_STATE, thermalState);
-
-            getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
-        }
-    }
-
-    @Override
-    public void onStart() {
-        publishBinderService(TAG, new BinderService());
-    }
-
-    private final class BinderService extends Binder {
-        @Override
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                    != PackageManager.PERMISSION_GRANTED) {
-                pw.println("Permission Denial: can't dump thermal observer service from from pid="
-                        + Binder.getCallingPid()
-                        + ", uid=" + Binder.getCallingUid());
-                return;
-            }
-
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                synchronized (mLock) {
-                    if (args == null || args.length == 0 || "-a".equals(args[0])) {
-                        pw.println("Current Thermal Observer Service state:");
-                        pw.println("  last state change: "
-                                + (mLastState != null ? mLastState : "none"));
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9d1b4c7..f99caba 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -62,7 +62,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -1280,7 +1279,7 @@
                         // Before going further -- if this app is not allowed to run in the
                         // background, then at this point we aren't going to let it period.
                         final int allowed = mAm.checkAllowBackgroundLocked(
-                                sInfo.applicationInfo.uid, sInfo.packageName, callingPid);
+                                sInfo.applicationInfo.uid, sInfo.packageName, callingPid, true);
                         if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                             Slog.w(TAG, "Background execution not allowed: service "
                                     + service + " to " + name.flattenToShortString()
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cecd01b..4bb3a54 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1468,6 +1468,9 @@
     static final int FIRST_COMPAT_MODE_MSG = 300;
     static final int FIRST_SUPERVISOR_STACK_MSG = 100;
 
+    static ServiceThread sKillThread = null;
+    static KillHandler sKillHandler = null;
+
     CompatModeDialog mCompatModeDialog;
     long mLastMemUsageReportTime = 0;
 
@@ -1488,10 +1491,33 @@
     final ServiceThread mHandlerThread;
     final MainHandler mHandler;
     final UiHandler mUiHandler;
-    final ProcessStartLogger mProcessStartLogger;
 
     PackageManagerInternal mPackageManagerInt;
 
+    final class KillHandler extends Handler {
+        static final int KILL_PROCESS_GROUP_MSG = 4000;
+
+        public KillHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case KILL_PROCESS_GROUP_MSG:
+                {
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup");
+                    Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                }
+                break;
+
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
     final class UiHandler extends Handler {
         public UiHandler() {
             super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -2460,7 +2486,13 @@
         mHandler = new MainHandler(mHandlerThread.getLooper());
         mUiHandler = new UiHandler();
 
-        mProcessStartLogger = new ProcessStartLogger();
+        /* static; one-time init here */
+        if (sKillHandler == null) {
+            sKillThread = new ServiceThread(TAG + ":kill",
+                    android.os.Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+            sKillThread.start();
+            sKillHandler = new KillHandler(sKillThread.getLooper());
+        }
 
         mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                 "foreground", BROADCAST_FG_TIMEOUT, false);
@@ -3010,9 +3042,13 @@
     }
 
     static void killProcessGroup(int uid, int pid) {
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "killProcessGroup");
-        Process.killProcessGroup(uid, pid);
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        if (sKillHandler != null) {
+            sKillHandler.sendMessage(
+                    sKillHandler.obtainMessage(KillHandler.KILL_PROCESS_GROUP_MSG, uid, pid));
+        } else {
+            Slog.w(TAG, "Asked to kill process group before system bringup!");
+            Process.killProcessGroup(uid, pid);
+        }
     }
 
     final void removeLruProcessLocked(ProcessRecord app) {
@@ -3594,7 +3630,12 @@
                     app.processName, hostingType,
                     hostingNameStr != null ? hostingNameStr : "");
 
-            mProcessStartLogger.logIfNeededLocked(app, startResult);
+            try {
+                AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
+                        app.info.seinfo, app.info.sourceDir, startResult.pid);
+            } catch (RemoteException ex) {
+                // Ignore
+            }
 
             if (app.persistent) {
                 Watchdog.getInstance().processStarted(app.processName, startResult.pid);
@@ -6537,8 +6578,6 @@
             }
         }, dumpheapFilter);
 
-        mProcessStartLogger.registerListener(mContext);
-
         // Let system services know.
         mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
@@ -7182,6 +7221,15 @@
         }
     }
 
+    // NOTE: this is an internal method used by the OnShellCommand implementation only and should
+    // be guarded by permission checking.
+    int getUidState(int uid) {
+        synchronized (this) {
+            UidRecord uidRec = mActiveUids.get(uid);
+            return uidRec == null ? ActivityManager.PROCESS_STATE_NONEXISTENT : uidRec.curProcState;
+        }
+    }
+
     @Override
     public boolean inMultiWindow(IBinder token) {
         final long origId = Binder.clearCallingIdentity();
@@ -7535,14 +7583,15 @@
 
     public int getAppStartMode(int uid, String packageName) {
         synchronized (this) {
-            return checkAllowBackgroundLocked(uid, packageName, -1);
+            return checkAllowBackgroundLocked(uid, packageName, -1, true);
         }
     }
 
-    int checkAllowBackgroundLocked(int uid, String packageName, int callingPid) {
+    int checkAllowBackgroundLocked(int uid, String packageName, int callingPid,
+            boolean allowWhenForeground) {
         UidRecord uidRec = mActiveUids.get(uid);
         if (!mLenientBackgroundCheck) {
-            if (uidRec == null
+            if (!allowWhenForeground || uidRec == null
                     || uidRec.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
                 if (mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid,
                         packageName) != AppOpsManager.MODE_ALLOWED) {
@@ -13204,6 +13253,7 @@
         }
     }
 
+    @Override
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
         enforceNotIsolatedCaller("getProcessesInErrorState");
         // assume our apps are happy - lazy create the list
@@ -13280,6 +13330,7 @@
         outInfo.processState = app.curProcState;
     }
 
+    @Override
     public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
         enforceNotIsolatedCaller("getRunningAppProcesses");
 
@@ -13331,6 +13382,7 @@
         return runList;
     }
 
+    @Override
     public List<ApplicationInfo> getRunningExternalApplications() {
         enforceNotIsolatedCaller("getRunningExternalApplications");
         List<ActivityManager.RunningAppProcessInfo> runningApps = getRunningAppProcesses();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0253976..d570be9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,10 +16,12 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.util.DebugUtils;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -64,6 +66,8 @@
                     return runIsUserStopped(pw);
                 case "lenient-background-check":
                     return runLenientBackgroundCheck(pw);
+                case "get-uid-state":
+                    return getUidState(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -170,6 +174,17 @@
         return 0;
     }
 
+    int getUidState(PrintWriter pw) throws RemoteException {
+        mInternal.enforceCallingPermission(android.Manifest.permission.DUMP,
+                "getUidState()");
+        int state = mInternal.getUidState(Integer.parseInt(getNextArgRequired()));
+        pw.print(state);
+        pw.print(" (");
+        pw.printf(DebugUtils.valueToString(ActivityManager.class, "PROCESS_STATE_", state));
+        pw.println(")");
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -212,7 +227,7 @@
             pw.println("  kill [--user <USER_ID> | all | current] <PACKAGE>");
             pw.println("    Kill all processes associated with the given application.");
             pw.println("  kill-all");
-            pw.println("    Kill all processes that are safe to kill (cached, etc)");
+            pw.println("    Kill all processes that are safe to kill (cached, etc).");
             pw.println("  write");
             pw.println("    Write all pending state to storage.");
             pw.println("  track-associations");
@@ -220,9 +235,11 @@
             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");
+            pw.println("    Returns whether <USER_ID> has been stopped or not.");
             pw.println("  lenient-background-check [<true|false>]");
-            pw.println("    optionally controls lenient background check mode, returns current mode.");
+            pw.println("    Optionally controls lenient background check mode, returns current mode.");
+            pw.println("  get-uid-state <UID>");
+            pw.println("    Gets the process state of an app given its <UID>.");
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 100bf26..a9ef1d6 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3089,10 +3089,14 @@
             return false;
         }
 
-        if (stack.isHomeStack()) {
+        final ActivityRecord top = stack.topRunningActivityLocked();
+
+        if (stack.isHomeStack() && (top == null || !top.visible)) {
+            // If we will be focusing on the home stack next and its current top activity isn't
+            // visible, then use the task return to value to determine the home task to display next.
             return mStackSupervisor.moveHomeStackTaskToTop(taskToReturnTo, reason);
         }
-        return mService.setFocusedActivityLocked(stack.topRunningActivityLocked(), myReason);
+        return mService.setFocusedActivityLocked(top, myReason);
     }
 
     final void stopActivityLocked(ActivityRecord r) {
@@ -4783,6 +4787,8 @@
                     "    Task id #" + task.taskId + "\n" +
                     "    mFullscreen=" + task.mFullscreen + "\n" +
                     "    mBounds=" + task.mBounds + "\n" +
+                    "    mMinimalWidth=" + task.mMinimalWidth + "\n" +
+                    "    mMinimalHeight=" + task.mMinimalHeight + "\n" +
                     "    mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
             if (printed) {
                 header = null;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ffa3b5b..3d42047 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1224,7 +1224,8 @@
     // WiFi keeps an accumulated total of stats, unlike Bluetooth.
     // Keep the last WiFi stats so we can compute a delta.
     @GuardedBy("mExternalStatsLock")
-    private WifiActivityEnergyInfo mLastInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+    private WifiActivityEnergyInfo mLastInfo =
+            new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0);
 
     @GuardedBy("mExternalStatsLock")
     private WifiActivityEnergyInfo pullWifiEnergyInfoLocked() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 45e3a76..37c7765 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -50,7 +50,6 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 import com.android.server.DeviceIdleController;
-import com.android.server.LocalServices;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
 
@@ -563,7 +562,7 @@
         }
         if (!skip) {
             final int allowed = mService.checkAllowBackgroundLocked(filter.receiverList.uid,
-                    filter.packageName, -1);
+                    filter.packageName, -1, true);
             if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                 Slog.w(TAG, "Background execution not allowed: receiving "
                         + r.intent
@@ -1102,21 +1101,21 @@
 
             if (!skip) {
                 final int allowed = mService.checkAllowBackgroundLocked(
-                        info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1);
+                        info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1,
+                        false);
                 if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                     // We won't allow this receiver to be launched if the app has been
-                    // completely disabled from launches, or it is delayed and the broadcast
-                    // was not explicitly sent to it and this would result in a new process
-                    // for it being created.
+                    // completely disabled from launches, or it was not explicitly sent
+                    // to it and the app is in a state that should not receive it
+                    // (depending on how checkAllowBackgroundLocked has determined that).
                     if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                         Slog.w(TAG, "Background execution disabled: receiving "
                                 + r.intent + " to "
                                 + component.flattenToShortString());
                         skip = true;
-                    }
-                    if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
+                    } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                             || (r.intent.getComponent() == null
-                                && r.intent.getPackage() == null && app == null
+                                && r.intent.getPackage() == null
                                 && ((r.intent.getFlags()
                                         & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0))) {
                         Slog.w(TAG, "Background execution not allowed: receiving "
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0993ce6..93d4060 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -560,7 +560,7 @@
             }
             EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
             Process.killProcessQuiet(pid);
-            Process.killProcessGroup(uid, pid);
+            ActivityManagerService.killProcessGroup(uid, pid);
             if (!persistent) {
                 killed = true;
                 killedByAm = true;
diff --git a/services/core/java/com/android/server/am/ProcessStartLogger.java b/services/core/java/com/android/server/am/ProcessStartLogger.java
deleted file mode 100644
index 39fbeb5..0000000
--- a/services/core/java/com/android/server/am/ProcessStartLogger.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.app.AppGlobals;
-import android.app.admin.SecurityLog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.Process.ProcessStartResult;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-
-/**
- * A class that logs process start information (including APK hash) to the security log.
- */
-class ProcessStartLogger {
-    private static final String CLASS_NAME = "ProcessStartLogger";
-    private static final String TAG = TAG_WITH_CLASS_NAME ? CLASS_NAME : TAG_AM;
-
-    final HandlerThread mHandlerProcessLoggingThread;
-    Handler mHandlerProcessLogging;
-    // Should only access in mHandlerProcessLoggingThread
-    final HashMap<String, String> mProcessLoggingApkHashes;
-
-    ProcessStartLogger() {
-        mHandlerProcessLoggingThread = new HandlerThread(CLASS_NAME,
-                Process.THREAD_PRIORITY_BACKGROUND);
-        mProcessLoggingApkHashes = new HashMap();
-    }
-
-    void logIfNeededLocked(ProcessRecord app, ProcessStartResult startResult) {
-        if (!SecurityLog.isLoggingEnabled()) {
-            return;
-        }
-        if (!mHandlerProcessLoggingThread.isAlive()) {
-            mHandlerProcessLoggingThread.start();
-            mHandlerProcessLogging = new Handler(mHandlerProcessLoggingThread.getLooper());
-        }
-        mHandlerProcessLogging.post(new ProcessLoggingRunnable(app, startResult,
-                System.currentTimeMillis()));
-    }
-
-    void registerListener(Context context) {
-        IntentFilter packageChangedFilter = new IntentFilter();
-        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        packageChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        context.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
-                        || Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
-                    int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                            getSendingUserId());
-                    String packageName = intent.getData().getSchemeSpecificPart();
-                    try {
-                        ApplicationInfo info = AppGlobals.getPackageManager().getApplicationInfo(
-                                packageName, 0, userHandle);
-                        invaildateCache(info.sourceDir);
-                    } catch (RemoteException e) {
-                    }
-                }
-            }
-        }, packageChangedFilter);
-    }
-
-    private void invaildateCache(final String apkPath) {
-        if (mHandlerProcessLogging != null) {
-            mHandlerProcessLogging.post(new Runnable() {
-                @Override
-                public void run() {
-                    mProcessLoggingApkHashes.remove(apkPath);
-                }
-            });
-        }
-    }
-
-    private class ProcessLoggingRunnable implements Runnable {
-
-        private final ProcessRecord app;
-        private final Process.ProcessStartResult startResult;
-        private final long startTimestamp;
-
-        public ProcessLoggingRunnable(ProcessRecord app, Process.ProcessStartResult startResult,
-                long startTimestamp){
-            this.app = app;
-            this.startResult = startResult;
-            this.startTimestamp = startTimestamp;
-        }
-
-        @Override
-        public void run() {
-            String apkHash = computeStringHashOfApk(app);
-            SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START,
-                    app.processName,
-                    startTimestamp,
-                    app.uid,
-                    startResult.pid,
-                    app.info.seinfo,
-                    apkHash);
-        }
-
-        private String computeStringHashOfApk(ProcessRecord app){
-            final String apkFile = app.info.sourceDir;
-            if(apkFile == null) {
-                return "No APK";
-            }
-            String apkHash = mProcessLoggingApkHashes.get(apkFile);
-            if (apkHash == null) {
-                try {
-                    byte[] hash = computeHashOfApkFile(apkFile);
-                    StringBuilder sb = new StringBuilder();
-                    for (int i = 0; i < hash.length; i++) {
-                        sb.append(String.format("%02x", hash[i]));
-                    }
-                    apkHash = sb.toString();
-                    mProcessLoggingApkHashes.put(apkFile, apkHash);
-                } catch (IOException | NoSuchAlgorithmException e) {
-                    Slog.w(TAG, "computeStringHashOfApk() failed", e);
-                }
-            }
-            return apkHash != null ? apkHash : "Failed to count APK hash";
-        }
-
-        private byte[] computeHashOfApkFile(String packageArchiveLocation)
-                throws IOException, NoSuchAlgorithmException {
-            MessageDigest md = MessageDigest.getInstance("SHA-256");
-            FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
-            byte[] buffer = new byte[65536];
-            int size;
-            while((size = input.read(buffer)) > 0) {
-                md.update(buffer, 0, size);
-            }
-            input.close();
-            return md.digest();
-        }
-    }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c59591e9..aef454e 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -434,35 +434,17 @@
                 stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                 stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                 stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
-                final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
-                // This is the result receiver for the final shutdown broadcast.
-                final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
-                    @Override
-                    public void performReceive(Intent intent, int resultCode, String data,
-                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        finishUserStop(uss);
-                    }
-                };
                 // This is the result receiver for the initial stopping broadcast.
                 final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        // On to the next.
-                        synchronized (mService) {
-                            if (uss.state != UserState.STATE_STOPPING) {
-                                // Whoops, we are being started back up.  Abort, abort!
-                                return;
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                finishUserStopping(userId, uss);
                             }
-                            uss.setState(UserState.STATE_SHUTDOWN);
-                        }
-                        mService.mBatteryStatsService.noteEvent(
-                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
-                                Integer.toString(userId), userId);
-                        mService.mSystemServiceManager.stopUser(userId);
-                        mService.broadcastIntentLocked(null, null, shutdownIntent,
-                                null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, MY_PID, SYSTEM_UID, userId);
+                        });
                     }
                 };
                 // Kick things off.
@@ -476,7 +458,45 @@
         }
     }
 
-    void finishUserStop(UserState uss) {
+    void finishUserStopping(final int userId, final UserState uss) {
+        // On to the next.
+        final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+        // This is the result receiver for the final shutdown broadcast.
+        final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
+            @Override
+            public void performReceive(Intent intent, int resultCode, String data,
+                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        finishUserStopped(uss);
+                    }
+                });
+            }
+        };
+
+        synchronized (mService) {
+            if (uss.state != UserState.STATE_STOPPING) {
+                // Whoops, we are being started back up.  Abort, abort!
+                return;
+            }
+            uss.setState(UserState.STATE_SHUTDOWN);
+        }
+
+        mService.mBatteryStatsService.noteEvent(
+                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+                Integer.toString(userId), userId);
+        mService.mSystemServiceManager.stopUser(userId);
+
+        synchronized (mService) {
+            mService.broadcastIntentLocked(null, null, shutdownIntent,
+                    null, shutdownReceiver, 0, null, null, null,
+                    AppOpsManager.OP_NONE,
+                    null, true, false, MY_PID, SYSTEM_UID, userId);
+        }
+    }
+
+    void finishUserStopped(UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         boolean stopped;
         ArrayList<IStopUserCallback> callbacks;
@@ -765,10 +785,17 @@
                         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                         mService.broadcastIntentLocked(null, null, intent, null,
                                 new IIntentReceiver.Stub() {
+                                    @Override
                                     public void performReceive(Intent intent, int resultCode,
                                             String data, Bundle extras, boolean ordered,
                                             boolean sticky, int sendingUser) {
-                                        onUserInitialized(uss, foreground, oldUserId, userId);
+                                        mHandler.post(new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                onUserInitialized(uss, foreground,
+                                                        oldUserId, userId);
+                                            }
+                                        });
                                     }
                                 }, 0, null, null, null, AppOpsManager.OP_NONE,
                                 null, true, false, MY_PID, SYSTEM_UID, userId);
diff --git a/services/core/java/com/android/server/backup/BackupUtils.java b/services/core/java/com/android/server/backup/BackupUtils.java
new file mode 100644
index 0000000..e5d564d
--- /dev/null
+++ b/services/core/java/com/android/server/backup/BackupUtils.java
@@ -0,0 +1,132 @@
+/*
+ * 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.backup;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.util.Slog;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class BackupUtils {
+    private static final String TAG = "BackupUtils";
+
+    private static final boolean DEBUG = false; // STOPSHIP if true
+
+    public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
+        if (target == null) {
+            return false;
+        }
+
+        // If the target resides on the system partition, we allow it to restore
+        // data from the like-named package in a restore set even if the signatures
+        // do not match.  (Unlike general applications, those flashed to the system
+        // partition will be signed with the device's platform certificate, so on
+        // different phones the same system app will have different signatures.)
+        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            if (DEBUG) Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
+            return true;
+        }
+
+        // Allow unsigned apps, but not signed on one device and unsigned on the other
+        // !!! TODO: is this the right policy?
+        Signature[] deviceSigs = target.signatures;
+        if (DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
+                + " device=" + deviceSigs);
+        if ((storedSigHashes == null || storedSigHashes.size() == 0)
+                && (deviceSigs == null || deviceSigs.length == 0)) {
+            return true;
+        }
+        if (storedSigHashes == null || deviceSigs == null) {
+            return false;
+        }
+
+        // !!! TODO: this demands that every stored signature match one
+        // that is present on device, and does not demand the converse.
+        // Is this this right policy?
+        final int nStored = storedSigHashes.size();
+        final int nDevice = deviceSigs.length;
+
+        // hash each on-device signature
+        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
+        for (int i = 0; i < nDevice; i++) {
+            deviceHashes.add(hashSignature(deviceSigs[i]));
+        }
+
+        // now ensure that each stored sig (hash) matches an on-device sig (hash)
+        for (int n = 0; n < nStored; n++) {
+            boolean match = false;
+            final byte[] storedHash = storedSigHashes.get(n);
+            for (int i = 0; i < nDevice; i++) {
+                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
+                    match = true;
+                    break;
+                }
+            }
+            // match is false when no on-device sig matched one of the stored ones
+            if (!match) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static byte[] hashSignature(byte[] signature) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            digest.update(signature);
+            return digest.digest();
+        } catch (NoSuchAlgorithmException e) {
+            Slog.w(TAG, "No SHA-256 algorithm found!");
+        }
+        return null;
+    }
+
+    public static byte[] hashSignature(Signature signature) {
+        return hashSignature(signature.toByteArray());
+    }
+
+    public static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
+        if (sigs == null) {
+            return null;
+        }
+
+        ArrayList<byte[]> hashes = new ArrayList<>(sigs.length);
+        for (Signature s : sigs) {
+            hashes.add(hashSignature(s));
+        }
+        return hashes;
+    }
+
+    public static ArrayList<byte[]> hashSignatureArray(List<byte[]> sigs) {
+        if (sigs == null) {
+            return null;
+        }
+
+        ArrayList<byte[]> hashes = new ArrayList<>(sigs.size());
+        for (byte[] s : sigs) {
+            hashes.add(hashSignature(s));
+        }
+        return hashes;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index da9c48a..d10df02 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
@@ -40,6 +41,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.wifi.WifiManager;
@@ -88,7 +90,7 @@
  */
 public class Tethering extends BaseNetworkObserver {
 
-    private Context mContext;
+    private final Context mContext;
     private final static String TAG = "Tethering";
     private final static boolean DBG = false;
     private final static boolean VDBG = false;
@@ -100,7 +102,7 @@
     private Collection<Integer> mUpstreamIfaceTypes;
 
     // used to synchronize public access to members
-    private Object mPublicSync;
+    private final Object mPublicSync;
 
     private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
     private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
@@ -112,7 +114,7 @@
 
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
-    private Looper mLooper;
+    private final Looper mLooper;
 
     private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
 
@@ -143,7 +145,9 @@
     private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
     private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
 
-    private StateMachine mTetherMasterSM;
+    private final StateMachine mTetherMasterSM;
+    private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    private String mCurrentUpstreamIface;
 
     private Notification.Builder mTetheredNotificationBuilder;
     private int mLastNotificationId;
@@ -167,6 +171,8 @@
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
         mTetherMasterSM.start();
 
+        mUpstreamNetworkMonitor = new UpstreamNetworkMonitor();
+
         mStateReceiver = new StateReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -505,7 +511,7 @@
         };
 
         // The following is necessary to avoid unmarshalling issues when sending the receiver
-        // across proccesses.
+        // across processes.
         Parcel parcel = Parcel.obtain();
         rr.writeToParcel(parcel,0);
         parcel.setDataPosition(0);
@@ -559,6 +565,7 @@
             }
         }
     }
+
     public int tether(String iface) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
         TetherInterfaceSM sm = null;
@@ -1371,15 +1378,115 @@
 
     }
 
+    /**
+     * A NetworkCallback class that relays information of interest to the
+     * tethering master state machine thread for subsequent processing.
+     */
+    class UpstreamNetworkCallback extends NetworkCallback {
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
+            mTetherMasterSM.sendMessage(
+                    TetherMasterSM.EVENT_UPSTREAM_LINKPROPERTIES_CHANGED,
+                    new NetworkState(null, newLp, null, network, null, null));
+        }
+
+        @Override
+        public void onLost(Network network) {
+            mTetherMasterSM.sendMessage(TetherMasterSM.EVENT_UPSTREAM_LOST, network);
+        }
+    }
+
+    /**
+     * A class to centralize all the network and link properties information
+     * pertaining to the current and any potential upstream network.
+     *
+     * Calling #start() registers two callbacks: one to track the system default
+     * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+     *
+     * The methods and data members of this class are only to be accessed and
+     * modified from the tethering master state machine thread. Any other
+     * access semantics would necessitate the addition of locking.
+     *
+     * TODO: Investigate whether more "upstream-specific" logic/functionality
+     * could/should be moved here.
+     */
+    class UpstreamNetworkMonitor {
+        final HashMap<Network, NetworkState> mNetworkMap = new HashMap();
+        NetworkCallback mDefaultNetworkCallback;
+        NetworkCallback mDunTetheringCallback;
+
+        void start() {
+            stop();
+
+            mDefaultNetworkCallback = new UpstreamNetworkCallback();
+            getConnectivityManager().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+
+            final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    .build();
+            mDunTetheringCallback = new UpstreamNetworkCallback();
+            getConnectivityManager().registerNetworkCallback(
+                    dunTetheringRequest, mDunTetheringCallback);
+        }
+
+        void stop() {
+            if (mDefaultNetworkCallback != null) {
+                getConnectivityManager().unregisterNetworkCallback(mDefaultNetworkCallback);
+                mDefaultNetworkCallback = null;
+            }
+
+            if (mDunTetheringCallback != null) {
+                getConnectivityManager().unregisterNetworkCallback(mDunTetheringCallback);
+                mDunTetheringCallback = null;
+            }
+
+            mNetworkMap.clear();
+        }
+
+        // Returns true if these updated LinkProperties pertain to the current
+        // upstream network interface, false otherwise (or if there is not
+        // currently any upstream tethering interface).
+        boolean processLinkPropertiesChanged(NetworkState networkState) {
+            if (networkState == null ||
+                    networkState.network == null ||
+                    networkState.linkProperties == null) {
+                return false;
+            }
+
+            mNetworkMap.put(networkState.network, networkState);
+
+            if (mCurrentUpstreamIface != null) {
+                for (String ifname : networkState.linkProperties.getAllInterfaceNames()) {
+                    if (mCurrentUpstreamIface.equals(ifname)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        void processNetworkLost(Network network) {
+            if (network != null) {
+                mNetworkMap.remove(network);
+            }
+        }
+    }
+
     class TetherMasterSM extends StateMachine {
         // an interface SM has requested Tethering
-        static final int CMD_TETHER_MODE_REQUESTED   = 1;
+        static final int CMD_TETHER_MODE_REQUESTED              = 1;
         // an interface SM has unrequested Tethering
-        static final int CMD_TETHER_MODE_UNREQUESTED = 2;
+        static final int CMD_TETHER_MODE_UNREQUESTED            = 2;
         // upstream connection change - do the right thing
-        static final int CMD_UPSTREAM_CHANGED        = 3;
+        static final int CMD_UPSTREAM_CHANGED                   = 3;
         // we don't have a valid upstream conn, check again after a delay
-        static final int CMD_RETRY_UPSTREAM          = 4;
+        static final int CMD_RETRY_UPSTREAM                     = 4;
+        // Events from NetworkCallbacks that we process on the master state
+        // machine thread on behalf of the UpstreamNetworkMonitor.
+        static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED  = 5;
+        static final int EVENT_UPSTREAM_LOST                    = 6;
 
         // This indicates what a timeout event relates to.  A state that
         // sends itself a delayed timeout event and handles incoming timeout events
@@ -1399,9 +1506,7 @@
         private ArrayList<TetherInterfaceSM> mNotifyList;
 
         private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
-        private ConnectivityManager.NetworkCallback mMobileUpstreamCallback;
-
-        private String mUpstreamIfaceName = null;
+        private NetworkCallback mMobileUpstreamCallback;
 
         private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
 
@@ -1430,8 +1535,6 @@
         }
 
         class TetherMasterUtilState extends State {
-            protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE     = false;
-
             @Override
             public boolean processMessage(Message m) {
                 return false;
@@ -1461,27 +1564,27 @@
                         return false;
                 }
 
-                NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
                 if (apnType == ConnectivityManager.TYPE_MOBILE_DUN) {
-                    builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                           .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+                    builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                           .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
                 } else {
                     builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
                 }
-                NetworkRequest mobileUpstreamRequest = builder.build();
-                // Other mechanisms notice network and interface changes and act upon them.
-                // TODO, imminently: replace with a proper NetworkCallback-based scheme.
-                //
+                final NetworkRequest mobileUpstreamRequest = builder.build();
+
+                // The UpstreamNetworkMonitor's callback will be notified.
+                // Therefore, to avoid duplicate notifications, we only register a no-op.
+                mMobileUpstreamCallback = new NetworkCallback();
+
                 // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
                 // moderate callback time (once timeout callbacks are implemented). This might
                 // be useful for updating some UI. Additionally, we should definitely log a
-                // message to aid in any subsequent debugging.
-                mMobileUpstreamCallback = new ConnectivityManager.NetworkCallback();
+                // message to aid in any subsequent debugging
                 if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
                 getConnectivityManager().requestNetwork(
                         mobileUpstreamRequest, mMobileUpstreamCallback, 0, apnType);
-
                 return true;
             }
 
@@ -1513,6 +1616,7 @@
                 }
                 return true;
             }
+
             protected boolean turnOffMasterTetherSettings() {
                 try {
                     mNMService.stopTethering();
@@ -1606,34 +1710,41 @@
                     }
 
                     if (iface != null) {
-                        String[] dnsServers = mDefaultDnsServers;
-                        Collection<InetAddress> dnses = linkProperties.getDnsServers();
-                        if (dnses != null && !dnses.isEmpty()) {
-                            // TODO: remove this invocation of NetworkUtils.makeStrings().
-                            dnsServers = NetworkUtils.makeStrings(dnses);
+                        Network network = getConnectivityManager().getNetworkForType(upType);
+                        if (network == null) {
+                            Log.e(TAG, "No Network for upstream type " + upType + "!");
                         }
-                        try {
-                            Network network = getConnectivityManager().getNetworkForType(upType);
-                            if (network == null) {
-                                Log.e(TAG, "No Network for upstream type " + upType + "!");
-                            }
-                            if (VDBG) {
-                                Log.d(TAG, "Setting DNS forwarders: Network=" + network +
-                                       ", dnsServers=" + Arrays.toString(dnsServers));
-                            }
-                            mNMService.setDnsForwarders(network, dnsServers);
-                        } catch (Exception e) {
-                            Log.e(TAG, "Setting DNS forwarders failed!");
-                            transitionTo(mSetDnsForwardersErrorState);
-                        }
+                        setDnsForwarders(network, linkProperties);
                     }
                 }
                 notifyTetheredOfNewUpstreamIface(iface);
             }
 
+            protected void setDnsForwarders(final Network network, final LinkProperties lp) {
+                String[] dnsServers = mDefaultDnsServers;
+                final Collection<InetAddress> dnses = lp.getDnsServers();
+                // TODO: Properly support the absence of DNS servers.
+                if (dnses != null && !dnses.isEmpty()) {
+                    // TODO: remove this invocation of NetworkUtils.makeStrings().
+                    dnsServers = NetworkUtils.makeStrings(dnses);
+                }
+                if (VDBG) {
+                    Log.d(TAG, "Setting DNS forwarders: Network=" + network +
+                           ", dnsServers=" + Arrays.toString(dnsServers));
+                }
+                try {
+                    mNMService.setDnsForwarders(network, dnsServers);
+                } catch (Exception e) {
+                    // TODO: Investigate how this can fail and what exactly
+                    // happens if/when such failures occur.
+                    Log.e(TAG, "Setting DNS forwarders failed!");
+                    transitionTo(mSetDnsForwardersErrorState);
+                }
+            }
+
             protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
                 if (DBG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
-                mUpstreamIfaceName = ifaceName;
+                mCurrentUpstreamIface = ifaceName;
                 for (TetherInterfaceSM sm : mNotifyList) {
                     sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
                             ifaceName);
@@ -1772,20 +1883,23 @@
         }
 
         class TetherModeAliveState extends TetherMasterUtilState {
-            boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+            boolean mTryCell = true;
             @Override
             public void enter() {
+                // TODO: examine if we should check the return value.
                 turnOnMasterTetherSettings(); // may transition us out
                 startListeningForSimChanges();
+                mUpstreamNetworkMonitor.start();
 
-                mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass
-                                                        // or crazy tests cases will fail
+                mTryCell = true;  // better try something first pass or crazy tests cases will fail
                 chooseUpstreamType(mTryCell);
                 mTryCell = !mTryCell;
             }
             @Override
             public void exit() {
+                // TODO: examine if we should check the return value.
                 turnOffUpstreamMobileConnection();
+                mUpstreamNetworkMonitor.stop();
                 stopListeningForSimChanges();
                 notifyTetheredOfNewUpstreamIface(null);
             }
@@ -1799,7 +1913,7 @@
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
                         mNotifyList.add(who);
                         who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
-                                mUpstreamIfaceName);
+                                mCurrentUpstreamIface);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
                         who = (TetherInterfaceSM)message.obj;
@@ -1823,7 +1937,7 @@
                         break;
                     case CMD_UPSTREAM_CHANGED:
                         // need to try DUN immediately if Wifi goes down
-                        mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+                        mTryCell = true;
                         chooseUpstreamType(mTryCell);
                         mTryCell = !mTryCell;
                         break;
@@ -1831,6 +1945,24 @@
                         chooseUpstreamType(mTryCell);
                         mTryCell = !mTryCell;
                         break;
+                    case EVENT_UPSTREAM_LINKPROPERTIES_CHANGED:
+                        NetworkState state = (NetworkState) message.obj;
+                        if (mUpstreamNetworkMonitor.processLinkPropertiesChanged(state)) {
+                            setDnsForwarders(state.network, state.linkProperties);
+                        } else if (mCurrentUpstreamIface == null) {
+                            // If we have no upstream interface, try to run through upstream
+                            // selection again.  If, for example, IPv4 connectivity has shown up
+                            // after IPv6 (e.g., 464xlat became available) we want the chance to
+                            // notice and act accordingly.
+                            chooseUpstreamType(false);
+                        }
+                        break;
+                    case EVENT_UPSTREAM_LOST:
+                        // TODO: Re-evaluate possible upstreams. Currently upstream reevaluation
+                        // is triggered via received CONNECTIVITY_ACTION broadcasts that result
+                        // in being passed a TetherMasterSM.CMD_UPSTREAM_CHANGED.
+                        mUpstreamNetworkMonitor.processNetworkLost((Network) message.obj);
+                        break;
                     default:
                         retValue = false;
                         break;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 3c04b78..e73beaa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -948,7 +948,7 @@
     // Must be called on handler.
     private void showMissingKeyboardLayoutNotification(InputDevice device) {
         if (!mKeyboardLayoutNotificationShown) {
-            final Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
+            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
             if (device != null) {
                 intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
             }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c303ceb..7c71fbc 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -335,40 +335,40 @@
             verifyCallingPackage(callingPackage);
             ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
 
-            if (!mShortcutServiceInternal.hasShortcutHostPermission(callingPackage,
-                    user.getIdentifier())) {
+            if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
+                    callingPackage)) {
                 throw new SecurityException("Caller can't access shortcut information");
             }
         }
 
         @Override
         public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, ComponentName componentName, int flags, UserHandle user)
-                throws RemoteException {
+                String packageName, ComponentName componentName, int flags, UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
 
             return new ParceledListSlice<>(
-                    mShortcutServiceInternal.getShortcuts(callingPackage, changedSince, packageName,
-                    componentName, flags, user.getIdentifier()));
+                    mShortcutServiceInternal.getShortcuts(getCallingUserId(),
+                            callingPackage, changedSince, packageName,
+                            componentName, flags, user.getIdentifier()));
         }
 
         @Override
         public ParceledListSlice getShortcutInfo(String callingPackage, String packageName,
-                List<String> ids, UserHandle user) throws RemoteException {
+                List<String> ids, UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
 
             return new ParceledListSlice<>(
-                    mShortcutServiceInternal.getShortcutInfo(callingPackage, packageName,
-                    ids, user.getIdentifier()));
+                    mShortcutServiceInternal.getShortcutInfo(getCallingUserId(),
+                            callingPackage, packageName, ids, user.getIdentifier()));
         }
 
         @Override
         public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
-                UserHandle user) throws RemoteException {
+                UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
 
-            mShortcutServiceInternal.pinShortcuts(callingPackage, packageName,
-                    ids, user.getIdentifier());
+            mShortcutServiceInternal.pinShortcuts(getCallingUserId(),
+                    callingPackage, packageName, ids, user.getIdentifier());
         }
 
         @Override
@@ -376,8 +376,8 @@
                 UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
 
-            return mShortcutServiceInternal.getShortcutIconResId(callingPackage, shortcut,
-                    user.getIdentifier());
+            return mShortcutServiceInternal.getShortcutIconResId(getCallingUserId(),
+                    callingPackage, shortcut, user.getIdentifier());
         }
 
         @Override
@@ -385,25 +385,31 @@
                 UserHandle user) {
             ensureShortcutPermission(callingPackage, user);
 
-            return mShortcutServiceInternal.getShortcutIconFd(callingPackage, shortcut,
-                    user.getIdentifier());
+            return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(),
+                    callingPackage, shortcut, user.getIdentifier());
         }
 
         @Override
-        public boolean hasShortcutHostPermission(String callingPackage) throws RemoteException {
+        public boolean hasShortcutHostPermission(String callingPackage) {
             verifyCallingPackage(callingPackage);
-            return mShortcutServiceInternal.hasShortcutHostPermission(callingPackage,
-                    getCallingUserId());
+            return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
+                    callingPackage);
         }
 
         @Override
         public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
-                Rect sourceBounds, Bundle startActivityOptions, UserHandle user)
-                throws RemoteException {
-            ensureShortcutPermission(callingPackage, user);
+                Rect sourceBounds, Bundle startActivityOptions, UserHandle user) {
+            verifyCallingPackage(callingPackage);
+            ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user);
 
-            final Intent intent = mShortcutServiceInternal.createShortcutIntent(callingPackage,
-                    packageName, shortcutId, user.getIdentifier());
+            // Even without the permission, pinned shortcuts are always launchable.
+            if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
+                    callingPackage, packageName, shortcutId, user.getIdentifier())) {
+                ensureShortcutPermission(callingPackage, user);
+            }
+
+            final Intent intent = mShortcutServiceInternal.createShortcutIntent(getCallingUserId(),
+                    callingPackage, packageName, shortcutId, user.getIdentifier());
             if (intent == null) {
                 return false;
             }
@@ -713,9 +719,11 @@
                     BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
                     if (!isEnabledProfileOf(user, cookie.user, "onShortcutChanged")) continue;
 
+                    final int launcherUserId = cookie.user.getIdentifier();
+
                     // Make sure the caller has the permission.
-                    if (!mShortcutServiceInternal.hasShortcutHostPermission(cookie.packageName,
-                            cookie.user.getIdentifier())) {
+                    if (!mShortcutServiceInternal.hasShortcutHostPermission(
+                            launcherUserId, cookie.packageName)) {
                         continue;
                     }
                     // Each launcher has a different set of pinned shortcuts, so we need to do a
@@ -723,8 +731,9 @@
                     // (As of now, only one launcher has the permission at a time, so it's bit
                     // moot, but we may change the permission model eventually.)
                     final List<ShortcutInfo> list =
-                            mShortcutServiceInternal.getShortcuts(cookie.packageName,
-                            /* changedSince= */ 0, packageName, /* component= */ null,
+                            mShortcutServiceInternal.getShortcuts(launcherUserId,
+                                    cookie.packageName,
+                                    /* changedSince= */ 0, packageName, /* component= */ null,
                                     ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
                                     | ShortcutQuery.FLAG_GET_PINNED
                                     | ShortcutQuery.FLAG_GET_DYNAMIC
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d2fd762..926ff37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -106,6 +106,7 @@
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.IDevicePolicyManager;
+import android.app.admin.SecurityLog;
 import android.app.backup.IBackupManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -459,6 +460,8 @@
 
     final PackageHandler mHandler;
 
+    private final ProcessLoggingHandler mProcessLoggingHandler;
+
     /**
      * Messages for {@link #mHandler} that need to wait for system ready before
      * being dispatched.
@@ -1712,6 +1715,8 @@
 
             // Send installed broadcasts if the install/update is not ephemeral
             if (!isEphemeral(res.pkg)) {
+                mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);
+
                 // Send added for users that see the package for the first time
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                         extras, 0 /*flags*/, null /*targetPackage*/,
@@ -2096,6 +2101,7 @@
                     Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
             mHandlerThread.start();
             mHandler = new PackageHandler(mHandlerThread.getLooper());
+            mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
 
             File dataDir = Environment.getDataDirectory();
@@ -6920,8 +6926,8 @@
     }
 
     @Override
-    public void extractPackagesIfNeeded() {
-        enforceSystemOrRoot("Only the system can request package extraction");
+    public void updatePackagesIfNeeded() {
+        enforceSystemOrRoot("Only the system can request package update");
 
         // We need to re-extract after an OTA.
         boolean causeUpgrade = isUpgrade();
@@ -6952,23 +6958,15 @@
                 Log.i(TAG, "Extracting app " + curr + " of " + total + ": " + pkg.packageName);
             }
 
-            if (!isFirstBoot()) {
-                try {
-                    ActivityManagerNative.getDefault().showBootMessage(
-                            mContext.getResources().getString(R.string.android_upgrading_apk,
-                                    curr, total), true);
-                } catch (RemoteException e) {
-                }
-            }
-
             if (PackageDexOptimizer.canOptimizePackage(pkg)) {
                 // If the cache was pruned, any compiled odex files will likely be out of date
                 // and would have to be patched (would be SELF_PATCHOAT, which is deprecated).
                 // Instead, force the extraction in this case.
-                performDexOpt(pkg.packageName, null /* instructionSet */,
-                         false /* checkProfiles */,
-                         causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
-                         causePrunedCache);
+                performDexOpt(pkg.packageName,
+                        null /* instructionSet */,
+                        false /* checkProfiles */,
+                        causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+                        false /* force */);
             }
         }
     }
@@ -7241,9 +7239,9 @@
     private void deleteProfilesLI(PackageParser.Package pkg, boolean destroy) {
         try {
             if (destroy) {
-                mInstaller.clearAppProfiles(pkg.packageName);
-            } else {
                 mInstaller.destroyAppProfiles(pkg.packageName);
+            } else {
+                mInstaller.clearAppProfiles(pkg.packageName);
             }
         } catch (InstallerException ex) {
             Log.e(TAG, "Could not delete profiles for package " + pkg.packageName);
@@ -14515,7 +14513,6 @@
             if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
             res = deletePackageLI(packageName, removeForUser, true, allUsers,
                     flags | REMOVE_CHATTY, info, true, null);
-            deleteProfilesLI(packageName, /*destroy*/ true);
             synchronized (mPackages) {
                 if (res) {
                     mEphemeralApplicationRegistry.onPackageUninstalledLPw(uninstalledPs.pkg);
@@ -19458,4 +19455,26 @@
             return new ArrayList<>(mPackages.values());
         }
     }
+
+    /**
+     * Logs process start information (including base APK hash) to the security log.
+     * @hide
+     */
+    public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
+            String apkFile, int pid) {
+        if (!SecurityLog.isLoggingEnabled()) {
+            return;
+        }
+        Bundle data = new Bundle();
+        data.putLong("startTimestamp", System.currentTimeMillis());
+        data.putString("processName", processName);
+        data.putInt("uid", uid);
+        data.putString("seinfo", seinfo);
+        data.putString("apkFile", apkFile);
+        data.putInt("pid", pid);
+        Message msg = mProcessLoggingHandler.obtainMessage(
+                ProcessLoggingHandler.LOG_APP_PROCESS_START_MSG);
+        msg.setData(data);
+        mProcessLoggingHandler.sendMessage(msg);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
new file mode 100644
index 0000000..c47dda4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
@@ -0,0 +1,112 @@
+/*
+ * 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.pm;
+
+import android.app.admin.SecurityLog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import android.util.Slog;
+
+public final class ProcessLoggingHandler extends Handler {
+
+    private static final String TAG = "ProcessLoggingHandler";
+    static final int LOG_APP_PROCESS_START_MSG = 1;
+    static final int INVALIDATE_BASE_APK_HASH_MSG = 2;
+
+    private final HashMap<String, String> mProcessLoggingBaseApkHashes = new HashMap();
+
+    ProcessLoggingHandler() {
+        super(BackgroundThread.getHandler().getLooper());
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case LOG_APP_PROCESS_START_MSG: {
+                Bundle bundle = msg.getData();
+                String processName = bundle.getString("processName");
+                int uid = bundle.getInt("uid");
+                String seinfo = bundle.getString("seinfo");
+                String apkFile = bundle.getString("apkFile");
+                int pid = bundle.getInt("pid");
+                long startTimestamp = bundle.getLong("startTimestamp");
+                String apkHash = computeStringHashOfApk(apkFile);
+                SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, processName,
+                        startTimestamp, uid, pid, seinfo, apkHash);
+                break;
+            }
+            case INVALIDATE_BASE_APK_HASH_MSG: {
+                Bundle bundle = msg.getData();
+                mProcessLoggingBaseApkHashes.remove(bundle.getString("apkFile"));
+                break;
+            }
+        }
+    }
+
+    void invalidateProcessLoggingBaseApkHash(String apkPath) {
+        Bundle data = new Bundle();
+        data.putString("apkFile", apkPath);
+        Message msg = obtainMessage(INVALIDATE_BASE_APK_HASH_MSG);
+        msg.setData(data);
+        sendMessage(msg);
+    }
+
+    private String computeStringHashOfApk(String apkFile) {
+        if (apkFile == null) {
+            return "No APK";
+        }
+        String apkHash = mProcessLoggingBaseApkHashes.get(apkFile);
+        if (apkHash == null) {
+            try {
+                byte[] hash = computeHashOfApkFile(apkFile);
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < hash.length; i++) {
+                    sb.append(String.format("%02x", hash[i]));
+                }
+                apkHash = sb.toString();
+                mProcessLoggingBaseApkHashes.put(apkFile, apkHash);
+            } catch (IOException | NoSuchAlgorithmException e) {
+                Slog.w(TAG, "computeStringHashOfApk() failed", e);
+            }
+        }
+        return apkHash != null ? apkHash : "Failed to count APK hash";
+    }
+
+    private byte[] computeHashOfApkFile(String packageArchiveLocation)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
+        byte[] buffer = new byte[65536];
+        int size;
+        while ((size = input.read(buffer)) > 0) {
+            md.update(buffer, 0, size);
+        }
+        input.close();
+        return md.digest();
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 740a8f7..7699f30 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -32,7 +32,7 @@
 /**
  * Launcher information used by {@link ShortcutService}.
  */
-class ShortcutLauncher implements ShortcutPackageItem {
+class ShortcutLauncher extends ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
 
     static final String TAG_ROOT = "launcher-pins";
@@ -40,37 +40,38 @@
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_PIN = "pin";
 
+    private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_PACKAGE_NAME = "package-name";
 
-    @UserIdInt
-    private final int mUserId;
-
-    @NonNull
-    private final String mPackageName;
+    private final int mOwnerUserId;
 
     /**
      * Package name -> IDs.
      */
     final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
 
-    ShortcutLauncher(@UserIdInt int userId, @NonNull String packageName) {
-        mUserId = userId;
-        mPackageName = packageName;
+    public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+            @UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
+        super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
+        mOwnerUserId = ownerUserId;
     }
 
-    @UserIdInt
-    public int getUserId() {
-        return mUserId;
+    public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
+            @UserIdInt int launcherUserId) {
+        this(launcherUserId, packageName, launcherUserId, null);
     }
 
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
+    @Override
+    public int getOwnerUserId() {
+        return mOwnerUserId;
     }
 
-    public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName,
-            @NonNull List<String> ids) {
+    public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
+            @NonNull String packageName, @NonNull List<String> ids) {
+        final ShortcutPackage packageShortcuts =
+                s.getPackageShortcutsLocked(packageName, packageUserId);
+
         final int idSize = ids.size();
         if (idSize == 0) {
             mPinnedShortcuts.remove(packageName);
@@ -80,8 +81,6 @@
             // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
             // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
 
-            final ShortcutPackage packageShortcuts =
-                    s.getPackageShortcutsLocked(packageName, mUserId);
             final ArraySet<String> newSet = new ArraySet<>();
 
             for (int i = 0; i < idSize; i++) {
@@ -96,7 +95,7 @@
             }
             mPinnedShortcuts.put(packageName, newSet);
         }
-        s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s);
+        packageShortcuts.refreshPinnedFlags(s);
     }
 
     /**
@@ -113,15 +112,18 @@
     /**
      * Persist.
      */
-    public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
+    @Override
+    public void saveToXml(XmlSerializer out, boolean forBackup)
+            throws IOException {
         final int size = mPinnedShortcuts.size();
         if (size == 0) {
             return; // Nothing to write.
         }
 
         out.startTag(null, TAG_ROOT);
-        ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
-                mPackageName);
+        ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
+        ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
+        getPackageInfo().saveToXml(out);
 
         for (int i = 0; i < size; i++) {
             out.startTag(null, TAG_PACKAGE);
@@ -142,12 +144,20 @@
     /**
      * Load.
      */
-    public static ShortcutLauncher loadFromXml(XmlPullParser parser, int userId)
-            throws IOException, XmlPullParserException {
+    public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId,
+            boolean fromBackup) throws IOException, XmlPullParserException {
         final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
                 ATTR_PACKAGE_NAME);
 
-        final ShortcutLauncher ret = new ShortcutLauncher(userId, launcherPackageName);
+        // If restoring, just use the real user ID.
+        final int launcherUserId =
+                fromBackup ? ownerUserId
+                : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId);
+
+        final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
+                launcherUserId);
+
+        ShortcutPackageInfo spi = null;
 
         ArraySet<String> ids = null;
         final int outerDepth = parser.getDepth();
@@ -159,21 +169,33 @@
             }
             final int depth = parser.getDepth();
             final String tag = parser.getName();
-            switch (tag) {
-                case TAG_PACKAGE: {
-                    final String packageName = ShortcutService.parseStringAttribute(parser,
-                            ATTR_PACKAGE_NAME);
-                    ids = new ArraySet<>();
-                    ret.mPinnedShortcuts.put(packageName, ids);
-                    continue;
-                }
-                case TAG_PIN: {
-                    ids.add(ShortcutService.parseStringAttribute(parser,
-                            ATTR_VALUE));
-                    continue;
+            if (depth == outerDepth + 1) {
+                switch (tag) {
+                    case ShortcutPackageInfo.TAG_ROOT:
+                        spi = ShortcutPackageInfo.loadFromXml(parser);
+                        continue;
+                    case TAG_PACKAGE: {
+                        final String packageName = ShortcutService.parseStringAttribute(parser,
+                                ATTR_PACKAGE_NAME);
+                        ids = new ArraySet<>();
+                        ret.mPinnedShortcuts.put(packageName, ids);
+                        continue;
+                    }
                 }
             }
-            throw ShortcutService.throwForInvalidTag(depth, tag);
+            if (depth == outerDepth + 2) {
+                switch (tag) {
+                    case TAG_PIN: {
+                        ids.add(ShortcutService.parseStringAttribute(parser,
+                                ATTR_VALUE));
+                        continue;
+                    }
+                }
+            }
+            ShortcutService.warnForInvalidTag(depth, tag);
+        }
+        if (spi != null) {
+            ret.replacePackageInfo(spi);
         }
         return ret;
     }
@@ -183,7 +205,12 @@
 
         pw.print(prefix);
         pw.print("Launcher: ");
-        pw.print(mPackageName);
+        pw.print(getPackageName());
+        pw.print("  Package user: ");
+        pw.print(getPackageUserId());
+        pw.println();
+
+        getPackageInfo().dump(s, pw, prefix + "  ");
         pw.println();
 
         final int size = mPinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 359ea1c..5916202 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -17,7 +17,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
@@ -41,7 +40,7 @@
 /**
  * Package information used by {@link ShortcutService}.
  */
-class ShortcutPackage implements ShortcutPackageItem {
+class ShortcutPackage extends ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
 
     static final String TAG_ROOT = "package";
@@ -56,6 +55,7 @@
     private static final String ATTR_ID = "id";
     private static final String ATTR_ACTIVITY = "activity";
     private static final String ATTR_TITLE = "title";
+    private static final String ATTR_TEXT = "text";
     private static final String ATTR_INTENT = "intent";
     private static final String ATTR_WEIGHT = "weight";
     private static final String ATTR_TIMESTAMP = "timestamp";
@@ -63,12 +63,6 @@
     private static final String ATTR_ICON_RES = "icon-res";
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
 
-    @UserIdInt
-    private final int mUserId;
-
-    @NonNull
-    private final String mPackageName;
-
     /**
      * All the shortcuts from the package, keyed on IDs.
      */
@@ -89,21 +83,23 @@
      */
     private long mLastResetTime;
 
-    ShortcutPackage(int userId, String packageName) {
-        mUserId = userId;
-        mPackageName = packageName;
+    public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
+        super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
     }
 
-    @UserIdInt
-    public int getUserId() {
-        return mUserId;
+    public ShortcutPackage(int packageUserId, String packageName) {
+        this(packageUserId, packageName, null);
     }
 
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
+    @Override
+    public int getOwnerUserId() {
+        // For packages, always owner user == package user.
+        return getPackageUserId();
     }
 
+    /**
+     * Note this does *not* provide a correct view to the calling launcher.
+     */
     @Nullable
     public ShortcutInfo findShortcutById(String id) {
         return mShortcuts.get(id);
@@ -113,7 +109,7 @@
             @NonNull String id) {
         final ShortcutInfo shortcut = mShortcuts.remove(id);
         if (shortcut != null) {
-            s.removeIcon(mUserId, shortcut);
+            s.removeIcon(getPackageUserId(), shortcut);
             shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
         }
         return shortcut;
@@ -121,7 +117,7 @@
 
     void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
         deleteShortcut(s, newShortcut.getId());
-        s.saveIconAndFixUpShortcut(mUserId, newShortcut);
+        s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
         mShortcuts.put(newShortcut.getId(), newShortcut);
     }
 
@@ -229,12 +225,13 @@
         }
 
         // Then, for the pinned set for each launcher, set the pin flag one by one.
-        final ArrayMap<String, ShortcutLauncher> launchers =
-                s.getUserShortcutsLocked(mUserId).getLaunchers();
+        final ArrayMap<ShortcutUser.PackageWithUser, ShortcutLauncher> launchers =
+                s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
 
         for (int l = launchers.size() - 1; l >= 0; l--) {
             final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
-            final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName);
+            final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
+                    getPackageName());
 
             if (pinned == null || pinned.size() == 0) {
                 continue;
@@ -301,13 +298,25 @@
      * Find all shortcuts that match {@code query}.
      */
     public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+            @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
+        findAll(s, result, query, cloneFlag, null, 0);
+    }
+
+    /**
+     * Find all shortcuts that match {@code query}.
+     *
+     * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned
+     * by the calling launcher will not be included in the result, and also "isPinned" will be
+     * adjusted for the caller too.
+     */
+    public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
-            @Nullable String callingLauncher) {
+            @Nullable String callingLauncher, int launcherUserId) {
 
         // Set of pinned shortcuts by the calling launcher.
         final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
-                : s.getLauncherShortcuts(callingLauncher, mUserId)
-                    .getPinnedShortcutIds(mPackageName);
+                : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
+                    .getPinnedShortcutIds(getPackageName());
 
         for (int i = 0; i < mShortcuts.size(); i++) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -347,7 +356,7 @@
 
         pw.print(prefix);
         pw.print("Package: ");
-        pw.print(mPackageName);
+        pw.print(getPackageName());
         pw.println();
 
         pw.print(prefix);
@@ -365,6 +374,9 @@
         pw.print(s.formatTime(mLastResetTime));
         pw.println();
 
+        getPackageInfo().dump(s, pw, prefix + "  ");
+        pw.println();
+
         pw.println("      Shortcuts:");
         long totalBitmapSize = 0;
         final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
@@ -391,6 +403,7 @@
         pw.println(")");
     }
 
+    @Override
     public void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException {
         final int size = mShortcuts.size();
@@ -401,10 +414,11 @@
 
         out.startTag(null, TAG_ROOT);
 
-        ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
+        ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
         ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+        getPackageInfo().saveToXml(out);
 
         for (int j = 0; j < size; j++) {
             saveShortcut(out, mShortcuts.valueAt(j), forBackup);
@@ -426,6 +440,7 @@
         ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
         // writeAttr(out, "icon", si.getIcon());  // We don't save it.
         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
+        ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
         ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
         ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
@@ -449,13 +464,14 @@
         out.endTag(null, TAG_SHORTCUT);
     }
 
-    public static ShortcutPackage loadFromXml(XmlPullParser parser, int userId)
+    public static ShortcutPackage loadFromXml(ShortcutService s, XmlPullParser parser,
+            int ownerUserId, boolean fromBackup)
             throws IOException, XmlPullParserException {
 
         final String packageName = ShortcutService.parseStringAttribute(parser,
                 ATTR_NAME);
 
-        final ShortcutPackage ret = new ShortcutPackage(userId, packageName);
+        final ShortcutPackage ret = new ShortcutPackage(ownerUserId, packageName);
 
         ret.mDynamicShortcutCount =
                 ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
@@ -463,6 +479,7 @@
                 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
         ret.mLastResetTime =
                 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+        ShortcutPackageInfo spi = null;
 
         final int outerDepth = parser.getDepth();
         int type;
@@ -473,15 +490,23 @@
             }
             final int depth = parser.getDepth();
             final String tag = parser.getName();
-            switch (tag) {
-                case TAG_SHORTCUT:
-                    final ShortcutInfo si = parseShortcut(parser, packageName);
+            if (depth == outerDepth + 1) {
+                switch (tag) {
+                    case ShortcutPackageInfo.TAG_ROOT:
+                        spi = ShortcutPackageInfo.loadFromXml(parser);
+                        continue;
+                    case TAG_SHORTCUT:
+                        final ShortcutInfo si = parseShortcut(parser, packageName);
 
-                    // Don't use addShortcut(), we don't need to save the icon.
-                    ret.mShortcuts.put(si.getId(), si);
-                    continue;
+                        // Don't use addShortcut(), we don't need to save the icon.
+                        ret.mShortcuts.put(si.getId(), si);
+                        continue;
+                }
             }
-            throw ShortcutService.throwForInvalidTag(depth, tag);
+            ShortcutService.warnForInvalidTag(depth, tag);
+        }
+        if (spi != null) {
+            ret.replacePackageInfo(spi);
         }
         return ret;
     }
@@ -492,6 +517,7 @@
         ComponentName activityComponent;
         // Icon icon;
         String title;
+        String text;
         Intent intent;
         PersistableBundle intentPersistableExtras = null;
         int weight;
@@ -505,10 +531,10 @@
         activityComponent = ShortcutService.parseComponentNameAttribute(parser,
                 ATTR_ACTIVITY);
         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
+        text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
         intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
         weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
-        lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
-                ATTR_TIMESTAMP);
+        lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
         flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
         iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
         bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
@@ -537,7 +563,7 @@
             throw ShortcutService.throwForInvalidTag(depth, tag);
         }
         return new ShortcutInfo(
-                id, packageName, activityComponent, /* icon =*/ null, title, intent,
+                id, packageName, activityComponent, /* icon =*/ null, title, text, intent,
                 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
                 iconRes, bitmapPath);
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 98dc608..5f706b8 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -15,14 +15,11 @@
  */
 package com.android.server.pm;
 
-import android.annotation.NonNull;
 import android.annotation.UserIdInt;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.Signature;
 import android.util.Slog;
 
-import com.android.internal.util.Preconditions;
+import com.android.server.backup.BackupUtils;
 
 import libcore.io.Base64;
 import libcore.util.HexEncoding;
@@ -33,34 +30,21 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
- *
- * TODO: The methods about signature hashes are copied from BackupManagerService, which is not
- * visible here.  Unify the code.
  */
-class ShortcutPackageInfo implements ShortcutPackageItem {
+class ShortcutPackageInfo {
     private static final String TAG = ShortcutService.TAG;
 
     static final String TAG_ROOT = "package-info";
-    private static final String ATTR_NAME = "name";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_SHADOW = "shadow";
 
     private static final String TAG_SIGNATURE = "signature";
     private static final String ATTR_SIGNATURE_HASH = "hash";
 
-    public interface ShortcutPackageInfoHolder {
-        ShortcutPackageInfo getShortcutPackageInfo();
-    }
-
-    private final String mPackageName;
-
     /**
      * When true, this package information was restored from the previous device, and the app hasn't
      * been installed yet.
@@ -69,17 +53,14 @@
     private int mVersionCode;
     private ArrayList<byte[]> mSigHashes;
 
-    private ShortcutPackageInfo(String packageName, int versionCode, ArrayList<byte[]> sigHashes,
-            boolean isShadow) {
+    private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
         mVersionCode = versionCode;
         mIsShadow = isShadow;
         mSigHashes = sigHashes;
-        mPackageName = Preconditions.checkNotNull(packageName);
     }
 
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
+    public static ShortcutPackageInfo newEmpty() {
+        return new ShortcutPackageInfo(0, new ArrayList<>(0), /* isShadow */ false);
     }
 
     public boolean isShadow() {
@@ -98,92 +79,13 @@
         return mVersionCode;
     }
 
-    private static byte[] hashSignature(Signature sig) {
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-256");
-            digest.update(sig.toByteArray());
-            return digest.digest();
-        } catch (NoSuchAlgorithmException e) {
-            Slog.w(TAG, "No SHA-256 algorithm found!");
-        }
-        return null;
-    }
-
-    private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
-        if (sigs == null) {
-            return null;
-        }
-
-        ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
-        for (Signature s : sigs) {
-            hashes.add(hashSignature(s));
-        }
-        return hashes;
-    }
-
-    private static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
-        if (target == null) {
-            return false;
-        }
-
-        // If the target resides on the system partition, we allow it to restore
-        // data from the like-named package in a restore set even if the signatures
-        // do not match.  (Unlike general applications, those flashed to the system
-        // partition will be signed with the device's platform certificate, so on
-        // different phones the same system app will have different signatures.)
-        if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-            return true;
-        }
-
-        // Allow unsigned apps, but not signed on one device and unsigned on the other
-        // !!! TODO: is this the right policy?
-        Signature[] deviceSigs = target.signatures;
-        if ((storedSigHashes == null || storedSigHashes.size() == 0)
-                && (deviceSigs == null || deviceSigs.length == 0)) {
-            return true;
-        }
-        if (storedSigHashes == null || deviceSigs == null) {
-            return false;
-        }
-
-        // !!! TODO: this demands that every stored signature match one
-        // that is present on device, and does not demand the converse.
-        // Is this this right policy?
-        final int nStored = storedSigHashes.size();
-        final int nDevice = deviceSigs.length;
-
-        // hash each on-device signature
-        ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
-        for (int i = 0; i < nDevice; i++) {
-            deviceHashes.add(hashSignature(deviceSigs[i]));
-        }
-
-        // now ensure that each stored sig (hash) matches an on-device sig (hash)
-        for (int n = 0; n < nStored; n++) {
-            boolean match = false;
-            final byte[] storedHash = storedSigHashes.get(n);
-            for (int i = 0; i < nDevice; i++) {
-                if (Arrays.equals(storedHash, deviceHashes.get(i))) {
-                    match = true;
-                    break;
-                }
-            }
-            // match is false when no on-device sig matched one of the stored ones
-            if (!match) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
     public boolean canRestoreTo(PackageInfo target) {
         if (target.versionCode < mVersionCode) {
             Slog.w(TAG, String.format("Package current version %d < backed up version %d",
                     target.versionCode, mVersionCode));
             return false;
         }
-        if (!signaturesMatch(mSigHashes, target)) {
+        if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
             Slog.w(TAG, "Package signature mismtach");
             return false;
         }
@@ -191,36 +93,34 @@
     }
 
     public static ShortcutPackageInfo generateForInstalledPackage(
-            ShortcutService s, String packageName, @UserIdInt int userId) {
-        final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, userId);
+            ShortcutService s, String packageName, @UserIdInt int packageUserId) {
+        final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
         if (pi.signatures == null || pi.signatures.length == 0) {
             Slog.e(TAG, "Can't get signatures: package=" + packageName);
             return null;
         }
-        final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, pi.versionCode,
-                hashSignatureArray(pi.signatures), /* shadow=*/ false);
+        final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode,
+                BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
 
         return ret;
     }
 
-    public void refreshAndSave(ShortcutService s, @UserIdInt int userId) {
-        final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, userId);
+    public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
+        // Note use mUserId here, rather than userId.
+        final PackageInfo pi = s.getPackageInfoWithSignatures(
+                pkg.getPackageName(), pkg.getPackageUserId());
         if (pi == null) {
-            Slog.w(TAG, "Package not found: " + mPackageName);
+            Slog.w(TAG, "Package not found: " + pkg.getPackageName());
             return;
         }
         mVersionCode = pi.versionCode;
-        mSigHashes = hashSignatureArray(pi.signatures);
-
-        s.scheduleSaveUser(userId);
+        mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
     }
 
-    public void saveToXml(XmlSerializer out, boolean forBackup)
-            throws IOException, XmlPullParserException {
+    public void saveToXml(XmlSerializer out) throws IOException {
 
         out.startTag(null, TAG_ROOT);
 
-        ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
 
@@ -235,7 +135,6 @@
     public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
             throws IOException, XmlPullParserException {
 
-        final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME);
         final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
         final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
 
@@ -251,26 +150,27 @@
             }
             final int depth = parser.getDepth();
             final String tag = parser.getName();
-            switch (tag) {
-                case TAG_SIGNATURE: {
-                    final String hash = ShortcutService.parseStringAttribute(
-                            parser, ATTR_SIGNATURE_HASH);
-                    hashes.add(Base64.decode(hash.getBytes()));
-                    continue;
+
+            if (depth == outerDepth + 1) {
+                switch (tag) {
+                    case TAG_SIGNATURE: {
+                        final String hash = ShortcutService.parseStringAttribute(
+                                parser, ATTR_SIGNATURE_HASH);
+                        hashes.add(Base64.decode(hash.getBytes()));
+                        continue;
+                    }
                 }
             }
-            throw ShortcutService.throwForInvalidTag(depth, tag);
+            ShortcutService.warnForInvalidTag(depth, tag);
         }
-        return new ShortcutPackageInfo(packageName, versionCode, hashes, shadow);
+        return new ShortcutPackageInfo(versionCode, hashes, shadow);
     }
 
     public void dump(ShortcutService s, PrintWriter pw, String prefix) {
         pw.println();
 
         pw.print(prefix);
-        pw.print("PackageInfo: ");
-        pw.print(mPackageName);
-        pw.println();
+        pw.println("PackageInfo:");
 
         pw.print(prefix);
         pw.print("  IsShadow: ");
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 526c84d..de2709d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -17,15 +17,69 @@
 
 import android.annotation.NonNull;
 
+import com.android.internal.util.Preconditions;
+
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 
-public interface ShortcutPackageItem {
-    @NonNull
-    String getPackageName();
+abstract class ShortcutPackageItem {
+    private final int mPackageUserId;
+    private final String mPackageName;
 
-    void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
+    private ShortcutPackageInfo mPackageInfo;
+
+    protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
+            @NonNull ShortcutPackageInfo packageInfo) {
+        mPackageUserId = packageUserId;
+        mPackageName = Preconditions.checkStringNotEmpty(packageName);
+        mPackageInfo = Preconditions.checkNotNull(packageInfo);
+    }
+
+    /**
+     * ID of the user who actually has this package running on.  For {@link ShortcutPackage},
+     * this is the same thing as {@link #getOwnerUserId}, but if it's a {@link ShortcutLauncher} and
+     * {@link #getOwnerUserId} is of a work profile, then this ID could be the user who owns the
+     * profile.
+     */
+    public int getPackageUserId() {
+        return mPackageUserId;
+    }
+
+    /**
+     * ID of the user who sees the shortcuts from this instance.
+     */
+    public abstract int getOwnerUserId();
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public ShortcutPackageInfo getPackageInfo() {
+        return mPackageInfo;
+    }
+
+    /**
+     * Should be only used when loading from a file.o
+     */
+    protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
+        mPackageInfo = Preconditions.checkNotNull(packageInfo);
+    }
+
+    public void refreshPackageInfoAndSave(ShortcutService s) {
+        mPackageInfo.refresh(s, this);
+        s.scheduleSaveUser(getOwnerUserId());
+    }
+
+    public void ensureNotShadowAndSave(ShortcutService s) {
+        if (mPackageInfo.isShadow()) {
+            mPackageInfo.setShadow(false);
+            s.scheduleSaveUser(getOwnerUserId());
+        }
+    }
+
+    public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException;
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 373e8cb..76a2dfa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -31,7 +31,6 @@
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
@@ -73,11 +72,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import libcore.io.IoUtils;
 
@@ -85,6 +84,10 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -92,6 +95,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
@@ -104,14 +108,7 @@
  *
  * - Default launcher check does take a few ms.  Worth caching.
  *
- * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.)
- *
- * - Extract the user/package/launcher classes to their own files.  Maybe rename so they all have
- *   the same "Shortcut" prefix.
- *
- * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res
- *   -> Need to scan all packages when a user starts too.
- *   -> Clear data -> remove all dynamic?  but not the pinned?
+ * - Clear data -> remove all dynamic?  but not the pinned?
  *
  * - Scan and remove orphan bitmaps (just in case).
  *
@@ -455,16 +452,24 @@
         return (int) parseLongAttribute(parser, attribute);
     }
 
+    static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
+        return (int) parseLongAttribute(parser, attribute, def);
+    }
+
     static long parseLongAttribute(XmlPullParser parser, String attribute) {
+        return parseLongAttribute(parser, attribute, 0);
+    }
+
+    static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
         final String value = parseStringAttribute(parser, attribute);
         if (TextUtils.isEmpty(value)) {
-            return 0;
+            return def;
         }
         try {
             return Long.parseLong(value);
         } catch (NumberFormatException e) {
             Slog.e(TAG, "Error parsing long " + value);
-            return 0;
+            return def;
         }
     }
 
@@ -630,31 +635,45 @@
         }
         path.mkdirs();
         final AtomicFile file = new AtomicFile(path);
-        FileOutputStream outs = null;
+        FileOutputStream os = null;
         try {
-            outs = file.startWrite();
+            os = file.startWrite();
 
-            // Write to XML
-            XmlSerializer out = new FastXmlSerializer();
-            out.setOutput(outs, StandardCharsets.UTF_8.name());
-            out.startDocument(null, true);
+            saveUserInternalLocked(userId, os, /* forBackup= */ false);
 
-            getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false);
-
-            out.endDocument();
-
-            // Close.
-            file.finishWrite(outs);
-        } catch (IOException|XmlPullParserException e) {
+            file.finishWrite(os);
+        } catch (XmlPullParserException|IOException e) {
             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
-            file.failWrite(outs);
+            file.failWrite(os);
         }
     }
 
+    private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
+            boolean forBackup) throws IOException, XmlPullParserException {
+
+        final BufferedOutputStream bos = new BufferedOutputStream(os);
+
+        // Write to XML
+        XmlSerializer out = new FastXmlSerializer();
+        out.setOutput(bos, StandardCharsets.UTF_8.name());
+        out.startDocument(null, true);
+
+        getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
+
+        out.endDocument();
+
+        bos.flush();
+        os.flush();
+    }
+
     static IOException throwForInvalidTag(int depth, String tag) throws IOException {
         throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
     }
 
+    static void warnForInvalidTag(int depth, String tag) throws IOException {
+        Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
+    }
+
     @Nullable
     private ShortcutUser loadUserLocked(@UserIdInt int userId) {
         final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
@@ -672,30 +691,8 @@
             }
             return null;
         }
-        ShortcutUser ret = null;
         try {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(in, StandardCharsets.UTF_8.name());
-
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-                final int depth = parser.getDepth();
-
-                final String tag = parser.getName();
-                if (DEBUG_LOAD) {
-                    Slog.d(TAG, String.format("depth=%d type=%d name=%s",
-                            depth, type, tag));
-                }
-                if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
-                    ret = ShortcutUser.loadFromXml(parser, userId);
-                    continue;
-                }
-                throwForInvalidTag(depth, tag);
-            }
-            return ret;
+            return loadUserInternal(userId, in, /* forBackup= */ false);
         } catch (IOException|XmlPullParserException e) {
             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
             return null;
@@ -704,6 +701,36 @@
         }
     }
 
+    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
+            boolean fromBackup) throws XmlPullParserException, IOException {
+
+        final BufferedInputStream bis = new BufferedInputStream(is);
+
+        ShortcutUser ret = null;
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(bis, StandardCharsets.UTF_8.name());
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final int depth = parser.getDepth();
+
+            final String tag = parser.getName();
+            if (DEBUG_LOAD) {
+                Slog.d(TAG, String.format("depth=%d type=%d name=%s",
+                        depth, type, tag));
+            }
+            if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
+                ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
+                continue;
+            }
+            throwForInvalidTag(depth, tag);
+        }
+        return ret;
+    }
+
     private void scheduleSaveBaseState() {
         scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
     }
@@ -825,8 +852,8 @@
     @GuardedBy("mLock")
     @NonNull
     ShortcutLauncher getLauncherShortcuts(
-            @NonNull String packageName, @UserIdInt int userId) {
-        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName);
+            @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
+        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
     }
 
     // === Caller validation ===
@@ -1039,6 +1066,10 @@
         Preconditions.checkState(isCallerShell(), "Caller must be shell");
     }
 
+    private void enforceSystem() {
+        Preconditions.checkState(isCallerSystem(), "Caller must be system");
+    }
+
     private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
         Preconditions.checkStringNotEmpty(packageName, "packageName");
 
@@ -1179,10 +1210,10 @@
         final int size = newShortcuts.size();
 
         synchronized (mLock) {
-            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureNotShadowAndSave(this);
+
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1216,10 +1247,10 @@
         final int size = newShortcuts.size();
 
         synchronized (mLock) {
-            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureNotShadowAndSave(this);
+
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1255,10 +1286,10 @@
         verifyCaller(packageName, userId);
 
         synchronized (mLock) {
-            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
-
             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
 
+            ps.ensureNotShadowAndSave(this);
+
             // Throttling.
             if (!ps.tryApiCall(this)) {
                 return false;
@@ -1324,8 +1355,7 @@
 
         final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
-        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags,
-                /* callingLauncher= */ null);
+        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
 
         return new ParceledListSlice<>(ret);
     }
@@ -1463,22 +1493,29 @@
     // === House keeping ===
 
     @VisibleForTesting
-    void cleanUpPackageLocked(String packageName, int userId) {
-        final boolean wasUserLoaded = isUserLoadedLocked(userId);
+    void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
 
-        final ShortcutUser mUser = getUserShortcutsLocked(userId);
+        // TODO Don't remove shadow packages' information.
+
+        final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
+
+        final ShortcutUser mUser = getUserShortcutsLocked(owningUserId);
         boolean doNotify = false;
 
         // First, remove the package from the package list (if the package is a publisher).
-        if (mUser.getPackages().remove(packageName) != null) {
-            doNotify = true;
+        if (packageUserId == owningUserId) {
+            if (mUser.getPackages().remove(packageName) != null) {
+                doNotify = true;
+            }
         }
+
         // Also remove from the launcher list (if the package is a launcher).
-        mUser.getLaunchers().remove(packageName);
+        mUser.removeLauncher(packageUserId, packageName);
 
         // Then remove pinned shortcuts from all launchers.
-        for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) {
-            mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName);
+        final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
+        for (int i = launchers.size() - 1; i >= 0; i--) {
+            launchers.valueAt(i).cleanUpPackage(packageName);
         }
         // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
         // step.  Remove them too.
@@ -1486,18 +1523,15 @@
             mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
         }
 
-        // Remove the package info too.
-        mUser.getPackageInfos().remove(packageName);
-
-        scheduleSaveUser(userId);
+        scheduleSaveUser(owningUserId);
 
         if (doNotify) {
-            notifyListeners(packageName, userId);
+            notifyListeners(packageName, owningUserId);
         }
 
         if (!wasUserLoaded) {
             // Note this will execute the scheduled save.
-            unloadUserLocked(userId);
+            unloadUserLocked(owningUserId);
         }
     }
 
@@ -1506,7 +1540,7 @@
      */
     private class LocalService extends ShortcutServiceInternal {
         @Override
-        public List<ShortcutInfo> getShortcuts(
+        public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable ComponentName componentName,
                 int queryFlags, int userId) {
@@ -1518,14 +1552,14 @@
 
             synchronized (mLock) {
                 if (packageName != null) {
-                    getShortcutsInnerLocked(
+                    getShortcutsInnerLocked(launcherUserId,
                             callingPackage, packageName, changedSince,
                             componentName, queryFlags, userId, ret, cloneFlag);
                 } else {
                     final ArrayMap<String, ShortcutPackage> packages =
                             getUserShortcutsLocked(userId).getPackages();
                     for (int i = packages.size() - 1; i >= 0; i--) {
-                        getShortcutsInnerLocked(
+                        getShortcutsInnerLocked(launcherUserId,
                                 callingPackage, packages.keyAt(i), changedSince,
                                 componentName, queryFlags, userId, ret, cloneFlag);
                     }
@@ -1534,7 +1568,7 @@
             return ret;
         }
 
-        private void getShortcutsInnerLocked(@NonNull String callingPackage,
+        private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
                 @Nullable String packageName,long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
@@ -1554,11 +1588,11 @@
                                 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
                                         && si.isPinned();
                         return matchDynamic || matchPinned;
-                    }, cloneFlag, callingPackage);
+                    }, cloneFlag, callingPackage, launcherUserId);
         }
 
         @Override
-        public List<ShortcutInfo> getShortcutInfo(
+        public List<ShortcutInfo> getShortcutInfo(int launcherUserId,
                 @NonNull String callingPackage,
                 @NonNull String packageName, @Nullable List<String> ids, int userId) {
             // Calling permission must be checked by LauncherAppsImpl.
@@ -1570,40 +1604,75 @@
                 getPackageShortcutsLocked(packageName, userId).findAll(
                         ShortcutService.this, ret,
                         (ShortcutInfo si) -> idSet.contains(si.getId()),
-                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage);
+                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage, launcherUserId);
             }
             return ret;
         }
 
         @Override
-        public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
+        public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
+                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+
+            synchronized (mLock) {
+                final ShortcutInfo si = getShortcutInfoLocked(
+                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                return si != null && si.isPinned();
+            }
+        }
+
+        public ShortcutInfo getShortcutInfoLocked(
+                int launcherUserId, @NonNull String callingPackage,
+                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+
+            final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
+            getPackageShortcutsLocked(packageName, userId).findAll(
+                    ShortcutService.this, list,
+                    (ShortcutInfo si) -> shortcutId.equals(si.getId()),
+                    /* clone flags=*/ 0, callingPackage, launcherUserId);
+            return list.size() == 0 ? null : list.get(0);
+        }
+
+        @Override
+        public void pinShortcuts(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull List<String> shortcutIds, int userId) {
             // Calling permission must be checked by LauncherAppsImpl.
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkNotNull(shortcutIds, "shortcutIds");
 
             synchronized (mLock) {
-                getUserShortcutsLocked(userId).ensurePackageInfo(
-                        ShortcutService.this, callingPackage, userId);
+                final ShortcutLauncher launcher =
+                        getLauncherShortcuts(callingPackage, userId, launcherUserId);
 
-                getLauncherShortcuts(callingPackage, userId).pinShortcuts(
-                        ShortcutService.this, packageName, shortcutIds);
+                launcher.ensureNotShadowAndSave(ShortcutService.this);
+
+                launcher.pinShortcuts(
+                        ShortcutService.this, userId, packageName, shortcutIds);
             }
             userPackageChanged(packageName, userId);
         }
 
         @Override
-        public Intent createShortcutIntent(@NonNull String callingPackage,
+        public Intent createShortcutIntent(int launcherUserId,
+                @NonNull String callingPackage,
                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
             // Calling permission must be checked by LauncherAppsImpl.
             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
 
             synchronized (mLock) {
-                final ShortcutInfo fullShortcut =
-                        getPackageShortcutsLocked(packageName, userId)
-                        .findShortcutById(shortcutId);
-                return fullShortcut == null ? null : fullShortcut.getIntent();
+                // Make sure the shortcut is actually visible to the launcher.
+                final ShortcutInfo si = getShortcutInfoLocked(
+                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                // "si == null" should suffice here, but check the flags too just to make sure.
+                if (si == null || !(si.isDynamic() || si.isPinned())) {
+                    return null;
+                }
+                return si.getIntent();
             }
         }
 
@@ -1615,7 +1684,8 @@
         }
 
         @Override
-        public int getShortcutIconResId(@NonNull String callingPackage,
+        public int getShortcutIconResId(int launcherUserId,
+                @NonNull String callingPackage,
                 @NonNull ShortcutInfo shortcut, int userId) {
             Preconditions.checkNotNull(shortcut, "shortcut");
 
@@ -1628,7 +1698,8 @@
         }
 
         @Override
-        public ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage,
+        public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
+                @NonNull String callingPackage,
                 @NonNull ShortcutInfo shortcutIn, int userId) {
             Preconditions.checkNotNull(shortcutIn, "shortcut");
 
@@ -1654,8 +1725,9 @@
         }
 
         @Override
-        public boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
-            return ShortcutService.this.hasShortcutHostPermission(callingPackage, userId);
+        public boolean hasShortcutHostPermission(int launcherUserId,
+                @NonNull String callingPackage) {
+            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
         }
     }
 
@@ -1684,28 +1756,29 @@
      * Called when a user is unlocked.  Check all known packages still exist, and otherwise
      * perform cleanup.
      */
-    private void cleanupGonePackages(@UserIdInt int userId) {
+    @VisibleForTesting
+    void cleanupGonePackages(@UserIdInt int userId) {
         if (DEBUG) {
             Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
         }
-        ArrayList<String> gonePackages = null;
+        final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
 
-        final ShortcutUser user = getUserShortcutsLocked(userId);
-        final ArrayMap<String, ShortcutPackageInfo> infos = user.getPackageInfos();
-        for (int i = infos.size() -1; i >= 0; i--) {
-            final ShortcutPackageInfo info = infos.valueAt(i);
-            if (info.isShadow()) {
-                continue;
-            }
-            if (isPackageInstalled(info.getPackageName(), userId)) {
-                continue;
-            }
-            gonePackages = ArrayUtils.add(gonePackages, info.getPackageName());
-        }
-        if (gonePackages != null) {
-            synchronized (mLock) {
+        synchronized (mLock) {
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+
+            user.forAllPackageItems(spi -> {
+                if (spi.getPackageInfo().isShadow()) {
+                    return; // Don't delete shadow information.
+                }
+                if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
+                    return;
+                }
+                gonePackages.add(PackageWithUser.of(spi));
+            });
+            if (gonePackages.size() > 0) {
                 for (int i = gonePackages.size() - 1; i >= 0; i--) {
-                    cleanUpPackageLocked(gonePackages.get(i), userId);
+                    final PackageWithUser pu = gonePackages.get(i);
+                    cleanUpPackageLocked(pu.packageName, userId, pu.userId);
                 }
             }
         }
@@ -1716,26 +1789,18 @@
             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
         }
         synchronized (mLock) {
-            final ArrayMap<String, ShortcutPackageInfo> infos =
-                    getUserShortcutsLocked(userId).getPackageInfos();
-            final ShortcutPackageInfo existing = infos.get(packageName);
-
-            if (existing != null && existing.isShadow()) {
-                Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented");
-            }
+            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
         }
     }
 
     private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
         if (DEBUG) {
-            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", packageName, userId));
+            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
+                    packageName, userId));
         }
+
         synchronized (mLock) {
-            final ShortcutPackageInfo spi =
-                    getUserShortcutsLocked(userId).getPackageInfos().get(packageName);
-            if (spi != null) {
-                spi.refreshAndSave(this, userId);
-            }
+            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
         }
     }
 
@@ -1744,17 +1809,18 @@
             Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
         }
         synchronized (mLock) {
-            cleanUpPackageLocked(packageName, userId);
+            cleanUpPackageLocked(packageName, userId, userId);
         }
     }
 
-    // === Backup & restore ===
+    // === PackageManager interaction ===
 
     PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
         return injectPackageInfo(packageName, userId, true);
     }
 
     int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
+        final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
                     , userId);
@@ -1762,12 +1828,15 @@
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
             return -1;
+        } finally {
+            injectRestoreCallingIdentity(token);
         }
     }
 
     @VisibleForTesting
     PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
+        final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
                     | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
@@ -1776,17 +1845,22 @@
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
             return null;
+        } finally {
+            injectRestoreCallingIdentity(token);
         }
     }
 
     @VisibleForTesting
     ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
+        final long token = injectClearCallingIdentity();
         try {
             return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
         } catch (RemoteException e) {
             // Shouldn't happen.
             Slog.wtf(TAG, "RemoteException", e);
             return null;
+        } finally {
+            injectRestoreCallingIdentity(token);
         }
     }
 
@@ -1795,12 +1869,61 @@
         return (ai != null) && ((ai.flags & flags) == flags);
     }
 
+    private boolean isPackageInstalled(String packageName, int userId) {
+        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
+    }
+
+    // === Backup & restore ===
+
     boolean shouldBackupApp(String packageName, int userId) {
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
     }
 
-    private boolean isPackageInstalled(String packageName, int userId) {
-        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
+    @Override
+    public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
+        enforceSystem();
+        if (DEBUG) {
+            Slog.d(TAG, "Backing up user " + userId);
+        }
+        synchronized (mLock) {
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+            if (user == null) {
+                Slog.w(TAG, "Can't backup: user not found: id=" + userId);
+                return null;
+            }
+
+            user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
+
+            // Then save.
+            final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
+            try {
+                saveUserInternalLocked(userId, os, /* forBackup */ true);
+            } catch (XmlPullParserException|IOException e) {
+                // Shouldn't happen.
+                Slog.w(TAG, "Backup failed.", e);
+                return null;
+            }
+            return os.toByteArray();
+        }
+    }
+
+    @Override
+    public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
+        enforceSystem();
+        if (DEBUG) {
+            Slog.d(TAG, "Restoring user " + userId);
+        }
+        final ShortcutUser user;
+        final ByteArrayInputStream is = new ByteArrayInputStream(payload);
+        try {
+            user = loadUserInternal(userId, is, /* fromBackup */ true);
+        } catch (XmlPullParserException|IOException e) {
+            Slog.w(TAG, "Restoration failed.", e);
+            return;
+        }
+        synchronized (mLock) {
+            mUsers.put(userId, user);
+        }
     }
 
     // === Dump ===
@@ -2158,14 +2281,4 @@
             return pkg.findShortcutById(shortcutId);
         }
     }
-
-    @VisibleForTesting
-    ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) {
-        synchronized (mLock) {
-            final ShortcutUser user = mUsers.get(userId);
-            if (user == null) return null;
-
-            return user.getPackageInfos().get(packageName);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 1a00cda..487558f 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,6 +21,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.util.Preconditions;
+
 import libcore.util.Objects;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -29,6 +31,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 /**
  * User information used by {@link ShortcutService}.
@@ -41,14 +44,50 @@
 
     private static final String ATTR_VALUE = "value";
 
+    static final class PackageWithUser {
+        final int userId;
+        final String packageName;
+
+        private PackageWithUser(int userId, String packageName) {
+            this.userId = userId;
+            this.packageName = Preconditions.checkNotNull(packageName);
+        }
+
+        public static PackageWithUser of(int launcherUserId, String packageName) {
+            return new PackageWithUser(launcherUserId, packageName);
+        }
+
+        public static PackageWithUser of(ShortcutPackageItem spi) {
+            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
+        }
+
+        @Override
+        public int hashCode() {
+            return packageName.hashCode() ^ userId;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PackageWithUser)) {
+                return false;
+            }
+            final PackageWithUser that = (PackageWithUser) obj;
+
+            return userId == that.userId && packageName.equals(that.packageName);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("{Launcher: %d, %s}", userId, packageName);
+        }
+    }
+
     @UserIdInt
     final int mUserId;
 
     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
 
-    private final ArrayMap<String, ShortcutLauncher> mLaunchers = new ArrayMap<>();
-
-    private final ArrayMap<String, ShortcutPackageInfo> mPackageInfos = new ArrayMap<>();
+    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
     private ComponentName mLauncherComponent;
 
@@ -60,12 +99,18 @@
         return mPackages;
     }
 
-    public ArrayMap<String, ShortcutLauncher> getLaunchers() {
+    public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
         return mLaunchers;
     }
 
-    public ArrayMap<String, ShortcutPackageInfo> getPackageInfos() {
-        return mPackageInfos;
+    public void addLauncher(ShortcutLauncher launcher) {
+        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
+                launcher.getPackageName()), launcher);
+    }
+
+    public ShortcutLauncher removeLauncher(
+            @UserIdInt int packageUserId, @NonNull String packageName) {
+        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
     }
 
     public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
@@ -77,28 +122,48 @@
         return ret;
     }
 
-    public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName) {
-        ShortcutLauncher ret = mLaunchers.get(packageName);
+    public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
+            @UserIdInt int launcherUserId) {
+        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
+        ShortcutLauncher ret = mLaunchers.get(key);
         if (ret == null) {
-            ret = new ShortcutLauncher(mUserId, packageName);
-            mLaunchers.put(packageName, ret);
+            ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
+            mLaunchers.put(key, ret);
         }
         return ret;
     }
 
-    public void ensurePackageInfo(ShortcutService s, String packageName, @UserIdInt int userId) {
-        final ShortcutPackageInfo existing = mPackageInfos.get(packageName);
+    public void forAllPackageItems(Consumer<ShortcutPackageItem> callback) {
+        {
+            final int size = mLaunchers.size();
+            for (int i = 0; i < size; i++) {
+                callback.accept(mLaunchers.valueAt(i));
+            }
+        }
+        {
+            final int size = mPackages.size();
+            for (int i = 0; i < size; i++) {
+                callback.accept(mPackages.valueAt(i));
+            }
+        }
+    }
 
-        if (existing != null) {
-            return;
-        }
-        if (ShortcutService.DEBUG) {
-            Slog.d(TAG, String.format("Fetching package info: %s user=%d", packageName, userId));
-        }
-        final ShortcutPackageInfo newSpi = ShortcutPackageInfo.generateForInstalledPackage(
-                s, packageName, userId);
-        mPackageInfos.put(packageName, newSpi);
-        s.scheduleSaveUser(mUserId);
+    public void unshadowPackage(ShortcutService s, @NonNull String packageName,
+            @UserIdInt int packageUserId) {
+        forPackageItem(packageName, packageUserId, spi -> {
+            Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
+            spi.ensureNotShadowAndSave(s);
+        });
+    }
+
+    public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
+            Consumer<ShortcutPackageItem> callback) {
+        forAllPackageItems(spi -> {
+            if ((spi.getPackageUserId() == packageUserId)
+                    && spi.getPackageName().equals(packageName)) {
+                callback.accept(spi);
+            }
+        });
     }
 
     public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
@@ -108,12 +173,7 @@
         ShortcutService.writeTagValue(out, TAG_LAUNCHER,
                 mLauncherComponent);
 
-        {
-            final int size = mPackageInfos.size();
-            for (int i = 0; i < size; i++) {
-                saveShortcutPackageItem(s, out, mPackageInfos.valueAt(i), forBackup);
-            }
-        }
+        // Can't use forEachPackageItem due to the checked exceptions.
         {
             final int size = mLaunchers.size();
             for (int i = 0; i < size; i++) {
@@ -132,14 +192,19 @@
 
     private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
-        if (forBackup && !s.shouldBackupApp(spi.getPackageName(), mUserId)) {
-            return; // Don't save.
+        if (forBackup) {
+            if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
+                return; // Don't save.
+            }
+            if (spi.getPackageUserId() != spi.getOwnerUserId()) {
+                return; // Don't save cross-user information.
+            }
         }
         spi.saveToXml(out, forBackup);
     }
 
-    public static ShortcutUser loadFromXml(XmlPullParser parser, int userId)
-            throws IOException, XmlPullParserException {
+    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
+            boolean fromBackup) throws IOException, XmlPullParserException {
         final ShortcutUser ret = new ShortcutUser(userId);
 
         final int outerDepth = parser.getDepth();
@@ -151,37 +216,30 @@
             }
             final int depth = parser.getDepth();
             final String tag = parser.getName();
-            switch (tag) {
-                case TAG_LAUNCHER: {
-                    ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
-                            parser, ATTR_VALUE);
-                    continue;
-                }
-                case ShortcutPackage.TAG_ROOT: {
-                    final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId);
 
-                    // Don't use addShortcut(), we don't need to save the icon.
-                    ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
-                    continue;
-                }
+            if (depth == outerDepth + 1) {
+                switch (tag) {
+                    case TAG_LAUNCHER: {
+                        ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
+                                parser, ATTR_VALUE);
+                        continue;
+                    }
+                    case ShortcutPackage.TAG_ROOT: {
+                        final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
+                                s, parser, userId, fromBackup);
 
-                case ShortcutLauncher.TAG_ROOT: {
-                    final ShortcutLauncher shortcuts =
-                            ShortcutLauncher.loadFromXml(parser, userId);
+                        // Don't use addShortcut(), we don't need to save the icon.
+                        ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
+                        continue;
+                    }
 
-                    ret.getLaunchers().put(shortcuts.getPackageName(), shortcuts);
-                    continue;
-                }
-
-                case ShortcutPackageInfo.TAG_ROOT: {
-                    final ShortcutPackageInfo pi =
-                            ShortcutPackageInfo.loadFromXml(parser);
-
-                    ret.getPackageInfos().put(pi.getPackageName(), pi);
-                    continue;
+                    case ShortcutLauncher.TAG_ROOT: {
+                        ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId, fromBackup));
+                        continue;
+                    }
                 }
             }
-            throw ShortcutService.throwForInvalidTag(depth, tag);
+            ShortcutService.warnForInvalidTag(depth, tag);
         }
         return ret;
     }
@@ -223,9 +281,5 @@
         for (int i = 0; i < mPackages.size(); i++) {
             mPackages.valueAt(i).dump(s, pw, prefix + "  ");
         }
-
-        for (int i = 0; i < mPackageInfos.size(); i++) {
-            mPackageInfos.valueAt(i).dump(s, pw, prefix + "  ");
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5263c37..7f0da1e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -31,7 +31,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
@@ -924,12 +923,12 @@
         }
         // Don't call them within the mRestrictionsLock.
         synchronized (mPackagesLock) {
-            if (globalChanged) {
-                writeUserListLP();
-            }
             if (localChanged) {
                 writeUserLP(getUserDataNoChecks(userId));
             }
+            if (globalChanged) {
+                writeUserListLP();
+            }
         }
 
         synchronized (mRestrictionsLock) {
@@ -1491,8 +1490,8 @@
         updateUserIds();
         initDefaultGuestRestrictions();
 
-        writeUserListLP();
         writeUserLP(userData);
+        writeUserListLP();
     }
 
     private String getOwnerName() {
@@ -1542,8 +1541,10 @@
             serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
             serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
                     Long.toString(userInfo.lastLoggedInTime));
-            serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
-                    userInfo.lastLoggedInFingerprint);
+            if (userInfo.lastLoggedInFingerprint != null) {
+                serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
+                        userInfo.lastLoggedInFingerprint);
+            }
             if (userInfo.iconPath != null) {
                 serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
             }
@@ -1599,7 +1600,7 @@
             serializer.endDocument();
             userFile.finishWrite(fos);
         } catch (Exception ioe) {
-            Slog.e(LOG_TAG, "Error writing user info " + userData.info.id + "\n" + ioe);
+            Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
             userFile.failWrite(fos);
         }
     }
@@ -1944,6 +1945,7 @@
                     userData.info = userInfo;
                     mUsers.put(userId, userData);
                 }
+                writeUserLP(userData);
                 writeUserListLP();
                 if (parent != null) {
                     if (isManagedProfile) {
@@ -2196,7 +2198,13 @@
     }
 
     private void removeUserState(final int userHandle) {
-        mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
+        try {
+            mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
+        } catch (IllegalStateException e) {
+            // This may be simply because the user was partially created.
+            Slog.i(LOG_TAG,
+                "Destroying key for user " + userHandle + " failed, continuing anyway", e);
+        }
         // Cleanup package manager settings
         mPm.cleanUpUser(this, userHandle);
 
@@ -2211,13 +2219,13 @@
             mCachedEffectiveUserRestrictions.remove(userHandle);
             mDevicePolicyLocalUserRestrictions.remove(userHandle);
         }
-        // Remove user file
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
-        userFile.delete();
         // Update the user list
         synchronized (mPackagesLock) {
             writeUserListLP();
         }
+        // Remove user file
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
+        userFile.delete();
         updateUserIds();
         File userDir = Environment.getUserSystemDirectory(userHandle);
         File renamedUserDir = Environment.getUserSystemDirectory(UserHandle.USER_NULL - userHandle);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c1f430c..574faa0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6299,8 +6299,7 @@
 
     @Override
     public boolean isNavBarForcedShownLw(WindowState windowState) {
-        return mForceShowSystemBars
-                && !windowState.getFrameLw().equals(windowState.getDisplayFrameLw());
+        return mForceShowSystemBars;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4a00ebd..dbbaa5e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -319,7 +319,7 @@
      */
     @Override
     public void disable2(int what, IBinder token, String pkg) {
-        disableForUser(what, token, pkg, mCurrentUserId);
+        disable2ForUser(what, token, pkg, mCurrentUserId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2c15818..b501398 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -21,6 +21,7 @@
 
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.app.Service;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -122,6 +123,12 @@
         }
     }
 
+    public void getMagnificationRegionsLocked(Region outMagnified, Region outAvailable) {
+        if (mDisplayMagnifier != null) {
+            mDisplayMagnifier.getMagnificationRegionsLocked(outMagnified, outAvailable);
+        }
+    }
+
     public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
         if (mDisplayMagnifier != null) {
             mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
@@ -392,6 +399,10 @@
             return spec;
         }
 
+        public void getMagnificationRegionsLocked(Region outMagnified, Region outAvailable) {
+            mMagnifedViewport.getBoundsLocked(outMagnified, outAvailable);
+        }
+
         public void destroyLocked() {
             mMagnifedViewport.destroyWindow();
         }
@@ -413,6 +424,7 @@
             private final Matrix mTempMatrix = new Matrix();
 
             private final Region mMagnifiedBounds = new Region();
+            private final Region mAvailableBounds = new Region();
             private final Region mOldMagnifiedBounds = new Region();
             private final Region mOldAvailableBounds = new Region();
 
@@ -450,6 +462,12 @@
                 recomputeBoundsLocked();
             }
 
+            public void getBoundsLocked(@NonNull Region outMagnified,
+                    @NonNull Region outAvailable) {
+                outMagnified.set(mMagnifiedBounds);
+                outAvailable.set(mAvailableBounds);
+            }
+
             public void updateMagnificationSpecLocked(MagnificationSpec spec) {
                 if (spec != null) {
                     mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
@@ -469,14 +487,11 @@
                 final int screenWidth = mTempPoint.x;
                 final int screenHeight = mTempPoint.y;
 
-                Region magnifiedBounds = mMagnifiedBounds;
-                magnifiedBounds.set(0, 0, 0, 0);
-
-                Region availableBounds = mTempRegion1;
-                availableBounds.set(0, 0, screenWidth, screenHeight);
+                mMagnifiedBounds.set(0, 0, 0, 0);
+                mAvailableBounds.set(0, 0, screenWidth, screenHeight);
 
                 if (mCircularPath != null) {
-                    availableBounds.setPath(mCircularPath, availableBounds);
+                    mAvailableBounds.setPath(mCircularPath, mAvailableBounds);
                 }
 
                 Region nonMagnifiedBounds = mTempRegion4;
@@ -494,36 +509,37 @@
                         continue;
                     }
 
-                    Region windowBounds = mTempRegion2;
+                    // Consider the touchable portion of the window
                     Matrix matrix = mTempMatrix;
                     populateTransformationMatrixLocked(windowState, matrix);
+                    Region touchableRegion = mTempRegion3;
+                    windowState.getTouchableRegion(touchableRegion);
+                    Rect touchableFrame = mTempRect1;
+                    touchableRegion.getBounds(touchableFrame);
                     RectF windowFrame = mTempRectF;
+                    windowFrame.set(touchableFrame);
+                    windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
+                    matrix.mapRect(windowFrame);
+                    Region windowBounds = mTempRegion2;
+                    windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                            (int) windowFrame.right, (int) windowFrame.bottom);
+                    // Only update new regions
+                    Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
+                    portionOfWindowAlreadyAccountedFor.set(mMagnifiedBounds);
+                    portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
+                    windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
                     if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
-                        windowFrame.set(windowState.mFrame);
-                        windowFrame.offset(-windowFrame.left, -windowFrame.top);
-                        matrix.mapRect(windowFrame);
-                        windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
-                                (int) windowFrame.right, (int) windowFrame.bottom);
-                        magnifiedBounds.op(windowBounds, Region.Op.UNION);
-                        magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
+                        mMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+                        mMagnifiedBounds.op(mAvailableBounds, Region.Op.INTERSECT);
                     } else {
-                        Region touchableRegion = mTempRegion3;
-                        windowState.getTouchableRegion(touchableRegion);
-                        Rect touchableFrame = mTempRect1;
-                        touchableRegion.getBounds(touchableFrame);
-                        windowFrame.set(touchableFrame);
-                        windowFrame.offset(-windowState.mFrame.left, -windowState.mFrame.top);
-                        matrix.mapRect(windowFrame);
-                        windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
-                                (int) windowFrame.right, (int) windowFrame.bottom);
                         nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
-                        windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
-                        availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+                        mAvailableBounds.op(windowBounds, Region.Op.DIFFERENCE);
                     }
 
+                    // Update accounted bounds
                     Region accountedBounds = mTempRegion2;
-                    accountedBounds.set(magnifiedBounds);
+                    accountedBounds.set(mMagnifiedBounds);
                     accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
                     accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
 
@@ -539,15 +555,15 @@
 
                 visibleWindows.clear();
 
-                magnifiedBounds.op(mDrawBorderInset, mDrawBorderInset,
+                mMagnifiedBounds.op(mDrawBorderInset, mDrawBorderInset,
                         screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
                         Region.Op.INTERSECT);
 
-                final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds);
-                final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds);
+                final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(mMagnifiedBounds);
+                final boolean availableChanged = !mOldAvailableBounds.equals(mAvailableBounds);
                 if (magnifiedChanged || availableChanged) {
                     if (magnifiedChanged) {
-                        mWindow.setBounds(magnifiedBounds);
+                        mWindow.setBounds(mMagnifiedBounds);
                         Rect dirtyRect = mTempRect1;
                         if (mFullRedrawNeeded) {
                             mFullRedrawNeeded = false;
@@ -557,23 +573,23 @@
                             mWindow.invalidate(dirtyRect);
                         } else {
                             Region dirtyRegion = mTempRegion3;
-                            dirtyRegion.set(magnifiedBounds);
+                            dirtyRegion.set(mMagnifiedBounds);
                             dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
                             dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
                             dirtyRegion.getBounds(dirtyRect);
                             mWindow.invalidate(dirtyRect);
                         }
 
-                        mOldMagnifiedBounds.set(magnifiedBounds);
+                        mOldMagnifiedBounds.set(mMagnifiedBounds);
                     }
 
                     if (availableChanged) {
-                        mOldAvailableBounds.set(availableBounds);
+                        mOldAvailableBounds.set(mAvailableBounds);
                     }
 
                     final SomeArgs args = SomeArgs.obtain();
-                    args.arg1 = Region.obtain(magnifiedBounds);
-                    args.arg2 = Region.obtain(availableBounds);
+                    args.arg1 = Region.obtain(mMagnifiedBounds);
+                    args.arg2 = Region.obtain(mAvailableBounds);
                     mHandler.obtainMessage(
                             MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
                 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6741aba..68ea4df 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -41,6 +41,8 @@
 
 import com.android.server.wm.DimLayer.DimLayerUser;
 
+import java.util.ArrayList;
+
 /**
  * Keeps information about the docked stack divider.
  */
@@ -87,7 +89,7 @@
     private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
-    private boolean mAnimating;
+    private boolean mAnimatingForMinimizedDockedStack;
     private boolean mAnimationStarted;
     private long mAnimationStartTime;
     private float mAnimationStart;
@@ -96,7 +98,8 @@
     private final Interpolator mMinimizedDockInterpolator;
     private float mMaximizeMeetFraction;
     private final Rect mTouchRegion = new Rect();
-    private boolean mAdjustingForIme;
+    private boolean mAnimatingForIme;
+    private boolean mAdjustedForIme;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -174,12 +177,11 @@
         return mLastVisibility;
     }
 
-    void setAdjustingForIme(boolean adjusting) {
-        mAdjustingForIme = adjusting;
-    }
-
-    boolean isAdjustingForIme() {
-        return mAdjustingForIme;
+    void setAdjustedForIme(boolean adjusted, boolean animate) {
+        if (mAdjustedForIme != adjusted) {
+            mAnimatingForIme = animate;
+            mAdjustedForIme = adjusted;
+        }
     }
 
     void positionDockedStackedDivider(Rect frame) {
@@ -342,6 +344,7 @@
         }
 
         mMinimizedDock = minimizedDock;
+        mAnimatingForIme = false;
         if (minimizedDock) {
             if (animate) {
                 startAdjustAnimation(0f, 1f);
@@ -358,7 +361,7 @@
     }
 
     private void startAdjustAnimation(float from, float to) {
-        mAnimating = true;
+        mAnimatingForMinimizedDockedStack = true;
         mAnimationStarted = false;
         mAnimationStart = from;
         mAnimationTarget = to;
@@ -380,10 +383,45 @@
     }
 
     public boolean animate(long now) {
-        if (!mAnimating) {
+        if (mAnimatingForMinimizedDockedStack) {
+            return animateForMinimizedDockedStack(now);
+        } else if (mAnimatingForIme) {
+            return animateForIme();
+        } else {
             return false;
         }
+    }
 
+    private boolean animateForIme() {
+        boolean updated = false;
+        boolean animating = false;
+
+        final ArrayList<TaskStack> stacks = mDisplayContent.getStacks();
+        for (int i = stacks.size() - 1; i >= 0; --i) {
+            final TaskStack stack = stacks.get(i);
+            if (stack != null && stack.isAdjustedForIme()) {
+                updated |= stack.updateAdjustForIme();
+                animating |= stack.isAnimatingForIme();
+            }
+        }
+
+        if (updated) {
+            mService.mWindowPlacerLocked.performSurfacePlacement();
+        }
+
+        if (!animating) {
+            mAnimatingForIme = false;
+            for (int i = stacks.size() - 1; i >= 0; --i) {
+                final TaskStack stack = stacks.get(i);
+                if (stack != null) {
+                    stack.clearImeGoingAway();
+                }
+            }
+        }
+        return animating;
+    }
+
+    private boolean animateForMinimizedDockedStack(long now) {
         final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
@@ -406,7 +444,7 @@
             }
         }
         if (t >= 1.0f) {
-            mAnimating = false;
+            mAnimatingForMinimizedDockedStack = false;
             return false;
         } else {
             return true;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index c0c1ed8..daeb860 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -195,10 +195,8 @@
 
     @Override
     public void repositionChild(IWindow window, int left, int top, int right, int bottom,
-            int requestedWidth, int requestedHeight,
             long deferTransactionUntilFrame, Rect outFrame) {
         mService.repositionChild(this, window, left, top, right, bottom,
-                requestedWidth, requestedHeight,
                 deferTransactionUntilFrame, outFrame);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4e8f19e..1f03c04 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -498,7 +498,15 @@
                 return;
             }
 
-            out.set(mBounds);
+            if (!mFullscreen) {
+                // When minimizing the docked stack when going home, we don't adjust the task bounds
+                // so we need to intersect the task bounds with the stack bounds here.
+                mStack.getBounds(mTmpRect);
+                mTmpRect.intersect(mBounds);
+                out.set(mTmpRect);
+            } else {
+                out.set(mBounds);
+            }
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8d67771..0bf7102 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -55,6 +55,11 @@
     // If the stack should be resized to fullscreen.
     private static final boolean FULLSCREEN = true;
 
+    // When we have a top-bottom split screen, we shift the bottom stack up to accommodate
+    // the IME window. The static flag below controls whether to run animation when the
+    // IME window goes away.
+    private static final boolean ANIMATE_IME_GOING_AWAY = false;
+
     /** Unique identifier */
     final int mStackId;
 
@@ -107,6 +112,7 @@
     private final Rect mLastContentBounds = new Rect();
     private final Rect mTmpAdjustedBounds = new Rect();
     private boolean mAdjustedForIme;
+    private boolean mImeGoingAway;
     private WindowState mImeWin;
     private float mMinimizeAmount;
     private final int mDockedStackMinimizeThickness;
@@ -796,19 +802,54 @@
     void setAdjustedForIme(WindowState imeWin) {
         mAdjustedForIme = true;
         mImeWin = imeWin;
-        if (updateAdjustedBounds()) {
-            getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+        mImeGoingAway = false;
+    }
+
+    boolean isAdjustedForIme() {
+        return mAdjustedForIme || mImeGoingAway;
+    }
+    void clearImeGoingAway() {
+        mImeGoingAway = false;
+    }
+
+    boolean isAnimatingForIme() {
+        return mImeWin != null && mImeWin.isAnimatingLw();
+    }
+
+    /**
+     * Update the stack's bounds (crop or position) according to the IME window's
+     * current position. When IME window is animated, the bottom stack is animated
+     * together to track the IME window's current position, and the top stack is
+     * cropped as necessary.
+     *
+     * @return true if a traversal should be performed after the adjustment.
+     */
+    boolean updateAdjustForIme() {
+        boolean stopped = false;
+        if (mImeGoingAway && (!ANIMATE_IME_GOING_AWAY || !isAnimatingForIme())) {
+            mImeWin = null;
+            mAdjustedForIme = false;
+            stopped = true;
         }
+        // Make sure to run a traversal when the animation stops so that the stack
+        // is moved to its final position.
+        return updateAdjustedBounds() || stopped;
     }
 
     /**
      * Resets the adjustment after it got adjusted for the IME.
+     * @param adjustBoundsNow if true, reset and update the bounds immediately and forget about
+     *                        animations; otherwise, set flag and animates the window away together
+     *                        with IME window.
      */
-    void resetAdjustedForIme() {
-        mAdjustedForIme = false;
-        mImeWin = null;
-        if (updateAdjustedBounds()) {
-            getDisplayContent().mDividerControllerLocked.setAdjustingForIme(true);
+    void resetAdjustedForIme(boolean adjustBoundsNow) {
+        if (adjustBoundsNow) {
+            mImeWin = null;
+            mAdjustedForIme = false;
+            mImeGoingAway = false;
+            updateAdjustedBounds();
+        } else {
+            mImeGoingAway |= mAdjustedForIme;
         }
     }
 
@@ -843,6 +884,12 @@
         getDisplayContent().getContentRect(displayContentRect);
         contentBounds.set(displayContentRect);
         int imeTop = Math.max(imeWin.getDisplayFrameLw().top, contentBounds.top);
+
+        // if IME window is animating, get its actual vertical shown position (but no smaller than
+        // the final target vertical position)
+        if (imeWin.isAnimatingLw()) {
+            imeTop = Math.max(imeTop, imeWin.getShownPositionLw().y);
+        }
         imeTop += imeWin.getGivenContentInsetsLw().top;
         if (contentBounds.bottom > imeTop) {
             contentBounds.bottom = imeTop;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5771d69..1a9e206 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2531,7 +2531,6 @@
 
     void repositionChild(Session session, IWindow client,
             int left, int top, int right, int bottom,
-            int requestedWidth, int requestedHeight,
             long deferTransactionUntilFrame, Rect outFrame) {
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
         long origId = Binder.clearCallingIdentity();
@@ -2547,7 +2546,6 @@
                             "repositionChild called but window is not"
                             + "attached to a parent win=" + win);
                 }
-                win.setRequestedSize(requestedWidth, requestedHeight);
 
                 win.mAttrs.x = left;
                 win.mAttrs.y = top;
@@ -7380,8 +7378,9 @@
         final WindowState imeWin = mInputMethodWindow;
         final TaskStack focusedStack =
                 mCurrentFocus != null ? mCurrentFocus.getStack() : null;
+        final boolean dockVisible = isStackVisibleLocked(DOCKED_STACK_ID);
         if (imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
-                && isStackVisibleLocked(DOCKED_STACK_ID)
+                && dockVisible
                 && focusedStack != null
                 && focusedStack.getDockSide() == DOCKED_BOTTOM){
             final ArrayList<TaskStack> stacks = displayContent.getStacks();
@@ -7391,12 +7390,14 @@
                     stack.setAdjustedForIme(imeWin);
                 }
             }
+            displayContent.mDividerControllerLocked.setAdjustedForIme(true, true);
         } else {
             final ArrayList<TaskStack> stacks = displayContent.getStacks();
             for (int i = stacks.size() - 1; i >= 0; --i) {
                 final TaskStack stack = stacks.get(i);
-                stack.resetAdjustedForIme();
+                stack.resetAdjustedForIme(!dockVisible);
             }
+            displayContent.mDividerControllerLocked.setAdjustedForIme(false, dockVisible);
         }
     }
 
@@ -10725,6 +10726,19 @@
         }
 
         @Override
+        public void getMagnificationRegions(@NonNull Region outMagnified,
+                @NonNull Region outAvailable) {
+            synchronized (mWindowMap) {
+                if (mAccessibilityController != null) {
+                    mAccessibilityController.getMagnificationRegionsLocked(
+                            outMagnified, outAvailable);
+                } else {
+                    throw new IllegalStateException("Magnification callbacks not set!");
+                }
+            }
+        }
+
+        @Override
         public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
             synchronized (mWindowMap) {
                 WindowState windowState = mWindowMap.get(windowToken);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 3b0081d..3e5ddbc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -693,16 +693,14 @@
                     // currently animating... let's do something.
                     final int left = w.mFrame.left;
                     final int top = w.mFrame.top;
-                    final boolean adjustedForMinimizedDockedStack = w.getTask() != null &&
-                            w.getTask().mStack.isAdjustedForMinimizedDockedStack();
+                    final boolean adjustedForMinimizedDockOrIme = task != null
+                                && (task.mStack.isAdjustedForMinimizedDockedStack()
+                                    || task.mStack.isAdjustedForIme());
                     if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
-                            && !w.isDragResizing() && !adjustedForMinimizedDockedStack
+                            && !w.isDragResizing() && !adjustedForMinimizedDockOrIme
                             && (task == null || !w.getTask().mStack.getFreezeMovementAnimations())
                             && !w.mWinAnimator.mLastHidden) {
                         winAnimator.setMoveAnimation(left, top);
-                    } else if (w.mAttrs.type  == TYPE_DOCK_DIVIDER &&
-                            displayContent.getDockedDividerController().isAdjustingForIme()) {
-                        winAnimator.setMoveAnimation(left, top);
                     }
 
                     //TODO (multidisplay): Accessibility supported only for the default display.
@@ -819,8 +817,6 @@
                 mService.updateResizingWindows(w);
             }
 
-            displayContent.getDockedDividerController().setAdjustingForIme(false);
-
             mService.mDisplayManagerInternal.setDisplayProperties(displayId,
                     mDisplayHasContent,
                     mPreferredRefreshRate,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d8b856b..3e368f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -654,8 +654,8 @@
         String shortSupportMessage = null;
         String longSupportMessage = null;
 
-        // Background color of confirm credentials screen. Default: gray.
-        static final int DEF_ORGANIZATION_COLOR = Color.GRAY;
+        // Background color of confirm credentials screen. Default: teal.
+        static final int DEF_ORGANIZATION_COLOR = Color.parseColor("#00796B");
         int organizationColor = DEF_ORGANIZATION_COLOR;
 
         // Default title of confirm credentials screen
@@ -4138,8 +4138,8 @@
     }
 
     @Override
-    public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, String alias,
-            boolean requestAccess) {
+    public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain,
+            String alias, boolean requestAccess) {
         enforceCanManageInstalledKeys(who);
 
         final int callingUid = mInjector.binderGetCallingUid();
@@ -4149,7 +4149,7 @@
                     KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid));
             try {
                 IKeyChainService keyChain = keyChainConnection.getService();
-                if (!keyChain.installKeyPair(privKey, cert, alias)) {
+                if (!keyChain.installKeyPair(privKey, cert, chain, alias)) {
                     return false;
                 }
                 if (requestAccess) {
@@ -4870,8 +4870,8 @@
 
         final ApplicationInfo ai;
         try {
-            ai = mContext.getPackageManager().getApplicationInfo(callerPackage, 0);
-        } catch (NameNotFoundException e) {
+            ai = mIPackageManager.getApplicationInfo(callerPackage, 0, userHandle);
+        } catch (RemoteException e) {
             throw new SecurityException(e);
         }
 
@@ -4880,7 +4880,7 @@
             legacyApp = true;
         } else if ("com.google.android.apps.enterprise.dmagent".equals(ai.packageName)
                 && ai.versionCode == 697) {
-            // TODO: remove this once a new prebuilt is dropped
+            // TODO: STOPSHIP remove this (revert ag/895987) once a new prebuilt is dropped
             legacyApp = true;
         }
 
@@ -5696,26 +5696,25 @@
     }
 
     @Override
-    public boolean setDeviceOwnerLockScreenInfo(ComponentName who, String info) {
+    public void setDeviceOwnerLockScreenInfo(ComponentName who, CharSequence info) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         if (!mHasFeature) {
-            return false;
+            return;
         }
 
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
             long token = mInjector.binderClearCallingIdentity();
             try {
-                mLockPatternUtils.setDeviceOwnerInfo(info);
+                mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null);
             } finally {
                 mInjector.binderRestoreCallingIdentity(token);
             }
-            return true;
         }
     }
 
     @Override
-    public String getDeviceOwnerLockScreenInfo() {
+    public CharSequence getDeviceOwnerLockScreenInfo() {
         return mLockPatternUtils.getDeviceOwnerInfo();
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5975405..b026bc5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -153,6 +153,8 @@
             "com.android.server.MountService$Lifecycle";
     private static final String SEARCH_MANAGER_SERVICE_CLASS =
             "com.android.server.search.SearchManagerService$Lifecycle";
+    private static final String THERMAL_OBSERVER_CLASS =
+            "com.google.android.clockwork.ThermalObserver";
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
@@ -695,6 +697,14 @@
         // as appropriate.
         mSystemServiceManager.startService(UiModeManagerService.class);
 
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "UpdatePackagesIfNeeded");
+        try {
+            mPackageManagerService.updatePackagesIfNeeded();
+        } catch (Throwable e) {
+            reportWtf("update packages", e);
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "PerformFstrimIfNeeded");
         try {
             mPackageManagerService.performFstrimIfNeeded();
@@ -703,14 +713,6 @@
         }
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
-        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "ExtractPackagesIfNeeded");
-        try {
-            mPackageManagerService.extractPackagesIfNeeded();
-        } catch (Throwable e) {
-            reportWtf("extract packages", e);
-        }
-        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-
         try {
             ActivityManagerNative.getDefault().showBootMessage(
                     context.getResources().getText(
@@ -959,7 +961,7 @@
 
                 if (context.getPackageManager().hasSystemFeature
                         (PackageManager.FEATURE_WATCH)) {
-                    mSystemServiceManager.startService(ThermalObserver.class);
+                    mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
                 }
             }
 
@@ -1023,7 +1025,8 @@
                     mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
                 }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
+                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+                    || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
                     mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
                 }
 
@@ -1129,7 +1132,9 @@
                 mSystemServiceManager.startService(TvInputManagerService.class);
             }
 
-            mSystemServiceManager.startService(MediaResourceMonitorService.class);
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
+                mSystemServiceManager.startService(MediaResourceMonitorService.class);
+            }
 
             if (!disableNonCoreServices) {
                 traceBeginAndSlog("StartMediaRouterService");
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index f169411..0ec50c4 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -43,4 +43,9 @@
         this.maximumApfProgramSize = maximumApfProgramSize;
         this.apfPacketFormat = apfPacketFormat;
     }
-}
\ No newline at end of file
+
+    public String toString() {
+        return String.format("%s{version: %d, maxSize: %d format: %d}", getClass().getSimpleName(),
+                apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+    }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 66c7909..c25fae3 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -45,6 +45,7 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.net.NetlinkTracker;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
@@ -211,6 +212,8 @@
         }
     }
 
+    public static final String DUMP_ARG = "ipmanager";
+
     private static final int CMD_STOP = 1;
     private static final int CMD_START = 2;
     private static final int CMD_CONFIRM = 3;
@@ -393,17 +396,18 @@
         }
     }
 
-    public void dumpApf(PrintWriter writer) {
-        writer.println("--------------------------------------------------------------------");
-        writer.println("APF dump:");
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println("APF dump:");
+        pw.increaseIndent();
         // Thread-unsafe access to mApfFilter but just used for debugging.
         ApfFilter apfFilter = mApfFilter;
         if (apfFilter != null) {
-            apfFilter.dump(new IndentingPrintWriter(writer, "  "));
+            apfFilter.dump(pw);
         } else {
-            writer.println("No apf support");
+            pw.println("No apf support");
         }
-        writer.println("--------------------------------------------------------------------");
+        pw.decreaseIndent();
     }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
index eb16a1d..c44ffa4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java
@@ -16,9 +16,16 @@
  */
 package com.android.server.pm;
 
+import android.content.ComponentName;
+import android.content.Intent;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.test.AndroidTestCase;
 
+import com.android.internal.util.Preconditions;
 import com.android.server.testutis.TestUtils;
 
 /**
@@ -33,12 +40,232 @@
  */
 public class ShortcutInfoTest extends AndroidTestCase {
 
-    public void testNoId() {
+    public void testMissingMandatoryFields() {
         TestUtils.assertExpectException(
                 IllegalArgumentException.class,
                 "ID must be provided",
                 () -> new ShortcutInfo.Builder(mContext).build());
+        TestUtils.assertExpectException(
+                IllegalArgumentException.class,
+                "title must be provided",
+                () -> new ShortcutInfo.Builder(mContext).setId("id").build()
+                        .enforceMandatoryFields());
+        TestUtils.assertExpectException(
+                NullPointerException.class,
+                "Intent must be provided",
+                () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
+                        .enforceMandatoryFields());
     }
 
-    // TODO Add more tests.
+    private ShortcutInfo parceled(ShortcutInfo si) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(si, 0);
+        p.setDataPosition(0);
+        ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
+        p.recycle();
+        return si2;
+    }
+
+    private Intent makeIntent(String action, Object... bundleKeysAndValues) {
+        final Intent intent = new Intent(action);
+        intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
+        return intent;
+    }
+
+    public void testParcel() {
+        ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
+                .setId("id")
+                .setTitle("title")
+                .setIntent(makeIntent("action"))
+                .build());
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals("title", si.getTitle());
+        assertEquals("action", si.getIntent().getAction());
+
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+
+        si = new ShortcutInfo.Builder(getContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        si.addFlags(ShortcutInfo.FLAG_PINNED);
+        si.setBitmapPath("abc");
+        si.setIconResourceId(456);
+
+        si = parceled(si);
+
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+    }
+
+    public void testClone() {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
+
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals("content://a.b.c/", si.getIcon().getUriString());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals("abc", si.getBitmapPath());
+        assertEquals(456, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
+
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals(null, si.getIntent());
+        assertEquals(123, si.getWeight());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
+
+        assertEquals(getContext().getPackageName(), si.getPackageName());
+        assertEquals("id", si.getId());
+        assertEquals(null, si.getActivityComponent());
+        assertEquals(null, si.getIcon());
+        assertEquals(null, si.getTitle());
+        assertEquals(null, si.getText());
+        assertEquals(null, si.getIntent());
+        assertEquals(0, si.getWeight());
+        assertEquals(null, si.getExtras());
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+        assertEquals(0, si.getIconResourceId());
+    }
+
+
+    public void testCopyNonNullFieldsFrom() {
+        PersistableBundle pb = new PersistableBundle();
+        pb.putInt("k", 1);
+        ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
+                .setId("id")
+                .setActivityComponent(new ComponentName("a", "b"))
+                .setIcon(Icon.createWithContentUri("content://a.b.c/"))
+                .setTitle("title")
+                .setText("text")
+                .setIntent(makeIntent("action", "key", "val"))
+                .setWeight(123)
+                .setExtras(pb)
+                .build();
+        sorig.addFlags(ShortcutInfo.FLAG_PINNED);
+        sorig.setBitmapPath("abc");
+        sorig.setIconResourceId(456);
+
+        ShortcutInfo si;
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setActivityComponent(new ComponentName("x", "y")).build());
+        assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
+        assertEquals("content://x.y.z/", si.getIcon().getUriString());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setTitle("xyz").build());
+        assertEquals("xyz", si.getTitle());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setText("xxx").build());
+        assertEquals("xxx", si.getText());
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setIntent(makeIntent("action2")).build());
+        assertEquals("action2", si.getIntent().getAction());
+        assertEquals(null, si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setIntent(makeIntent("action3", "key", "x")).build());
+        assertEquals("action3", si.getIntent().getAction());
+        assertEquals("x", si.getIntent().getStringExtra("key"));
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setWeight(999).build());
+        assertEquals(999, si.getWeight());
+
+
+        PersistableBundle pb2 = new PersistableBundle();
+        pb2.putInt("x", 99);
+
+        si = sorig.clone(/* flags=*/ 0);
+        si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
+                .setExtras(pb2).build());
+        assertEquals(99, si.getExtras().getInt("x"));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
index bd4ed16..5d29242 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -20,11 +20,14 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
@@ -72,6 +75,7 @@
 import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
+import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import libcore.io.IoUtils;
 
@@ -118,7 +122,8 @@
      */
     private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
 
-    private class BaseContext extends MockContext {
+    // public for mockito
+    public class BaseContext extends MockContext {
         @Override
         public Object getSystemService(String name) {
             switch (name) {
@@ -152,7 +157,7 @@
     }
 
     /** Context used in the client side */
-    private class ClientContext extends BaseContext {
+    public class ClientContext extends BaseContext {
         @Override
         public String getPackageName() {
             return mInjectedClientPackage;
@@ -160,7 +165,7 @@
     }
 
     /** Context used in the service side */
-    private final class ServiceContext extends BaseContext {
+    public class ServiceContext extends BaseContext {
         long injectClearCallingIdentity() {
             final int prevCallingUid = mInjectedCallingUid;
             mInjectedCallingUid = Process.SYSTEM_UID;
@@ -170,6 +175,11 @@
         void injectRestoreCallingIdentity(long token) {
             mInjectedCallingUid = (int) token;
         }
+
+        @Override
+        public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
+                UserHandle userId) {
+        }
     }
 
     /** ShortcutService with injection override methods. */
@@ -310,9 +320,17 @@
             if (getCallingUserId() == userToCheck.getIdentifier()) {
                 return; // okay
             }
+            if (getCallingUserId() == USER_0 && userToCheck.getIdentifier() == USER_P0) {
+                return; // profile, okay.
+            }
+            if (getCallingUserId() == USER_P0 && userToCheck.getIdentifier() == USER_0) {
+                return; // profile, okay.
+            }
 
-            assertEquals(Process.SYSTEM_UID, mInjectedCallingUid);
-            // SKIP
+            if (mInjectedCallingUid != Process.SYSTEM_UID) {
+                throw new SecurityException("To access other users, you need to be SYSTEM" +
+                        ", but current UID=" + mInjectedCallingUid);
+            }
         }
 
         @Override
@@ -333,6 +351,11 @@
             r.run();
             mContext.injectRestoreCallingIdentity(token);
         }
+
+        @Override
+        int injectBinderCallingUid() {
+            return mInjectedCallingUid;
+        }
     }
 
     private class LauncherAppsTestable extends LauncherApps {
@@ -371,6 +394,8 @@
 
     private Map<String, PackageInfo> mInjectedPackages;
 
+    private ArrayList<PackageWithUser> mUninstalledPackages;
+
     private PackageManager mMockPackageManager;
     private PackageManagerInternal mMockPackageManagerInternal;
     private UserManager mMockUserManager;
@@ -393,6 +418,13 @@
     private static final int USER_0 = UserHandle.USER_SYSTEM;
     private static final int USER_10 = 10;
     private static final int USER_11 = 11;
+    private static final int USER_P0 = 20; // profile of user 0
+
+    private static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0);
+    private static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10);
+    private static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11);
+    private static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0);
+
 
     private static final long START_TIME = 1440000000101L;
 
@@ -410,7 +442,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        mServiceContext = new ServiceContext();
+        mServiceContext = spy(new ServiceContext());
         mClientContext = new ClientContext();
 
         mMockPackageManager = mock(PackageManager.class);
@@ -428,6 +460,8 @@
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
         addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
 
+        mUninstalledPackages = new ArrayList<>();
+
         mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
 
         // Empty the data directory.
@@ -466,6 +500,10 @@
         addPackage(packageName, uid, version, packageName);
     }
 
+    private <T> List<T> list(T... array) {
+        return Arrays.asList(array);
+    }
+
     private Signature[] genSignatures(String... signatures) {
         final Signature[] sigs = new Signature[signatures.length];
         for (int i = 0; i < signatures.length; i++){
@@ -491,6 +529,10 @@
         mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
     }
 
+    private void uninstallPackage(int userId, String packageName) {
+        mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
+    }
+
     PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
             boolean getSignatures) {
         final PackageInfo pi = mInjectedPackages.get(packageName);
@@ -501,6 +543,9 @@
         ret.versionCode = pi.versionCode;
         ret.applicationInfo = new ApplicationInfo(pi.applicationInfo);
         ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
+        if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
+            ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+        }
 
         if (getSignatures) {
             ret.signatures = pi.signatures;
@@ -601,7 +646,7 @@
         runTestOnUiThread(() -> {});
     }
 
-    private static Bundle makeBundle(Object... keysAndValues) {
+    public static Bundle makeBundle(Object... keysAndValues) {
         Preconditions.checkState((keysAndValues.length % 2) == 0);
 
         if (keysAndValues.length == 0) {
@@ -752,8 +797,7 @@
     @NonNull
     private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts,
             String... expectedIds) {
-        assertEquals(expectedIds.length, actualShortcuts.size());
-        final HashSet<String> expected = new HashSet<>(Arrays.asList(expectedIds));
+        final HashSet<String> expected = new HashSet<>(list(expectedIds));
         final HashSet<String> actual = new HashSet<>();
         for (ShortcutInfo s : actualShortcuts) {
             actual.add(s.getId());
@@ -884,6 +928,21 @@
         return actualShortcuts;
     }
 
+    private void assertDynamicOnly(ShortcutInfo si) {
+        assertTrue(si.isDynamic());
+        assertFalse(si.isPinned());
+    }
+
+    private void assertPinnedOnly(ShortcutInfo si) {
+        assertFalse(si.isDynamic());
+        assertTrue(si.isPinned());
+    }
+
+    private void assertDynamicAndPinned(ShortcutInfo si) {
+        assertTrue(si.isDynamic());
+        assertTrue(si.isPinned());
+    }
+
     private void assertBitmapSize(int expectedWidth, int expectedHeight, @NonNull Bitmap bitmap) {
         assertEquals("width", expectedWidth, bitmap.getWidth());
         assertEquals("height", expectedHeight, bitmap.getHeight());
@@ -913,18 +972,6 @@
         assertTrue(b == null || b.size() == 0);
     }
 
-    private void assertShortcutPackageInfo(String packageName, int userId, int expectedVersion) {
-        ShortcutPackageInfo spi = mService.getPackageInfoForTest(packageName, userId);
-        assertNotNull(spi);
-        assertEquals(expectedVersion, spi.getVersionCode());
-
-        assertTrue(spi.canRestoreTo(genPackage(packageName, /*uid*/ 0, 9999999, packageName)));
-    }
-
-    private void assertNoShortcutPackageInfo(String packageName, int userId) {
-        assertNull(mService.getPackageInfoForTest(packageName, userId));
-    }
-
     private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) {
         return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
     }
@@ -937,6 +984,39 @@
         assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null);
     }
 
+    private Intent launchShortcutAndGetIntent(
+            @NonNull String packageName, @NonNull String shortcutId, int userId) {
+        reset(mServiceContext);
+        assertTrue(mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+                UserHandle.of(userId)));
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mServiceContext).startActivityAsUser(
+                intentCaptor.capture(),
+                any(Bundle.class),
+                eq(UserHandle.of(userId)));
+        return intentCaptor.getValue();
+    }
+
+    private void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId,
+            int userId) {
+        assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId));
+    }
+
+    private void assertShortcutNotLaunchable(@NonNull String packageName,
+            @NonNull String shortcutId, int userId) {
+        try {
+            final boolean ok = mLauncherApps.startShortcut(packageName, shortcutId, null, null,
+                    UserHandle.of(userId));
+            if (!ok) {
+                return; // didn't launch, okay.
+            }
+            fail();
+        } catch (SecurityException expected) {
+            // security exception is okay too.
+        }
+    }
+
     private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
         return getPackageShortcut(packageName, shortcutId, getCallingUserId());
     }
@@ -1130,26 +1210,21 @@
                 /* weight */ 12);
         final ShortcutInfo si3 = makeShortcut("shortcut3");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mManager.getDynamicShortcuts()),
                 "shortcut1", "shortcut2");
         assertEquals(2, mManager.getRemainingCallCount());
 
-        assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_10);
-
         // TODO: Check fields
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mManager.getDynamicShortcuts()),
                 "shortcut1");
         assertEquals(1, mManager.getRemainingCallCount());
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList()));
+        assertTrue(mManager.setDynamicShortcuts(list()));
         assertEquals(0, mManager.getDynamicShortcuts().size());
         assertEquals(0, mManager.getRemainingCallCount());
 
@@ -1160,18 +1235,13 @@
 
         dumpsysOnLogcat();
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2, si3)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2, si3)));
         assertEquals(2, mManager.getDynamicShortcuts().size());
 
         // TODO Check max number
 
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(makeShortcut("s1"))));
-
-            assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-            assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_0);
-            assertNoShortcutPackageInfo(CALLING_PACKAGE_1, USER_10);
-            assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2);
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
         });
     }
 
@@ -1184,7 +1254,7 @@
 
         assertEquals(3, mManager.getRemainingCallCount());
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mManager.getDynamicShortcuts()),
@@ -1209,7 +1279,6 @@
 
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
-            assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_10, 2);
         });
     }
 
@@ -1218,7 +1287,7 @@
         final ShortcutInfo si2 = makeShortcut("shortcut2");
         final ShortcutInfo si3 = makeShortcut("shortcut3");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mManager.getDynamicShortcuts()),
                 "shortcut1", "shortcut2", "shortcut3");
@@ -1260,7 +1329,7 @@
         final ShortcutInfo si2 = makeShortcut("shortcut2");
         final ShortcutInfo si3 = makeShortcut("shortcut3");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
         assertShortcutIds(assertAllNotKeyFieldsOnly(
                 mManager.getDynamicShortcuts()),
                 "shortcut1", "shortcut2", "shortcut3");
@@ -1277,7 +1346,7 @@
         assertEquals(0, mManager.getDynamicShortcuts().size());
 
         // This should still work.
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
         assertEquals(3, mManager.getDynamicShortcuts().size());
 
         // Still 1 call left
@@ -1289,62 +1358,62 @@
     public void testThrottling() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(1, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         // Reached the max
 
         mInjectedCurrentTimeLillis++;
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         // Still throttled
         mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
         // Now it should work.
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); // fail
+        assertTrue(mManager.setDynamicShortcuts(list(si1))); // fail
         assertEquals(2, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(1, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
 
         // 4 days later...
         mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(1, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
 
@@ -1354,7 +1423,7 @@
         assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
     }
@@ -1362,7 +1431,7 @@
     public void testThrottling_rewind() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
         assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
 
@@ -1381,7 +1450,7 @@
         mInjectedCurrentTimeLillis = START_TIME - 100000;
         assertEquals(3, mManager.getRemainingCallCount());
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
 
         // Forward again, should be reset now.
@@ -1392,21 +1461,21 @@
     public void testThrottling_perPackage() {
         final ShortcutInfo si1 = makeShortcut("shortcut1");
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(2, mManager.getRemainingCallCount());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(1, mManager.getRemainingCallCount());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
 
         // Reached the max
 
         mInjectedCurrentTimeLillis++;
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
 
         // Try from a different caller.
         mInjectedClientPackage = CALLING_PACKAGE_2;
@@ -1417,11 +1486,11 @@
 
         assertEquals(3, mManager.getRemainingCallCount());
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
         assertEquals(2, mManager.getRemainingCallCount());
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
         assertEquals(1, mManager.getRemainingCallCount());
 
         // Back to the original caller, still throttled.
@@ -1430,37 +1499,37 @@
 
         mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
         assertEquals(0, mManager.getRemainingCallCount());
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
         assertEquals(0, mManager.getRemainingCallCount());
 
         // Now it should work.
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
 
         mInjectedCurrentTimeLillis++;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
 
         mInjectedCurrentTimeLillis++;
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
 
         mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1)));
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertTrue(mManager.setDynamicShortcuts(list(si1)));
+        assertFalse(mManager.setDynamicShortcuts(list(si1)));
 
         mInjectedClientPackage = CALLING_PACKAGE_2;
         mInjectedCallingUid = CALLING_UID_2;
 
         assertEquals(3, mManager.getRemainingCallCount());
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2)));
-        assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertTrue(mManager.setDynamicShortcuts(list(si2)));
+        assertFalse(mManager.setDynamicShortcuts(list(si2)));
     }
 
     public void testIcons() {
@@ -1477,7 +1546,7 @@
 
         // Set from package 1
         setCaller(CALLING_PACKAGE_1);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+        assertTrue(mManager.setDynamicShortcuts(list(
                 makeShortcutWithIcon("res32x32", res32x32),
                 makeShortcutWithIcon("res64x64", res64x64),
                 makeShortcutWithIcon("bmp32x32", bmp32x32),
@@ -1497,7 +1566,7 @@
 
         // Call from another caller with the same ID, just to make sure storage is per-package.
         setCaller(CALLING_PACKAGE_2);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+        assertTrue(mManager.setDynamicShortcuts(list(
                 makeShortcutWithIcon("res32x32", res512x512),
                 makeShortcutWithIcon("res64x64", res512x512),
                 makeShortcutWithIcon("none", res512x512)
@@ -1515,26 +1584,25 @@
         Bitmap bmp;
 
         setCaller(LAUNCHER_1);
-
         // Check hasIconResource()/hasIconFile().
         assertShortcutIds(assertAllHaveIconResId(mLauncherApps.getShortcutInfo(
-                CALLING_PACKAGE_1, Arrays.asList("res32x32"),
+                CALLING_PACKAGE_1, list("res32x32"),
                 getCallingUser())), "res32x32");
 
         assertShortcutIds(assertAllHaveIconResId(mLauncherApps.getShortcutInfo(
-                CALLING_PACKAGE_1, Arrays.asList("res64x64"), getCallingUser())),
+                CALLING_PACKAGE_1, list("res64x64"), getCallingUser())),
                 "res64x64");
 
         assertShortcutIds(assertAllHaveIconFile(mLauncherApps.getShortcutInfo(
-                CALLING_PACKAGE_1, Arrays.asList("bmp32x32"), getCallingUser())),
+                CALLING_PACKAGE_1, list("bmp32x32"), getCallingUser())),
                 "bmp32x32");
 
         assertShortcutIds(assertAllHaveIconFile(mLauncherApps.getShortcutInfo(
-                CALLING_PACKAGE_1, Arrays.asList("bmp64x64"), getCallingUser())),
+                CALLING_PACKAGE_1, list("bmp64x64"), getCallingUser())),
                 "bmp64x64");
 
         assertShortcutIds(assertAllHaveIconFile(mLauncherApps.getShortcutInfo(
-                CALLING_PACKAGE_1, Arrays.asList("bmp512x512"), getCallingUser())),
+                CALLING_PACKAGE_1, list("bmp512x512"), getCallingUser())),
                 "bmp512x512");
 
         // Check
@@ -1629,7 +1697,7 @@
         final File p11_1_3 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
 
         // Make sure their paths are all unique
-        assertAllUnique(Arrays.asList(
+        assertAllUnique(list(
                 p10_1_1,
                 p10_1_2,
                 p10_1_3,
@@ -1658,7 +1726,7 @@
         assertEquals(p11_1_1.getParent(), p11_1_3.getParent());
 
         // Check the parents are still unique.
-        assertAllUnique(Arrays.asList(
+        assertAllUnique(list(
                 p10_1_1.getParent(),
                 p10_2_1.getParent(),
                 p11_1_1.getParent()
@@ -1683,7 +1751,7 @@
 
     public void testUpdateShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"),
                     makeShortcut("s2"),
                     makeShortcut("s3"),
@@ -1692,7 +1760,7 @@
             )));
         });
         runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"),
                     makeShortcut("s2"),
                     makeShortcut("s3"),
@@ -1701,9 +1769,9 @@
             )));
         });
         runWithCaller(LAUNCHER_1, UserHandle.USER_SYSTEM, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s2", "s3"),
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s3"),
                     getCallingUser());
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s4", "s5"),
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s4", "s5"),
                     getCallingUser());
         });
         runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
@@ -1743,7 +1811,7 @@
                     .setTitle("new title")
                     .build();
 
-            mManager.updateShortcuts(Arrays.asList(s2, s4));
+            mManager.updateShortcuts(list(s2, s4));
         });
         runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
             ShortcutInfo s2 = makeShortcutBuilder()
@@ -1757,7 +1825,7 @@
                     .setIntent(new Intent(Intent.ACTION_ALL_APPS))
                     .build();
 
-            mManager.updateShortcuts(Arrays.asList(s2, s4));
+            mManager.updateShortcuts(list(s2, s4));
         });
 
         runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
@@ -1805,12 +1873,7 @@
         // TODO Check bitmap removal too.
 
         runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
-            assertNoShortcutPackageInfo(CALLING_PACKAGE_2, USER_11);
-
-            mManager.updateShortcuts(Arrays.asList());
-
-            // Even an empty update call will populate the package info.
-            assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_11, 2);
+            mManager.updateShortcuts(list());
         });
     }
 
@@ -1838,17 +1901,17 @@
         final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 5000);
         final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 1000);
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
 
         setCaller(CALLING_PACKAGE_2);
         final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
         final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
         final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4)));
+        assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
 
         setCaller(CALLING_PACKAGE_3);
-        final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", 5000);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s3_2)));
+        final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000);
+        assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
 
         setCaller(LAUNCHER_1);
 
@@ -1886,7 +1949,7 @@
 
         // Pin some shortcuts.
         mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                Arrays.asList("s3", "s4"), getCallingUser());
+                list("s3", "s4"), getCallingUser());
 
         // Pinned ones only
         assertAllPinned(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
@@ -1929,7 +1992,7 @@
                 makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                 /* weight */ 12);
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
         dumpsysOnLogcat();
 
         setCaller(CALLING_PACKAGE_2);
@@ -1941,14 +2004,14 @@
                 makeIntent(Intent.ACTION_ANSWER, ShortcutActivity2.class,
                         "key1", "val1", "nest", makeBundle("key", 123)),
                 /* weight */ 10);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_1)));
+        assertTrue(mManager.setDynamicShortcuts(list(s2_1)));
         dumpsysOnLogcat();
 
         // Pin some.
         setCaller(LAUNCHER_1);
 
         mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                Arrays.asList("s2"), getCallingUser());
+                list("s2"), getCallingUser());
 
         dumpsysOnLogcat();
 
@@ -1967,19 +2030,19 @@
         list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
                 assertAllNotKeyFieldsOnly(
                 mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
-                Arrays.asList("s2", "s1", "s3", null), getCallingUser())))),
+                list("s2", "s1", "s3", null), getCallingUser())))),
                 "s1", "s2");
         assertEquals("Title 1", findById(list, "s1").getTitle());
         assertEquals("Title 2", findById(list, "s2").getTitle());
 
         assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
                 mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
-                        Arrays.asList("s3"), getCallingUser())))
+                        list("s3"), getCallingUser())))
                 /* none */);
 
         list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
                 mLauncherApps.getShortcutInfo(CALLING_PACKAGE_2,
-                        Arrays.asList("s1", "s2", "s3"), getCallingUser()))),
+                        list("s1", "s2", "s3"), getCallingUser()))),
                 "s1");
         assertEquals("ABC", findById(list, "s1").getTitle());
     }
@@ -1990,38 +2053,31 @@
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
 
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+            assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
         });
 
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
             final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
             final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
             final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4)));
+            assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
         });
 
         runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
             final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000);
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s3_2)));
+            assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
         });
 
         // Pin some.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            assertNoShortcutPackageInfo(LAUNCHER_1, USER_0);
-
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    Arrays.asList("s2", "s3"), getCallingUser());
-
-            assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
-            assertNoShortcutPackageInfo(LAUNCHER_1, USER_10);
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
+                    list("s2", "s3"), getCallingUser());
 
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                    Arrays.asList("s3", "s4", "s5"), getCallingUser());
+                    list("s3", "s4", "s5"), getCallingUser());
 
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_3,
-                    Arrays.asList("s3"), getCallingUser());  // Note ID doesn't exist
+                    list("s3"), getCallingUser());  // Note ID doesn't exist
         });
 
         // Delete some.
@@ -2066,32 +2122,24 @@
     public void testPinShortcutAndGetPinnedShortcuts_multi() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
         });
 
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
         });
 
         dumpsysOnLogcat();
 
-        assertNoShortcutPackageInfo(LAUNCHER_1, USER_0);
-        assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
-        assertNoShortcutPackageInfo(LAUNCHER_1, USER_10);
-        assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
         // Pin some.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    Arrays.asList("s3", "s4"), getCallingUser());
-
-            assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
+                    list("s3", "s4"), getCallingUser());
 
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                    Arrays.asList("s1", "s2", "s4"), getCallingUser());
+                    list("s1", "s2", "s4"), getCallingUser());
         });
 
         dumpsysOnLogcat();
@@ -2163,18 +2211,12 @@
                                     | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
                     "s2");
 
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_0);
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
-
             // Now pin some.
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                    Arrays.asList("s1", "s2"), getCallingUser());
-
-            assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
-            assertNoShortcutPackageInfo(LAUNCHER_2, USER_10);
+                    list("s1", "s2"), getCallingUser());
 
             mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                    Arrays.asList("s1", "s2"), getCallingUser());
+                    list("s1", "s2"), getCallingUser());
 
             assertShortcutIds(assertAllDynamic(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
@@ -2190,12 +2232,6 @@
                     "s2");
         });
 
-        assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-        assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-        assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
-        assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
-
         // Re-initialize and load from the files.
         mService.saveDirtyInfo();
         initService();
@@ -2204,12 +2240,6 @@
         mService.handleUnlockUser(USER_0);
 
         // Make sure package info is restored too.
-        assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-        assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-        assertShortcutPackageInfo(LAUNCHER_1, USER_0, 4);
-        assertShortcutPackageInfo(LAUNCHER_2, USER_0, 5);
-
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
@@ -2266,7 +2296,7 @@
 
             // Update pined.  Note s2 and s3 are actually available, but not visible to this
             // launcher, so still can't be pinned.
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s1", "s2", "s3", "s4"),
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
                     getCallingUser());
 
             assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
@@ -2288,7 +2318,7 @@
                     "s3");
 
             // Now "s1" is visible, so can be pinned.
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s1", "s2", "s3", "s4"),
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
                     getCallingUser());
 
             assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
@@ -2299,8 +2329,8 @@
 
         // Now clear pinned shortcuts.  First, from launcher 1.
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList(), getCallingUser());
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
 
             assertEquals(0,
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
@@ -2320,8 +2350,8 @@
 
         // Clear all pins from launcher 2.
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList(), getCallingUser());
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
 
             assertEquals(0,
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
@@ -2340,7 +2370,502 @@
         });
     }
 
-    public void testCreateShortcutIntent() {
+    public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
+                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
+        });
+
+        // Pin some shortcuts and see the result.
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1", "s2", "s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s2", "s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1", "s2", "s3"), HANDLE_USER_10);
+        });
+
+        // Cross profile pinning.
+        final int PIN_AND_DYNAMIC = ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC;
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3", "s4", "s5", "s6");
+        });
+
+        // Remove some dynamic shortcuts.
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_10, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
+                    "s1", "s2", "s3");
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+
+        // Save & load and make sure we still have the same information.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s2", "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s2", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+        runWithCaller(LAUNCHER_2, USER_P0, () -> {
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutIds(assertAllPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
+                    "s3");
+            assertShortcutIds(assertAllDynamic(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
+                    "s1");
+            assertShortcutIds(assertAllDynamicOrPinned(
+                    mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
+                    "s1", "s3");
+
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
+
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
+            assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
+
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
+            assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
+        });
+    }
+
+    public void testStartShortcut() {
         // Create some shortcuts.
         setCaller(CALLING_PACKAGE_1);
         final ShortcutInfo s1_1 = makeShortcut(
@@ -2360,7 +2885,7 @@
                 makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                 /* weight */ 12);
 
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2)));
+        assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
 
         setCaller(CALLING_PACKAGE_2);
         final ShortcutInfo s2_1 = makeShortcut(
@@ -2371,15 +2896,15 @@
                 makeIntent(Intent.ACTION_ANSWER, ShortcutActivity.class,
                         "key1", "val1", "nest", makeBundle("key", 123)),
                 /* weight */ 10);
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_1)));
+        assertTrue(mManager.setDynamicShortcuts(list(s2_1)));
 
         // Pin all.
         setCaller(LAUNCHER_1);
         mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
-                Arrays.asList("s1", "s2"), getCallingUser());
+                list("s1", "s2"), getCallingUser());
 
         mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
-                Arrays.asList("s1"), getCallingUser());
+                list("s1"), getCallingUser());
 
         // Just to make it complicated, delete some.
         setCaller(CALLING_PACKAGE_1);
@@ -2387,17 +2912,16 @@
 
         // intent and check.
         setCaller(LAUNCHER_1);
+
         Intent intent;
-        intent = mInternal.createShortcutIntent(getCallingPackage(),
-                CALLING_PACKAGE_1, "s1", getCallingUserId());
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s1", USER_0);
         assertEquals(ShortcutActivity2.class.getName(), intent.getComponent().getClassName());
 
-        intent = mInternal.createShortcutIntent(getCallingPackage(),
-                CALLING_PACKAGE_1, "s2", getCallingUserId());
+
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s2", USER_0);
         assertEquals(ShortcutActivity3.class.getName(), intent.getComponent().getClassName());
 
-        intent = mInternal.createShortcutIntent(getCallingPackage(),
-                CALLING_PACKAGE_2, "s1", getCallingUserId());
+        intent = launchShortcutAndGetIntent(CALLING_PACKAGE_2, "s1", USER_0);
         assertEquals(ShortcutActivity.class.getName(), intent.getComponent().getClassName());
 
         // TODO Check extra, etc
@@ -2419,7 +2943,7 @@
         });
 
         runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
         });
 
@@ -2428,7 +2952,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_1),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
                 "s1", "s2", "s3");
@@ -2436,7 +2960,7 @@
         // From different package.
         reset(c0);
         runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
         });
         waitOnMainThread();
@@ -2444,7 +2968,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_2),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
                 "s1", "s2", "s3");
@@ -2452,7 +2976,7 @@
         // Different user, callback shouldn't be called.
         reset(c0);
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
         });
         waitOnMainThread();
@@ -2473,7 +2997,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_1),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
                 "s1", "s2", "s3", "s4");
@@ -2489,7 +3013,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_1),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
                 "s2", "s3", "s4");
@@ -2497,7 +3021,7 @@
         // Test for update
         reset(c0);
         runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
-            assertTrue(mManager.updateShortcuts(Arrays.asList(
+            assertTrue(mManager.updateShortcuts(list(
                     makeShortcut("s1"), makeShortcut("s2"))));
         });
 
@@ -2506,7 +3030,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_1),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
                 "s2", "s3", "s4");
@@ -2522,13 +3046,13 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_1),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertEquals(0, shortcuts.getValue().size());
 
         // Remove CALLING_PACKAGE_2
         reset(c0);
-        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
 
         // Should get a callback with an empty list.
         waitOnMainThread();
@@ -2536,7 +3060,7 @@
         verify(c0).onShortcutsChanged(
                 eq(CALLING_PACKAGE_2),
                 shortcuts.capture(),
-                eq(UserHandle.of(USER_0))
+                eq(HANDLE_USER_0)
         );
         assertEquals(0, shortcuts.getValue().size());
     }
@@ -2544,7 +3068,7 @@
     // === Test for persisting ===
 
     public void testSaveAndLoadUser_empty() {
-        assertTrue(mManager.setDynamicShortcuts(Arrays.asList()));
+        assertTrue(mManager.setDynamicShortcuts(list()));
 
         Log.i(TAG, "Saved state");
         dumpsysOnLogcat();
@@ -2584,7 +3108,7 @@
                     makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                         /* weight */ 12);
 
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
 
             assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
             assertEquals(2, mManager.getRemainingCallCount());
@@ -2611,7 +3135,7 @@
                     makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                         /* weight */ 12);
 
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
 
             assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
             assertEquals(2, mManager.getRemainingCallCount());
@@ -2638,7 +3162,7 @@
                     makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
                         /* weight */ 12);
 
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2)));
+            assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
 
             assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
             assertEquals(2, mManager.getRemainingCallCount());
@@ -2647,10 +3171,6 @@
         mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent(
                 mService, new ComponentName("pkg1", "class"));
 
-        assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-        assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-
         // Restore.
         mService.saveDirtyInfo();
         initService();
@@ -2661,10 +3181,6 @@
         // this will pre-load the per-user info.
         mService.handleUnlockUser(UserHandle.USER_SYSTEM);
 
-        assertShortcutPackageInfo(CALLING_PACKAGE_1, USER_0, 1);
-        assertShortcutPackageInfo(CALLING_PACKAGE_2, USER_0, 2);
-        assertNoShortcutPackageInfo(CALLING_PACKAGE_3, USER_0);
-
         // Now it's loaded.
         assertEquals(1, mService.getShortcutsForTest().size());
 
@@ -2715,45 +3231,45 @@
 
     public void testCleanupPackage() {
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s0_1"))));
         });
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s0_2"))));
         });
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"),
-                    UserHandle.of(USER_0));
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"),
-                    UserHandle.of(USER_0));
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
+                    HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
+                    HANDLE_USER_0);
         });
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s0_1"),
-                    UserHandle.of(USER_0));
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s0_2"),
-                    UserHandle.of(USER_0));
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
+                    HANDLE_USER_0);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
+                    HANDLE_USER_0);
         });
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s10_1"))));
         });
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
-            assertTrue(mManager.setDynamicShortcuts(Arrays.asList(
+            assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s10_2"))));
         });
         runWithCaller(LAUNCHER_1, USER_10, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"),
-                    UserHandle.of(USER_10));
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"),
-                    UserHandle.of(USER_10));
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
+                    HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
+                    HANDLE_USER_10);
         });
         runWithCaller(LAUNCHER_2, USER_10, () -> {
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, Arrays.asList("s10_1"),
-                    UserHandle.of(USER_10));
-            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, Arrays.asList("s10_2"),
-                    UserHandle.of(USER_10));
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
+                    HANDLE_USER_10);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
+                    HANDLE_USER_10);
         });
 
         // Remove all dynamic shortcuts; now all shortcuts are just pinned.
@@ -2781,15 +3297,19 @@
 
 
         // Check the registered packages.
-
+        dumpsysOnLogcat();
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2803,25 +3323,24 @@
         assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
         assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
 
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
         mService.saveDirtyInfo();
 
         // Nonexistent package.
-        mService.cleanUpPackageLocked("abc", USER_0);
+        mService.cleanUpPackageLocked("abc", USER_0, USER_0);
 
         // No changes.
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2835,24 +3354,23 @@
         assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
         assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
 
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
         mService.saveDirtyInfo();
 
         // Remove a package.
-        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_10, LAUNCHER_1),
+                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2866,24 +3384,22 @@
         assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
         assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
 
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
         mService.saveDirtyInfo();
 
         // Remove a launcher.
-        mService.cleanUpPackageLocked(LAUNCHER_1, USER_10);
+        mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(LAUNCHER_2),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2898,16 +3414,19 @@
         mService.saveDirtyInfo();
 
         // Remove a package.
-        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(LAUNCHER_2),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2919,24 +3438,22 @@
         assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
         assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
 
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-
         mService.saveDirtyInfo();
 
         // Remove the other launcher from user 10 too.
-        mService.cleanUpPackageLocked(LAUNCHER_2, USER_10);
+        mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(CALLING_PACKAGE_1),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
-        assertEquals(makeSet(),
-                set(user10.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
+        assertEquals(
+                makeSet(),
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2951,16 +3468,18 @@
         mService.saveDirtyInfo();
 
         // More remove.
-        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10);
+        mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
 
         assertEquals(makeSet(CALLING_PACKAGE_2),
                 set(user0.getPackages().keySet()));
         assertEquals(makeSet(),
                 set(user10.getPackages().keySet()));
-        assertEquals(makeSet(LAUNCHER_1, LAUNCHER_2),
-                set(user0.getLaunchers().keySet()));
+        assertEquals(
+                makeSet(PackageWithUser.of(USER_0, LAUNCHER_1),
+                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(user0.getAllLaunchers().keySet()));
         assertEquals(makeSet(),
-                set(user10.getLaunchers().keySet()));
+                set(user10.getAllLaunchers().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
@@ -2975,6 +3494,353 @@
         mService.saveDirtyInfo();
     }
 
+
+    public void testSaveAndLoadUser_forBackup() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Pin some.
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), HANDLE_USER_10);
+        });
+
+        // Check the state.
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Make sure all the information is persisted.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+    }
+
+    public void testHandleGonePackage_crossProfile() {
+        // Create some shortcuts.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Pin some.
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s1"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s3"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_P0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s2"), HANDLE_USER_0);
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), UserHandle.of(USER_P0));
+
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
+                    list("s1"), HANDLE_USER_0);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("s3"), HANDLE_USER_10);
+        });
+
+        // Check the state.
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Make sure all the information is persisted.
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Start uninstalling.
+        uninstallPackage(USER_10, LAUNCHER_1);
+        mService.cleanupGonePackages(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Uninstall.
+        uninstallPackage(USER_10, CALLING_PACKAGE_1);
+        mService.cleanupGonePackages(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_P0, LAUNCHER_1);
+        mService.cleanupGonePackages(USER_0);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        mService.cleanupGonePackages(USER_P0);
+        
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_P0, CALLING_PACKAGE_1);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        // Uninstall
+        uninstallPackage(USER_0, LAUNCHER_1);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+
+        uninstallPackage(USER_0, CALLING_PACKAGE_2);
+
+        mService.saveDirtyInfo();
+        initService();
+        mService.handleUnlockUser(USER_0);
+        mService.handleUnlockUser(USER_P0);
+        mService.handleUnlockUser(USER_10);
+
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
+        assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
+
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
+        assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
+    }
+
     // TODO Detailed test for hasShortcutPermissionInner().
 
     // TODO Add tests for the command line functions too.
@@ -3027,27 +3893,6 @@
         checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
     }
 
-    public void testShortcutPackageInfoRefresh() {
-        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
-
-        final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage(
-                mService, CALLING_PACKAGE_1, USER_0);
-
-        checkCanRestoreTo(true, spi1, 10, "sig1");
-
-        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 11, "sig1", "sig2");
-
-        spi1.refreshAndSave(mService, USER_0);
-
-        mService.handleCleanupUser(USER_0);
-        initService();
-
-        checkCanRestoreTo(false, spi1, 10, "sig1", "sig2");
-        checkCanRestoreTo(false, spi1, 11, "sig", "sig2");
-        checkCanRestoreTo(false, spi1, 11, "sig1", "sig");
-        checkCanRestoreTo(true, spi1, 11, "sig1", "sig2");
-    }
-
     public void testHandlePackageDelete() {
         setCaller(CALLING_PACKAGE_1, USER_0);
         assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
@@ -3067,73 +3912,56 @@
         setCaller(CALLING_PACKAGE_3, USER_10);
         assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
 
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
         mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
 
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
         mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
 
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
         mInjectedPackages.remove(CALLING_PACKAGE_1);
         mInjectedPackages.remove(CALLING_PACKAGE_3);
 
         mService.handleUnlockUser(USER_0);
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
 
         mService.handleUnlockUser(USER_10);
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_0));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_0));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_10));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_2, USER_10));
-        assertNull(mService.getPackageInfoForTest(CALLING_PACKAGE_3, USER_10));
+
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
+        assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
+        assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
     }
 
     public void testHandlePackageUpdate() {
-        setCaller(CALLING_PACKAGE_1, USER_0);
-        assertTrue(mManager.addDynamicShortcut(makeShortcut("s1")));
-
-        assertNotNull(mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0));
-        assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
-        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 123);
-
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageUpdateIntent("abc", USER_0));
-        assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageUpdateIntent("abc", USER_10));
-        assertEquals(1, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0).getVersionCode());
-
-        mService.mPackageMonitor.onReceive(getTestContext(),
-                genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
-        assertEquals(123, mService.getPackageInfoForTest(CALLING_PACKAGE_1, USER_0)
-                .getVersionCode());
+        // TODO: Make sure unshadow is called.
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
new file mode 100644
index 0000000..c016e61
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.pm.backup;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.Signature;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.backup.BackupUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@SmallTest
+public class BackupUtilsTest extends AndroidTestCase {
+
+    private Signature[] genSignatures(String... signatures) {
+        final Signature[] sigs = new Signature[signatures.length];
+        for (int i = 0; i < signatures.length; i++){
+            sigs[i] = new Signature(signatures[i].getBytes());
+        }
+        return sigs;
+    }
+
+    private PackageInfo genPackage(String... signatures) {
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = "package";
+        pi.applicationInfo = new ApplicationInfo();
+        pi.signatures = genSignatures(signatures);
+
+        return pi;
+    }
+
+    public void testSignaturesMatch() {
+        final ArrayList<byte[]> stored1 = BackupUtils.hashSignatureArray(Arrays.asList(
+                "abc".getBytes()));
+        final ArrayList<byte[]> stored2 = BackupUtils.hashSignatureArray(Arrays.asList(
+                "abc".getBytes(), "def".getBytes()));
+
+        PackageInfo pi;
+
+        // False for null package.
+        assertFalse(BackupUtils.signaturesMatch(stored1, null));
+
+        // If it's a system app, signatures don't matter.
+        pi = genPackage("xyz");
+        pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        assertTrue(BackupUtils.signaturesMatch(stored1, pi));
+
+        // Non system apps.
+        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc")));
+
+        // Superset is okay.
+        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc", "xyz")));
+        assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "abc")));
+
+        assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz")));
+        assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "def")));
+
+        assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("def", "abc")));
+        assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("x", "def", "abc", "y")));
+
+        // Subset is not okay.
+        assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("abc")));
+        assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("def")));
+    }
+
+    public void testHashSignature() {
+        final byte[] sig1 = "abc".getBytes();
+        final byte[] sig2 = "def".getBytes();
+
+        final byte[] hash1a = BackupUtils.hashSignature(sig1);
+        final byte[] hash1b = BackupUtils.hashSignature(new Signature(sig1));
+
+        final byte[] hash2a = BackupUtils.hashSignature(sig2);
+        final byte[] hash2b = BackupUtils.hashSignature(new Signature(sig2));
+
+        assertEquals(32, hash1a.length);
+        MoreAsserts.assertEquals(hash1a, hash1b);
+
+        assertEquals(32, hash2a.length);
+        MoreAsserts.assertEquals(hash2a, hash2b);
+
+        assertFalse(Arrays.equals(hash1a, hash2a));
+
+        final ArrayList<byte[]> listA = BackupUtils.hashSignatureArray(Arrays.asList(
+                "abc".getBytes(), "def".getBytes()));
+
+        final ArrayList<byte[]> listB = BackupUtils.hashSignatureArray(new Signature[]{
+                new Signature("abc".getBytes()), new Signature("def".getBytes())});
+
+        assertEquals(2, listA.size());
+        assertEquals(2, listB.size());
+
+        MoreAsserts.assertEquals(hash1a, listA.get(0));
+        MoreAsserts.assertEquals(hash1a, listB.get(0));
+
+        MoreAsserts.assertEquals(hash2a, listA.get(1));
+        MoreAsserts.assertEquals(hash2a, listB.get(1));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
index 52e8f37..d2a4484 100644
--- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
@@ -24,19 +24,14 @@
     }
 
     public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
-            Runnable r) {
-        assertExpectException(expectedExceptionType, null, r);
-    }
-
-    public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
             String expectedExceptionMessageRegex, Runnable r) {
         try {
             r.run();
-            Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName()
+            Assert.fail("Expected exception type " + expectedExceptionType.getName()
                     + " was not thrown");
         } catch (Throwable e) {
             Assert.assertTrue(
-                    "Expected exception type was " + expectedExceptionType.getClass().getName()
+                    "Expected exception type was " + expectedExceptionType.getName()
                     + " but caught " + e.getClass().getName(),
                     expectedExceptionType.isAssignableFrom(e.getClass()));
             if (expectedExceptionMessageRegex != null) {
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 06851ee..1ce4ade 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -694,7 +694,7 @@
         if (mPreviousExtraKeys != null) {
             List<String> toRemove = new ArrayList<String>();
             for (String oldKey : mPreviousExtraKeys) {
-                if (!extras.containsKey(oldKey)) {
+                if (extras == null || !extras.containsKey(oldKey)) {
                     toRemove.add(oldKey);
                 }
             }
@@ -710,7 +710,9 @@
             mPreviousExtraKeys = new ArraySet<String>();
         }
         mPreviousExtraKeys.clear();
-        mPreviousExtraKeys.addAll(extras.keySet());
+        if (extras != null) {
+            mPreviousExtraKeys.addAll(extras.keySet());
+        }
     }
 
     /**
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 310c957..d83cdb8 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -1887,7 +1887,7 @@
         if (mPreviousExtraKeys != null) {
             List<String> toRemove = new ArrayList<String>();
             for (String oldKey : mPreviousExtraKeys) {
-                if (!extras.containsKey(oldKey)) {
+                if (extras == null || !extras.containsKey(oldKey)) {
                     toRemove.add(oldKey);
                 }
             }
@@ -1902,7 +1902,9 @@
             mPreviousExtraKeys = new ArraySet<String>();
         }
         mPreviousExtraKeys.clear();
-        mPreviousExtraKeys.addAll(extras.keySet());
+        if (extras != null) {
+            mPreviousExtraKeys.addAll(extras.keySet());
+        }
     }
 
     /**
@@ -2317,7 +2319,7 @@
      */
     public void sendConnectionEvent(String event, Bundle extras) {
         for (Listener l : mListeners) {
-            l.onConnectionEvent(this, event, null);
+            l.onConnectionEvent(this, event, extras);
         }
     }
 }
diff --git a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
index cb77118..a169b18 100644
--- a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
+++ b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
@@ -1,35 +1,35 @@
-/*

- * 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 com.android.locationtracker;

-

-import android.os.Bundle;

-import android.preference.PreferenceActivity;

-

-/**

- * Settings preference screen for location tracker

- */

-public class SettingsActivity extends PreferenceActivity {

-

-    @Override

-    protected void onCreate(Bundle savedInstanceState) {

-        super.onCreate(savedInstanceState);

-

-        // Load the preferences from an XML resource

-        addPreferencesFromResource(R.xml.preferences);

-    }

-

-}

+/*
+ * 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 com.android.locationtracker;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Settings preference screen for location tracker
+ */
+public class SettingsActivity extends PreferenceActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preferences);
+    }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
index 55d4d1e..adc39b3 100644
--- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
@@ -1,75 +1,75 @@
-/*

- * 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 com.android.locationtracker.data;

-

-import android.app.ListActivity;

-import android.content.Context;

-import android.database.Cursor;

-import android.view.View;

-import android.widget.ResourceCursorAdapter;

-import android.widget.TextView;

-

-import com.android.locationtracker.R;

-

-/**

- * Used to bind Tracker data to a list view UI

- */

-public class TrackerListHelper extends TrackerDataHelper {

-

-    private ListActivity mActivity;

-

-    // sort entries by most recent first

-    private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";

-

-    public TrackerListHelper(ListActivity activity) {

-        super(activity, TrackerDataHelper.CSV_FORMATTER);

-        mActivity = activity;

-    }

-

-    /**

-     * Helper method for binding the list activities UI to the tracker data

-     * Tracker data will be sorted in most-recent first order

-     * Will enable automatic UI changes as tracker data changes

-     *

-     * @param layout - layout to populate data

-     */

-    public void bindListUI(int layout) {

-        Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,

-                TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);

-        // Used to map tracker entries from the database to views

-        TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);

-        mActivity.setListAdapter(adapter);

-        cursor.setNotificationUri(mActivity.getContentResolver(),

-                TrackerProvider.CONTENT_URI);

-

-    }

-

-    private class TrackerAdapter extends ResourceCursorAdapter {

-

-        public TrackerAdapter(Context context, int layout, Cursor c) {

-            super(context, layout, c);

-        }

-

-        @Override

-        public void bindView(View view, Context context, Cursor cursor) {

-            final TextView v = (TextView) view

-                    .findViewById(R.id.entrylist_item);

-            String rowText = mFormatter.getOutput(TrackerEntry

-                    .createEntry(cursor));

-            v.setText(rowText);

-        }

-    }

-}

+/*
+ * 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 com.android.locationtracker.data;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.locationtracker.R;
+
+/**
+ * Used to bind Tracker data to a list view UI
+ */
+public class TrackerListHelper extends TrackerDataHelper {
+
+    private ListActivity mActivity;
+
+    // sort entries by most recent first
+    private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";
+
+    public TrackerListHelper(ListActivity activity) {
+        super(activity, TrackerDataHelper.CSV_FORMATTER);
+        mActivity = activity;
+    }
+
+    /**
+     * Helper method for binding the list activities UI to the tracker data
+     * Tracker data will be sorted in most-recent first order
+     * Will enable automatic UI changes as tracker data changes
+     *
+     * @param layout - layout to populate data
+     */
+    public void bindListUI(int layout) {
+        Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,
+                TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);
+        // Used to map tracker entries from the database to views
+        TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);
+        mActivity.setListAdapter(adapter);
+        cursor.setNotificationUri(mActivity.getContentResolver(),
+                TrackerProvider.CONTENT_URI);
+
+    }
+
+    private class TrackerAdapter extends ResourceCursorAdapter {
+
+        public TrackerAdapter(Context context, int layout, Cursor c) {
+            super(context, layout, c);
+        }
+
+        @Override
+        public void bindView(View view, Context context, Cursor cursor) {
+            final TextView v = (TextView) view
+                    .findViewById(R.id.entrylist_item);
+            String rowText = mFormatter.getOutput(TrackerEntry
+                    .createEntry(cursor));
+            v.setText(rowText);
+        }
+    }
+}
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index dc4cdb5..71d6001 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,7 +2,7 @@
         package="com.android.test.soundtrigger">
 
     <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
-    <uses-permission android:name="android.permission.WAKE_LOCk" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <application>
         <activity
             android:name="TestSoundTriggerActivity"
diff --git a/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml b/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml
new file mode 100644
index 0000000..4f05090
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/btn_radio_on_to_off_bundle.xml
@@ -0,0 +1,190 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+                 xmlns:android="http://schemas.android.com/apk/res/android">
+    <aapt:attr name="android:drawable">
+        <vector
+                android:width="32dp"
+                android:viewportWidth="32"
+                android:height="32dp"
+                android:viewportHeight="32">
+            <group
+                    android:name="btn_radio_to_off_mtrl_0"
+                    android:translateX="16"
+                    android:translateY="16">
+                <group
+                        android:name="ring_outer">
+                    <path
+                            android:name="ring_outer_path"
+                            android:strokeColor="#FF000000"
+                            android:strokeWidth="2"
+                            android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z"/>
+                </group>
+                <group
+                        android:name="dot_group">
+                    <path
+                            android:name="dot_path"
+                            android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+                            android:fillColor="#FF000000"/>
+                </group>
+            </group>
+        </vector>
+    </aapt:attr>
+    <target android:name="ring_outer">
+        <aapt:attr name="android:animation">
+            <set
+                    xmlns:android="http://schemas.android.com/apk/res/android" >
+                <set
+                        android:ordering="sequentially" >
+                    <objectAnimator
+                            android:duration="183"
+                            android:propertyName="scaleX"
+                            android:valueFrom="1.0"
+                            android:valueTo="0.9"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in" />
+                    <objectAnimator
+                            android:duration="16"
+                            android:propertyName="scaleX"
+                            android:valueFrom="0.9"
+                            android:valueTo="0.5"
+                            android:valueType="floatType"
+                            android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+                    <objectAnimator
+                            android:duration="300"
+                            android:propertyName="scaleX"
+                            android:valueFrom="0.5"
+                            android:valueTo="1.0"
+                            android:valueType="floatType"
+                            android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+                </set>
+                <set
+                        android:ordering="sequentially" >
+                    <objectAnimator
+                            android:duration="183"
+                            android:propertyName="scaleY"
+                            android:valueFrom="1.0"
+                            android:valueTo="0.9"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in" />
+                    <objectAnimator
+                            android:duration="16"
+                            android:propertyName="scaleY"
+                            android:valueFrom="0.9"
+                            android:valueTo="0.5"
+                            android:valueType="floatType"
+                            android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+                    <objectAnimator
+                            android:duration="300"
+                            android:propertyName="scaleY"
+                            android:valueFrom="0.5"
+                            android:valueTo="1.0"
+                            android:valueType="floatType"
+                            android:interpolator="@interpolator/btn_radio_to_off_mtrl_animation_interpolator_0" />
+                </set>
+            </set>
+        </aapt:attr>
+    </target>
+
+    <target android:name="ring_outer_path">
+        <aapt:attr name="android:animation">
+            <set
+                    xmlns:android="http://schemas.android.com/apk/res/android">
+                <set
+                        android:ordering="sequentially">
+                    <objectAnimator
+                            android:duration="183"
+                            android:propertyName="strokeWidth"
+                            android:valueFrom="2.0"
+                            android:valueTo="2.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="16"
+                            android:propertyName="strokeWidth"
+                            android:valueFrom="2.0"
+                            android:valueTo="18.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="300"
+                            android:propertyName="strokeWidth"
+                            android:valueFrom="18.0"
+                            android:valueTo="2.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                </set>
+
+            </set>
+        </aapt:attr>
+    </target>
+    <target
+            android:name="dot_group">
+        <aapt:attr name="android:animation">
+            <set
+                    xmlns:android="http://schemas.android.com/apk/res/android">
+                <set
+                        android:ordering="sequentially">
+                    <objectAnimator
+                            android:duration="183"
+                            android:propertyName="scaleX"
+                            android:valueFrom="1.0"
+                            android:valueTo="1.4"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="16"
+                            android:propertyName="scaleX"
+                            android:valueFrom="1.4"
+                            android:valueTo="0.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="300"
+                            android:propertyName="scaleX"
+                            android:valueFrom="0.0"
+                            android:valueTo="0.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                </set>
+                <set
+                        android:ordering="sequentially">
+                    <objectAnimator
+                            android:duration="183"
+                            android:propertyName="scaleY"
+                            android:valueFrom="1.0"
+                            android:valueTo="1.4"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="16"
+                            android:propertyName="scaleY"
+                            android:valueFrom="1.4"
+                            android:valueTo="0.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                    <objectAnimator
+                            android:duration="300"
+                            android:propertyName="scaleY"
+                            android:valueFrom="0.0"
+                            android:valueTo="0.0"
+                            android:valueType="floatType"
+                            android:interpolator="@android:interpolator/fast_out_slow_in"/>
+                </set>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
index 96fd70e..a6da114 100644
--- a/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_linear_progress_bar.xml
@@ -17,7 +17,7 @@
     android:height="4dp"
     android:viewportHeight="4"
     android:viewportWidth="360"
-    android:width="360dp" >
+    android:width="36dp" >
 
     <group
         android:name="linear_indeterminate"
diff --git a/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml b/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml
new file mode 100644
index 0000000..d3728c4
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/btn_radio_to_off_mtrl_animation_interpolator_0.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<pathInterpolator
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:pathData="M 0.0,0.0 c 0.4,0.0 0.4,1.0 1.0,1.0" />
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
index 087e68a..4026e5e 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
@@ -29,6 +29,7 @@
     private static final String LOGCAT = "AnimatedVectorDrawableTest";
 
     protected int[] icon = {
+            R.drawable.btn_radio_on_to_off_bundle,
             R.drawable.ic_rotate_2_portrait_v2_animation,
             R.drawable.ic_signal_airplane_v2_animation,
             R.drawable.ic_hourglass_animation,
@@ -43,33 +44,37 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        final int[] layerTypes = {View.LAYER_TYPE_SOFTWARE, View.LAYER_TYPE_HARDWARE};
         super.onCreate(savedInstanceState);
 
         ScrollView scrollView = new ScrollView(this);
         GridLayout container = new GridLayout(this);
         scrollView.addView(container);
-        container.setColumnCount(1);
+        container.setColumnCount(2);
 
         for (int i = 0; i < icon.length; i++) {
-            Button button = new Button(this);
-            button.setWidth(400);
-            button.setHeight(400);
-            button.setBackgroundResource(icon[i]);
-            AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground();
-            d.registerAnimationCallback(new Animatable2.AnimationCallback() {
-                @Override
-                public void onAnimationStart(Drawable drawable) {
-                    Log.v(LOGCAT, "Animator start");
-                }
+            for (int j = 0; j < layerTypes.length; j++) {
+                Button button = new Button(this);
+                button.setWidth(400);
+                button.setHeight(400);
+                button.setLayerType(layerTypes[j], null);
+                button.setBackgroundResource(icon[i]);
+                AnimatedVectorDrawable d = (AnimatedVectorDrawable) button.getBackground();
+                d.registerAnimationCallback(new Animatable2.AnimationCallback() {
+                    @Override
+                    public void onAnimationStart(Drawable drawable) {
+                        Log.v(LOGCAT, "Animator start");
+                    }
 
-                @Override
-                public void onAnimationEnd(Drawable drawable) {
+                    @Override
+                    public void onAnimationEnd(Drawable drawable) {
                         Log.v(LOGCAT, "Animator end");
-                }
-            });
+                    }
+                });
 
-            container.addView(button);
-            button.setOnClickListener(this);
+                container.addView(button);
+                button.setOnClickListener(this);
+            }
         }
 
         setContentView(scrollView);
diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
index d59dc60..0b7129a 100644
--- a/tools/aapt2/integration-tests/StaticLibOne/Android.mk
+++ b/tools/aapt2/integration-tests/StaticLibOne/Android.mk
@@ -22,4 +22,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+# We need this to compile the Java sources of AaptTestStaticLibTwo using javac.
+LOCAL_JAR_EXCLUDE_FILES := none
 include $(BUILD_STATIC_JAVA_LIBRARY)
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 53adb41..5a6a00f 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
@@ -97,7 +97,6 @@
 
     @Override
     public void repositionChild(IWindow window, int left, int top, int right, int bottom,
-            int requestedWidth, int requestedHeight,
             long deferTransactionUntilFrame, Rect outFrame) {
         // pass for now.
         return;
diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
index 035317e..4c38c9b 100644
--- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
+++ b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+
 /**
  * Record of energy and activity information from controller and
  * underlying wifi stack state. Timestamp the record with elapsed
@@ -44,6 +46,11 @@
     /**
      * @hide
      */
+    public long[] mControllerTxTimePerLevelMs;
+
+    /**
+     * @hide
+     */
     public long mControllerRxTimeMs;
 
     /**
@@ -62,10 +69,12 @@
     public static final int STACK_STATE_STATE_IDLE = 3;
 
     public WifiActivityEnergyInfo(long timestamp, int stackState,
-                                  long txTime, long rxTime, long idleTime, long energyUsed) {
+                                  long txTime, long[] txTimePerLevel, long rxTime, long idleTime,
+                                  long energyUsed) {
         mTimestamp = timestamp;
         mStackState = stackState;
         mControllerTxTimeMs = txTime;
+        mControllerTxTimePerLevelMs = txTimePerLevel;
         mControllerRxTimeMs = rxTime;
         mControllerIdleTimeMs = idleTime;
         mControllerEnergyUsed = energyUsed;
@@ -77,6 +86,7 @@
             + " timestamp=" + mTimestamp
             + " mStackState=" + mStackState
             + " mControllerTxTimeMs=" + mControllerTxTimeMs
+            + " mControllerTxTimePerLevelMs=" + Arrays.toString(mControllerTxTimePerLevelMs)
             + " mControllerRxTimeMs=" + mControllerRxTimeMs
             + " mControllerIdleTimeMs=" + mControllerIdleTimeMs
             + " mControllerEnergyUsed=" + mControllerEnergyUsed
@@ -89,11 +99,12 @@
             long timestamp = in.readLong();
             int stackState = in.readInt();
             long txTime = in.readLong();
+            long[] txTimePerLevel = in.createLongArray();
             long rxTime = in.readLong();
             long idleTime = in.readLong();
             long energyUsed = in.readLong();
             return new WifiActivityEnergyInfo(timestamp, stackState,
-                    txTime, rxTime, idleTime, energyUsed);
+                    txTime, txTimePerLevel, rxTime, idleTime, energyUsed);
         }
         public WifiActivityEnergyInfo[] newArray(int size) {
             return new WifiActivityEnergyInfo[size];
@@ -104,6 +115,7 @@
         out.writeLong(mTimestamp);
         out.writeInt(mStackState);
         out.writeLong(mControllerTxTimeMs);
+        out.writeLongArray(mControllerTxTimePerLevelMs);
         out.writeLong(mControllerRxTimeMs);
         out.writeLong(mControllerIdleTimeMs);
         out.writeLong(mControllerEnergyUsed);
@@ -128,6 +140,16 @@
     }
 
     /**
+     * @return tx time at power level provided in ms
+     */
+    public long getControllerTxTimeMillisAtLevel(int level) {
+        if (level < mControllerTxTimePerLevelMs.length) {
+            return mControllerTxTimePerLevelMs[level];
+        }
+        return 0;
+    }
+
+    /**
      * @return rx time in ms
      */
     public long getControllerRxTimeMillis() {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 06dea07..fb2bdd4 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -938,6 +938,15 @@
         private boolean mSeenInLastQualifiedNetworkSelection;
 
         /**
+         * Boolean indicating if we have ever successfully connected to this network.
+         *
+         * This value will be set to true upon a successful connection.
+         * This value will be set to false if a previous value was not stored in the config or if
+         * the credentials are updated (ex. a password change).
+         */
+        private boolean mHasEverConnected;
+
+        /**
          * set whether this network is visible in latest Qualified Network Selection
          * @param seen value set to candidate
          */
@@ -1027,7 +1036,18 @@
             return QUALITY_NETWORK_SELECTION_STATUS[mStatus];
         }
 
-        private NetworkSelectionStatus() {};
+        public void setHasEverConnected(boolean value) {
+            mHasEverConnected = value;
+        }
+
+        public boolean getHasEverConnected() {
+            return mHasEverConnected;
+        }
+
+        private NetworkSelectionStatus() {
+            // previously stored configs will not have this parameter, so we default to false.
+            mHasEverConnected = false;
+        };
 
         /**
          * @param reason specific error reason
@@ -1226,6 +1246,7 @@
             mNetworkSelectionBSSID = source.mNetworkSelectionBSSID;
             setConnectChoice(source.getConnectChoice());
             setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
+            setHasEverConnected(source.getHasEverConnected());
         }
 
         public void writeToParcel(Parcel dest) {
@@ -1244,6 +1265,7 @@
             } else {
                 dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
             }
+            dest.writeInt(getHasEverConnected() ? 1 : 0);
         }
 
         public void readFromParcel(Parcel in) {
@@ -1262,6 +1284,7 @@
                 setConnectChoice(null);
                 setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
             }
+            setHasEverConnected(in.readInt() != 0);
         }
     }
 
@@ -1389,6 +1412,8 @@
             sbuf.append(" connect choice set time: ").append(mNetworkSelectionStatus
                     .getConnectChoiceTimestamp());
         }
+        sbuf.append(" hasEverConnected: ")
+                .append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
 
         if (this.numAssociation > 0) {
             sbuf.append(" numAssociation ").append(this.numAssociation).append("\n");
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 9f8af6e..8d5efba 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -320,7 +320,8 @@
             if (!TextUtils.isEmpty(unicode)) {
                 return "\"" + unicode + "\"";
             } else {
-                return mWifiSsid.getHexString();
+                String hex = mWifiSsid.getHexString();
+                return (hex != null) ? hex : WifiSsid.NONE;
             }
         }
         return WifiSsid.NONE;
diff --git a/wifi/java/android/net/wifi/WifiLinkLayerStats.java b/wifi/java/android/net/wifi/WifiLinkLayerStats.java
index 1de4fd8..edd400b 100644
--- a/wifi/java/android/net/wifi/WifiLinkLayerStats.java
+++ b/wifi/java/android/net/wifi/WifiLinkLayerStats.java
@@ -19,6 +19,8 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 
+import java.util.Arrays;
+
 /**
  * A class representing link layer statistics collected over a Wifi Interface.
  */
@@ -101,6 +103,8 @@
     /** {@hide} */
     public int tx_time;
     /** {@hide} */
+    public int[] tx_time_per_level;
+    /** {@hide} */
     public int rx_time;
     /** {@hide} */
     public int on_time_scan;
@@ -141,9 +145,10 @@
                 .append(" lost=").append(Long.toString(this.lostmpdu_vo))
                 .append(" retries=").append(Long.toString(this.retries_vo)).append('\n');
         sbuf.append(" on_time : ").append(Integer.toString(this.on_time))
-                .append(" tx_time=").append(Integer.toString(this.tx_time))
                 .append(" rx_time=").append(Integer.toString(this.rx_time))
-                .append(" scan_time=").append(Integer.toString(this.on_time_scan)).append('\n');
+                .append(" scan_time=").append(Integer.toString(this.on_time_scan)).append('\n')
+                .append(" tx_time=").append(Integer.toString(this.tx_time))
+                .append(" tx_time_per_level=" + Arrays.toString(tx_time_per_level));
         return sbuf.toString();
     }
 
@@ -179,6 +184,7 @@
         dest.writeString(BSSID);
         dest.writeInt(on_time);
         dest.writeInt(tx_time);
+        dest.writeIntArray(tx_time_per_level);
         dest.writeInt(rx_time);
         dest.writeInt(on_time_scan);
     }
@@ -192,6 +198,7 @@
                 stats.BSSID = in.readString();
                 stats.on_time = in.readInt();
                 stats.tx_time = in.readInt();
+                stats.tx_time_per_level = in.createIntArray();
                 stats.rx_time = in.readInt();
                 stats.on_time_scan = in.readInt();
                 return stats;
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 508bdff..bd4220f 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.WorkSource;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -661,12 +662,26 @@
      *                 scans should also not share this object.
      */
     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
+        startBackgroundScan(settings, listener, null);
+    }
+
+    /** start wifi scan in background
+     * @param settings specifies various parameters for the scan; for more information look at
+     * {@link ScanSettings}
+     * @param workSource WorkSource to blame for power usage
+     * @param listener specifies the object to report events to. This object is also treated as a
+     *                 key for this scan, and must also be specified to cancel the scan. Multiple
+     *                 scans should also not share this object.
+     */
+    public void startBackgroundScan(ScanSettings settings, ScanListener listener,
+            WorkSource workSource) {
         Preconditions.checkNotNull(listener, "listener cannot be null");
         int key = addListener(listener);
         if (key == INVALID_KEY) return;
         validateChannel();
         sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, settings);
     }
+
     /**
      * stop an ongoing wifi scan
      * @param listener specifies which scan to cancel; must be same object as passed in {@link
@@ -698,6 +713,19 @@
      *                 scans should also not share this object.
      */
     public void startScan(ScanSettings settings, ScanListener listener) {
+        startScan(settings, listener, null);
+    }
+
+    /**
+     * starts a single scan and reports results asynchronously
+     * @param settings specifies various parameters for the scan; for more information look at
+     * {@link ScanSettings}
+     * @param workSource WorkSource to blame for power usage
+     * @param listener specifies the object to report events to. This object is also treated as a
+     *                 key for this scan, and must also be specified to cancel the scan. Multiple
+     *                 scans should also not share this object.
+     */
+    public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
         Preconditions.checkNotNull(listener, "listener cannot be null");
         int key = addListener(listener);
         if (key == INVALID_KEY) return;
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index f8ba95d..c53cd3c 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -205,7 +205,7 @@
         for (int i = 0; i < octets.size(); i++) {
             out += String.format(Locale.US, "%02x", ssidbytes[i]);
         }
-        return out;
+        return (octets.size() > 0) ? out : null;
     }
 
     /** Implement the Parcelable interface {@hide} */