Merge "Removed use of leanback mode in ActivityManager."
diff --git a/Android.mk b/Android.mk
index 9029f4e..5713f3e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -686,7 +686,7 @@
 # Common sources for doc check and api check
 common_src_files := \
 	$(call find-other-html-files, $(html_dirs)) \
-	$(addprefix ../../libcore/, $(libcore_to_document)) \
+	$(addprefix ../../, $(libcore_to_document)) \
 	$(addprefix ../../external/junit/, $(junit_to_document))
 
 # These are relative to frameworks/base
diff --git a/api/current.txt b/api/current.txt
index a1b898f..f75ef0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1395,6 +1395,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -1420,7 +1421,6 @@
     field public static final int windowNoTitle = 16842838; // 0x1010056
     field public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
-    field public static final int windowResizingBackground = 16844035; // 0x1010503
     field public static final int windowReturnTransition = 16843950; // 0x10104ae
     field public static final int windowSharedElementEnterTransition = 16843833; // 0x1010439
     field public static final int windowSharedElementExitTransition = 16843834; // 0x101043a
@@ -25035,6 +25035,7 @@
     field public static final int OUTGOING_TYPE = 2; // 0x2
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -35332,8 +35333,10 @@
     field public static final int KEYCODE_CLEAR = 28; // 0x1c
     field public static final int KEYCODE_COMMA = 55; // 0x37
     field public static final int KEYCODE_CONTACTS = 207; // 0xcf
+    field public static final int KEYCODE_COPY = 278; // 0x116
     field public static final int KEYCODE_CTRL_LEFT = 113; // 0x71
     field public static final int KEYCODE_CTRL_RIGHT = 114; // 0x72
+    field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
@@ -35451,6 +35454,7 @@
     field public static final int KEYCODE_PAGE_DOWN = 93; // 0x5d
     field public static final int KEYCODE_PAGE_UP = 92; // 0x5c
     field public static final int KEYCODE_PAIRING = 225; // 0xe1
+    field public static final int KEYCODE_PASTE = 279; // 0x117
     field public static final int KEYCODE_PERIOD = 56; // 0x38
     field public static final int KEYCODE_PICTSYMBOLS = 94; // 0x5e
     field public static final int KEYCODE_PLUS = 81; // 0x51
@@ -39231,6 +39235,7 @@
     method public abstract java.lang.String getDefaultTextEncodingName();
     method public static java.lang.String getDefaultUserAgent(android.content.Context);
     method public abstract deprecated android.webkit.WebSettings.ZoomDensity getDefaultZoom();
+    method public abstract int getDisabledActionModeMenuItems();
     method public abstract boolean getDisplayZoomControls();
     method public abstract boolean getDomStorageEnabled();
     method public abstract java.lang.String getFantasyFontFamily();
@@ -39274,6 +39279,7 @@
     method public abstract void setDefaultFontSize(int);
     method public abstract void setDefaultTextEncodingName(java.lang.String);
     method public abstract deprecated void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
+    method public abstract void setDisabledActionModeMenuItems(int);
     method public abstract void setDisplayZoomControls(boolean);
     method public abstract void setDomStorageEnabled(boolean);
     method public abstract deprecated void setEnableSmoothTransition(boolean);
@@ -39313,6 +39319,10 @@
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
     field public static final deprecated int LOAD_NORMAL = 0; // 0x0
     field public static final int LOAD_NO_CACHE = 2; // 0x2
+    field public static final int MENU_ITEM_NONE = 0; // 0x0
+    field public static final int MENU_ITEM_PROCESS_TEXT = 4; // 0x4
+    field public static final int MENU_ITEM_SHARE = 1; // 0x1
+    field public static final int MENU_ITEM_WEB_SEARCH = 2; // 0x2
     field public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; // 0x0
     field public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; // 0x2
     field public static final int MIXED_CONTENT_NEVER_ALLOW = 1; // 0x1
diff --git a/api/system-current.txt b/api/system-current.txt
index 60d57c6..c4c6f1c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1491,6 +1491,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -1516,7 +1517,6 @@
     field public static final int windowNoTitle = 16842838; // 0x1010056
     field public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
-    field public static final int windowResizingBackground = 16844035; // 0x1010503
     field public static final int windowReturnTransition = 16843950; // 0x10104ae
     field public static final int windowSharedElementEnterTransition = 16843833; // 0x1010439
     field public static final int windowSharedElementExitTransition = 16843834; // 0x101043a
@@ -26993,6 +26993,7 @@
     field public static final int OUTGOING_TYPE = 2; // 0x2
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+    field public static final java.lang.String POST_DIAL_DIGITS = "post_dial_digits";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -37629,8 +37630,10 @@
     field public static final int KEYCODE_CLEAR = 28; // 0x1c
     field public static final int KEYCODE_COMMA = 55; // 0x37
     field public static final int KEYCODE_CONTACTS = 207; // 0xcf
+    field public static final int KEYCODE_COPY = 278; // 0x116
     field public static final int KEYCODE_CTRL_LEFT = 113; // 0x71
     field public static final int KEYCODE_CTRL_RIGHT = 114; // 0x72
+    field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
@@ -37748,6 +37751,7 @@
     field public static final int KEYCODE_PAGE_DOWN = 93; // 0x5d
     field public static final int KEYCODE_PAGE_UP = 92; // 0x5c
     field public static final int KEYCODE_PAIRING = 225; // 0xe1
+    field public static final int KEYCODE_PASTE = 279; // 0x117
     field public static final int KEYCODE_PERIOD = 56; // 0x38
     field public static final int KEYCODE_PICTSYMBOLS = 94; // 0x5e
     field public static final int KEYCODE_PLUS = 81; // 0x51
@@ -41593,6 +41597,7 @@
     method public abstract java.lang.String getDefaultTextEncodingName();
     method public static java.lang.String getDefaultUserAgent(android.content.Context);
     method public abstract deprecated android.webkit.WebSettings.ZoomDensity getDefaultZoom();
+    method public abstract int getDisabledActionModeMenuItems();
     method public abstract boolean getDisplayZoomControls();
     method public abstract boolean getDomStorageEnabled();
     method public abstract java.lang.String getFantasyFontFamily();
@@ -41642,6 +41647,7 @@
     method public abstract void setDefaultFontSize(int);
     method public abstract void setDefaultTextEncodingName(java.lang.String);
     method public abstract deprecated void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
+    method public abstract void setDisabledActionModeMenuItems(int);
     method public abstract void setDisplayZoomControls(boolean);
     method public abstract void setDomStorageEnabled(boolean);
     method public abstract deprecated void setEnableSmoothTransition(boolean);
@@ -41686,6 +41692,10 @@
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
     field public static final deprecated int LOAD_NORMAL = 0; // 0x0
     field public static final int LOAD_NO_CACHE = 2; // 0x2
+    field public static final int MENU_ITEM_NONE = 0; // 0x0
+    field public static final int MENU_ITEM_PROCESS_TEXT = 4; // 0x4
+    field public static final int MENU_ITEM_SHARE = 1; // 0x1
+    field public static final int MENU_ITEM_WEB_SEARCH = 2; // 0x2
     field public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; // 0x0
     field public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; // 0x2
     field public static final int MIXED_CONTENT_NEVER_ALLOW = 1; // 0x1
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index df7e586..201afee 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -108,13 +108,14 @@
      * of a key character map for a particular keyboard layout.  The label on the receiver
      * is used to name the collection of keyboard layouts provided by this receiver in the
      * keyboard layout settings.
-     * <pre></code>
+     * <pre><code>
      * &lt;?xml version="1.0" encoding="utf-8"?>
      * &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
      *     &lt;keyboard-layout android:name="keyboard_layout_english_us"
      *             android:label="@string/keyboard_layout_english_us_label"
      *             android:keyboardLayout="@raw/keyboard_layout_english_us" />
      * &lt;/keyboard-layouts>
+     * </pre></code>
      * </p><p>
      * The <code>android:name</code> attribute specifies an identifier by which
      * the keyboard layout will be known in the package.
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index dc2aa3a..d1000ad 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1410,9 +1410,11 @@
     }
 
     private void removeMessages() {
-        mHandler.removeMessages(MSG_REPEAT);
-        mHandler.removeMessages(MSG_LONGPRESS);
-        mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        if (mHandler != null) {
+            mHandler.removeMessages(MSG_REPEAT);
+            mHandler.removeMessages(MSG_LONGPRESS);
+            mHandler.removeMessages(MSG_SHOW_PREVIEW);
+        }
     }
 
     @Override
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 342f8c7..4b63c36 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -38,7 +38,6 @@
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
@@ -406,6 +405,14 @@
         public static final String SUB_ID = "sub_id";
 
         /**
+         * The post-dial portion of a dialed number, including any digits dialed after a
+         * {@link TelecomManager#DTMF_CHARACTER_PAUSE} or a {@link
+         * TelecomManager#DTMF_CHARACTER_WAIT} and these characters themselves.
+         * <P>Type: TEXT</P>
+         */
+        public static final String POST_DIAL_DIGITS = "post_dial_digits";
+
+        /**
          * If a successful call is made that is longer than this duration, update the phone number
          * in the ContactsProvider with the normalized version of the number, based on the user's
          * current country code.
@@ -436,7 +443,7 @@
         public static Uri addCall(CallerInfo ci, Context context, String number,
                 int presentation, int callType, int features, PhoneAccountHandle accountHandle,
                 long start, int duration, Long dataUsage) {
-            return addCall(ci, context, number, presentation, callType, features, accountHandle,
+            return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
                     start, duration, dataUsage, false, false);
         }
 
@@ -466,10 +473,11 @@
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                                  int presentation, int callType, int features, PhoneAccountHandle accountHandle,
-                                  long start, int duration, Long dataUsage, boolean addForAllUsers) {
-            return addCall(ci, context, number, presentation, callType, features, accountHandle,
-                    start, duration, dataUsage, addForAllUsers, false);
+                String postDialDigits, int presentation, int callType, int features,
+                PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+                boolean addForAllUsers) {
+            return addCall(ci, context, number, postDialDigits, presentation, callType, features,
+                    accountHandle, start, duration, dataUsage, addForAllUsers, false);
         }
 
         /**
@@ -479,6 +487,8 @@
          * if the contact is unknown.
          * @param context the context used to get the ContentResolver
          * @param number the phone number to be added to the calls db
+         * @param postDialDigits the post-dial digits that were dialed after the number,
+         *        if it was outgoing. Otherwise it is ''.
          * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
          *        is set by the network and denotes the number presenting rules for
          *        "allowed", "payphone", "restricted" or "unknown"
@@ -499,8 +509,9 @@
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
-                long start, int duration, Long dataUsage, boolean addForAllUsers, boolean is_read) {
+                String postDialDigits, int presentation, int callType, int features,
+                PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
+                boolean addForAllUsers, boolean is_read) {
             final ContentResolver resolver = context.getContentResolver();
             int numberPresentation = PRESENTATION_ALLOWED;
 
@@ -551,6 +562,7 @@
             ContentValues values = new ContentValues(6);
 
             values.put(NUMBER, number);
+            values.put(POST_DIAL_DIGITS, postDialDigits);
             values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
             values.put(TYPE, Integer.valueOf(callType));
             values.put(FEATURES, features);
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index 379651e..0d5c135 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -37,6 +37,11 @@
  */
 public final class LocaleList {
     private final Locale[] mList;
+    // This is a comma-separated list of the locales in the LocaleList created at construction time,
+    // basically the result of running each locale's toLanguageTag() method and concatenating them
+    // with commas in between.
+    private final String mStringRepresentation;
+
     private static final Locale[] sEmptyList = new Locale[0];
     private static final LocaleList sEmptyLocaleList = new LocaleList();
 
@@ -95,15 +100,9 @@
         return sb.toString();
     }
 
+    @NonNull
     public String toLanguageTags() {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < mList.length; ++i) {
-            sb.append(mList[i].toLanguageTag());
-            if (i < mList.length - 1) {
-                sb.append(',');
-            }
-        }
-        return sb.toString();
+        return mStringRepresentation;
     }
 
     /**
@@ -112,6 +111,7 @@
      */
     public LocaleList() {
         mList = sEmptyList;
+        mStringRepresentation = "";
     }
 
     /**
@@ -121,9 +121,11 @@
     public LocaleList(@Nullable Locale locale) {
         if (locale == null) {
             mList = sEmptyList;
+            mStringRepresentation = "";
         } else {
             mList = new Locale[1];
             mList[0] = (Locale) locale.clone();
+            mStringRepresentation = locale.toLanguageTag();
         }
     }
 
@@ -134,9 +136,11 @@
     public LocaleList(@Nullable Locale[] list) {
         if (list == null || list.length == 0) {
             mList = sEmptyList;
+            mStringRepresentation = "";
         } else {
             final Locale[] localeList = new Locale[list.length];
             final HashSet<Locale> seenLocales = new HashSet<Locale>();
+            final StringBuilder sb = new StringBuilder();
             for (int i = 0; i < list.length; ++i) {
                 final Locale l = list[i];
                 if (l == null) {
@@ -144,11 +148,17 @@
                 } else if (seenLocales.contains(l)) {
                     throw new IllegalArgumentException();
                 } else {
-                    seenLocales.add(l);
-                    localeList[i] = (Locale) l.clone();
+                    final Locale localeClone = (Locale) l.clone();
+                    localeList[i] = localeClone;
+                    sb.append(localeClone.toLanguageTag());
+                    if (i < list.length - 1) {
+                        sb.append(',');
+                    }
+                    seenLocales.add(localeClone);
                 }
             }
             mList = localeList;
+            mStringRepresentation = sb.toString();
         }
     }
 
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 954dcfb..cebb8f0 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -660,7 +660,7 @@
             // and http://www.spaceroots.org/documents/ellipse/node22.html
 
             // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
 
             double eta1 = start;
             double cosTheta = Math.cos(theta);
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1c20cab..55cf56f 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -790,8 +790,14 @@
     public static final int KEYCODE_MEDIA_STEP_BACKWARD = 275;
     /** Key code constant: put device to sleep unless a wakelock is held. */
     public static final int KEYCODE_SOFT_SLEEP = 276;
+    /** Key code constant: Cut key. */
+    public static final int KEYCODE_CUT = 277;
+    /** Key code constant: Copy key. */
+    public static final int KEYCODE_COPY = 278;
+    /** Key code constant: Paste key. */
+    public static final int KEYCODE_PASTE = 279;
 
-    private static final int LAST_KEYCODE = KEYCODE_SOFT_SLEEP;
+    private static final int LAST_KEYCODE = KEYCODE_PASTE;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index d0dde00..1da305f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -43,6 +43,8 @@
     private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
     private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
 
+    private boolean mIsAllWindowsCached;
+
     private final SparseArray<AccessibilityWindowInfo> mWindowCache =
             new SparseArray<>();
 
@@ -52,6 +54,24 @@
     private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
             new SparseArray<>();
 
+    public void setWindows(List<AccessibilityWindowInfo> windows) {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Set windows");
+            }
+            clearWindowCache();
+            if (windows == null) {
+                return;
+            }
+            final int windowCount = windows.size();
+            for (int i = 0; i < windowCount; i++) {
+                final AccessibilityWindowInfo window = windows.get(i);
+                addWindow(window);
+            }
+            mIsAllWindowsCached = true;
+        }
+    }
+
     public void addWindow(AccessibilityWindowInfo window) {
         synchronized (mLock) {
             if (DEBUG) {
@@ -186,6 +206,10 @@
 
     public List<AccessibilityWindowInfo> getWindows() {
         synchronized (mLock) {
+            if (!mIsAllWindowsCached) {
+                return null;
+            }
+
             final int windowCount = mWindowCache.size();
             if (windowCount > 0) {
                 // Careful to return the windows in a decreasing layer order.
@@ -280,12 +304,7 @@
             if (DEBUG) {
                 Log.i(LOG_TAG, "clear()");
             }
-            final int windowCount = mWindowCache.size();
-            for (int i = windowCount - 1; i >= 0; i--) {
-                AccessibilityWindowInfo window = mWindowCache.valueAt(i);
-                window.recycle();
-                mWindowCache.removeAt(i);
-            }
+            clearWindowCache();
             final int nodesForWindowCount = mNodeCache.size();
             for (int i = 0; i < nodesForWindowCount; i++) {
                 final int windowId = mNodeCache.keyAt(i);
@@ -297,6 +316,16 @@
         }
     }
 
+    private void clearWindowCache() {
+        final int windowCount = mWindowCache.size();
+        for (int i = windowCount - 1; i >= 0; i--) {
+            AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+            window.recycle();
+            mWindowCache.removeAt(i);
+        }
+        mIsAllWindowsCached = false;
+    }
+
     private void clearNodesForWindowLocked(int windowId) {
         if (DEBUG) {
             Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index b49cbc6..1406fbd 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -228,11 +228,7 @@
                 windows = connection.getWindows();
                 Binder.restoreCallingIdentity(identityToken);
                 if (windows != null) {
-                    final int windowCount = windows.size();
-                    for (int i = 0; i < windowCount; i++) {
-                        AccessibilityWindowInfo window = windows.get(i);
-                        sAccessibilityCache.addWindow(window);
-                    }
+                    sAccessibilityCache.setWindows(windows);
                     return windows;
                 }
             } else {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index f62b4cc..9442226 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1363,4 +1363,50 @@
      * offscreen but attached to a window.
      */
     public abstract boolean getOffscreenPreRaster();
+
+    /**
+     * Disables the action mode menu items according to {@code menuItems} flag.
+     * @param menuItems an integer field flag for the menu items to be disabled.
+     */
+    public abstract void setDisabledActionModeMenuItems(int menuItems);
+
+    /**
+     * Gets the action mode menu items that are disabled, expressed in an integer field flag.
+     * The default value is {@link #MENU_ITEM_NONE}
+     *
+     * @return all the disabled menu item flags combined with OR.
+     */
+    public abstract int getDisabledActionModeMenuItems();
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * No menu items should be disabled.
+     */
+    public static final int MENU_ITEM_NONE = 0;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable menu item "Share".
+     */
+    public static final int MENU_ITEM_SHARE = 1 << 0;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable menu item "Web Search".
+     */
+    public static final int MENU_ITEM_WEB_SEARCH = 1 << 1;
+
+    /**
+     * Used with {@link #setDisabledActionModeMenuItems}.
+     *
+     * Disable all the action mode menu items for text processing.
+     * By default WebView searches for activities that are able to handle
+     * {@link android.content.Intent#ACTION_PROCESS_TEXT} and show them in the
+     * action mode menu. If this flag is set via {@link
+     * #setDisabledActionModeMenuItems}, these menu items will be disabled.
+     */
+    public static final int MENU_ITEM_PROCESS_TEXT = 1 << 2;
 }
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 986c0f8..8e5af79 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -29,6 +29,8 @@
 
 import java.util.Locale;
 
+import libcore.icu.LocaleData;
+
 /**
  * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
  * <p>
@@ -303,6 +305,16 @@
         void onValidationChanged(boolean valid);
     }
 
+    static String[] getAmPmStrings(Context context) {
+        final Locale locale = context.getResources().getConfiguration().locale;
+        final LocaleData d = LocaleData.get(locale);
+
+        final String[] result = new String[2];
+        result[0] = d.amPm[0].length() > 4 ? d.narrowAm : d.amPm[0];
+        result[1] = d.amPm[1].length() > 4 ? d.narrowPm : d.amPm[1];
+        return result;
+    }
+
     /**
      * An abstract class which can be used as a start for TimePicker implementations
      */
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 6d41c1d..4dc5fd3e 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -29,21 +29,22 @@
 import android.text.format.DateUtils;
 import android.text.style.TtsSpan;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.StateSet;
 import android.view.HapticFeedbackConstants;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.R;
+import com.android.internal.widget.NumericTextView;
+import com.android.internal.widget.NumericTextView.OnValueChangedListener;
 
-import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Locale;
 
@@ -52,7 +53,12 @@
  */
 class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements
         RadialTimePickerView.OnValueSelectedListener {
-    private static final String TAG = "TimePickerClockDelegate";
+    /**
+     * Delay in milliseconds before valid but potentially incomplete, for
+     * example "1" but not "12", keyboard edits are propagated from the
+     * hour / minute fields to the radial picker.
+     */
+    private static final long DELAY_COMMIT_MILLIS = 2000;
 
     // Index used by RadialPickerLayout
     private static final int HOUR_INDEX = 0;
@@ -61,9 +67,6 @@
     // NOT a real index for the purpose of what's showing.
     private static final int AMPM_INDEX = 2;
 
-    // Also NOT a real index, just used for keyboard mode.
-    private static final int ENABLE_PICKER_INDEX = 3;
-
     private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
 
@@ -74,18 +77,14 @@
 
     private static final int HOURS_IN_HALF_DAY = 12;
 
-    private final View mHeaderView;
-    private final TextView mHourView;
-    private final TextView mMinuteView;
+    private final NumericTextView mHourView;
+    private final NumericTextView mMinuteView;
     private final View mAmPmLayout;
-    private final CheckedTextView mAmLabel;
-    private final CheckedTextView mPmLabel;
+    private final RadioButton mAmLabel;
+    private final RadioButton mPmLabel;
     private final RadialTimePickerView mRadialTimePickerView;
     private final TextView mSeparatorView;
 
-    private final String mAmText;
-    private final String mPmText;
-
     private boolean mIsEnabled = true;
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
@@ -93,20 +92,14 @@
     private boolean mIs24HourView;
     private boolean mIsAmPmAtStart;
 
-    // For hardware IME input.
-    private char mPlaceholderText;
-    private String mDoublePlaceholderText;
-    private String mDeletedKeyFormat;
-    private boolean mInKbMode;
-    private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
-    private Node mLegalTimesTree;
-    private int mAmKeyCode;
-    private int mPmKeyCode;
-
     // Accessibility strings.
     private String mSelectHours;
     private String mSelectMinutes;
 
+    // Localization data.
+    private boolean mHourFormatShowLeadingZero;
+    private boolean mHourFormatStartsAtZero;
+
     // Most recent time announcement values for accessibility.
     private CharSequence mLastAnnouncedText;
     private boolean mLastAnnouncedIsHour;
@@ -127,43 +120,42 @@
         mSelectHours = res.getString(R.string.select_hours);
         mSelectMinutes = res.getString(R.string.select_minutes);
 
-        String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context);
-        mAmText = amPmStrings[0];
-        mPmText = amPmStrings[1];
-
         final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                 R.layout.time_picker_material);
         final View mainView = inflater.inflate(layoutResourceId, delegator);
-
-        mHeaderView = mainView.findViewById(R.id.time_header);
+        final View headerView = mainView.findViewById(R.id.time_header);
+        headerView.setOnTouchListener(new NearestTouchDelegate());
 
         // Set up hour/minute labels.
-        mHourView = (TextView) mainView.findViewById(R.id.hours);
+        mHourView = (NumericTextView) mainView.findViewById(R.id.hours);
         mHourView.setOnClickListener(mClickListener);
+        mHourView.setOnFocusChangeListener(mFocusListener);
+        mHourView.setOnDigitEnteredListener(mDigitEnteredListener);
         mHourView.setAccessibilityDelegate(
                 new ClickActionDelegate(context, R.string.select_hours));
         mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
-        mMinuteView = (TextView) mainView.findViewById(R.id.minutes);
+        mMinuteView = (NumericTextView) mainView.findViewById(R.id.minutes);
         mMinuteView.setOnClickListener(mClickListener);
+        mMinuteView.setOnFocusChangeListener(mFocusListener);
+        mMinuteView.setOnDigitEnteredListener(mDigitEnteredListener);
         mMinuteView.setAccessibilityDelegate(
                 new ClickActionDelegate(context, R.string.select_minutes));
-
-        // Now that we have text appearances out of the way, make sure the hour
-        // and minute views are correctly sized.
-        mHourView.setMinWidth(computeStableWidth(mHourView, 24));
-        mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));
-
-        final SpannableStringBuilder amLabel = new SpannableStringBuilder()
-                .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0);
+        mMinuteView.setRange(0, 59);
 
         // Set up AM/PM labels.
         mAmPmLayout = mainView.findViewById(R.id.ampm_layout);
-        mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
+        mAmPmLayout.setOnTouchListener(new NearestTouchDelegate());
+
+        final String[] amPmStrings = TimePicker.getAmPmStrings(context);
+        mAmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.am_label);
         mAmLabel.setText(obtainVerbatim(amPmStrings[0]));
         mAmLabel.setOnClickListener(mClickListener);
-        mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
+        ensureMinimumTextWidth(mAmLabel);
+
+        mPmLabel = (RadioButton) mAmPmLayout.findViewById(R.id.pm_label);
         mPmLabel.setText(obtainVerbatim(amPmStrings[1]));
         mPmLabel.setOnClickListener(mClickListener);
+        ensureMinimumTextWidth(mPmLabel);
 
         // For the sake of backwards compatibility, attempt to extract the text
         // color from the header time text appearance. If it's set, we'll let
@@ -195,7 +187,7 @@
 
         // Set up header background, if available.
         if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) {
-            mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+            headerView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
         }
 
         a.recycle();
@@ -207,18 +199,66 @@
 
         mAllowAutoAdvance = true;
 
-        // Set up for keyboard mode.
-        mDoublePlaceholderText = res.getString(R.string.time_placeholder);
-        mDeletedKeyFormat = res.getString(R.string.deleted_key);
-        mPlaceholderText = mDoublePlaceholderText.charAt(0);
-        mAmKeyCode = mPmKeyCode = -1;
-        generateLegalTimesTree();
+        // Updates mHourFormat variables used below.
+        updateHourFormat(mCurrentLocale, mIs24HourView);
 
-        // Initialize with current time
-        final Calendar calendar = Calendar.getInstance(mCurrentLocale);
-        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
-        final int currentMinute = calendar.get(Calendar.MINUTE);
-        initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
+        // Update hour text field.
+        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+        final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
+        mHourView.setRange(minHour, maxHour);
+        mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+
+        // Initialize with current time.
+        mTempCalendar = Calendar.getInstance(mCurrentLocale);
+        final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
+        final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
+        initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+    }
+
+    /**
+     * Ensures that a TextView is wide enough to contain its text without
+     * wrapping or clipping. Measures the specified view and sets the minimum
+     * width to the view's desired width.
+     *
+     * @param v the text view to measure
+     */
+    private static void ensureMinimumTextWidth(TextView v) {
+        v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+        // Set both the TextView and the View version of minimum
+        // width because they are subtly different.
+        final int minWidth = v.getMeasuredWidth();
+        v.setMinWidth(minWidth);
+        v.setMinimumWidth(minWidth);
+    }
+
+    /**
+     * Determines how the hour should be formatted and updates member variables
+     * related to hour formatting.
+     *
+     * @param locale the locale in which the view is displayed
+     * @param is24Hour whether the view is in 24-hour (hour-of-day) mode
+     */
+    private void updateHourFormat(Locale locale, boolean is24Hour) {
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(
+                locale, is24Hour ? "Hm" : "hm");
+        final int lengthPattern = bestDateTimePattern.length();
+        boolean showLeadingZero = false;
+        char hourFormat = '\0';
+
+        for (int i = 0; i < lengthPattern; i++) {
+            final char c = bestDateTimePattern.charAt(i);
+            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+                hourFormat = c;
+                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+                    showLeadingZero = true;
+                }
+                break;
+            }
+        }
+
+        mHourFormatShowLeadingZero = showLeadingZero;
+        mHourFormatStartsAtZero = hourFormat == 'K' || hourFormat == 'H';
     }
 
     private static final CharSequence obtainVerbatim(String text) {
@@ -290,51 +330,24 @@
         }
     }
 
-    private int computeStableWidth(TextView v, int maxNumber) {
-        int maxWidth = 0;
-
-        for (int i = 0; i < maxNumber; i++) {
-            final String text = String.format("%02d", i);
-            v.setText(text);
-            v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-
-            final int width = v.getMeasuredWidth();
-            if (width > maxWidth) {
-                maxWidth = width;
-            }
-        }
-
-        return maxWidth;
-    }
-
     private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
         mInitialHourOfDay = hourOfDay;
         mInitialMinute = minute;
         mIs24HourView = is24HourView;
-        mInKbMode = false;
         updateUI(index);
     }
 
     private void setupListeners() {
-        mHeaderView.setOnKeyListener(mKeyListener);
-        mHeaderView.setOnFocusChangeListener(mFocusListener);
-        mHeaderView.setFocusable(true);
-
         mRadialTimePickerView.setOnValueSelectedListener(this);
     }
 
     private void updateUI(int index) {
-        // Update RadialPicker values
-        updateRadialPicker(index);
-        // Enable or disable the AM/PM view.
         updateHeaderAmPm();
-        // Update Hour and Minutes
         updateHeaderHour(mInitialHourOfDay, false);
-        // Update time separator
         updateHeaderSeparator();
-        // Update Minutes
         updateHeaderMinute(mInitialMinute, false);
-        // Invalidate everything
+        updateRadialPicker(index);
+
         mDelegator.invalidate();
     }
 
@@ -447,14 +460,11 @@
         if (is24HourView == mIs24HourView) {
             return;
         }
+
         mIs24HourView = is24HourView;
-        generateLegalTimesTree();
-        int hour = mRadialTimePickerView.getCurrentHour();
-        mInitialHourOfDay = hour;
-        updateHeaderHour(hour, false);
-        updateHeaderAmPm();
-        updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
-        mDelegator.invalidate();
+        mInitialHourOfDay = getCurrentHour();
+
+        updateUI(mRadialTimePickerView.getCurrentItemShowing());
     }
 
     /**
@@ -499,20 +509,14 @@
     @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
         return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
-                is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
+                is24HourView(), getCurrentItemShowing());
     }
 
     @Override
     public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-        setInKbMode(ss.inKbMode());
-        setTypedTimes(ss.getTypesTimes());
+        final SavedState ss = (SavedState) state;
         initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
         mRadialTimePickerView.invalidate();
-        if (mInKbMode) {
-            tryStartingKbMode(-1);
-            mHourView.invalidate();
-        }
     }
 
     @Override
@@ -543,33 +547,6 @@
     }
 
     /**
-     * Set whether in keyboard mode or not.
-     *
-     * @param inKbMode True means in keyboard mode.
-     */
-    private void setInKbMode(boolean inKbMode) {
-        mInKbMode = inKbMode;
-    }
-
-    /**
-     * @return true if in keyboard mode
-     */
-    private boolean inKbMode() {
-        return mInKbMode;
-    }
-
-    private void setTypedTimes(ArrayList<Integer> typeTimes) {
-        mTypedTimes = typeTimes;
-    }
-
-    /**
-     * @return an array of typed times
-     */
-    private ArrayList<Integer> getTypedTimes() {
-        return mTypedTimes;
-    }
-
-    /**
      * @return the index of the current item showing
      */
     private int getCurrentItemShowing() {
@@ -595,19 +572,14 @@
         private final int mHour;
         private final int mMinute;
         private final boolean mIs24HourMode;
-        private final boolean mInKbMode;
-        private final ArrayList<Integer> mTypedTimes;
         private final int mCurrentItemShowing;
 
         private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
-                           boolean isKbMode, ArrayList<Integer> typedTimes,
-                           int currentItemShowing) {
+                int currentItemShowing) {
             super(superState);
             mHour = hour;
             mMinute = minute;
             mIs24HourMode = is24HourMode;
-            mInKbMode = isKbMode;
-            mTypedTimes = typedTimes;
             mCurrentItemShowing = currentItemShowing;
         }
 
@@ -616,8 +588,6 @@
             mHour = in.readInt();
             mMinute = in.readInt();
             mIs24HourMode = (in.readInt() == 1);
-            mInKbMode = (in.readInt() == 1);
-            mTypedTimes = in.readArrayList(getClass().getClassLoader());
             mCurrentItemShowing = in.readInt();
         }
 
@@ -633,14 +603,6 @@
             return mIs24HourMode;
         }
 
-        public boolean inKbMode() {
-            return mInKbMode;
-        }
-
-        public ArrayList<Integer> getTypesTimes() {
-            return mTypedTimes;
-        }
-
         public int getCurrentItemShowing() {
             return mCurrentItemShowing;
         }
@@ -651,13 +613,11 @@
             dest.writeInt(mHour);
             dest.writeInt(mMinute);
             dest.writeInt(mIs24HourMode ? 1 : 0);
-            dest.writeInt(mInKbMode ? 1 : 0);
-            dest.writeList(mTypedTimes);
             dest.writeInt(mCurrentItemShowing);
         }
 
         @SuppressWarnings({"unused", "hiding"})
-        public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
             public SavedState createFromParcel(Parcel in) {
                 return new SavedState(in);
             }
@@ -703,12 +663,6 @@
             case AMPM_INDEX:
                 updateAmPmLabelStates(newValue);
                 break;
-            case ENABLE_PICKER_INDEX:
-                if (!isTypedTimeFullyLegal()) {
-                    mTypedTimes.clear();
-                }
-                finishKbMode();
-                break;
         }
 
         if (mOnTimeChangedListener != null) {
@@ -716,61 +670,41 @@
         }
     }
 
-    private void updateHeaderHour(int value, boolean announce) {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
-                (mIs24HourView) ? "Hm" : "hm");
-        final int lengthPattern = bestDateTimePattern.length();
-        boolean hourWithTwoDigit = false;
-        char hourFormat = '\0';
-        // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
-        // the hour format that we found.
-        for (int i = 0; i < lengthPattern; i++) {
-            final char c = bestDateTimePattern.charAt(i);
-            if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
-                hourFormat = c;
-                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
-                    hourWithTwoDigit = true;
-                }
-                break;
-            }
+    /**
+     * Converts hour-of-day (0-23) time into a localized hour number.
+     *
+     * @param hourOfDay the hour-of-day (0-23)
+     * @return a localized hour number
+     */
+    private int getLocalizedHour(int hourOfDay) {
+        if (!mIs24HourView) {
+            // Convert to hour-of-am-pm.
+            hourOfDay %= 12;
         }
-        final String format;
-        if (hourWithTwoDigit) {
-            format = "%02d";
-        } else {
-            format = "%d";
+
+        if (!mHourFormatStartsAtZero && hourOfDay == 0) {
+            // Convert to clock-hour (either of-day or of-am-pm).
+            hourOfDay = mIs24HourView ? 24 : 12;
         }
-        if (mIs24HourView) {
-            // 'k' means 1-24 hour
-            if (hourFormat == 'k' && value == 0) {
-                value = 24;
-            }
-        } else {
-            // 'K' means 0-11 hour
-            value = modulo12(value, hourFormat == 'K');
-        }
-        CharSequence text = String.format(format, value);
-        mHourView.setText(text);
+
+        return hourOfDay;
+    }
+
+    private void updateHeaderHour(int hourOfDay, boolean announce) {
+        final int localizedHour = getLocalizedHour(hourOfDay);
+        mHourView.setValue(localizedHour);
+
         if (announce) {
-            tryAnnounceForAccessibility(text, true);
+            tryAnnounceForAccessibility(mHourView.getText(), true);
         }
     }
 
-    private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
-        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
-            // TODO: Find a better solution, potentially live regions?
-            mDelegator.announceForAccessibility(text);
-            mLastAnnouncedText = text;
-            mLastAnnouncedIsHour = isHour;
-        }
-    }
+    private void updateHeaderMinute(int minuteOfHour, boolean announce) {
+        mMinuteView.setValue(minuteOfHour);
 
-    private static int modulo12(int n, boolean startWithZero) {
-        int value = n % 12;
-        if (value == 0 && !startWithZero) {
-            value = 12;
+        if (announce) {
+            tryAnnounceForAccessibility(mMinuteView.getText(), false);
         }
-        return value;
     }
 
     /**
@@ -812,14 +746,12 @@
         return -1;
     }
 
-    private void updateHeaderMinute(int value, boolean announceForAccessibility) {
-        if (value == 60) {
-            value = 0;
-        }
-        final CharSequence text = String.format(mCurrentLocale, "%02d", value);
-        mMinuteView.setText(text);
-        if (announceForAccessibility) {
-            tryAnnounceForAccessibility(text, false);
+    private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
+        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
+            // TODO: Find a better solution, potentially live regions?
+            mDelegator.announceForAccessibility(text);
+            mLastAnnouncedText = text;
+            mLastAnnouncedIsHour = isHour;
         }
     }
 
@@ -848,477 +780,82 @@
         mRadialTimePickerView.setAmOrPm(amOrPm);
     }
 
-    /**
-     * For keyboard mode, processes key events.
-     *
-     * @param keyCode the pressed key.
-     *
-     * @return true if the key was successfully processed, false otherwise.
-     */
-    private boolean processKeyUp(int keyCode) {
-        if (keyCode == KeyEvent.KEYCODE_DEL) {
-            if (mInKbMode) {
-                if (!mTypedTimes.isEmpty()) {
-                    int deleted = deleteLastTypedKey();
-                    String deletedKeyStr;
-                    if (deleted == getAmOrPmKeyCode(AM)) {
-                        deletedKeyStr = mAmText;
-                    } else if (deleted == getAmOrPmKeyCode(PM)) {
-                        deletedKeyStr = mPmText;
-                    } else {
-                        deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+    private final OnValueChangedListener mDigitEnteredListener = new OnValueChangedListener() {
+        @Override
+        public void onValueChanged(NumericTextView view, int value,
+                boolean isValid, boolean isFinished) {
+            final Runnable commitCallback;
+            final View nextFocusTarget;
+            if (view == mHourView) {
+                commitCallback = mCommitHour;
+                nextFocusTarget = view.isFocused() ? mMinuteView : null;
+            } else if (view == mMinuteView) {
+                commitCallback = mCommitMinute;
+                nextFocusTarget = null;
+            } else {
+                return;
+            }
+
+            view.removeCallbacks(commitCallback);
+
+            if (isValid) {
+                if (isFinished) {
+                    // Done with hours entry, make visual updates
+                    // immediately and move to next focus if needed.
+                    commitCallback.run();
+
+                    if (nextFocusTarget != null) {
+                        nextFocusTarget.requestFocus();
                     }
-                    mDelegator.announceForAccessibility(
-                            String.format(mDeletedKeyFormat, deletedKeyStr));
-                    updateDisplay(true);
-                }
-            }
-        } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
-                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
-                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
-                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
-                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
-                || (!mIs24HourView &&
-                (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
-            if (!mInKbMode) {
-                if (mRadialTimePickerView == null) {
-                    // Something's wrong, because time picker should definitely not be null.
-                    Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
-                    return true;
-                }
-                mTypedTimes.clear();
-                tryStartingKbMode(keyCode);
-                return true;
-            }
-            // We're already in keyboard mode.
-            if (addKeyIfLegal(keyCode)) {
-                updateDisplay(false);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Try to start keyboard mode with the specified key.
-     *
-     * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
-     * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
-     * key.
-     */
-    private void tryStartingKbMode(int keyCode) {
-        if (keyCode == -1 || addKeyIfLegal(keyCode)) {
-            mInKbMode = true;
-            onValidationChanged(false);
-            updateDisplay(false);
-            mRadialTimePickerView.setInputEnabled(false);
-        }
-    }
-
-    private boolean addKeyIfLegal(int keyCode) {
-        // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
-        // we'll need to see if AM/PM have been typed.
-        if ((mIs24HourView && mTypedTimes.size() == 4) ||
-                (!mIs24HourView && isTypedTimeFullyLegal())) {
-            return false;
-        }
-
-        mTypedTimes.add(keyCode);
-        if (!isTypedTimeLegalSoFar()) {
-            deleteLastTypedKey();
-            return false;
-        }
-
-        int val = getValFromKeyCode(keyCode);
-        mDelegator.announceForAccessibility(String.format("%d", val));
-        // Automatically fill in 0's if AM or PM was legally entered.
-        if (isTypedTimeFullyLegal()) {
-            if (!mIs24HourView && mTypedTimes.size() <= 3) {
-                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
-                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
-            }
-            onValidationChanged(true);
-        }
-
-        return true;
-    }
-
-    /**
-     * Traverse the tree to see if the keys that have been typed so far are legal as is,
-     * or may become legal as more keys are typed (excluding backspace).
-     */
-    private boolean isTypedTimeLegalSoFar() {
-        Node node = mLegalTimesTree;
-        for (int keyCode : mTypedTimes) {
-            node = node.canReach(keyCode);
-            if (node == null) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Check if the time that has been typed so far is completely legal, as is.
-     */
-    private boolean isTypedTimeFullyLegal() {
-        if (mIs24HourView) {
-            // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
-            // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
-            int[] values = getEnteredTime(null);
-            return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
-        } else {
-            // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
-            // legally added at specific times based on the tree's algorithm.
-            return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
-                    mTypedTimes.contains(getAmOrPmKeyCode(PM)));
-        }
-    }
-
-    private int deleteLastTypedKey() {
-        int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
-        if (!isTypedTimeFullyLegal()) {
-            onValidationChanged(false);
-        }
-        return deleted;
-    }
-
-    /**
-     * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
-     */
-    private void finishKbMode() {
-        mInKbMode = false;
-        if (!mTypedTimes.isEmpty()) {
-            int values[] = getEnteredTime(null);
-            mRadialTimePickerView.setCurrentHour(values[0]);
-            mRadialTimePickerView.setCurrentMinute(values[1]);
-            if (!mIs24HourView) {
-                mRadialTimePickerView.setAmOrPm(values[2]);
-            }
-            mTypedTimes.clear();
-        }
-        updateDisplay(false);
-        mRadialTimePickerView.setInputEnabled(true);
-    }
-
-    /**
-     * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
-     * empty, either show an empty display (filled with the placeholder text), or update from the
-     * timepicker's values.
-     *
-     * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
-     * Otherwise, revert to the timepicker's values.
-     */
-    private void updateDisplay(boolean allowEmptyDisplay) {
-        if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
-            int hour = mRadialTimePickerView.getCurrentHour();
-            int minute = mRadialTimePickerView.getCurrentMinute();
-            updateHeaderHour(hour, false);
-            updateHeaderMinute(minute, false);
-            if (!mIs24HourView) {
-                updateAmPmLabelStates(hour < 12 ? AM : PM);
-            }
-            setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
-            onValidationChanged(true);
-        } else {
-            boolean[] enteredZeros = {false, false};
-            int[] values = getEnteredTime(enteredZeros);
-            String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
-            String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
-            String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
-                    String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
-            String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
-                    String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
-            mHourView.setText(hourStr);
-            mHourView.setActivated(false);
-            mMinuteView.setText(minuteStr);
-            mMinuteView.setActivated(false);
-            if (!mIs24HourView) {
-                updateAmPmLabelStates(values[2]);
-            }
-        }
-    }
-
-    private int getValFromKeyCode(int keyCode) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_0:
-                return 0;
-            case KeyEvent.KEYCODE_1:
-                return 1;
-            case KeyEvent.KEYCODE_2:
-                return 2;
-            case KeyEvent.KEYCODE_3:
-                return 3;
-            case KeyEvent.KEYCODE_4:
-                return 4;
-            case KeyEvent.KEYCODE_5:
-                return 5;
-            case KeyEvent.KEYCODE_6:
-                return 6;
-            case KeyEvent.KEYCODE_7:
-                return 7;
-            case KeyEvent.KEYCODE_8:
-                return 8;
-            case KeyEvent.KEYCODE_9:
-                return 9;
-            default:
-                return -1;
-        }
-    }
-
-    /**
-     * Get the currently-entered time, as integer values of the hours and minutes typed.
-     *
-     * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
-     * may then be used for the caller to know whether zeros had been explicitly entered as either
-     * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
-     *
-     * @return A size-3 int array. The first value will be the hours, the second value will be the
-     * minutes, and the third will be either AM or PM.
-     */
-    private int[] getEnteredTime(boolean[] enteredZeros) {
-        int amOrPm = -1;
-        int startIndex = 1;
-        if (!mIs24HourView && isTypedTimeFullyLegal()) {
-            int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
-            if (keyCode == getAmOrPmKeyCode(AM)) {
-                amOrPm = AM;
-            } else if (keyCode == getAmOrPmKeyCode(PM)){
-                amOrPm = PM;
-            }
-            startIndex = 2;
-        }
-        int minute = -1;
-        int hour = -1;
-        for (int i = startIndex; i <= mTypedTimes.size(); i++) {
-            int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
-            if (i == startIndex) {
-                minute = val;
-            } else if (i == startIndex+1) {
-                minute += 10 * val;
-                if (enteredZeros != null && val == 0) {
-                    enteredZeros[1] = true;
-                }
-            } else if (i == startIndex+2) {
-                hour = val;
-            } else if (i == startIndex+3) {
-                hour += 10 * val;
-                if (enteredZeros != null && val == 0) {
-                    enteredZeros[0] = true;
+                } else {
+                    // May still be making changes. Postpone visual
+                    // updates to prevent distracting the user.
+                    view.postDelayed(commitCallback, DELAY_COMMIT_MILLIS);
                 }
             }
         }
+    };
 
-        return new int[] { hour, minute, amOrPm };
-    }
+    private final Runnable mCommitHour = new Runnable() {
+        @Override
+        public void run() {
+            setCurrentHour(mHourView.getValue());
+        }
+    };
 
-    /**
-     * Get the keycode value for AM and PM in the current language.
-     */
-    private int getAmOrPmKeyCode(int amOrPm) {
-        // Cache the codes.
-        if (mAmKeyCode == -1 || mPmKeyCode == -1) {
-            // Find the first character in the AM/PM text that is unique.
-            final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-            final CharSequence amText = mAmText.toLowerCase(mCurrentLocale);
-            final CharSequence pmText = mPmText.toLowerCase(mCurrentLocale);
-            final int N = Math.min(amText.length(), pmText.length());
-            for (int i = 0; i < N; i++) {
-                final char amChar = amText.charAt(i);
-                final char pmChar = pmText.charAt(i);
-                if (amChar != pmChar) {
-                    // There should be 4 events: a down and up for both AM and PM.
-                    final KeyEvent[] events = kcm.getEvents(new char[] { amChar, pmChar });
-                    if (events != null && events.length == 4) {
-                        mAmKeyCode = events[0].getKeyCode();
-                        mPmKeyCode = events[2].getKeyCode();
-                    } else {
-                        Log.e(TAG, "Unable to find keycodes for AM and PM.");
-                    }
-                    break;
+    private final Runnable mCommitMinute = new Runnable() {
+        @Override
+        public void run() {
+            setCurrentMinute(mMinuteView.getValue());
+        }
+    };
+
+    private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
+        @Override
+        public void onFocusChange(View v, boolean focused) {
+            if (focused) {
+                switch (v.getId()) {
+                    case R.id.am_label:
+                        setAmOrPm(AM);
+                        break;
+                    case R.id.pm_label:
+                        setAmOrPm(PM);
+                        break;
+                    case R.id.hours:
+                        setCurrentItemShowing(HOUR_INDEX, true, true);
+                        break;
+                    case R.id.minutes:
+                        setCurrentItemShowing(MINUTE_INDEX, true, true);
+                        break;
+                    default:
+                        // Failed to handle this click, don't vibrate.
+                        return;
                 }
+
+                tryVibrate();
             }
         }
-
-        if (amOrPm == AM) {
-            return mAmKeyCode;
-        } else if (amOrPm == PM) {
-            return mPmKeyCode;
-        }
-
-        return -1;
-    }
-
-    /**
-     * Create a tree for deciding what keys can legally be typed.
-     */
-    private void generateLegalTimesTree() {
-        // Create a quick cache of numbers to their keycodes.
-        final int k0 = KeyEvent.KEYCODE_0;
-        final int k1 = KeyEvent.KEYCODE_1;
-        final int k2 = KeyEvent.KEYCODE_2;
-        final int k3 = KeyEvent.KEYCODE_3;
-        final int k4 = KeyEvent.KEYCODE_4;
-        final int k5 = KeyEvent.KEYCODE_5;
-        final int k6 = KeyEvent.KEYCODE_6;
-        final int k7 = KeyEvent.KEYCODE_7;
-        final int k8 = KeyEvent.KEYCODE_8;
-        final int k9 = KeyEvent.KEYCODE_9;
-
-        // The root of the tree doesn't contain any numbers.
-        mLegalTimesTree = new Node();
-        if (mIs24HourView) {
-            // We'll be re-using these nodes, so we'll save them.
-            Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
-            Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            // The first digit must be followed by the second digit.
-            minuteFirstDigit.addChild(minuteSecondDigit);
-
-            // The first digit may be 0-1.
-            Node firstDigit = new Node(k0, k1);
-            mLegalTimesTree.addChild(firstDigit);
-
-            // When the first digit is 0-1, the second digit may be 0-5.
-            Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-            // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
-            Node thirdDigit = new Node(k6, k7, k8, k9);
-            // The time must now be finished. E.g. 0:55, 1:08.
-            secondDigit.addChild(thirdDigit);
-
-            // When the first digit is 0-1, the second digit may be 6-9.
-            secondDigit = new Node(k6, k7, k8, k9);
-            firstDigit.addChild(secondDigit);
-            // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // The first digit may be 2.
-            firstDigit = new Node(k2);
-            mLegalTimesTree.addChild(firstDigit);
-
-            // When the first digit is 2, the second digit may be 0-3.
-            secondDigit = new Node(k0, k1, k2, k3);
-            firstDigit.addChild(secondDigit);
-            // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
-            secondDigit.addChild(minuteFirstDigit);
-
-            // When the first digit is 2, the second digit may be 4-5.
-            secondDigit = new Node(k4, k5);
-            firstDigit.addChild(secondDigit);
-            // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
-            secondDigit.addChild(minuteSecondDigit);
-
-            // The first digit may be 3-9.
-            firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
-            mLegalTimesTree.addChild(firstDigit);
-            // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
-            firstDigit.addChild(minuteFirstDigit);
-        } else {
-            // We'll need to use the AM/PM node a lot.
-            // Set up AM and PM to respond to "a" and "p".
-            Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
-
-            // The first hour digit may be 1.
-            Node firstDigit = new Node(k1);
-            mLegalTimesTree.addChild(firstDigit);
-            // We'll allow quick input of on-the-hour times. E.g. 1pm.
-            firstDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit may be 0-2.
-            Node secondDigit = new Node(k0, k1, k2);
-            firstDigit.addChild(secondDigit);
-            // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
-            secondDigit.addChild(ampm);
-
-            // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
-            Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
-            secondDigit.addChild(thirdDigit);
-            // The time may be finished now. E.g. 1:02pm, 1:25am.
-            thirdDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
-            // the fourth digit may be 0-9.
-            Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            thirdDigit.addChild(fourthDigit);
-            // The time must be finished now. E.g. 10:49am, 12:40pm.
-            fourthDigit.addChild(ampm);
-
-            // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
-            thirdDigit = new Node(k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 1:08am, 1:26pm.
-            thirdDigit.addChild(ampm);
-
-            // When the first digit is 1, the second digit may be 3-5.
-            secondDigit = new Node(k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-
-            // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
-            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 1:39am, 1:50pm.
-            thirdDigit.addChild(ampm);
-
-            // The hour digit may be 2-9.
-            firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
-            mLegalTimesTree.addChild(firstDigit);
-            // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
-            firstDigit.addChild(ampm);
-
-            // When the first digit is 2-9, the second digit may be 0-5.
-            secondDigit = new Node(k0, k1, k2, k3, k4, k5);
-            firstDigit.addChild(secondDigit);
-
-            // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
-            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
-            secondDigit.addChild(thirdDigit);
-            // The time must be finished now. E.g. 2:57am, 9:30pm.
-            thirdDigit.addChild(ampm);
-        }
-    }
-
-    /**
-     * Simple node class to be used for traversal to check for legal times.
-     * mLegalKeys represents the keys that can be typed to get to the node.
-     * mChildren are the children that can be reached from this node.
-     */
-    private class Node {
-        private int[] mLegalKeys;
-        private ArrayList<Node> mChildren;
-
-        public Node(int... legalKeys) {
-            mLegalKeys = legalKeys;
-            mChildren = new ArrayList<Node>();
-        }
-
-        public void addChild(Node child) {
-            mChildren.add(child);
-        }
-
-        public boolean containsKey(int key) {
-            for (int i = 0; i < mLegalKeys.length; i++) {
-                if (mLegalKeys[i] == key) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        public Node canReach(int key) {
-            if (mChildren == null) {
-                return null;
-            }
-            for (Node child : mChildren) {
-                if (child.containsKey(key)) {
-                    return child;
-                }
-            }
-            return null;
-        }
-    }
+    };
 
     private final View.OnClickListener mClickListener = new View.OnClickListener() {
         @Override
@@ -1347,28 +884,55 @@
         }
     };
 
-    private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
-        @Override
-        public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-                return processKeyUp(keyCode);
+    /**
+     * Delegates unhandled touches in a view group to the nearest child view.
+     */
+    private static class NearestTouchDelegate implements View.OnTouchListener {
+            private View mInitialTouchTarget;
+
+            @Override
+            public boolean onTouch(View view, MotionEvent motionEvent) {
+                final int actionMasked = motionEvent.getActionMasked();
+                if (actionMasked == MotionEvent.ACTION_DOWN) {
+                    mInitialTouchTarget = findNearestChild((ViewGroup) view,
+                            (int) motionEvent.getX(), (int) motionEvent.getY());
+                }
+
+                final View child = mInitialTouchTarget;
+                if (child == null) {
+                    return false;
+                }
+
+                final float offsetX = view.getScrollX() - child.getLeft();
+                final float offsetY = view.getScrollY() - child.getTop();
+                motionEvent.offsetLocation(offsetX, offsetY);
+                final boolean handled = child.dispatchTouchEvent(motionEvent);
+                motionEvent.offsetLocation(-offsetX, -offsetY);
+
+                if (actionMasked == MotionEvent.ACTION_UP
+                        || actionMasked == MotionEvent.ACTION_CANCEL) {
+                    mInitialTouchTarget = null;
+                }
+
+                return handled;
             }
-            return false;
-        }
-    };
 
-    private final View.OnFocusChangeListener mFocusListener = new View.OnFocusChangeListener() {
-        @Override
-        public void onFocusChange(View v, boolean hasFocus) {
-            if (!hasFocus && mInKbMode && isTypedTimeFullyLegal()) {
-                finishKbMode();
+        private View findNearestChild(ViewGroup v, int x, int y) {
+            View bestChild = null;
+            int bestDist = Integer.MAX_VALUE;
 
-                if (mOnTimeChangedListener != null) {
-                    mOnTimeChangedListener.onTimeChanged(mDelegator,
-                            mRadialTimePickerView.getCurrentHour(),
-                            mRadialTimePickerView.getCurrentMinute());
+            for (int i = 0, count = v.getChildCount(); i < count; i++) {
+                final View child = v.getChildAt(i);
+                final int dX = x - (child.getLeft() + child.getWidth() / 2);
+                final int dY = y - (child.getTop() + child.getHeight() / 2);
+                final int dist = dX * dX + dY * dY;
+                if (bestDist > dist) {
+                    bestChild = child;
+                    bestDist = dist;
                 }
             }
+
+            return bestChild;
         }
-    };
+    }
 }
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index aa603c1..4f17c39 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -17,7 +17,6 @@
 package com.android.internal.policy;
 
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.view.ContextThemeWrapper;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -31,7 +30,6 @@
 class DecorContext extends ContextThemeWrapper {
     private PhoneWindow mPhoneWindow;
     private WindowManager mWindowManager;
-    private TypedArray mWindowStyle;
 
     public DecorContext(Context context) {
         super(context, null);
@@ -54,13 +52,4 @@
         }
         return super.getSystemService(name);
     }
-
-    public TypedArray getWindowStyle() {
-        synchronized (this) {
-            if (mWindowStyle == null) {
-                mWindowStyle = obtainStyledAttributes(com.android.internal.R.styleable.Window);
-            }
-            return mWindowStyle;
-        }
-    }
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 8b81812..4644211 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -31,7 +31,6 @@
 import android.animation.ObjectAnimator;
 import android.app.ActivityManagerNative;
 import android.app.SearchManager;
-import android.graphics.drawable.ColorDrawable;
 import android.os.Build;
 import android.os.UserHandle;
 
@@ -5438,20 +5437,7 @@
      * */
     private Drawable getResizingBackgroundDrawable() {
         final Context context = mDecor.getContext();
-        final TypedArray windowStyle;
-        if (context instanceof DecorContext) {
-            windowStyle = ((DecorContext) context).getWindowStyle();
-        } else {
-            windowStyle = getWindowStyle();
-        }
-        final int resourceId =
-                windowStyle.getResourceId(R.styleable.Window_windowResizingBackground, 0);
-        if (resourceId != 0) {
-            return context.getDrawable(resourceId);
-        }
 
-        // The app didn't set a resizing background color. In this case we try to use the app's
-        // background drawable for the resizing background.
         if (mBackgroundResource != 0) {
             final Drawable drawable = context.getDrawable(mBackgroundResource);
             if (drawable != null) {
@@ -5459,8 +5445,6 @@
             }
         }
 
-        // The app background drawable isn't currently set. This might be because the app cleared
-        // it. In this case we try to use the app's background fallback drawable.
         if (mBackgroundFallbackResource != 0) {
             final Drawable fallbackDrawable = context.getDrawable(mBackgroundFallbackResource);
             if (fallbackDrawable != null) {
@@ -5468,7 +5452,9 @@
             }
         }
 
-        return new ColorDrawable(context.getResources().getInteger(
-                com.android.internal.R.integer.config_windowResizingBackgroundColorARGB));
+        // We shouldn't really get here as the background fallback should be always available since
+        // it is defaulted by the system.
+        Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + this);
+        return null;
     }
 }
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
new file mode 100644
index 0000000..27c5834
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -0,0 +1,328 @@
+/*
+ * 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.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+/**
+ * Extension of TextView that can handle displaying and inputting a range of
+ * numbers.
+ * <p>
+ * Clients of this view should never call {@link #setText(CharSequence)} or
+ * {@link #setHint(CharSequence)} directly. Instead, they should call
+ * {@link #setValue(int)} to modify the currently displayed value.
+ */
+public class NumericTextView extends TextView {
+    private static final int RADIX = 10;
+    private static final double LOG_RADIX = Math.log(RADIX);
+
+    private int mMinValue = 0;
+    private int mMaxValue = 99;
+
+    /** Number of digits in the maximum value. */
+    private int mMaxCount = 2;
+
+    private boolean mShowLeadingZeroes = true;
+
+    private int mValue;
+
+    /** Number of digits entered during editing mode. */
+    private int mCount;
+
+    /** Used to restore the value after an aborted edit. */
+    private int mPreviousValue;
+
+    private OnValueChangedListener mListener;
+
+    public NumericTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Generate the hint text color based on disabled state.
+        final int textColorDisabled = getTextColors().getColorForState(StateSet.get(0), 0);
+        setHintTextColor(textColorDisabled);
+
+        setFocusable(true);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+        if (focused) {
+            mPreviousValue = mValue;
+            mValue = 0;
+            mCount = 0;
+
+            // Transfer current text to hint.
+            setHint(getText());
+            setText("");
+        } else {
+            if (mCount == 0) {
+                // No digits were entered, revert to previous value.
+                mValue = mPreviousValue;
+
+                setText(getHint());
+                setHint("");
+            }
+
+            // Ensure the committed value is within range.
+            if (mValue < mMinValue) {
+                mValue = mMinValue;
+            }
+
+            setValue(mValue);
+
+            if (mListener != null) {
+                mListener.onValueChanged(this, mValue, true, true);
+            }
+        }
+    }
+
+    /**
+     * Sets the currently displayed value.
+     * <p>
+     * The specified {@code value} must be within the range specified by
+     * {@link #setRange(int, int)} (e.g. between {@link #getRangeMinimum()}
+     * and {@link #getRangeMaximum()}).
+     *
+     * @param value the value to display
+     */
+    public final void setValue(int value) {
+        if (mValue != value) {
+            mValue = value;
+
+            updateDisplayedValue();
+        }
+    }
+
+    /**
+     * Returns the currently displayed value.
+     * <p>
+     * If the value is currently being edited, returns the live value which may
+     * not be within the range specified by {@link #setRange(int, int)}.
+     *
+     * @return the currently displayed value
+     */
+    public final int getValue() {
+        return mValue;
+    }
+
+    /**
+     * Sets the valid range (inclusive).
+     *
+     * @param minValue the minimum valid value (inclusive)
+     * @param maxValue the maximum valid value (inclusive)
+     */
+    public final void setRange(int minValue, int maxValue) {
+        if (mMinValue != minValue) {
+            mMinValue = minValue;
+        }
+
+        if (mMaxValue != maxValue) {
+            mMaxValue = maxValue;
+            mMaxCount = 1 + (int) (Math.log(maxValue) / LOG_RADIX);
+
+            updateMinimumWidth();
+            updateDisplayedValue();
+        }
+    }
+
+    /**
+     * @return the minimum value value (inclusive)
+     */
+    public final int getRangeMinimum() {
+        return mMinValue;
+    }
+
+    /**
+     * @return the maximum value value (inclusive)
+     */
+    public final int getRangeMaximum() {
+        return mMaxValue;
+    }
+
+    /**
+     * Sets whether this view shows leading zeroes.
+     * <p>
+     * When leading zeroes are shown, the displayed value will be padded
+     * with zeroes to the width of the maximum value as specified by
+     * {@link #setRange(int, int)} (see also {@link #getRangeMaximum()}.
+     * <p>
+     * For example, with leading zeroes shown, a maximum of 99 and value of
+     * 9 would display "09". A maximum of 100 and a value of 9 would display
+     * "009". With leading zeroes hidden, both cases would show "9".
+     *
+     * @param showLeadingZeroes {@code true} to show leading zeroes,
+     *                          {@code false} to hide them
+     */
+    public final void setShowLeadingZeroes(boolean showLeadingZeroes) {
+        if (mShowLeadingZeroes != showLeadingZeroes) {
+            mShowLeadingZeroes = showLeadingZeroes;
+
+            updateDisplayedValue();
+        }
+    }
+
+    public final boolean getShowLeadingZeroes() {
+        return mShowLeadingZeroes;
+    }
+
+    /**
+     * Computes the display value and updates the text of the view.
+     * <p>
+     * This method should be called whenever the current value or display
+     * properties (leading zeroes, max digits) change.
+     */
+    private void updateDisplayedValue() {
+        final String format;
+        if (mShowLeadingZeroes) {
+            format = "%0" + mMaxCount + "d";
+        } else {
+            format = "%d";
+        }
+
+        // Always use String.format() rather than Integer.toString()
+        // to obtain correctly localized values.
+        setText(String.format(format, mValue));
+    }
+
+    /**
+     * Computes the minimum width in pixels required to display all possible
+     * values and updates the minimum width of the view.
+     * <p>
+     * This method should be called whenever the maximum value changes.
+     */
+    private void updateMinimumWidth() {
+        final CharSequence previousText = getText();
+        int maxWidth = 0;
+
+        for (int i = 0; i < mMaxValue; i++) {
+            setText(String.format("%0" + mMaxCount + "d", i));
+            measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+            final int width = getMeasuredWidth();
+            if (width > maxWidth) {
+                maxWidth = width;
+            }
+        }
+
+        setText(previousText);
+        setMinWidth(maxWidth);
+        setMinimumWidth(maxWidth);
+    }
+
+    public final void setOnDigitEnteredListener(OnValueChangedListener listener) {
+        mListener = listener;
+    }
+
+    public final OnValueChangedListener getOnDigitEnteredListener() {
+        return mListener;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return isKeyCodeNumeric(keyCode)
+                || (keyCode == KeyEvent.KEYCODE_DEL)
+                || super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return isKeyCodeNumeric(keyCode)
+                || (keyCode == KeyEvent.KEYCODE_DEL)
+                || super.onKeyMultiple(keyCode, repeatCount, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return handleKeyUp(keyCode)
+                || super.onKeyUp(keyCode, event);
+    }
+
+    private boolean handleKeyUp(int keyCode) {
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
+            // Backspace removes the least-significant digit, if available.
+            if (mCount > 0) {
+                mValue /= RADIX;
+                mCount--;
+            }
+        } else if (isKeyCodeNumeric(keyCode)) {
+            if (mCount < mMaxCount) {
+                final int keyValue = numericKeyCodeToInt(keyCode);
+                final int newValue = mValue * RADIX + keyValue;
+                if (newValue <= mMaxValue) {
+                    mValue = newValue;
+                    mCount++;
+                }
+            }
+        } else {
+            return false;
+        }
+
+        final String formattedValue;
+        if (mCount > 0) {
+            // If the user types 01, we should always show the leading 0 even if
+            // getShowLeadingZeroes() is false. Preserve typed leading zeroes by
+            // using the number of digits entered as the format width.
+            formattedValue = String.format("%0" + mCount + "d", mValue);
+        } else {
+            formattedValue = "";
+        }
+
+        setText(formattedValue);
+
+        if (mListener != null) {
+            final boolean isValid = mValue >= mMinValue;
+            final boolean isFinished = mCount >= mMaxCount || mValue * RADIX > mMaxValue;
+            mListener.onValueChanged(this, mValue, isValid, isFinished);
+        }
+
+        return true;
+    }
+
+    private static boolean isKeyCodeNumeric(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9;
+    }
+
+    private static int numericKeyCodeToInt(int keyCode) {
+        return keyCode - KeyEvent.KEYCODE_0;
+    }
+
+    public interface OnValueChangedListener {
+        /**
+         * Called when the value displayed by {@code view} changes.
+         *
+         * @param view the view whose value changed
+         * @param value the new value
+         * @param isValid {@code true} if the value is valid (e.g. within the
+         *                range specified by {@link #setRange(int, int)}),
+         *                {@code false} otherwise
+         * @param isFinished {@code true} if the no more digits may be entered,
+         *                   {@code false} if more digits may be entered
+         */
+        void onValueChanged(NumericTextView view, int value, boolean isValid, boolean isFinished);
+    }
+}
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index d1780d6..7fd288a 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -33,11 +33,11 @@
     FontStyle resolved = resolvedFace->fStyle;
 
     /* Prepare minikin FontStyle */
-    const std::string& lang = paint->getTextLocale();
-    FontLanguage minikinLang(lang.c_str(), lang.size());
+    const std::string& langs = paint->getTextLocales();
+    FontLanguages minikinLangs(langs.c_str(), langs.size());
     FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
             : VARIANT_COMPACT;
-    FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
+    FontStyle minikinStyle(minikinLangs, minikinVariant, resolved.getWeight(), resolved.getItalic());
 
     /* Prepare minikin Paint */
     // Note: it would be nice to handle fractional size values (it would improve smooth zoom
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 314e4b6..b50046f 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,7 @@
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkMaskFilter.h"
+#include "SkPath.h"
 #include "SkRasterizer.h"
 #include "SkShader.h"
 #include "SkTypeface.h"
@@ -43,12 +44,10 @@
 #include "Paint.h"
 #include "TypefaceImpl.h"
 
-#include <vector>
+#include <cassert>
+#include <cstring>
 #include <memory>
-
-// temporary for debugging
-#include <Caches.h>
-#include <utils/Log.h>
+#include <vector>
 
 namespace android {
 
@@ -71,12 +70,12 @@
     paint->setTextEncoding(Paint::kGlyphID_TextEncoding);
 }
 
-struct LocaleCacheEntry {
-    std::string javaLocale;
-    std::string languageTag;
+struct LocalesCacheEntry {
+    std::string javaLocales;
+    std::string languageTags;
 };
 
-static thread_local LocaleCacheEntry sSingleEntryLocaleCache;
+static thread_local LocalesCacheEntry sSingleEntryLocalesCache;
 
 namespace PaintGlue {
     enum MoveOpt {
@@ -360,17 +359,57 @@
         output[0] = '\0';
     }
 
-    static void setTextLocale(JNIEnv* env, jobject clazz, jlong objHandle, jstring locale) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        ScopedUtfChars localeChars(env, locale);
-        if (sSingleEntryLocaleCache.javaLocale != localeChars.c_str()) {
-            sSingleEntryLocaleCache.javaLocale = localeChars.c_str();
-            char langTag[ULOC_FULLNAME_CAPACITY];
-            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
-            sSingleEntryLocaleCache.languageTag = langTag;
+    static void toLanguageTags(std::string* output, const char* locales) {
+        if (output == NULL) {
+            return;
+        }
+        if (locales == NULL) {
+            output->clear();
+            return;
         }
 
-        obj->setTextLocale(sSingleEntryLocaleCache.languageTag);
+        char langTag[ULOC_FULLNAME_CAPACITY];
+        const char* commaLoc = strchr(locales, ',');
+        if (commaLoc == NULL) {
+            assert(locales[0] != '\0');  // the string should not be empty
+            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locales);
+            *output = langTag;
+            return;
+        }
+
+        size_t len = strlen(locales);
+        char locale[len];
+        output->clear();
+        output->reserve(len);
+        const char* lastStart = locales;
+        do {
+            assert(lastStart > commaLoc);  // the substring should not be empty
+            strncpy(locale, lastStart, commaLoc - lastStart);
+            locale[commaLoc - lastStart] = '\0';
+            toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
+            if (langTag[0] != '\0') {
+                output->append(langTag);
+                output->push_back(',');
+            }
+            lastStart = commaLoc + 1;
+            commaLoc = strchr(lastStart, ',');
+        } while (commaLoc != NULL);
+        assert(lastStart[0] != '\0');  // the final substring should not be empty
+        toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, lastStart);
+        if (langTag[0] != '\0') {
+            output->append(langTag);
+        }
+    }
+
+    static void setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
+        Paint* obj = reinterpret_cast<Paint*>(objHandle);
+        ScopedUtfChars localesChars(env, locales);
+        if (sSingleEntryLocalesCache.javaLocales != localesChars.c_str()) {
+            sSingleEntryLocalesCache.javaLocales = localesChars.c_str();
+            toLanguageTags(&sSingleEntryLocalesCache.languageTags, localesChars.c_str());
+        }
+
+        obj->setTextLocales(sSingleEntryLocalesCache.languageTags);
     }
 
     static jboolean isElegantTextHeight(JNIEnv* env, jobject, jlong paintHandle) {
@@ -952,7 +991,7 @@
     {"nSetRasterizer","!(JJ)J", (void*) PaintGlue::setRasterizer},
     {"nGetTextAlign","!(J)I", (void*) PaintGlue::getTextAlign},
     {"nSetTextAlign","!(JI)V", (void*) PaintGlue::setTextAlign},
-    {"nSetTextLocale","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocale},
+    {"nSetTextLocales","!(JLjava/lang/String;)V", (void*) PaintGlue::setTextLocales},
     {"nIsElegantTextHeight","!(J)Z", (void*) PaintGlue::isElegantTextHeight},
     {"nSetElegantTextHeight","!(JZ)V", (void*) PaintGlue::setElegantTextHeight},
     {"nGetTextSize","!(J)F", (void*) PaintGlue::getTextSize},
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index 6df22ff..7a34bc2 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -53,12 +53,12 @@
         return mFontFeatureSettings;
     }
 
-    void setTextLocale(const std::string &textLocale) {
-        mTextLocale = textLocale;
+    void setTextLocales(const std::string &textLocales) {
+        mTextLocales = textLocales;
     }
 
-    const std::string& getTextLocale() const {
-        return mTextLocale;
+    const std::string& getTextLocales() const {
+        return mTextLocales;
     }
 
     void setFontVariant(FontVariant variant) {
@@ -80,7 +80,7 @@
 private:
     float mLetterSpacing = 0;
     std::string mFontFeatureSettings;
-    std::string mTextLocale;
+    std::string mTextLocales;
     FontVariant mFontVariant;
     uint32_t mHyphenEdit = 0;
 };
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index da85018..d5a0972 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -23,12 +23,12 @@
 namespace android {
 
 Paint::Paint() : SkPaint(),
-        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocales(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::Paint(const Paint& paint) : SkPaint(paint),
         mLetterSpacing(paint.mLetterSpacing), mFontFeatureSettings(paint.mFontFeatureSettings),
-        mTextLocale(paint.mTextLocale), mFontVariant(paint.mFontVariant),
+        mTextLocales(paint.mTextLocales), mFontVariant(paint.mFontVariant),
         mHyphenEdit(paint.mHyphenEdit) {
 }
 
@@ -39,7 +39,7 @@
     SkPaint::operator=(other);
     mLetterSpacing = other.mLetterSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
-    mTextLocale = other.mTextLocale;
+    mTextLocales = other.mTextLocales;
     mFontVariant = other.mFontVariant;
     mHyphenEdit = other.mHyphenEdit;
     return *this;
@@ -49,7 +49,7 @@
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
             && a.mLetterSpacing == b.mLetterSpacing
             && a.mFontFeatureSettings == b.mFontFeatureSettings
-            && a.mTextLocale == b.mTextLocale
+            && a.mTextLocales == b.mTextLocales
             && a.mFontVariant == b.mFontVariant
             && a.mHyphenEdit == b.mHyphenEdit;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f300741..39eda58 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -241,7 +241,6 @@
     <protected-broadcast android:name="android.intent.action.DREAMING_STARTED" />
     <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
     <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
-    <protected-broadcast android:name="android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN" />
 
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
diff --git a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
index baa8958..905fd4d 100644
--- a/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
+++ b/core/res/res/color/primary_text_secondary_when_activated_material_inverse.xml
@@ -1,8 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
+        android:state_enabled="false"
+        android:color="?attr/textColorSecondaryInverse"
+        android:alpha="?attr/disabledAlpha" />
+    <item
         android:state_activated="true"
         android:color="?attr/textColorPrimaryInverse" />
     <item
         android:color="?attr/textColorSecondaryInverse" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/core/res/res/drawable/time_picker_editable_background.xml b/core/res/res/drawable/time_picker_editable_background.xml
new file mode 100644
index 0000000..72e863b
--- /dev/null
+++ b/core/res/res/drawable/time_picker_editable_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <layer-list>
+            <item android:gravity="bottom"
+                  android:height="10dp">
+                <shape android:shape="line"
+                       android:tint="?attr/textColorPrimaryInverse">
+                    <stroke android:color="@color/white"
+                            android:width="2dp" />
+                </shape>
+            </item>
+        </layer-list>
+    </item>
+    <item android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index bb347cb..7a0c38f 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -51,15 +51,17 @@
 
             <!-- The hour should always be to the left of the separator,
                  regardless of the current locale's layout direction. -->
-            <TextView
+            <com.android.internal.widget.NumericTextView
                 android:id="@+id/hours"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+                android:background="@drawable/time_picker_editable_background"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:gravity="right"
-                android:includeFontPadding="false" />
+                android:focusable="true"
+                android:nextFocusForward="@+id/minutes" />
 
             <TextView
                 android:id="@+id/separator"
@@ -71,57 +73,56 @@
 
             <!-- The minutes should always be to the right of the separator,
                  regardless of the current locale's layout direction. -->
-            <TextView
+            <com.android.internal.widget.NumericTextView
                 android:id="@+id/minutes"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+                android:background="@drawable/time_picker_editable_background"
                 android:singleLine="true"
                 android:ellipsize="none"
                 android:gravity="left"
-                android:includeFontPadding="false" />
+                android:focusable="true"
+                android:nextFocusForward="@+id/am_label" />
         </LinearLayout>
 
-        <!-- The layout alignment of this view will switch between toRightOf
-             @id/minutes and toLeftOf @id/hours depending on the locale. -->
-        <LinearLayout
+        <RadioGroup
             android:id="@+id/ampm_layout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_below="@+id/time_layout"
             android:layout_centerHorizontal="true"
+            android:paddingTop="4dp"
+            android:paddingBottom="4dp"
             android:orientation="vertical"
             android:layoutDirection="locale">
-
-            <CheckedTextView
+            <RadioButton
                 android:id="@+id/am_label"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minHeight="48dp"
-                android:minWidth="48dp"
-                android:gravity="bottom"
+                android:padding="8dp"
+                android:layout_marginBottom="-8dp"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
-                android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingTop="4dp"
-                android:paddingBottom="6dp"
                 android:lines="1"
-                android:ellipsize="none" />
-
-            <CheckedTextView
+                android:ellipsize="none"
+                android:focusable="true"
+                android:background="?android:attr/selectableItemBackground"
+                android:includeFontPadding="false"
+                android:nextFocusForward="@+id/pm_label"
+                android:button="@null" />
+            <RadioButton
                 android:id="@+id/pm_label"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minHeight="48dp"
-                android:minWidth="48dp"
-                android:gravity="top"
+                android:padding="8dp"
                 android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
-                android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
-                android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
                 android:lines="1"
                 android:ellipsize="none"
-                android:includeFontPadding="false" />
-        </LinearLayout>
+                android:focusable="true"
+                android:background="?android:attr/selectableItemBackground"
+                android:includeFontPadding="false"
+                android:button="@null" />
+        </RadioGroup>
     </RelativeLayout>
 
     <ViewStub
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index acdc509..7019ced 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -21,23 +21,24 @@
                 android:id="@+id/time_header"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:orientation="horizontal"
-                android:padding="@dimen/timepicker_separator_padding"
                 android:paddingStart="16dp"
                 android:paddingEnd="16dp">
 
     <!-- The hour should always be to the left of the separator,
          regardless of the current locale's layout direction. -->
-    <TextView
+    <com.android.internal.widget.NumericTextView
         android:id="@+id/hours"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toLeftOf="@+id/separator"
         android:layout_alignBaseline="@+id/separator"
         android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+        android:background="@drawable/time_picker_editable_background"
         android:singleLine="true"
         android:ellipsize="none"
-        android:gravity="right" />
+        android:gravity="right"
+        android:focusable="true"
+        android:nextFocusForward="@+id/minutes" />
 
     <TextView
         android:id="@+id/separator"
@@ -51,50 +52,57 @@
 
     <!-- The minutes should always be to the left of the separator,
          regardless of the current locale's layout direction. -->
-    <TextView
+    <com.android.internal.widget.NumericTextView
         android:id="@+id/minutes"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/separator"
         android:layout_alignBaseline="@+id/separator"
         android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel"
+        android:background="@drawable/time_picker_editable_background"
         android:singleLine="true"
         android:ellipsize="none"
-        android:gravity="left" />
+        android:gravity="left"
+        android:focusable="true"
+        android:nextFocusForward="@+id/am_label" />
 
     <!-- The layout alignment of this view will switch between toRightOf
          @id/minutes and toLeftOf @id/hours depending on the locale. -->
-    <LinearLayout
+    <RadioGroup
         android:id="@+id/ampm_layout"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/minutes"
         android:layout_alignBaseline="@+id/minutes"
-        android:paddingStart="8dp"
-        android:paddingEnd="8dp"
+        android:paddingStart="4dp"
+        android:paddingEnd="4dp"
         android:orientation="vertical"
         android:baselineAlignedChildIndex="1">
-        <CheckedTextView
+        <RadioButton
             android:id="@+id/am_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="48dp"
-            android:minHeight="48dp"
-            android:gravity="bottom"
-            android:paddingTop="@dimen/timepicker_am_top_padding"
+            android:padding="8dp"
+            android:layout_marginBottom="-8dp"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
             android:lines="1"
-            android:ellipsize="none" />
-        <CheckedTextView
+            android:ellipsize="none"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackground"
+            android:includeFontPadding="false"
+            android:nextFocusForward="@+id/pm_label"
+            android:button="@null" />
+        <RadioButton
             android:id="@+id/pm_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="48dp"
-            android:minHeight="48dp"
-            android:gravity="top"
-            android:paddingTop="@dimen/timepicker_pm_top_padding"
+            android:padding="8dp"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
             android:lines="1"
-            android:ellipsize="none" />
-    </LinearLayout>
+            android:ellipsize="none"
+            android:focusable="true"
+            android:background="?android:attr/selectableItemBackground"
+            android:includeFontPadding="false"
+            android:button="@null" />
+    </RadioGroup>
 </RelativeLayout>
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index fb639ca..0f39e42 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -44,14 +44,5 @@
         <item>false</item>
     </string-array>
 
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
-         Or string format of ApnSettingV3.
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string-array translatable="false" name="config_tether_apndata">
-        <item>[ApnSettingV3]SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,204,04,,DUN,,,true,0,,,,,,,gid,5A</item>
-    </string-array>
-
     <string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
 </resources>
diff --git a/core/res/res/values-mcc302-mnc780/config.xml b/core/res/res/values-mcc302-mnc780/config.xml
index 51abd36..a48f695 100644
--- a/core/res/res/values-mcc302-mnc780/config.xml
+++ b/core/res/res/values-mcc302-mnc780/config.xml
@@ -21,25 +21,6 @@
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-      <item>1</item>
-      <item>4</item>
-      <item>7</item>
-      <item>9</item>
-    </integer-array>
-
-    <!-- String containing the apn value for tethering.  May be overriden by secure settings
-         TETHER_DUN_APN.  Value is a comma separated series of strings:
-         "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
-         Or string format of ApnSettingV3.
-         note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
-    <string-array translatable="false" name="config_tether_apndata">
-      <item>SaskTel Tethering,inet.stm.sk.ca,,,,,,,,,302,780,,DUN</item>
-    </string-array>
-
     <!-- Don't use roaming icon for considered operators -->
     <string-array translatable="false" name="config_operatorConsideredNonRoaming">
         <item>302</item>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index 8181f6c..4c1fc88 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -87,7 +87,7 @@
     <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"कलर ID पूर्वनिर्धारितदेखि प्रतिबन्धित छैन। अर्को कल: प्रतिबन्धित छ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"सेवाको व्यवस्था छैन।"</string>
-    <string name="CLIRPermanent" msgid="3377371145926835671">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
+    <string name="CLIRPermanent" msgid="3377371145926835671">"तपाईँ कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
     <string name="RestrictedChangedTitle" msgid="5592189398956187498">"प्रतिबन्धित पहुँच परिवर्तन भएको छ"</string>
     <string name="RestrictedOnData" msgid="8653794784690065540">"डेटा सेवा रोकिएको छ।"</string>
     <string name="RestrictedOnEmergency" msgid="6581163779072833665">"आपतकालीन सेवा रोकिएको छ।"</string>
@@ -198,7 +198,7 @@
     <string name="shutdown_confirm" product="tv" msgid="476672373995075359">"तपाईंको TV बन्द हुनेछ।"</string>
     <string name="shutdown_confirm" product="watch" msgid="3490275567476369184">"तपाईँको घडी बन्द गरिने छ।"</string>
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"तपाईँको फोन बन्द हुने छ।"</string>
-    <string name="shutdown_confirm_question" msgid="2906544768881136183">"के तपाईं बन्द गर्न चाहनुहुन्छ?"</string>
+    <string name="shutdown_confirm_question" msgid="2906544768881136183">"के तपाईँ बन्द गर्न चाहनुहुन्छ?"</string>
     <string name="reboot_safemode_title" msgid="7054509914500140361">"सुरक्षित मोडमा पुनःबुट गर्नुहोस्"</string>
     <string name="reboot_safemode_confirm" msgid="55293944502784668">"सुरक्षित मोडमा तपाईँ पुनःबुट गर्न चाहनु हुन्छ? तपाईँले स्थापना गरेका सबै तेस्रो पक्षका अनुप्रयोगहरूलाई असक्षम गराउने छ।"</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"नयाँ"</string>
@@ -786,7 +786,7 @@
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"तपाईँको भ्वाइसमेल इनबक्समा सन्देश थप्नको लागि अनुप्रयोगलाई अनुमति दिन्छ।"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"भूस्थान अनुमतिहरू ब्राउजर परिवर्तन गर्नुहोस्"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ब्राउजरको भू-स्थान अनुमतिहरू परिमार्जन गर्न अनुप्रयोगलाई अनुमति दिन्छ। खराब अनुप्रयोगहरूले  स्थान सूचना मनपरी वेब साइटहरूमा पठाउने अनुमतिको लागि यसलाई प्रयोग गर्न सक्छन्।"</string>
-    <string name="save_password_message" msgid="767344687139195790">"के तपाईं ब्राउजरले यो पासवर्ड सम्झेको चाहनुहुन्छ?"</string>
+    <string name="save_password_message" msgid="767344687139195790">"के तपाईँ ब्राउजरले यो पासवर्ड सम्झेको चाहनुहुन्छ?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"अहिले होइन"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"सम्झनुहोस्"</string>
     <string name="save_password_never" msgid="8274330296785855105">"कहिल्यै पनि होइन"</string>
@@ -907,13 +907,13 @@
     <string name="aerr_process_silence" msgid="4226685530196000222">"साइलेन्स पुनःबुट नभएसम्म <xliff:g id="PROCESS">%1$s</xliff:g> बाट क्र्यास हुन्छ।"</string>
     <string name="anr_title" msgid="4351948481459135709"></string>
     <string name="anr_activity_application" msgid="1904477189057199066">"<xliff:g id="APPLICATION">%2$s</xliff:g>ले कार्य गरिरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
-    <string name="anr_activity_process" msgid="5776209883299089767">"गतिविधि <xliff:g id="ACTIVITY">%1$s</xliff:g> ले प्रतिक्रिया देखाइरहेको छैन।\n\nके तपाईं यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
+    <string name="anr_activity_process" msgid="5776209883299089767">"गतिविधि <xliff:g id="ACTIVITY">%1$s</xliff:g> ले प्रतिक्रिया देखाइरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="anr_application_process" msgid="8941757607340481057">"<xliff:g id="APPLICATION">%1$s</xliff:g> जवाफ दिइरहेको छैन। के तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="anr_process" msgid="6513209874880517125">"प्रक्रिया <xliff:g id="PROCESS">%1$s</xliff:g>ले कार्य गरिरहेको छैन।\n\nके तपाईँ यसलाई बन्द गर्न चाहनु हुन्छ?"</string>
     <string name="force_close" msgid="8346072094521265605">"ठिक छ"</string>
     <string name="report" msgid="4060218260984795706">"रिपोर्ट गर्नुहोस्"</string>
     <string name="wait" msgid="7147118217226317732">"प्रतीक्षा गर्नुहोस्"</string>
-    <string name="webpage_unresponsive" msgid="3272758351138122503">"पृष्ठ गैर जिम्मेवारी भएको छ।\n\nके तपाईं यसलाई बन्द गर्न चाहनुहुन्छ?"</string>
+    <string name="webpage_unresponsive" msgid="3272758351138122503">"पृष्ठ गैर जिम्मेवारी भएको छ।\n\nके तपाईँ यसलाई बन्द गर्न चाहनुहुन्छ?"</string>
     <string name="launch_warning_title" msgid="1547997780506713581">"अनुप्रयोग पुनः निर्देशीत"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले चलिरहेको छ।"</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g> वास्तविक सुरुवात भएको थियो।"</string>
@@ -1103,7 +1103,7 @@
     <string name="deny" msgid="2081879885755434506">"अस्वीकार गर्नुहोस्"</string>
     <string name="permission_request_notification_title" msgid="6486759795926237907">"अनुरोध गरिएको अनुमति"</string>
     <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">\n"खाता <xliff:g id="ACCOUNT">%s</xliff:g>को लागि अनुरोध गरिएको अनुमति।"</string>
-    <string name="forward_intent_to_owner" msgid="1207197447013960896">"तपाईं तपाईँको कार्य प्रोफाइल बाहिर यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
+    <string name="forward_intent_to_owner" msgid="1207197447013960896">"तपाईँ तपाईँको कार्य प्रोफाइल बाहिर यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
     <string name="forward_intent_to_work" msgid="621480743856004612">"तपाईँ आफ्नो कार्य प्रोफाइलमा यो अनुप्रयोग प्रयोग गरिरहनु भएको छ"</string>
     <string name="input_method_binding_label" msgid="1283557179944992649">"इनपुट विधि"</string>
     <string name="sync_binding_label" msgid="3687969138375092423">"सिङ्क गर्नुहोस्"</string>
@@ -1151,7 +1151,7 @@
     <string name="gpsVerifYes" msgid="2346566072867213563">"हो"</string>
     <string name="gpsVerifNo" msgid="1146564937346454865">"होइन"</string>
     <string name="sync_too_many_deletes" msgid="5296321850662746890">"सीमा नाघेकाहरू मेट्नुहोस्"</string>
-    <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"त्यहाँ <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> मेटाइएका आइटमहरू छन् <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>को लागि, खाता <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>। तपाईं के गर्न चाहनु हुन्छ?"</string>
+    <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"त्यहाँ <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> मेटाइएका आइटमहरू छन् <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>को लागि, खाता <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>। तपाईँ के गर्न चाहनु हुन्छ?"</string>
     <string name="sync_really_delete" msgid="2572600103122596243">"वस्तुहरू मेट्नुहोस्"</string>
     <string name="sync_undo_deletes" msgid="2941317360600338602">"मेटिएकाहरू पूर्ववत बनाउनुहोस्।"</string>
     <string name="sync_do_nothing" msgid="3743764740430821845">"अहिलेको लागि केही नगर्नुहोस्"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 49a3862..aac6ba3 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1414,7 +1414,7 @@
     </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Försök igen senare"</string>
     <string name="immersive_cling_title" msgid="8394201622932303336">"Visar på fullskärm"</string>
-    <string name="immersive_cling_description" msgid="3482371193207536040">"Dra nedåt från skärmens överkant för att avsluta."</string>
+    <string name="immersive_cling_description" msgid="3482371193207536040">"Svep nedåt från skärmens överkant för att avsluta."</string>
     <string name="immersive_cling_positive" msgid="5016839404568297683">"OK"</string>
     <string name="done_label" msgid="2093726099505892398">"Klart"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Cirkelreglage för timmar"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 7cbf3d2..960a314 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Yanlış PIN kodu."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Acil durum numarası"</string>
-    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Hizmet yok"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Servis yok"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Ekran kilitli."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Kilidi açmak veya acil çağrı yapmak için Menü\'ye basın."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Kilidi açmak için Menü\'ye basın."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 5ae3e1e..8c0b816 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -650,7 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Mã PIN không chính xác."</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Để mở khóa, hãy nhấn vào Menu sau đó nhấn 0."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Số khẩn cấp"</string>
-    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Không có dịch vụ nào"</string>
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Không có dịch vụ"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Màn hình đã khóa."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Nhấn vào Menu để mở khóa hoặc thực hiện cuộc gọi khẩn cấp."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Nhấn vào Menu để mở khóa."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 602f607..29b729a 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -650,8 +650,7 @@
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"PIN码有误。"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"急救或报警电话"</string>
-    <!-- no translation found for lockscreen_carrier_default (6169005837238288522) -->
-    <skip />
+    <string name="lockscreen_carrier_default" msgid="6169005837238288522">"没有服务"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"屏幕已锁定。"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"按 Menu 解锁或进行紧急呼救。"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按 MENU 解锁。"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 90306fd..093ea80 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -323,14 +323,10 @@
         <attr name="windowBackground" format="reference" />
         <!-- Drawable to draw selectively within the inset areas when the windowBackground
              has been set to null. This protects against seeing visual garbage in the
-             surface when the app has not drawn any content into this area. -->
+             surface when the app has not drawn any content into this area. One example is
+             when the user is resizing a window of an activity that has
+             {@link android.R.attr#resizeableActivity} set for multi-window mode. -->
         <attr name="windowBackgroundFallback" format="reference" />
-        <!-- Drawable used to fill in areas the app has not rendered content to yet when the user is
-             resizing the window of an activity that has {@link android.R.attr#resizeableActivity}
-             set for multi-window mode. If unset, the system will try to use windowBackground if
-             set, then windowBackgroundFallback if set. Otherwise, the system default resizing
-             background color -->
-        <attr name="windowResizingBackground" format="reference" />
         <!-- Drawable to use as a frame around the window. -->
         <attr name="windowFrame" format="reference" />
         <!-- Flag indicating whether there should be no title on this window. -->
@@ -1832,6 +1828,9 @@
         <enum name="KEYCODE_MEDIA_STEP_FORWARD" value="274" />
         <enum name="KEYCODE_MEDIA_STEP_BACKWARD" value="275" />
         <enum name="KEYCODE_SOFT_SLEEP" value="276" />
+        <enum name="KEYCODE_CUT" value="277" />
+        <enum name="KEYCODE_COPY" value="278" />
+        <enum name="KEYCODE_PASTE" value="279" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -1847,7 +1846,6 @@
     <declare-styleable name="Window">
         <attr name="windowBackground" />
         <attr name="windowBackgroundFallback" />
-        <attr name="windowResizingBackground" />
         <attr name="windowContentOverlay" />
         <attr name="windowFrame" />
         <attr name="windowNoTitle" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d20b09f..400c822 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1249,6 +1249,9 @@
     <!-- Operating volatage for bluetooth controller. 0 by default-->
     <integer translatable="false" name="config_bluetooth_operating_voltage_mv">4</integer>
 
+    <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
+    <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
+
     <!-- The default data-use polling period. -->
     <integer name="config_datause_polling_period_sec">600</integer>
 
@@ -1285,6 +1288,10 @@
          device is data-only. -->
     <bool name="config_voice_capable">true</bool>
 
+    <!-- Flag indicating that an outbound call must have a call capable phone account
+         that has declared it can process the call's handle. -->
+    <bool name="config_requireCallCapableAccountForHandle">false</bool>
+
     <!-- Flag indicating if the user is notified when the mobile network access is restricted -->
     <bool name="config_user_notification_of_restrictied_mobile_access">true</bool>
 
@@ -2349,9 +2356,6 @@
          is non-interactive. -->
     <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
 
-    <!-- Default background color to use when resizing a window if the app didn't specify one. -->
-    <integer name="config_windowResizingBackgroundColorARGB">0xFF808080</integer>
-
     <!-- Name of the component to handle network policy notifications. If present,
          disables NetworkPolicyManagerService's presentation of data-usage notifications. -->
     <string translatable="false" name="config_networkPolicyNotificationComponent"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 839e81b..c05b585 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2677,7 +2677,7 @@
     <public type="attr" name="level" />
     <public type="attr" name="contextPopupMenuStyle" />
     <public type="attr" name="textAppearancePopupMenuHeader" />
-    <public type="attr" name="windowResizingBackground" />
+    <public type="attr" name="windowBackgroundFallback" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6f239e6..951a825 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,7 @@
   <java-symbol type="bool" name="config_ui_enableFadingMarquee" />
   <java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
   <java-symbol type="bool" name="config_voice_capable" />
+  <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
   <java-symbol type="bool" name="config_user_notification_of_restrictied_mobile_access" />
   <java-symbol type="bool" name="config_wifiDisplaySupportsProtectedBuffers" />
   <java-symbol type="bool" name="preferences_prefer_dual_pane" />
@@ -370,6 +371,7 @@
   <java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
+  <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -2332,7 +2334,5 @@
 
   <java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
 
-  <java-symbol type="integer" name="config_windowResizingBackgroundColorARGB" />
-
   <java-symbol type="string" name="config_packagedKeyboardName" />
 </resources>
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f10ba96..2a10bdd 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -152,11 +152,11 @@
 # key 130 "KEY_PROPS"
 # key 131 "KEY_UNDO"
 # key 132 "KEY_FRONT"
-# key 133 "KEY_COPY"
+key 133   COPY
 # key 134 "KEY_OPEN"
-# key 135 "KEY_PASTE"
+key 135   PASTE
 # key 136 "KEY_FIND"
-# key 137 "KEY_CUT"
+key 137   CUT
 # key 138 "KEY_HELP"
 key 139   MENU
 key 140   CALCULATOR
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 11b4a9e..35182f9 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1335,7 +1335,7 @@
             return;
         }
         mLocales = new LocaleList(locale);
-        nSetTextLocale(mNativePaint, locale.toString());
+        nSetTextLocales(mNativePaint, locale.toString());
     }
 
     /**
@@ -1372,8 +1372,7 @@
         }
         if (locales.equals(mLocales)) return;
         mLocales = locales;
-        // TODO: Pass the whole LocaleList to native code
-        nSetTextLocale(mNativePaint, locales.getPrimary().toString());
+        nSetTextLocales(mNativePaint, locales.toLanguageTags());
     }
 
     /**
@@ -2715,8 +2714,8 @@
     private static native void nSetTextAlign(long paintPtr,
                                                    int align);
 
-    private static native void nSetTextLocale(long paintPtr,
-                                                    String locale);
+    private static native void nSetTextLocales(long paintPtr,
+                                                    String locales);
 
     private static native float nGetTextAdvances(long paintPtr, long typefacePtr,
             char[] text, int index, int count, int contextIndex, int contextCount,
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index e235a99..3ed6a78 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -379,6 +379,7 @@
                 r, theme, attrs, R.styleable.AnimatedStateListDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedStateListDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 521c74b..6bf3afd 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -291,6 +291,7 @@
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimationDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.AnimationDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 1f7d996..ac72734 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.graphics.PorterDuff.Mode;
 import android.os.SystemClock;
+import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.util.SparseArray;
 import android.view.View;
@@ -581,6 +582,17 @@
         return mCurrDrawable;
     }
 
+    /**
+     * Updates the source density based on the resources used to inflate
+     * density-dependent values. Implementing classes should call this method
+     * during inflation.
+     *
+     * @param res the resources used to inflate density-dependent values
+     */
+    final void updateDensity(Resources res) {
+        mDrawableContainerState.updateDensity(res);
+    }
+
     @Override
     public void applyTheme(Theme theme) {
         mDrawableContainerState.applyTheme(theme);
@@ -638,22 +650,22 @@
      */
     public abstract static class DrawableContainerState extends ConstantState {
         final DrawableContainer mOwner;
-        final Resources mRes;
 
-        SparseArray<ConstantStateFuture> mDrawableFutures;
-
+        Resources mSourceRes;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
         int mChangingConfigurations;
         int mChildrenChangingConfigurations;
 
+        SparseArray<ConstantStateFuture> mDrawableFutures;
         Drawable[] mDrawables;
         int mNumChildren;
 
         boolean mVariablePadding = false;
-        boolean mPaddingChecked;
+        boolean mCheckedPadding;
         Rect mConstantPadding;
 
         boolean mConstantSize = false;
-        boolean mComputedConstantSize;
+        boolean mCheckedConstantSize;
         int mConstantWidth;
         int mConstantHeight;
         int mConstantMinimumWidth;
@@ -689,7 +701,13 @@
         DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                 Resources res) {
             mOwner = owner;
-            mRes = res != null ? res : orig != null ? orig.mRes : null;
+
+            final Resources sourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
+            mSourceRes = sourceRes;
+
+            final int densityDpi = sourceRes == null ? 0 : sourceRes.getDisplayMetrics().densityDpi;
+            final int sourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+            mDensity = sourceDensity;
 
             if (orig != null) {
                 mChangingConfigurations = orig.mChangingConfigurations;
@@ -713,21 +731,30 @@
                 mHasTintList = orig.mHasTintList;
                 mHasTintMode = orig.mHasTintMode;
 
-                // Cloning the following values may require creating futures.
-                mConstantPadding = orig.getConstantPadding();
-                mPaddingChecked = true;
+                if (orig.mDensity == sourceDensity) {
+                    if (orig.mCheckedPadding) {
+                        mConstantPadding = new Rect(orig.mConstantPadding);
+                        mCheckedPadding = true;
+                    }
 
-                mConstantWidth = orig.getConstantWidth();
-                mConstantHeight = orig.getConstantHeight();
-                mConstantMinimumWidth = orig.getConstantMinimumWidth();
-                mConstantMinimumHeight = orig.getConstantMinimumHeight();
-                mComputedConstantSize = true;
+                    if (orig.mCheckedConstantSize) {
+                        mConstantWidth = orig.mConstantWidth;
+                        mConstantHeight = orig.mConstantHeight;
+                        mConstantMinimumWidth = orig.mConstantMinimumWidth;
+                        mConstantMinimumHeight = orig.mConstantMinimumHeight;
+                        mCheckedConstantSize = true;
+                    }
+                }
 
-                mOpacity = orig.getOpacity();
-                mCheckedOpacity = true;
+                if (orig.mCheckedOpacity) {
+                    mOpacity = orig.mOpacity;
+                    mCheckedOpacity = true;
+                }
 
-                mStateful = orig.isStateful();
-                mCheckedStateful = true;
+                if (orig.mCheckedStateful) {
+                    mStateful = orig.mStateful;
+                    mCheckedStateful = true;
+                }
 
                 // Postpone cloning children and futures until we're absolutely
                 // sure that we're done computing values for the original state.
@@ -783,8 +810,8 @@
             mCheckedOpacity = false;
 
             mConstantPadding = null;
-            mPaddingChecked = false;
-            mComputedConstantSize = false;
+            mCheckedPadding = false;
+            mCheckedConstantSize = false;
 
             return pos;
         }
@@ -863,6 +890,31 @@
             return changed;
         }
 
+        /**
+         * Updates the source density based on the resources used to inflate
+         * density-dependent values.
+         *
+         * @param res the resources used to inflate density-dependent values
+         */
+        final void updateDensity(Resources res) {
+            if (mSourceRes != null) {
+                mSourceRes = res;
+            }
+
+            // The density may have changed since the last update (if any). Any
+            // dimension-type attributes will need their default values scaled.
+            final int densityDpi = res.getDisplayMetrics().densityDpi;
+            final int newSourceDensity = densityDpi == 0 ?
+                    DisplayMetrics.DENSITY_DEFAULT : densityDpi;
+            final int oldSourceDensity = mDensity;
+            mDensity = newSourceDensity;
+
+            if (oldSourceDensity != newSourceDensity) {
+                mCheckedConstantSize = false;
+                mCheckedPadding = false;
+            }
+        }
+
         final void applyTheme(Theme theme) {
             if (theme != null) {
                 createAllFutures();
@@ -877,6 +929,8 @@
                         mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
                     }
                 }
+
+                updateDensity(theme.getResources());
             }
         }
 
@@ -941,7 +995,7 @@
                 return null;
             }
 
-            if ((mConstantPadding != null) || mPaddingChecked) {
+            if ((mConstantPadding != null) || mCheckedPadding) {
                 return mConstantPadding;
             }
 
@@ -961,7 +1015,7 @@
                 }
             }
 
-            mPaddingChecked = true;
+            mCheckedPadding = true;
             return (mConstantPadding = r);
         }
 
@@ -974,7 +1028,7 @@
         }
 
         public final int getConstantWidth() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -982,7 +1036,7 @@
         }
 
         public final int getConstantHeight() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -990,7 +1044,7 @@
         }
 
         public final int getConstantMinimumWidth() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -998,7 +1052,7 @@
         }
 
         public final int getConstantMinimumHeight() {
-            if (!mComputedConstantSize) {
+            if (!mCheckedConstantSize) {
                 computeConstantSize();
             }
 
@@ -1006,7 +1060,7 @@
         }
 
         protected void computeConstantSize() {
-            mComputedConstantSize = true;
+            mCheckedConstantSize = true;
 
             createAllFutures();
 
@@ -1146,10 +1200,10 @@
              */
             public Drawable get(DrawableContainerState state) {
                 final Drawable result;
-                if (state.mRes == null) {
+                if (state.mSourceRes == null) {
                     result = mConstantState.newDrawable();
                 } else {
-                    result = mConstantState.newDrawable(state.mRes);
+                    result = mConstantState.newDrawable(state.mSourceRes);
                 }
                 result.setLayoutDirection(state.mLayoutDirection);
                 result.setCallback(state.mOwner);
diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java
index b01c643..4ce52d1 100644
--- a/graphics/java/android/graphics/drawable/LevelListDrawable.java
+++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java
@@ -88,7 +88,13 @@
     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
             throws XmlPullParserException, IOException {
         super.inflate(r, parser, attrs, theme);
+        updateDensity(r);
 
+        inflateChildElements(r, parser, attrs, theme);
+    }
+
+    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
+            Theme theme) throws XmlPullParserException, IOException {
         int type;
 
         int low = 0;
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 758410a..64a9eb5 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -110,6 +110,7 @@
         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
         super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
         updateStateFromTypedArray(a);
+        updateDensity(r);
         a.recycle();
 
         inflateChildElements(r, parser, attrs, theme);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index f1eaa23..e161f3d 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -603,7 +603,7 @@
         final int densityDpi = a.getResources().getDisplayMetrics().densityDpi;
         final int newSourceDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
         final int oldSourceDensity = pathRenderer.mSourceDensity;
-        final float densityScale = oldSourceDensity / (float) newSourceDensity;
+        final float densityScale = newSourceDensity / (float) oldSourceDensity;
         pathRenderer.mSourceDensity = newSourceDensity;
 
         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 842f575..4385e70 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -27,6 +27,7 @@
     utils/LinearAllocator.cpp \
     utils/NinePatchImpl.cpp \
     utils/StringUtils.cpp \
+    utils/TestWindowContext.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -84,8 +85,7 @@
 hwui_cflags := \
     -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
     -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
-    -Wall -Wno-unused-parameter -Wunreachable-code \
-    -ffast-math -O3 -Werror
+    -Wall -Wno-unused-parameter -Wunreachable-code -Werror
 
 ifeq (true, $(HWUI_NEW_OPS))
     hwui_src_files += \
@@ -261,7 +261,8 @@
 
 LOCAL_SRC_FILES += \
     microbench/DisplayListCanvasBench.cpp \
-    microbench/LinearAllocatorBench.cpp
+    microbench/LinearAllocatorBench.cpp \
+    microbench/ShadowBench.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
     LOCAL_SRC_FILES += \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 4d9f9b4..94806ca 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -74,53 +74,64 @@
 #endif
 }
 
-void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) {
+void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
     LOG_ALWAYS_FATAL("unsupported operation");
 }
 
-void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-    info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
-    Texture* texture = info->getTexture(op.bitmap);
+void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
+    info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+    Texture* texture = info.getTexture(op.bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
     const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
             ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshTexturedUnitQuad(texture->uvMapper)
             .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshUnitQuad()
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewMapUnitToRect(op.unmappedBounds)
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
-void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
     Glop glop;
-    GlopBuilder(info->renderState, info->caches, &glop)
+    GlopBuilder(info.renderState, info.caches, &glop)
             .setRoundRectClipState(state.roundRectClipState)
             .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
             .setFillPaint(*op.paint, state.alpha)
             .setTransform(state.computedState.transform, TransformFlags::None)
             .setModelViewOffsetRect(0, 0, op.unmappedBounds)
             .build();
-    info->renderGlop(state, glop);
+    info.renderGlop(state, glop);
 }
 
+void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("unsupported operation");
+}
 
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index b8b4426..f45dbe4 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -65,7 +65,7 @@
      * These functions will perform the actual rendering of the individual operations in OpenGL,
      * given the transform/clip and other state built into the BakedOpState object passed in.
      */
-    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state);
+    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
     MAP_OPS(BAKED_OP_RENDERER_METHOD);
 };
 
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index e2201ca..ddb8c84 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -68,7 +68,7 @@
         // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
         clipRect = recordedOp.localClipRect;
         snapshot.transform->mapRect(clipRect);
-        clipRect.doIntersect(snapshot.getClipRect());
+        clipRect.doIntersect(snapshot.getRenderTargetClip());
         clipRect.snapToPixelBoundaries();
 
         // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index eca71c6..6a6cc42 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -259,7 +259,7 @@
     currentTransform()->mapRect(r);
     r.snapGeometryToPixelBoundaries(snapOut);
 
-    Rect clipRect(currentClipRect());
+    Rect clipRect(currentRenderTargetClip());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
@@ -287,7 +287,7 @@
     currentTransform()->mapRect(r);
     r.roundOut(); // rounded out to be conservative
 
-    Rect clipRect(currentClipRect());
+    Rect clipRect(currentRenderTargetClip());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index be57f44..4709ef4 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -147,7 +147,7 @@
     void setInvisible(bool value) { mSnapshot->invisible = value; }
 
     inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
-    inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
+    inline const Rect& currentRenderTargetClip() const { return currentSnapshot()->getRenderTargetClip(); }
     inline Region* currentRegion() const { return currentSnapshot()->region; }
     inline int currentFlags() const { return currentSnapshot()->flags; }
     const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 7c0e257..c1417c4 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -52,7 +52,8 @@
     const std::vector<BakedOpState*>& getOps() const { return mOps; }
 
     void dump() const {
-        ALOGD("    Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
+        ALOGD("    Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+                this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
     }
 protected:
     batchid_t mBatchId;
@@ -201,17 +202,106 @@
     Rect mClipRect;
 };
 
-class NullClient: public CanvasStateClient {
-    void onViewportInitialized() override {}
-    void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-    GLuint getTargetFbo() const override { return 0; }
-};
-static NullClient sNullClient;
+// iterate back toward target to see if anything drawn since should overlap the new op
+// if no target, merging ops still interate to find similar batch to insert after
+void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
+        BatchBase** targetBatch, size_t* insertBatchIndex) const {
+    for (int i = mBatches.size() - 1; i >= 0; i--) {
+        BatchBase* overBatch = mBatches[i];
+
+        if (overBatch == *targetBatch) break;
+
+        // TODO: also consider shader shared between batch types
+        if (batchId == overBatch->getBatchId()) {
+            *insertBatchIndex = i + 1;
+            if (!*targetBatch) break; // found insert position, quit
+        }
+
+        if (overBatch->intersects(clippedBounds)) {
+            // NOTE: it may be possible to optimize for special cases where two operations
+            // of the same batch/paint could swap order, such as with a non-mergeable
+            // (clipped) and a mergeable text operation
+            *targetBatch = nullptr;
+            break;
+        }
+    }
+}
+
+void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
+        BakedOpState* op, batchid_t batchId) {
+    OpBatch* targetBatch = mBatchLookup[batchId];
+
+    size_t insertBatchIndex = mBatches.size();
+    if (targetBatch) {
+        locateInsertIndex(batchId, op->computedState.clippedBounds,
+                (BatchBase**)(&targetBatch), &insertBatchIndex);
+    }
+
+    if (targetBatch) {
+        targetBatch->batchOp(op);
+    } else  {
+        // new non-merging batch
+        targetBatch = new (allocator) OpBatch(batchId, op);
+        mBatchLookup[batchId] = targetBatch;
+        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+    }
+}
+
+// insertion point of a new batch, will hopefully be immediately after similar batch
+// (generally, should be similar shader)
+void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
+        BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+    MergingOpBatch* targetBatch = nullptr;
+
+    // Try to merge with any existing batch with same mergeId
+    auto getResult = mMergingBatchLookup[batchId].find(mergeId);
+    if (getResult != mMergingBatchLookup[batchId].end()) {
+        targetBatch = getResult->second;
+        if (!targetBatch->canMergeWith(op)) {
+            targetBatch = nullptr;
+        }
+    }
+
+    size_t insertBatchIndex = mBatches.size();
+    locateInsertIndex(batchId, op->computedState.clippedBounds,
+            (BatchBase**)(&targetBatch), &insertBatchIndex);
+
+    if (targetBatch) {
+        targetBatch->mergeOp(op);
+    } else  {
+        // new merging batch
+        targetBatch = new (allocator) MergingOpBatch(batchId, op);
+        mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
+
+        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+    }
+}
+
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
+    for (const BatchBase* batch : mBatches) {
+        // TODO: different behavior based on batch->isMerging()
+        for (const BakedOpState* op : batch->getOps()) {
+            receivers[op->op->opId](arg, *op->op, *op);
+        }
+    }
+}
+
+void OpReorderer::LayerReorderer::dump() const {
+    for (const BatchBase* batch : mBatches) {
+        batch->dump();
+    }
+}
 
 OpReorderer::OpReorderer()
-        : mCanvasState(sNullClient) {
+        : mCanvasState(*this) {
+    mLayerReorderers.emplace_back();
+    mLayerStack.push_back(0);
 }
 
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
 void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
         const std::vector< sp<RenderNode> >& nodes) {
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
@@ -244,11 +334,11 @@
  * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
  * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
  */
-#define OP_RECIEVER(Type) \
+#define OP_RECEIVER(Type) \
         [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
 void OpReorderer::deferImpl(const DisplayList& displayList) {
     static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
-        MAP_OPS(OP_RECIEVER)
+        MAP_OPS(OP_RECEIVER)
     };
     for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
@@ -260,23 +350,18 @@
 
 void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
     ATRACE_NAME("flush drawing commands");
-    for (const BatchBase* batch : mBatches) {
-        // TODO: different behavior based on batch->isMerging()
-        for (const BakedOpState* op : batch->getOps()) {
-            receivers[op->op->opId](arg, *op->op, *op);
-        }
+    // Relay through layers in reverse order, since layers
+    // later in the list will be drawn by earlier ones
+    for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
+        mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
     }
 }
 
-BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
-    return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
-}
-
 void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
     if (op.renderNode->nothingToDraw()) {
         return;
     }
-    mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+    int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
 
     // apply state from RecordedOp
     mCanvasState.concatMatrix(op.localMatrix);
@@ -285,10 +370,10 @@
 
     // apply RenderProperties state
     if (op.renderNode->applyViewProperties(mCanvasState)) {
-        // not rejected do ops...
+        // if node not rejected based on properties, do ops...
         deferImpl(op.renderNode->getDisplayList());
     }
-    mCanvasState.restore();
+    mCanvasState.restoreToCount(count);
 }
 
 static batchid_t tessellatedBatchId(const SkPaint& paint) {
@@ -298,104 +383,70 @@
 }
 
 void OpReorderer::onBitmapOp(const BitmapOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
     if (!bakedStateOp) return; // quick rejected
 
     mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
     // TODO: AssetAtlas
-
-    deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
+    currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
 }
 
 void OpReorderer::onRectOp(const RectOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
     if (!bakedStateOp) return; // quick rejected
-    deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
 }
 
 void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
-    BakedOpState* bakedStateOp = bakeOpState(op);
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
     if (!bakedStateOp) return; // quick rejected
-    deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-// iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still interate to find similar batch to insert after
-void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
-        BatchBase** targetBatch, size_t* insertBatchIndex) const {
-    for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
-        BatchBase* overBatch = mBatches[i];
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+    mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+    mCanvasState.writableSnapshot()->transform->loadIdentity();
+    mCanvasState.writableSnapshot()->initializeViewport(
+            (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+    mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
 
-        if (overBatch == *targetBatch) break;
+    // create a new layer, and push its index on the stack
+    mLayerStack.push_back(mLayerReorderers.size());
+    mLayerReorderers.emplace_back();
+    mLayerReorderers.back().beginLayerOp = &op;
+}
 
-        // TODO: also consider shader shared between batch types
-        if (batchId == overBatch->getBatchId()) {
-            *insertBatchIndex = i + 1;
-            if (!*targetBatch) break; // found insert position, quit
-        }
+void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+    mCanvasState.restore();
 
-        if (overBatch->intersects(clippedBounds)) {
-            // NOTE: it may be possible to optimize for special cases where two operations
-            // of the same batch/paint could swap order, such as with a non-mergeable
-            // (clipped) and a mergeable text operation
-            *targetBatch = nullptr;
-            break;
-        }
+    const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
+
+    // pop finished layer off of the stack
+    int finishedLayerIndex = mLayerStack.back();
+    mLayerStack.pop_back();
+
+    // record the draw operation into the previous layer's list of draw commands
+    // uses state from the associated beginLayerOp, since it has all the state needed for drawing
+    LayerOp* drawLayerOp = new (mAllocator) LayerOp(
+            beginLayerOp.unmappedBounds,
+            beginLayerOp.localMatrix,
+            beginLayerOp.localClipRect,
+            beginLayerOp.paint);
+    BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+
+    if (bakedOpState) {
+        // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+        currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+    } else {
+        // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+        mLayerReorderers[finishedLayerIndex].clear();
+        return;
     }
 }
 
-void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
-    OpBatch* targetBatch = mBatchLookup[batchId];
-
-    size_t insertBatchIndex = mBatches.size();
-    if (targetBatch) {
-        locateInsertIndex(batchId, op->computedState.clippedBounds,
-                (BatchBase**)(&targetBatch), &insertBatchIndex);
-    }
-
-    if (targetBatch) {
-        targetBatch->batchOp(op);
-    } else  {
-        // new non-merging batch
-        targetBatch = new (mAllocator) OpBatch(batchId, op);
-        mBatchLookup[batchId] = targetBatch;
-        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
-    }
-}
-
-// insertion point of a new batch, will hopefully be immediately after similar batch
-// (generally, should be similar shader)
-void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
-    MergingOpBatch* targetBatch = nullptr;
-
-    // Try to merge with any existing batch with same mergeId
-    auto getResult = mMergingBatches[batchId].find(mergeId);
-    if (getResult != mMergingBatches[batchId].end()) {
-        targetBatch = getResult->second;
-        if (!targetBatch->canMergeWith(op)) {
-            targetBatch = nullptr;
-        }
-    }
-
-    size_t insertBatchIndex = mBatches.size();
-    locateInsertIndex(batchId, op->computedState.clippedBounds,
-            (BatchBase**)(&targetBatch), &insertBatchIndex);
-
-    if (targetBatch) {
-        targetBatch->mergeOp(op);
-    } else  {
-        // new merging batch
-        targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
-        mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
-
-        mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
-    }
-}
-
-void OpReorderer::dump() {
-    for (const BatchBase* batch : mBatches) {
-        batch->dump();
-    }
+void OpReorderer::onLayerOp(const LayerOp& op) {
+    LOG_ALWAYS_FATAL("unsupported");
 }
 
 } // namespace uirenderer
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 6776a3c..73dc9af 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -54,19 +54,63 @@
     };
 }
 
-class OpReorderer {
+class OpReorderer : public CanvasStateClient {
+    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
+
+    /**
+     * Stores the deferred render operations and state used to compute ordering
+     * for a single FBO/layer.
+     */
+    class LayerReorderer {
+    public:
+        // iterate back toward target to see if anything drawn since should overlap the new op
+        // if no target, merging ops still iterate to find similar batch to insert after
+        void locateInsertIndex(int batchId, const Rect& clippedBounds,
+                BatchBase** targetBatch, size_t* insertBatchIndex) const;
+
+        void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
+
+        // insertion point of a new batch, will hopefully be immediately after similar batch
+        // (generally, should be similar shader)
+        void deferMergeableOp(LinearAllocator& allocator,
+                BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
+
+        void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+
+        void clear() {
+            mBatches.clear();
+        }
+
+        void dump() const;
+
+        const BeginLayerOp* beginLayerOp = nullptr;
+
+    private:
+        std::vector<BatchBase*> mBatches;
+
+        /**
+         * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+         * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+         * collide, which avoids the need to resolve mergeid collisions.
+         */
+        std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
+
+        // Maps batch ids to the most recent *non-merging* batch of that id
+        OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+    };
 public:
     OpReorderer();
+    virtual ~OpReorderer() {}
 
     // TODO: not final, just presented this way for simplicity. Layers too?
     void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
             const std::vector< sp<RenderNode> >& nodes);
 
     void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
-    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
 
     /**
-     * replayBakedOps() is templated based on what class will recieve ops being replayed.
+     * replayBakedOps() is templated based on what class will receive ops being replayed.
      *
      * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
      * state->op->opId to lookup a receiver that will be called when the op is replayed.
@@ -77,19 +121,37 @@
      */
 #define BAKED_OP_RECEIVER(Type) \
     [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
-        StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \
+        StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
     },
     template <typename StaticReceiver, typename Arg>
-    void replayBakedOps(Arg* arg) {
+    void replayBakedOps(Arg& arg) {
         static BakedOpReceiver receivers[] = {
             MAP_OPS(BAKED_OP_RECEIVER)
         };
-        StaticReceiver::startFrame(*arg);
-        replayBakedOpsImpl((void*)arg, receivers);
-        StaticReceiver::endFrame(*arg);
+        StaticReceiver::startFrame(arg);
+        replayBakedOpsImpl((void*)&arg, receivers);
+        StaticReceiver::endFrame(arg);
     }
+
+    void dump() const {
+        for (auto&& layer : mLayerReorderers) {
+            layer.dump();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    /// CanvasStateClient interface
+    ///////////////////////////////////////////////////////////////////
+    virtual void onViewportInitialized() override;
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+    virtual GLuint getTargetFbo() const override { return 0; }
+
 private:
-    BakedOpState* bakeOpState(const RecordedOp& recordedOp);
+    LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
+
+    BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
+        return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
+    }
 
     void deferImpl(const DisplayList& displayList);
 
@@ -105,36 +167,27 @@
     void on##Type(const Type& op);
     MAP_OPS(INTERNAL_OP_HANDLER)
 
-    // iterate back toward target to see if anything drawn since should overlap the new op
-    // if no target, merging ops still iterate to find similar batch to insert after
-    void locateInsertIndex(int batchId, const Rect& clippedBounds,
-            BatchBase** targetBatch, size_t* insertBatchIndex) const;
+    // List of every deferred layer's render state. Replayed in reverse order to render a frame.
+    std::vector<LayerReorderer> mLayerReorderers;
 
-    void deferUnmergeableOp(BakedOpState* op, batchid_t batchId);
+    /*
+     * Stack of indices within mLayerReorderers representing currently active layers. If drawing
+     * layerA within a layerB, will contain, in order:
+     *  - 0 (representing FBO 0, always present)
+     *  - layerB's index
+     *  - layerA's index
+     *
+     * Note that this doesn't vector doesn't always map onto all values of mLayerReorderers. When a
+     * layer is finished deferring, it will still be represented in mLayerReorderers, but it's index
+     * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
+     * ops added to it.
+    */
+    std::vector<size_t> mLayerStack;
 
-    // insertion point of a new batch, will hopefully be immediately after similar batch
-    // (generally, should be similar shader)
-    void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
-
-    void dump();
-
-    std::vector<BatchBase*> mBatches;
-
-    /**
-     * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
-     * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
-     * collide, which avoids the need to resolve mergeid collisions.
-     */
-    std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count];
-
-    // Maps batch ids to the most recent *non-merging* batch of that id
-    OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
     CanvasState mCanvasState;
 
     // contains ResolvedOps and Batches
     LinearAllocator mAllocator;
-
-    int mEarliestBatchIndex = 0;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index cd03ac4..d4f65b6 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -223,7 +223,7 @@
 void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     // Since we don't know what the functor will draw, let's dirty
@@ -488,7 +488,7 @@
     currentTransform()->mapRect(bounds);
 
     // Layers only make sense if they are in the framebuffer's bounds
-    bounds.doIntersect(mState.currentClipRect());
+    bounds.doIntersect(mState.currentRenderTargetClip());
     if (!bounds.isEmpty()) {
         // We cannot work with sub-pixels in this case
         bounds.snapToPixelBoundaries();
@@ -1036,7 +1036,7 @@
 }
 
 void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
-    bounds.doIntersect(mState.currentClipRect());
+    bounds.doIntersect(mState.currentRenderTargetClip());
     if (!bounds.isEmpty()) {
         bounds.snapToPixelBoundaries();
         android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
@@ -1084,7 +1084,7 @@
                 .setMeshIndexedQuads(&mesh[0], quadCount)
                 .setFillClear()
                 .setTransform(*currentSnapshot(), transformFlags)
-                .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect()))
+                .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getRenderTargetClip()))
                 .build();
         renderGlop(glop, GlopRenderType::LayerClear);
 
@@ -1099,7 +1099,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
-    const Rect& currentClip = mState.currentClipRect();
+    const Rect& currentClip = mState.currentRenderTargetClip();
     const mat4* currentMatrix = currentTransform();
 
     if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1187,7 +1187,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::setScissorFromClip() {
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom,
@@ -1430,7 +1430,7 @@
             return;
         }
 
-        DeferredDisplayList deferredList(mState.currentClipRect());
+        DeferredDisplayList deferredList(mState.currentRenderTargetClip());
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
         renderNode->defer(deferStruct, 0);
 
@@ -1765,7 +1765,7 @@
     // No need to check against the clip, we fill the clip region
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(mState.currentClipRect());
+    Rect clip(mState.currentRenderTargetClip());
     clip.snapToPixelBoundaries();
 
     SkPaint paint;
@@ -2030,7 +2030,7 @@
     }
     fontRenderer.setTextureFiltering(linearFilter);
 
-    const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
+    const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
@@ -2191,7 +2191,7 @@
     fontRenderer.setTextureFiltering(linearFilter);
 
     // TODO: Implement better clipping for scaled/rotated text
-    const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
+    const Rect* clip = !pureTranslate ? nullptr : &mState.currentRenderTargetClip();
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 36a8dac..c0c61db 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -29,7 +29,6 @@
 bool Properties::debugOverdraw = false;
 bool Properties::showDirtyRegions = false;
 bool Properties::skipEmptyFrames = true;
-bool Properties::swapBuffersWithDamage = true;
 bool Properties::useBufferAge = true;
 bool Properties::enablePartialUpdates = true;
 
@@ -117,7 +116,6 @@
     }
 
     skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true);
-    swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true);
     useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true);
     enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 3512c36..74cd74b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -142,13 +142,6 @@
 #define PROPERTY_SKIP_EMPTY_DAMAGE "debug.hwui.skip_empty_damage"
 
 /**
- * Setting this property will enable or disable usage of EGL_KHR_swap_buffers_with_damage
- * See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt
- * Default is "true"
- */
-#define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage"
-
-/**
  * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension
  * to do partial invalidates. Setting this to "false" will fall back to
  * using BUFFER_PRESERVED instead
@@ -271,8 +264,6 @@
     static bool showDirtyRegions;
     // TODO: Remove after stabilization period
     static bool skipEmptyFrames;
-    // TODO: Remove after stabilization period
-    static bool swapBuffersWithDamage;
     static bool useBufferAge;
     static bool enablePartialUpdates;
 
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index a69f030..dd01637 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -41,7 +41,10 @@
         OP_FN(BitmapOp) \
         OP_FN(RectOp) \
         OP_FN(RenderNodeOp) \
-        OP_FN(SimpleRectsOp)
+        OP_FN(SimpleRectsOp) \
+        OP_FN(BeginLayerOp) \
+        OP_FN(EndLayerOp) \
+        OP_FN(LayerOp)
 
 // Generate OpId enum
 #define IDENTITY_FN(Type) Type,
@@ -112,6 +115,31 @@
     const size_t vertexCount;
 };
 
+/**
+ * Stateful operation! denotes the creation of an off-screen layer,
+ * and that commands following will render into it.
+ */
+struct BeginLayerOp : RecordedOp {
+    BeginLayerOp(BASE_PARAMS)
+            : SUPER(BeginLayerOp) {}
+};
+
+/**
+ * Stateful operation! Denotes end of off-screen layer, and that
+ * commands since last BeginLayerOp should be drawn into parent FBO.
+ *
+ * State in this op is empty, it just serves to signal that a layer has been finished.
+ */
+struct EndLayerOp : RecordedOp {
+    EndLayerOp()
+            : RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
+};
+
+struct LayerOp : RecordedOp {
+    LayerOp(BASE_PARAMS)
+            : SUPER(LayerOp) {}
+};
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3b413aa..1f113bc 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -73,6 +73,20 @@
 }
 
 // ----------------------------------------------------------------------------
+// CanvasStateClient implementation
+// ----------------------------------------------------------------------------
+
+void RecordingCanvas::onViewportInitialized() {
+
+}
+
+void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+    if (removed.flags & Snapshot::kFlagIsFboLayer) {
+        addOp(new (alloc()) EndLayerOp());
+    }
+}
+
+// ----------------------------------------------------------------------------
 // android/graphics/Canvas state operations
 // ----------------------------------------------------------------------------
 // Save (layer)
@@ -97,8 +111,66 @@
 
 int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
         SkCanvas::SaveFlags flags) {
-    LOG_ALWAYS_FATAL("TODO");
-    return 0;
+    if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) {
+        LOG_ALWAYS_FATAL("unclipped layers not supported");
+    }
+    // force matrix/clip isolation for layer
+    flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+
+
+    const Snapshot& previous = *mState.currentSnapshot();
+
+    // initialize the snapshot as though it almost represents an FBO layer so deferred draw
+    // operations will be able to store and restore the current clip and transform info, and
+    // quick rejection will be correct (for display lists)
+
+    const Rect untransformedBounds(left, top, right, bottom);
+
+    // determine clipped bounds relative to previous viewport.
+    Rect visibleBounds = untransformedBounds;
+    previous.transform->mapRect(visibleBounds);
+
+
+    visibleBounds.doIntersect(previous.getRenderTargetClip());
+    visibleBounds.snapToPixelBoundaries();
+
+    Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
+    visibleBounds.doIntersect(previousViewport);
+
+    // Map visible bounds back to layer space, and intersect with parameter bounds
+    Rect layerBounds = visibleBounds;
+    Matrix4 inverse;
+    inverse.loadInverse(*previous.transform);
+    inverse.mapRect(layerBounds);
+    layerBounds.doIntersect(untransformedBounds);
+
+    int saveValue = mState.save((int) flags);
+    Snapshot& snapshot = *mState.writableSnapshot();
+
+    // layerBounds is now original bounds, but with clipped to clip
+    // and viewport to ensure it's minimal size.
+    if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) {
+        // Don't bother recording layer, since it's been rejected
+        snapshot.resetClip(0, 0, 0, 0);
+        return saveValue;
+    }
+
+    snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
+    snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
+    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+
+    Rect clip = layerBounds;
+    clip.translate(-untransformedBounds.left, -untransformedBounds.top);
+    snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
+    snapshot.roundRectClipState = nullptr;
+
+    addOp(new (alloc()) BeginLayerOp(
+            Rect(left, top, right, bottom),
+            *previous.transform, // transform to *draw* with
+            previous.getRenderTargetClip(), // clip to *draw* with
+            refPaint(paint)));
+
+    return saveValue;
 }
 
 // Matrix
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 2179e4c..9c32b1a 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -52,8 +52,8 @@
 // ----------------------------------------------------------------------------
 // CanvasStateClient interface
 // ----------------------------------------------------------------------------
-    virtual void onViewportInitialized() override {}
-    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {}
+    virtual void onViewportInitialized() override;
+    virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
     virtual GLuint getTargetFbo() const override { return -1; }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 894a2bd..351fbaa 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -929,7 +929,7 @@
     const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
     renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
 
-    // If the projection reciever has an outline, we mask projected content to it
+    // If the projection receiver has an outline, we mask projected content to it
     // (which we know, apriori, are all tessellated paths)
     renderer.setProjectionPathMask(alloc, projectionReceiverOutline);
 
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index f824cc0..abef806 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -203,8 +203,8 @@
         return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
     }
 
-    bool setProjectionReceiver(bool shouldRecieve) {
-        return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
+    bool setProjectionReceiver(bool shouldReceive) {
+        return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive);
     }
 
     bool isProjectionReceiver() const {
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index aeeda96..4789b33 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -158,13 +158,12 @@
     /**
      * Returns the current clip in render target coordinates.
      */
-    const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+    const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); }
 
     /*
      * Accessor functions so that the clip area can stay private
      */
     bool clipIsEmpty() const { return mClipArea->isEmpty(); }
-    const Rect& getClipRect() const { return mClipArea->getClipRect(); }
     const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
     bool clipIsSimple() const { return mClipArea->isSimple(); }
     const ClipArea& getClipArea() const { return *mClipArea; }
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
index 12a3e76..0835c29 100644
--- a/libs/hwui/TessellationCache.cpp
+++ b/libs/hwui/TessellationCache.cpp
@@ -217,7 +217,7 @@
     }
 }
 
-static void tessellateShadows(
+void tessellateShadows(
         const Matrix4* drawTransform, const Rect* localClip,
         bool isCasterOpaque, const SkPath* casterPerimeter,
         const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
index b54666b..06e567e 100644
--- a/libs/hwui/TessellationCache.h
+++ b/libs/hwui/TessellationCache.h
@@ -17,16 +17,22 @@
 #ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
 #define ANDROID_HWUI_TESSELLATION_CACHE_H
 
-#include <utils/LruCache.h>
-#include <utils/Mutex.h>
-
 #include "Debug.h"
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "thread/TaskProcessor.h"
 #include "utils/Macros.h"
 #include "utils/Pair.h"
 
+#include <SkPaint.h>
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
 class SkBitmap;
 class SkCanvas;
-class SkPaint;
 class SkPath;
 struct SkRect;
 
@@ -185,6 +191,13 @@
 
 }; // class TessellationCache
 
+void tessellateShadows(
+        const Matrix4* drawTransform, const Rect* localClip,
+        bool isCasterOpaque, const SkPath* casterPerimeter,
+        const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
+        const Vector3& lightCenter, float lightRadius,
+        VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer);
+
 }; // namespace uirenderer
 }; // namespace android
 
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 7c3f2fd..6367dbd 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -135,8 +135,8 @@
     }
 
 
-    void dump() {
-        ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
+    void dump(const char* label = "Vector3") const {
+        ALOGD("%s[%.2f, %.2f, %.2f]", label, x, y, z);
     }
 };
 
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 4c8dedf..cf96d44 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -65,7 +65,7 @@
             MicroBench::DoNotOptimize(&reorderer);
 
             BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
-            reorderer.replayBakedOps<BakedOpRenderer>(&info);
+            reorderer.replayBakedOps<BakedOpRenderer>(info);
         }
         StopBenchmarkTiming();
     });
diff --git a/libs/hwui/microbench/ShadowBench.cpp b/libs/hwui/microbench/ShadowBench.cpp
new file mode 100644
index 0000000..1b0f5ea
--- /dev/null
+++ b/libs/hwui/microbench/ShadowBench.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <benchmark/Benchmark.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+#include "TessellationCache.h"
+#include "microbench/MicroBench.h"
+
+#include <SkPath.h>
+
+#include <memory>
+
+using namespace android;
+using namespace android::uirenderer;
+
+struct ShadowTestData {
+    Matrix4 drawTransform;
+    Rect localClip;
+    Matrix4 casterTransformXY;
+    Matrix4 casterTransformZ;
+    Vector3 lightCenter;
+    float lightRadius;
+};
+
+void createShadowTestData(ShadowTestData* out) {
+    static float SAMPLE_DRAW_TRANSFORM[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            0, 0, 0, 1,
+    };
+    static float SAMPLE_CASTERXY[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            32, 32, 0, 1,
+    };
+    static float SAMPLE_CASTERZ[] = {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            32, 32, 32, 1,
+    };
+    static Rect SAMPLE_CLIP(0, 0, 1536, 2048);
+    static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600};
+    static float SAMPLE_LIGHT_RADIUS = 1600;
+
+    out->drawTransform.load(SAMPLE_DRAW_TRANSFORM);
+    out->localClip = SAMPLE_CLIP;
+    out->casterTransformXY.load(SAMPLE_CASTERXY);
+    out->casterTransformZ.load(SAMPLE_CASTERZ);
+    out->lightCenter = SAMPLE_LIGHT_CENTER;
+    out->lightRadius = SAMPLE_LIGHT_RADIUS;
+}
+
+static inline void tessellateShadows(ShadowTestData& testData, bool opaque,
+        const SkPath& shape, VertexBuffer* ambient, VertexBuffer* spot) {
+    tessellateShadows(&testData.drawTransform, &testData.localClip,
+            opaque, &shape, &testData.casterTransformXY,
+            &testData.casterTransformZ, testData.lightCenter,
+            testData.lightRadius, *ambient, *spot);
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_opaque);
+void BM_TessellateShadows_roundrect_opaque::Run(int iters) {
+    ShadowTestData shadowData;
+    createShadowTestData(&shadowData);
+    SkPath path;
+    path.addRoundRect(SkRect::MakeWH(100, 100), 5, 5);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        VertexBuffer ambient;
+        VertexBuffer spot;
+        tessellateShadows(shadowData, true, path, &ambient, &spot);
+        MicroBench::DoNotOptimize(&ambient);
+        MicroBench::DoNotOptimize(&spot);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_translucent);
+void BM_TessellateShadows_roundrect_translucent::Run(int iters) {
+    ShadowTestData shadowData;
+    createShadowTestData(&shadowData);
+    SkPath path;
+    path.reset();
+    path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5);
+
+    StartBenchmarkTiming();
+    for (int i = 0; i < iters; i++) {
+        std::unique_ptr<VertexBuffer> ambient(new VertexBuffer);
+        std::unique_ptr<VertexBuffer> spot(new VertexBuffer);
+        tessellateShadows(shadowData, false, path, ambient.get(), spot.get());
+        MicroBench::DoNotOptimize(ambient.get());
+        MicroBench::DoNotOptimize(spot.get());
+    }
+    StopBenchmarkTiming();
+}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 238cf06..f571426 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -323,7 +323,7 @@
     BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
             frame.width(), frame.height(), mOpaque);
     // TODO: profiler().draw(mCanvas);
-    reorderer.replayBakedOps<BakedOpRenderer>(&info);
+    reorderer.replayBakedOps<BakedOpRenderer>(info);
 
     bool drew = info.didDraw;
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c9b9637..b353ae6 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -137,6 +137,8 @@
     StringCollection extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
     EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
     EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
+    LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
+            "Missing required extension EGL_KHR_swap_buffers_with_damage");
 }
 
 bool EglManager::hasEglContext() {
@@ -322,18 +324,10 @@
     }
 #endif
 
-#ifdef EGL_KHR_swap_buffers_with_damage
-    if (CC_LIKELY(Properties::swapBuffersWithDamage)) {
-        EGLint rects[4];
-        frame.map(screenDirty, rects);
-        eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
-                screenDirty.isEmpty() ? 0 : 1);
-    } else {
-        eglSwapBuffers(mEglDisplay, frame.mSurface);
-    }
-#else
-    eglSwapBuffers(mEglDisplay, frame.mSurface);
-#endif
+    EGLint rects[4];
+    frame.map(screenDirty, rects);
+    eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects,
+            screenDirty.isEmpty() ? 0 : 1);
 
     EGLint err = eglGetError();
     if (CC_LIKELY(err == EGL_SUCCESS)) {
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index e1249fb..d02f89d 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -27,26 +27,69 @@
 namespace android {
 namespace uirenderer {
 
-#define UNSUPPORTED_OP(Info, Type) \
-        static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
+/**
+ * Class that redirects static operation dispatch to virtual methods on a Client class.
+ *
+ * The client is recreated for every op (so data cannot be persisted between operations), but the
+ * virtual dispatch allows for default behaviors to be specified without enumerating each operation
+ * for every test.
+ *
+ * onXXXOp methods fail by default - tests should override ops they expect
+ * startFrame/endFrame do nothing by default - tests should override to intercept
+ */
+template<class CustomClient, class Arg>
+class TestReceiver {
+public:
+#define CLIENT_METHOD(Type) \
+    virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
+    class Client {
+    public:
+        virtual ~Client() {};
+        MAP_OPS(CLIENT_METHOD)
+
+        virtual void startFrame(Arg& info) {}
+        virtual void endFrame(Arg& info) {}
+    };
+
+#define DISPATCHER_METHOD(Type) \
+    static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \
+        CustomClient client; client.on##Type(arg, op, state); \
+    }
+    MAP_OPS(DISPATCHER_METHOD)
+
+    static void startFrame(Arg& info) {
+        CustomClient client;
+        client.startFrame(info);
+    }
+
+    static void endFrame(Arg& info) {
+        CustomClient client;
+        client.endFrame(info);
+    }
+};
 
 class Info {
 public:
     int index = 0;
 };
 
-class SimpleReceiver {
+// Receiver class which will fail if it receives any ops
+class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {};
+
+class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
 public:
-    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-        EXPECT_EQ(1, info->index++);
+    void startFrame(Info& info) override {
+        EXPECT_EQ(0, info.index++);
     }
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        EXPECT_EQ(0, info->index++);
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, info.index++);
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(2, info.index++);
+    }
+    void endFrame(Info& info) override {
+        EXPECT_EQ(3, info.index++);
+    }
 };
 TEST(OpReorderer, simple) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
@@ -54,28 +97,39 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-
     OpReorderer reorderer;
     reorderer.defer(200, 200, *dl);
 
     Info info;
-    reorderer.replayBakedOps<SimpleReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
+    EXPECT_EQ(4, info.index); // 2 ops + start + end
+}
+
+
+TEST(OpReorderer, simpleRejection) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+    });
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
 }
 
 
 static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingReceiver {
+class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
 public:
-    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-        EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS);
     }
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS);
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
 };
 TEST(OpReorderer, simpleBatching) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
@@ -96,15 +150,14 @@
     reorderer.defer(200, 200, *dl);
 
     Info info;
-    reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
     EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
 }
 
-class RenderNodeReceiver {
+class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client {
 public:
-    UNSUPPORTED_OP(Info, BitmapOp)
-    static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
-        switch(info->index++) {
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        switch(info.index++) {
         case 0:
             EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
             EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
@@ -117,10 +170,6 @@
             FAIL();
         }
     }
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
 };
 TEST(OpReorderer, renderNode) {
     sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
@@ -151,22 +200,17 @@
     reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
 
     Info info;
-    reorderer.replayBakedOps<RenderNodeReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
 }
 
-class ClippedReceiver {
+class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client {
 public:
-    static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
-        EXPECT_EQ(0, info->index++);
+    void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(0, info.index++);
         EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
         EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
         EXPECT_TRUE(state.computedState.transform.isIdentity());
     }
-    UNSUPPORTED_OP(Info, RectOp)
-    UNSUPPORTED_OP(Info, RenderNodeOp)
-    UNSUPPORTED_OP(Info, SimpleRectsOp)
-    static void startFrame(Info& info) {}
-    static void endFrame(Info& info) {}
 };
 TEST(OpReorderer, clipped) {
     sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
@@ -182,8 +226,106 @@
             200, 200, nodes);
 
     Info info;
-    reorderer.replayBakedOps<ClippedReceiver>(&info);
+    reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info);
 }
 
+
+class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
+public:
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(0, info.index++);
+        EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
+        EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
+
+        Matrix4 expectedTransform;
+        expectedTransform.loadTranslate(-10, -10, 0);
+        EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+    }
+    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+        EXPECT_EQ(1, info.index++);
+        EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+        EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
+        EXPECT_TRUE(state.computedState.transform.isIdentity());
+    }
+};
+TEST(OpReorderer, saveLayerSimple) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.drawRect(10, 10, 190, 190, SkPaint());
+        canvas.restore();
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
+    EXPECT_EQ(2, info.index);
 }
+
+
+// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
+class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+public:
+    void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 0) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+        } else if (index == 1) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+        } else { FAIL(); }
+    }
+    void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+        const int index = info.index++;
+        if (index == 2) {
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+        } else if (index == 3) {
+            EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+        } else { FAIL(); }
+    }
+};
+TEST(OpReorderer, saveLayerNested) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
+        {
+            canvas.drawRect(0, 0, 800, 800, SkPaint());
+            canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+            {
+                canvas.drawRect(0, 0, 400, 400, SkPaint());
+            }
+            canvas.restore();
+        }
+        canvas.restore();
+    });
+
+    OpReorderer reorderer;
+    reorderer.defer(800, 800, *dl);
+
+    Info info;
+    reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
+    EXPECT_EQ(4, info.index);
 }
+
+TEST(OpReorderer, saveLayerContentRejection) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
+        canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+
+        // draw within save layer may still be recorded, but shouldn't be drawn
+        canvas.drawRect(200, 200, 400, 400, SkPaint());
+
+        canvas.restore();
+        canvas.restore();
+    });
+    OpReorderer reorderer;
+    reorderer.defer(200, 200, *dl);
+    Info info;
+
+    // should see no ops, even within the layer, since the layer should be rejected
+    reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index ce25fc6..c023123 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -24,11 +24,11 @@
 namespace uirenderer {
 
 static void playbackOps(const DisplayList& displayList,
-        std::function<void(const RecordedOp&)> opReciever) {
+        std::function<void(const RecordedOp&)> opReceiver) {
     for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
         for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
             RecordedOp* op = displayList.getOps()[opIndex];
-            opReciever(*op);
+            opReceiver(*op);
         }
     }
 }
@@ -109,5 +109,123 @@
     ASSERT_EQ(2, count); // two draws observed
 }
 
+TEST(RecordingCanvas, saveLayerSimple) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(10, 20, 190, 180, SkPaint());
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        Matrix4 expectedMatrix;
+        switch(count++) {
+        case 0:
+            EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
+            // TODO: add asserts
+            break;
+        case 1:
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+            EXPECT_EQ(Rect(0, 0, 180, 160), op.localClipRect);
+            EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+            expectedMatrix.loadTranslate(-10, -20, 0);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+            break;
+        case 2:
+            EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+            // TODO: add asserts
+            break;
+        default:
+            FAIL();
+        }
+    });
+    EXPECT_EQ(3, count);
 }
+
+TEST(RecordingCanvas, saveLayerViewportCrop) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        // shouldn't matter, since saveLayer will clip to its bounds
+        canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
+
+        canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // recorded clip rect should be intersection of
+            // viewport and saveLayer bounds, in layer space
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+            expectedMatrix.loadTranslate(-100, -100, 0);
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
 }
+
+TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(100, 100);
+        canvas.rotate(45);
+        canvas.translate(-50, -50);
+
+        canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 100, 100, SkPaint());
+        canvas.restore();
+
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayerRotateClipped) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(100, 100);
+        canvas.rotate(45);
+        canvas.translate(-200, -200);
+
+        // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
+        canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+        canvas.restore();
+
+        canvas.restore();
+    });
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        if (count++ == 1) {
+            Matrix4 expectedMatrix;
+            EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+            // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
+            // the parent 200x200 viewport, but prior to rotation
+            EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
+            EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+            expectedMatrix.loadIdentity();
+            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+        }
+    });
+    EXPECT_EQ(3, count);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 80d83a2..99ecc9b 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -31,6 +31,12 @@
 #define EXPECT_MATRIX_APPROX_EQ(a, b) \
     EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
 
+#define EXPECT_RECT_APPROX_EQ(a, b) \
+    EXPECT_TRUE(MathUtils::areEqual(a.left, b.left) \
+            && MathUtils::areEqual(a.top, b.top) \
+            && MathUtils::areEqual(a.right, b.right) \
+            && MathUtils::areEqual(a.bottom, b.bottom));
+
 class TestUtils {
 public:
     static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
new file mode 100644
index 0000000..84aae75
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "TestWindowContext.h"
+
+#include "AnimationContext.h"
+#include "DisplayListCanvas.h"
+#include "IContextFactory.h"
+#include "RenderNode.h"
+#include "SkTypes.h"
+#include "gui/BufferQueue.h"
+#include "gui/CpuConsumer.h"
+#include "gui/IGraphicBufferConsumer.h"
+#include "gui/IGraphicBufferProducer.h"
+#include "gui/Surface.h"
+#include "renderthread/RenderProxy.h"
+
+
+namespace {
+
+/**
+ * Helper class for setting up android::uirenderer::renderthread::RenderProxy.
+ */
+class ContextFactory : public android::uirenderer::IContextFactory {
+public:
+    android::uirenderer::AnimationContext* createAnimationContext
+        (android::uirenderer::renderthread::TimeLord& clock) override {
+        return new android::uirenderer::AnimationContext(clock);
+    }
+};
+
+} // anonymous namespace
+
+namespace android {
+namespace uirenderer {
+
+/**
+  Android strong pointers (android::sp) can't hold forward-declared classes,
+  so we have to use pointer-to-implementation here if we want to hide the
+  details from our non-framework users.
+*/
+
+class TestWindowContext::TestWindowData {
+
+public:
+
+    TestWindowData(SkISize size) : mSize(size) {
+        android::BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+        mCpuConsumer = new android::CpuConsumer(mConsumer, 1);
+        mCpuConsumer->setName(android::String8("TestWindowContext"));
+        mCpuConsumer->setDefaultBufferSize(mSize.width(), mSize.height());
+        mAndroidSurface = new android::Surface(mProducer);
+        native_window_set_buffers_dimensions(mAndroidSurface.get(),
+                                             mSize.width(), mSize.height());
+        native_window_set_buffers_format(mAndroidSurface.get(),
+                                         android::PIXEL_FORMAT_RGBA_8888);
+        native_window_set_usage(mAndroidSurface.get(),
+                                GRALLOC_USAGE_SW_READ_OFTEN |
+                                GRALLOC_USAGE_SW_WRITE_NEVER |
+                                GRALLOC_USAGE_HW_RENDER);
+        mRootNode.reset(new android::uirenderer::RenderNode());
+        mRootNode->incStrong(nullptr);
+        mRootNode->mutateStagingProperties().setLeftTopRightBottom
+            (0, 0, mSize.width(), mSize.height());
+        mRootNode->mutateStagingProperties().setClipToBounds(false);
+        mRootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
+        ContextFactory factory;
+        mProxy.reset
+            (new android::uirenderer::renderthread::RenderProxy(false,
+                                                                mRootNode.get(),
+                                                                &factory));
+        mProxy->loadSystemProperties();
+        mProxy->initialize(mAndroidSurface.get());
+        float lightX = mSize.width() / 2.0f;
+        android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
+        mProxy->setup(mSize.width(), mSize.height(), 800.0f,
+                             255 * 0.075f, 255 * 0.15f);
+        mProxy->setLightCenter(lightVector);
+        mCanvas.reset(new
+            android::uirenderer::DisplayListCanvas(mSize.width(),
+                                                   mSize.height()));
+    }
+
+    SkCanvas* prepareToDraw() {
+        //mCanvas->reset(mSize.width(), mSize.height());
+        mCanvas->clipRect(0, 0, mSize.width(), mSize.height(),
+                               SkRegion::Op::kReplace_Op);
+        return mCanvas->asSkCanvas();
+    }
+
+    void finishDrawing() {
+        mRootNode->setStagingDisplayList(mCanvas->finishRecording());
+        mProxy->syncAndDrawFrame();
+        // Surprisingly, calling mProxy->fence() here appears to make no difference to
+        // the timings we record.
+    }
+
+    void fence() {
+        mProxy->fence();
+    }
+
+    bool capturePixels(SkBitmap* bmp) {
+        SkImageInfo destinationConfig =
+            SkImageInfo::Make(mSize.width(), mSize.height(),
+                              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+        bmp->allocPixels(destinationConfig);
+        sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
+                    mSize.width() * mSize.height());
+
+        android::CpuConsumer::LockedBuffer nativeBuffer;
+        android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer);
+        if (retval == android::BAD_VALUE) {
+            SkDebugf("write_canvas_png() got no buffer; returning transparent");
+            // No buffer ready to read - commonly triggered by dm sending us
+            // a no-op source, or calling code that doesn't do anything on this
+            // backend.
+            bmp->eraseColor(SK_ColorTRANSPARENT);
+            return false;
+        } else if (retval) {
+            SkDebugf("Failed to lock buffer to read pixels: %d.", retval);
+            return false;
+        }
+
+        // Move the pixels into the destination SkBitmap
+
+        SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
+                       "Native buffer not RGBA!");
+        SkImageInfo nativeConfig =
+            SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
+                              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+        // Android stride is in pixels, Skia stride is in bytes
+        SkBitmap nativeWrapper;
+        bool success =
+            nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
+        if (!success) {
+            SkDebugf("Failed to wrap HWUI buffer in a SkBitmap");
+            return false;
+        }
+
+        SK_ALWAYSBREAK(bmp->colorType() == kRGBA_8888_SkColorType &&
+                       "Destination buffer not RGBA!");
+        success =
+            nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
+        if (!success) {
+            SkDebugf("Failed to extract pixels from HWUI buffer");
+            return false;
+        }
+
+        mCpuConsumer->unlockBuffer(nativeBuffer);
+
+        return true;
+    }
+
+private:
+
+    std::unique_ptr<android::uirenderer::RenderNode> mRootNode;
+    std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy;
+    std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas;
+    android::sp<android::IGraphicBufferProducer> mProducer;
+    android::sp<android::IGraphicBufferConsumer> mConsumer;
+    android::sp<android::CpuConsumer> mCpuConsumer;
+    android::sp<android::Surface> mAndroidSurface;
+    SkISize mSize;
+};
+
+
+TestWindowContext::TestWindowContext() :
+    mData (nullptr) { }
+
+void TestWindowContext::initialize(int width, int height)  {
+    mData = new TestWindowData(SkISize::Make(width, height));
+}
+
+SkCanvas* TestWindowContext::prepareToDraw() {
+    return mData ? mData->prepareToDraw() : nullptr;
+}
+
+void TestWindowContext::finishDrawing() {
+    if (mData) {
+        mData->finishDrawing();
+    }
+}
+
+void TestWindowContext::fence() {
+    if (mData) {
+        mData->fence();
+    }
+}
+
+bool TestWindowContext::capturePixels(SkBitmap* bmp) {
+    return mData ? mData->capturePixels(bmp) : false;
+}
+
+} // namespace uirenderer
+} // namespace android
+
diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h
new file mode 100644
index 0000000..445a11b
--- /dev/null
+++ b/libs/hwui/utils/TestWindowContext.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+#ifndef TESTWINDOWCONTEXT_H_
+#define TESTWINDOWCONTEXT_H_
+
+#include <cutils/compiler.h>
+
+class SkBitmap;
+class SkCanvas;
+
+namespace android {
+
+namespace uirenderer {
+
+/**
+  Wraps all libui/libgui classes and types that external tests depend on,
+  exposing only primitive Skia types.
+*/
+
+class ANDROID_API TestWindowContext {
+
+public:
+
+    TestWindowContext();
+
+    /// We need to know the size of the window.
+    void initialize(int width, int height);
+
+    /// Returns a canvas to draw into; NULL if not yet initialize()d.
+    SkCanvas* prepareToDraw();
+
+    /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d.
+    void finishDrawing();
+
+    /// Blocks until HWUI has processed all pending drawing commands;
+    /// no-op if not yet initialize()d.
+    void fence();
+
+    /// Returns false if not yet initialize()d.
+    bool capturePixels(SkBitmap* bmp);
+
+private:
+    /// Hidden implementation.
+    class TestWindowData;
+
+    TestWindowData* mData;
+
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // TESTWINDOWCONTEXT_H_
+
diff --git a/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml b/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
index 473802e..66d340c 100644
--- a/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-ne-rNP/strings.xml
@@ -21,14 +21,14 @@
     <string name="backup_confirm_text" msgid="1878021282758896593">"एउटा जोडिएको डेस्कटप कम्प्युटरमा सबै डेटाको एउटा पूर्ण जगेडाको अनुरोध गरिएको छ। के तपाईँ यो हुन दिन चाहनुहुन्छ? \n\nयदि तपाईँले जगेडाको लागि आफैँ अनुरोध गर्नु भएन भने प्रक्रियालाई अगाडि बढ्न अनुमति नदिनुहोस्।"</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"मेरो डेटा ब्याकअप गर्नुहोस्"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"जगेडा नगर्नुहोस्"</string>
-    <string name="restore_confirm_text" msgid="7499866728030461776">"एउटा जडित डेस्कटप कम्प्युटरबाट सबै डेटाको पूर्ण पुनःबहाली अनुरोध गरियो। के तपाईं यो हुन अनुमति दिनुहुन्छ?\n\nयदि तपाईं आफैं पुनःबहाली अनुरोध गर्नुहुन्न भने अपरेसनलाई अघि बढाउन अनुमति नदिनुहोस्। यसले उपकरणमा भएको कुनै पनि डेटालाई बदल्ने छ!"</string>
+    <string name="restore_confirm_text" msgid="7499866728030461776">"एउटा जडित डेस्कटप कम्प्युटरबाट सबै डेटाको पूर्ण पुनःबहाली अनुरोध गरियो। के तपाईँ यो हुन अनुमति दिनुहुन्छ?\n\nयदि तपाईँ आफैं पुनःबहाली अनुरोध गर्नुहुन्न भने अपरेसनलाई अघि बढाउन अनुमति नदिनुहोस्। यसले उपकरणमा भएको कुनै पनि डेटालाई बदल्ने छ!"</string>
     <string name="allow_restore_button_label" msgid="3081286752277127827">"मेरो डेटा पुनःबहाली गर्नुहोस्"</string>
     <string name="deny_restore_button_label" msgid="1724367334453104378">"पुन:स्थापना नगर्नुहोस्"</string>
     <string name="current_password_text" msgid="8268189555578298067">"कृपया तल तपाईंको हालको ब्याकअप पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="device_encryption_restore_text" msgid="1570864916855208992">"कृपया तल तपाईंको उपकरण एन्क्रिप्सन पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="device_encryption_backup_text" msgid="5866590762672844664">"कृपया तल तपाईंको उपकरण एन्क्रिप्सन पासवर्ड प्रविष्टि गर्नुहोस्: यो ब्याकप सँग्रह एन्क्रिप्ट गर्न पनि प्रयोग हुने छ।"</string>
     <string name="backup_enc_password_text" msgid="4981585714795233099">"ब्याकप डेटालाई encrypt गर्न पासवर्ड प्रविष्टि गर्नुहोस्, यदि यो खालि छोडिएको खण्डमा तपाईको पुरानै पासवर्ड प्रयोग हुने छ।"</string>
-    <string name="backup_enc_password_optional" msgid="1350137345907579306">"यदि तपाईं पूर्ण ब्याकअप डेटा इन्क्रिप्ट गर्न चाहनु हुन्छ भने तल पासवर्ड प्रविष्टि गर्नुहोस्।"</string>
+    <string name="backup_enc_password_optional" msgid="1350137345907579306">"यदि तपाईँ पूर्ण ब्याकअप डेटा इन्क्रिप्ट गर्न चाहनु हुन्छ भने तल पासवर्ड प्रविष्टि गर्नुहोस्।"</string>
     <string name="backup_enc_password_required" msgid="7889652203371654149">"तपाईँको उपकरण गुप्तिकरण गरिए देखि, तपाईंले आफ्नो जगेडा गुप्तिकरण गर्न आवश्यक छ। कृपया तल पासवर्ड प्रविष्ट गर्नुहोस्:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"यदि पुनःबहाली डेटा इन्क्रिप्ट छ भने कृपया तल पासवर्ड प्रविष्टि गर्नुहोस्:"</string>
     <string name="toast_backup_started" msgid="550354281452756121">"जगेडा राख्न सुरु हुँदै..."</string>
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index 085df35..eba00a6 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.documentsui.DocListItem xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@drawable/item_doc_list_background"
@@ -137,4 +137,4 @@
 
     </LinearLayout>
 
-</com.android.documentsui.DocListItem>
+</com.android.documentsui.ListItem>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index a317ef2..45a8907 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -98,10 +98,12 @@
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
+import com.android.internal.annotations.GuardedBy;
 
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -1743,6 +1745,9 @@
         private Context mContext;
         private int mCursorCount;
         private boolean mIsLoading;
+        @GuardedBy("mPendingDelete")
+        private Boolean mPendingDelete = false;
+        @GuardedBy("mPendingDelete")
         private SparseBooleanArray mMarkedForDeletion = new SparseBooleanArray();
         private UpdateListener mUpdateListener;
         @Nullable private Cursor mCursor;
@@ -1787,35 +1792,39 @@
         }
 
         int getItemCount() {
-            return mCursorCount - mMarkedForDeletion.size();
+            synchronized(mPendingDelete) {
+                return mCursorCount - mMarkedForDeletion.size();
+            }
         }
 
         Cursor getItem(int position) {
-            // Items marked for deletion are masked out of the UI.  To do this, for every marked
-            // item whose position is less than the requested item position, advance the requested
-            // position by 1.
-            final int originalPos = position;
-            final int size = mMarkedForDeletion.size();
-            for (int i = 0; i < size; ++i) {
-                // It'd be more concise, but less efficient, to iterate over positions while calling
-                // mMarkedForDeletion.get.  Instead, iterate over deleted entries.
-                if (mMarkedForDeletion.keyAt(i) <= position && mMarkedForDeletion.valueAt(i)) {
-                    ++position;
+            synchronized(mPendingDelete) {
+                // Items marked for deletion are masked out of the UI.  To do this, for every marked
+                // item whose position is less than the requested item position, advance the requested
+                // position by 1.
+                final int originalPos = position;
+                final int size = mMarkedForDeletion.size();
+                for (int i = 0; i < size; ++i) {
+                    // It'd be more concise, but less efficient, to iterate over positions while calling
+                    // mMarkedForDeletion.get.  Instead, iterate over deleted entries.
+                    if (mMarkedForDeletion.keyAt(i) <= position && mMarkedForDeletion.valueAt(i)) {
+                        ++position;
+                    }
                 }
-            }
 
-            if (DEBUG && position != originalPos) {
-                Log.d(TAG, "Item position adjusted for deletion.  Original: " + originalPos
-                        + "  Adjusted: " + position);
-            }
+                if (DEBUG && position != originalPos) {
+                    Log.d(TAG, "Item position adjusted for deletion.  Original: " + originalPos
+                            + "  Adjusted: " + position);
+                }
 
-            if (position >= mCursorCount) {
-                throw new IndexOutOfBoundsException("Attempt to retrieve " + position + " of " +
-                        mCursorCount + " items");
-            }
+                if (position >= mCursorCount) {
+                    throw new IndexOutOfBoundsException("Attempt to retrieve " + position + " of " +
+                            mCursorCount + " items");
+                }
 
-            mCursor.moveToPosition(position);
-            return mCursor;
+                mCursor.moveToPosition(position);
+                return mCursor;
+            }
         }
 
         private boolean isEmpty() {
@@ -1848,17 +1857,19 @@
         }
 
         List<DocumentInfo> getDocumentsMarkedForDeletion() {
-            final int size = mMarkedForDeletion.size();
-            List<DocumentInfo> docs =  new ArrayList<>(size);
+            synchronized (mPendingDelete) {
+                final int size = mMarkedForDeletion.size();
+                List<DocumentInfo> docs =  new ArrayList<>(size);
 
-            for (int i = 0; i < size; ++i) {
-                final int position = mMarkedForDeletion.keyAt(i);
-                checkState(position < mCursorCount);
-                mCursor.moveToPosition(position);
-                final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(mCursor);
-                docs.add(doc);
+                for (int i = 0; i < size; ++i) {
+                    final int position = mMarkedForDeletion.keyAt(i);
+                    checkState(position < mCursorCount);
+                    mCursor.moveToPosition(position);
+                    final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(mCursor);
+                    docs.add(doc);
+                }
+                return docs;
             }
-            return docs;
         }
 
         /**
@@ -1869,17 +1880,23 @@
          * @param selected A selection representing the files to delete.
          */
         void markForDeletion(Selection selected) {
-            // Only one deletion operation at a time.
-            checkState(mMarkedForDeletion.size() == 0);
-            // There should never be more to delete than what exists.
-            checkState(mCursorCount >= selected.size());
+            synchronized (mPendingDelete) {
+                mPendingDelete = true;
+                // Only one deletion operation at a time.
+                checkState(mMarkedForDeletion.size() == 0);
+                // There should never be more to delete than what exists.
+                checkState(mCursorCount >= selected.size());
 
-            final int size = selected.size();
-            for (int i = 0; i < size; ++i) {
-                int position = selected.get(i);
-                if (DEBUG) Log.d(TAG, "Marked position " + position + " for deletion");
-                mMarkedForDeletion.append(position, true);
-                mViewAdapter.notifyItemRemoved(position);
+                int[] positions = selected.getAll();
+                Arrays.sort(positions);
+
+                // Walk backwards through the set, since we're removing positions.
+                // Otherwise, positions would change after the first modification.
+                for (int p = positions.length - 1; p >= 0; p--) {
+                    mMarkedForDeletion.append(positions[p], true);
+                    mViewAdapter.notifyItemRemoved(positions[p]);
+                    if (DEBUG) Log.d(TAG, "Scheduled " + positions[p] + " for delete.");
+                }
             }
         }
 
@@ -1888,17 +1905,24 @@
          * unmarked, and restored in the UI.  See {@link #markForDeletion(Selection)}.
          */
         void undoDeletion() {
-            // Iterate over deleted items, temporarily marking them false in the deletion list, and
-            // re-adding them to the UI.
-            final int size = mMarkedForDeletion.size();
-            for (int i = 0; i < size; ++i) {
-                final int position = mMarkedForDeletion.keyAt(i);
-                mMarkedForDeletion.put(position, false);
-                mViewAdapter.notifyItemInserted(position);
+            synchronized (mPendingDelete) {
+                // Iterate over deleted items, temporarily marking them false in the deletion list, and
+                // re-adding them to the UI.
+                final int size = mMarkedForDeletion.size();
+                for (int i = 0; i < size; ++i) {
+                    final int position = mMarkedForDeletion.keyAt(i);
+                    mMarkedForDeletion.put(position, false);
+                    mViewAdapter.notifyItemInserted(position);
+                }
+                resetDeleteData();
             }
+        }
 
-            // Then, clear the deletion list.
-            mMarkedForDeletion.clear();
+        private void resetDeleteData() {
+            synchronized (mPendingDelete) {
+                mPendingDelete = false;
+                mMarkedForDeletion.clear();
+            }
         }
 
         /**
@@ -1909,9 +1933,16 @@
          * snackbars) for errors, info, etc.
          */
         void finalizeDeletion(DeletionListener listener) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
-            task.execute();
+            synchronized (mPendingDelete) {
+                if (mPendingDelete) {
+                    // Necessary to avoid b/25072545. Even when that's resolved, this
+                    // is a nice safe thing to day.
+                    mPendingDelete = false;
+                    final ContentResolver resolver = mContext.getContentResolver();
+                    DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
+                    task.execute();
+                }
+            }
         }
 
         /**
@@ -1968,7 +1999,7 @@
                 } else {
                     if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
                 }
-                mMarkedForDeletion.clear();
+                resetDeleteData();
 
                 mListener.onCompletion();
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index c06ea0a..d4c3ba3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -38,9 +38,7 @@
      * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isTouchEvent(MotionEvent e) {
-        return isTouchType(e.getToolType(0))
-                // Temporarily work around uiautomator's missing tool type support.
-                || isUnknownType(e.getToolType(0));
+        return isTouchType(e.getToolType(0));
     }
 
     /**
@@ -51,7 +49,7 @@
     }
 
     /**
-     * Returns true if type is finger or stylus.
+     * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isTouchType(int toolType) {
         return toolType == MotionEvent.TOOL_TYPE_FINGER
@@ -59,13 +57,6 @@
     }
 
     /**
-     * Returns true if type is unknown.
-     */
-    static boolean isUnknownType(int toolType) {
-        return toolType == MotionEvent.TOOL_TYPE_UNKNOWN;
-    }
-
-    /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
     static boolean isActionDown(MotionEvent e) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index b9c4da1..ef53d53 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -601,6 +601,14 @@
             mTotalSelection = new SparseBooleanArray();
         }
 
+        @VisibleForTesting
+        public Selection(int... positions) {
+            this();
+            for (int i = 0; i < positions.length; i++) {
+                add(positions[i]);
+            }
+        }
+
         /**
          * @param position
          * @return true if the position is currently selected.
@@ -624,6 +632,18 @@
         }
 
         /**
+         * Returns an unordered array of selected positions.
+         */
+        public int[] getAll() {
+            final int size = size();
+            int[] positions = new int[size];
+            for (int i = 0; i < size; i++) {
+                positions[i] = get(i);
+            }
+            return positions;
+        }
+
+        /**
          * @return size of the selection.
          */
         public int size() {
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
new file mode 100644
index 0000000..8baadba
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := TestDocumentsProvider
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := tests
+#LOCAL_SDK_VERSION := current
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
new file mode 100644
index 0000000..66988a1
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.documentsui.testing">
+    <application>
+        <provider android:name="TestDocumentsProvider"
+                android:authorities="com.android.documentsui.testing"
+                android:exported="true"
+                android:grantUriPermissions="true"
+                android:permission="android.permission.MANAGE_DOCUMENTS">
+            <intent-filter>
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+            </intent-filter>
+        </provider>
+    </application>
+</manifest>
diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
new file mode 100644
index 0000000..63ff0de
--- /dev/null
+++ b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java
@@ -0,0 +1,299 @@
+/*
+ * 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.documentsui.testing;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.os.AsyncTask;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TestDocumentsProvider extends DocumentsProvider {
+    private static final String TAG = "TestDocumentsProvider";
+
+    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+            Root.COLUMN_ROOT_ID,
+            Root.COLUMN_FLAGS,
+            Root.COLUMN_ICON,
+            Root.COLUMN_TITLE,
+            Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+    };
+
+    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID,
+            Document.COLUMN_MIME_TYPE,
+            Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_LAST_MODIFIED,
+            Document.COLUMN_FLAGS,
+            Document.COLUMN_SIZE,
+    };
+
+    private static String[] resolveRootProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+    }
+
+    private static String[] resolveDocumentProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+    }
+
+    @Override
+    public boolean onCreate() {
+        resetRoots();
+        return true;
+    }
+
+    @Override
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+
+        RowBuilder row = result.newRow();
+        row.add(Root.COLUMN_ROOT_ID, "local");
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
+        row.add(Root.COLUMN_TITLE, "TEST-Local");
+        row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary");
+        row.add(Root.COLUMN_DOCUMENT_ID, "doc:local");
+
+        row = result.newRow();
+        row.add(Root.COLUMN_ROOT_ID, "create");
+        row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
+        row.add(Root.COLUMN_TITLE, "TEST-Create");
+        row.add(Root.COLUMN_DOCUMENT_ID, "doc:create");
+
+        return result;
+    }
+
+    private Map<String, Doc> mDocs = new HashMap<>();
+
+    private Doc mLocalRoot;
+    private Doc mCreateRoot;
+
+    private Doc buildDoc(String docId, String displayName, String mimeType) {
+        final Doc doc = new Doc();
+        doc.docId = docId;
+        doc.displayName = displayName;
+        doc.mimeType = mimeType;
+        mDocs.put(doc.docId, doc);
+        return doc;
+    }
+
+    public void resetRoots() {
+        Log.d(TAG, "resetRoots()");
+
+        mDocs.clear();
+
+        mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR);
+
+        mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR);
+        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+
+        {
+            Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1");
+            file1.contents = "fileone".getBytes();
+            file1.flags = Document.FLAG_SUPPORTS_WRITE;
+            mLocalRoot.children.add(file1);
+            mCreateRoot.children.add(file1);
+        }
+
+        {
+            Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2");
+            file2.contents = "filetwo".getBytes();
+            file2.flags = Document.FLAG_SUPPORTS_WRITE;
+            mLocalRoot.children.add(file2);
+            mCreateRoot.children.add(file2);
+        }
+
+        Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR);
+        mLocalRoot.children.add(dir1);
+
+        {
+            Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3");
+            file3.contents = "filethree".getBytes();
+            file3.flags = Document.FLAG_SUPPORTS_WRITE;
+            dir1.children.add(file3);
+        }
+
+        Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR);
+        mCreateRoot.children.add(dir2);
+
+        {
+            Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4");
+            file4.contents = "filefour".getBytes();
+            file4.flags = Document.FLAG_SUPPORTS_WRITE;
+            dir2.children.add(file4);
+        }
+    }
+
+    private static class Doc {
+        public String docId;
+        public int flags;
+        public String displayName;
+        public long size;
+        public String mimeType;
+        public long lastModified;
+        public byte[] contents;
+        public List<Doc> children = new ArrayList<>();
+
+        public void include(MatrixCursor result) {
+            final RowBuilder row = result.newRow();
+            row.add(Document.COLUMN_DOCUMENT_ID, docId);
+            row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+            row.add(Document.COLUMN_SIZE, size);
+            row.add(Document.COLUMN_MIME_TYPE, mimeType);
+            row.add(Document.COLUMN_FLAGS, flags);
+            row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
+        }
+    }
+
+    @Override
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        for (Doc doc : mDocs.get(parentDocumentId).children) {
+            if (doc.docId.equals(documentId)) {
+                return true;
+            }
+            if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
+                return isChildDocument(doc.docId, documentId);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String createDocument(String parentDocumentId, String mimeType, String displayName)
+            throws FileNotFoundException {
+        final String docId = "doc:" + System.currentTimeMillis();
+        final Doc doc = buildDoc(docId, displayName, mimeType);
+        doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME;
+        mDocs.get(parentDocumentId).children.add(doc);
+        return docId;
+    }
+
+    @Override
+    public String renameDocument(String documentId, String displayName)
+            throws FileNotFoundException {
+        mDocs.get(documentId).displayName = displayName;
+        return null;
+    }
+
+    @Override
+    public void deleteDocument(String documentId) throws FileNotFoundException {
+        mDocs.remove(documentId);
+        for (Doc doc : mDocs.values()) {
+            doc.children.remove(documentId);
+        }
+    }
+
+    @Override
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        mDocs.get(documentId).include(result);
+        return result;
+    }
+
+    @Override
+    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+            String sortOrder) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        for (Doc doc : mDocs.get(parentDocumentId).children) {
+            doc.include(result);
+        }
+        return result;
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(String documentId, String mode,
+            CancellationSignal signal) throws FileNotFoundException {
+        final Doc doc = mDocs.get(documentId);
+        if (doc == null) {
+            throw new FileNotFoundException();
+        }
+        final ParcelFileDescriptor[] pipe;
+        try {
+            pipe = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+        if (mode.contains("w")) {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (doc) {
+                        try {
+                            final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(
+                                    pipe[0]);
+                            doc.contents = readFullyNoClose(is);
+                            is.close();
+                            doc.notifyAll();
+                        } catch (IOException e) {
+                            Log.w(TAG, "Failed to stream", e);
+                        }
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+            return pipe[1];
+        } else {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    synchronized (doc) {
+                        try {
+                            final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(
+                                    pipe[1]);
+                            while (doc.contents == null) {
+                                doc.wait();
+                            }
+                            os.write(doc.contents);
+                            os.close();
+                        } catch (IOException e) {
+                            Log.w(TAG, "Failed to stream", e);
+                        } catch (InterruptedException e) {
+                            Log.w(TAG, "Interuppted", e);
+                        }
+                    }
+                    return null;
+                }
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+            return pipe[0];
+        }
+    }
+
+    private static byte[] readFullyNoClose(InputStream in) throws IOException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int count;
+        while ((count = in.read(buffer)) != -1) {
+            bytes.write(buffer, 0, count);
+        }
+        return bytes.toByteArray();
+    }
+}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index 3f191a9..cf486b1 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -3,11 +3,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
+#LOCAL_SDK_VERSION := current
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator
 
 LOCAL_PACKAGE_NAME := DocumentsUITests
 LOCAL_INSTRUMENTATION_FOR := DocumentsUI
@@ -15,3 +16,5 @@
 LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
+
+include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
index ace9e27..36d880a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
@@ -32,10 +32,6 @@
 import com.android.documentsui.model.DocumentInfo;
 
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
 
 public class DirectoryFragmentModelTest extends AndroidTestCase {
 
@@ -117,15 +113,6 @@
         assertEquals("0", docs.get(0).documentId);
         assertEquals("1", docs.get(1).documentId);
         assertEquals("4", docs.get(2).documentId);
-
-        TestDeletionListener testListener = new TestDeletionListener();
-        model.finalizeDeletion(testListener);
-        testListener.waitForDone();
-
-        docs = getDocumentInfo(0, 1, 2);
-        assertEquals("0", docs.get(0).documentId);
-        assertEquals("1", docs.get(1).documentId);
-        assertEquals("2", docs.get(2).documentId);
     }
 
     // Tests that Model.getItem returns the right items after a deletion is undone.
@@ -149,20 +136,12 @@
         };
     }
 
-    private void delete(int... items) {
-        Selection sel = new Selection();
-        for (int item: items) {
-            sel.add(item);
-        }
-        model.markForDeletion(sel);
+    private void delete(int... positions) {
+        model.markForDeletion(new Selection(positions));
     }
 
-    private List<DocumentInfo> getDocumentInfo(int... items) {
-        Selection sel = new Selection();
-        for (int item: items) {
-            sel.add(item);
-        }
-        return model.getDocuments(sel);
+    private List<DocumentInfo> getDocumentInfo(int... positions) {
+        return model.getDocuments(new Selection(positions));
     }
 
     private static class DummyListener extends Model.UpdateListener {
@@ -177,20 +156,4 @@
             return null;
         }
     }
-
-    private static class TestDeletionListener extends Model.DeletionListener {
-        final CountDownLatch mSignal = new CountDownLatch(1);
-
-        @Override
-        public void onCompletion() {
-            mSignal.countDown();
-        }
-
-        public void waitForDone() {
-            try {
-                boolean timeout = mSignal.await(10, TimeUnit.SECONDS);
-                assertTrue("Timed out waiting for deletion completion", timeout);
-            } catch (InterruptedException e) {}
-        }
-    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
new file mode 100644
index 0000000..1f4b751
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.documentsui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.view.MotionEvent;
+
+import java.util.concurrent.TimeoutException;
+
+public class FilesActivityUiTest extends InstrumentationTestCase {
+
+    private static final String TAG = "FilesActivityUiTest";
+    private static final String TARGET_PKG = "com.android.documentsui";
+    private static final String LAUNCHER_PKG = "com.android.launcher";
+    private static final int ONE_SECOND = 1000;
+    private static final int FIVE_SECONDS = 5 * ONE_SECOND;
+
+    private ActionBar mBar;
+    private UiDevice mDevice;
+    private Context mContext;
+
+    public void setUp() throws TimeoutException {
+        // Initialize UiDevice instance.
+        mDevice = UiDevice.getInstance(getInstrumentation());
+
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+
+        // Start from the home screen.
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS);
+
+        // Launch app.
+        mContext = getInstrumentation().getContext();
+        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear.
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS);
+        mDevice.waitForIdle();
+
+        mBar = new ActionBar();
+    }
+
+    public void testSwitchMode() throws Exception {
+        UiObject2 mode = mBar.gridMode(100);
+        if (mode != null) {
+            mode.click();
+            assertNotNull(mBar.listMode(ONE_SECOND));
+        } else {
+            mBar.listMode(100).click();
+            assertNotNull(mBar.gridMode(ONE_SECOND));
+        }
+    }
+
+    private class ActionBar {
+
+        public UiObject2 gridMode(int timeout) {
+            // Note that we're using By.desc rather than By.res, because of b/25285770
+            BySelector selector = By.desc("Grid view");
+            if (timeout > 0) {
+                mDevice.wait(Until.findObject(selector), timeout);
+            }
+            return mDevice.findObject(selector);
+        }
+
+        public UiObject2 listMode(int timeout) {
+            // Note that we're using By.desc rather than By.res, because of b/25285770
+            BySelector selector = By.desc("List view");
+            if (timeout > 0) {
+                mDevice.wait(Until.findObject(selector), timeout);
+            }
+            return mDevice.findObject(selector);
+        }
+    }
+}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
new file mode 100644
index 0000000..fe82c8d
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_workman.kcm
@@ -0,0 +1,334 @@
+# 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.
+
+#
+# English (US), Workman keyboard layout.
+# Unlike the default (generic) keyboard layout, English (US) does not contain any
+# special ALT characters.
+#
+
+type OVERLAY
+
+map key 17 D
+map key 18 R
+map key 19 W
+map key 20 B
+map key 21 J
+map key 22 F
+map key 23 U
+map key 24 P
+map key 25 SEMICOLON
+map key 32 H
+map key 33 T
+map key 34 G
+map key 35 Y
+map key 36 N
+map key 37 E
+map key 38 O
+map key 39 I
+map key 46 M
+map key 47 C
+map key 48 V
+map key 49 K
+map key 50 L
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '^'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift, capslock:                    ':'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift:                              'O'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+}
+
+### ROW 4
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
index d67a9fd..a36d01e 100644
--- a/packages/InputDevices/res/values-af/strings.xml
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internasionale styl"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak-styl"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak-styl"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engels (VSA), Workman-styl"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index 3e84794..efabbbf 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
index a922a46..1d3d9f5 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"الإنجليزية (الولايات المتحدة)، النمط الدولي"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏الإنجليزية (الولايات المتحدة)، نمط Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏الإنجليزية (الولايات المتحدة)، نمط Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"الإنجليزية (الولايات المتحدة)، الحرفيّون"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"الألمانية"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"الفرنسية"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"الفرنسية (كندا)"</string>
diff --git a/packages/InputDevices/res/values-az-rAZ/strings.xml b/packages/InputDevices/res/values-az-rAZ/strings.xml
index d69059c..25d7c91 100644
--- a/packages/InputDevices/res/values-az-rAZ/strings.xml
+++ b/packages/InputDevices/res/values-az-rAZ/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilis (ABŞ), Beynəlxalq üslub"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilis (ABŞ), Colemak üslubu"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilis (ABŞ), Dvorak üslubu"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"İngilis dili (ABŞ), Workman üslubu"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransız"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransız dili (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index d68a347..e5e2c96 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англ. (САЩ) – стил „Mеждународна“"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английски (САЩ) – стил „Коулмак“"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английски (САЩ) – стил „Дворак“"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"англ. (САЩ) – стил „Работническа“"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немски"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"френски"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"френски (Канада)"</string>
diff --git a/packages/InputDevices/res/values-bn-rBD/strings.xml b/packages/InputDevices/res/values-bn-rBD/strings.xml
index 1676a95..bfbf3d3 100644
--- a/packages/InputDevices/res/values-bn-rBD/strings.xml
+++ b/packages/InputDevices/res/values-bn-rBD/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ইংরেজি (US), আন্তর্জাতিক শৈলী"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ইংরেজি (US), কোলেম্যাক শৈলী"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ইংরেজি (US), ডিভোরাক শৈলী"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"জার্মান"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ফরাসী"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ফরাসী (কানাডা)"</string>
diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml
index 6baa5b8..ee25a74 100644
--- a/packages/InputDevices/res/values-ca/strings.xml
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglès (EUA), estil internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglès (EUA), estil Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglès (EUA), estil Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglès (EUA), estil Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemany"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francès"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francès (Canadà)"</string>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 1c502fe..614ba56 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
index 043a5b3..228c51a 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), international stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelsk (USA), Workman-stil"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index 40722f6..ce25623 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 025a288..0a99dbb 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string>
diff --git a/packages/InputDevices/res/values-en-rAU/strings.xml b/packages/InputDevices/res/values-en-rAU/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rAU/strings.xml
+++ b/packages/InputDevices/res/values-en-rAU/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rGB/strings.xml
+++ b/packages/InputDevices/res/values-en-rGB/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml
index d5797a0..01c2979 100644
--- a/packages/InputDevices/res/values-en-rIN/strings.xml
+++ b/packages/InputDevices/res/values-en-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"English (US), International style"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"English (US), Colemak style"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"English (US), Dvorak style"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 0a9d2f3..20d677b 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml
index 6e41abf..af1492a 100644
--- a/packages/InputDevices/res/values-es/strings.xml
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE.UU.), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE.UU.), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE.UU.), estilo Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EE. UU.) estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-et-rEE/strings.xml b/packages/InputDevices/res/values-et-rEE/strings.xml
index 0d931ce..d03b82e 100644
--- a/packages/InputDevices/res/values-et-rEE/strings.xml
+++ b/packages/InputDevices/res/values-et-rEE/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglise (USA), rahvusvaheline stiil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglise (USA), Colemaki stiil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglise (USA), Dvoraki stiil"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglise (USA), Workmani stiil"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Saksa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prantsuse"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prantsuse (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-eu-rES/strings.xml b/packages/InputDevices/res/values-eu-rES/strings.xml
index d4d7b6a..d18c6f8 100644
--- a/packages/InputDevices/res/values-eu-rES/strings.xml
+++ b/packages/InputDevices/res/values-eu-rES/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ingelesa (AEB), nazioarteko estiloa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ingelesa (AEB), Colemak estiloa"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ingelesa (AEB), Dvorak estiloa"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ingelesa (AEB), Workman estiloa"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemaniarra"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frantsesa"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frantsesa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index e87fbad..a05d071 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بین‌المللی"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏انگلیسی (ایالات متحده)، سبک Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏انگلیسی (ایالات متحده)، سبک Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)‏"</string>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 5b39dfd..250333c 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 9973918..3842584 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index fa2977b..679dba9 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string>
diff --git a/packages/InputDevices/res/values-gl-rES/strings.xml b/packages/InputDevices/res/values-gl-rES/strings.xml
index d9babd5..ab76a14 100644
--- a/packages/InputDevices/res/values-gl-rES/strings.xml
+++ b/packages/InputDevices/res/values-gl-rES/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-gu-rIN/strings.xml b/packages/InputDevices/res/values-gu-rIN/strings.xml
index 3abae49..63c3874 100644
--- a/packages/InputDevices/res/values-gu-rIN/strings.xml
+++ b/packages/InputDevices/res/values-gu-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"અંગ્રેજી (યુએસ), આંતરરાષ્ટ્રીય શૈલી"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"અંગ્રેજી (યુએસ), કોલેમેક શૈલી"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"અંગ્રેજી (યુએસ), ડ્વોરક શૈલી"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"જર્મન"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ફ્રેન્ચ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ફ્રેન્ચ (કેનેડા)"</string>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
index fcd7f84..41966e4 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अंग्रेज़ी (यूएस), अंतर्राष्ट्रीय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अंग्रेज़ी (यूएस), कोलमैक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अंग्रेज़ी (यूएस), ड्वोरक शैली"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"अंग्रेज़ी (यूएस), वर्कमैन शैली"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ़्रांसीसी"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ़्रांसीसी (कनाडा)"</string>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index bad973d..829f817 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml
index 510591d..6fbc3eb 100644
--- a/packages/InputDevices/res/values-hu/strings.xml
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angol (USA), nemzetközi stílus"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angol (USA), Colemak-stílus"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angol (USA), Dvorak-stílus"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"angol (USA), „Workman” stílus"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"német"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francia"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francia (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml
index 9ffa8bb..f47aba2 100644
--- a/packages/InputDevices/res/values-hy-rAM/strings.xml
+++ b/packages/InputDevices/res/values-hy-rAM/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string>
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
index fccfa67..b2cbd6e 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inggris (AS), gaya Internasional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inggris (AS), gaya Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inggris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inggris (AS), gaya Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Jerman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancis"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancis (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-is-rIS/strings.xml b/packages/InputDevices/res/values-is-rIS/strings.xml
index e48fa49..35a5a17 100644
--- a/packages/InputDevices/res/values-is-rIS/strings.xml
+++ b/packages/InputDevices/res/values-is-rIS/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Enskt (Bandaríkin), alþjóðlegt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Enskt (Bandaríkin), Colemak-stíll"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Enskt (Bandaríkin), Dvorak-stíll"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Þýskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
index 83dba70..c1c7faa 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglese (USA), stile internazionale"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglese (USA), stile Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglese (USA), stile Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglese (USA), stile Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tedesco"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francese"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francese (Canada)"</string>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 26fe662..0c8b4e9 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"‏אנגלית (ארה\"ב), סגנון Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"‏אנגלית (ארה\"ב), סגנון Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index e2b154d..7fbddd7 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string>
diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml
index eff4b04..56bd3e3 100644
--- a/packages/InputDevices/res/values-ka-rGE/strings.xml
+++ b/packages/InputDevices/res/values-ka-rGE/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string>
diff --git a/packages/InputDevices/res/values-kk-rKZ/strings.xml b/packages/InputDevices/res/values-kk-rKZ/strings.xml
index 7086bf7..df2da88 100644
--- a/packages/InputDevices/res/values-kk-rKZ/strings.xml
+++ b/packages/InputDevices/res/values-kk-rKZ/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ағылшын (АҚШ), Халықаралық стилі"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ағылшын (АҚШ), Colemak стилі"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ағылшын (АҚШ), Dvorak стилі"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Неміс"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-km-rKH/strings.xml b/packages/InputDevices/res/values-km-rKH/strings.xml
index 60a28b1..acd01ee 100644
--- a/packages/InputDevices/res/values-km-rKH/strings.xml
+++ b/packages/InputDevices/res/values-km-rKH/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម​​អន្តរជាតិ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក​)​, ​​រចនាប័ទ្ម Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"អង់គ្លេស (អាមេរិក) រចនាបទ Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"អាល្លឺម៉ង់"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"បារាំង"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"បារាំង (កាណាដា​)"</string>
diff --git a/packages/InputDevices/res/values-kn-rIN/strings.xml b/packages/InputDevices/res/values-kn-rIN/strings.xml
index 6b43517..f88076c 100644
--- a/packages/InputDevices/res/values-kn-rIN/strings.xml
+++ b/packages/InputDevices/res/values-kn-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ಇಂಗ್ಲಿಷ್ (US), ಅಂತರರಾಷ್ಟ್ರೀಯ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ಇಂಗ್ಲಿಷ್ (US), ಕೋಲ್ಮಾರ್ಕ್ ಶೈಲಿ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ಇಂಗ್ಲಿಷ್ (US), ಡಿವೊರಾಕ್ ಶೈಲಿ"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ಜರ್ಮನ್"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ಫ್ರೆಂಚ್"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ಫ್ರೆಂಚ್‌ (ಕೆನಡಾ)"</string>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index 3f563d1..d215375 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string>
diff --git a/packages/InputDevices/res/values-ky-rKG/strings.xml b/packages/InputDevices/res/values-ky-rKG/strings.xml
index 0828222..ee9cc5a 100644
--- a/packages/InputDevices/res/values-ky-rKG/strings.xml
+++ b/packages/InputDevices/res/values-ky-rKG/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англис (АКШ), эл аралык"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англис (АКШ), Colemak стили"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англис (АКШ), Dvorak стили"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Немис"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string>
diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml
index fb3fe17..33c9f61 100644
--- a/packages/InputDevices/res/values-lo-rLA/strings.xml
+++ b/packages/InputDevices/res/values-lo-rLA/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string>
diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml
index d0eb1f6..1dae850 100644
--- a/packages/InputDevices/res/values-lt/strings.xml
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglų k. (JAV), tarptautinis stilius"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglų k. (JAV), „Colemak“ stilius"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglų k. (JAV), „Dvorak“ stilius"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglų k. (JAV), „Workman“ stilius"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vokiečių k."</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Prancūzų k."</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Prancūzų k. (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml
index 0608bf0..96dc53b 100644
--- a/packages/InputDevices/res/values-lv/strings.xml
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angļu (ASV), starptautiska"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angļu (ASV), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angļu (ASV), Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Angļu (ASV), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Vācu"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franču"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franču (Kanāda)"</string>
diff --git a/packages/InputDevices/res/values-mk-rMK/strings.xml b/packages/InputDevices/res/values-mk-rMK/strings.xml
index 8662984..2fae1cf 100644
--- a/packages/InputDevices/res/values-mk-rMK/strings.xml
+++ b/packages/InputDevices/res/values-mk-rMK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англиски (САД), меѓународен стил"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англиски (САД), Colemak стил"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англиски (САД), Dvorak стил"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англиски (САД), Workman стил"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Германски"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француски"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француски (Канада)"</string>
diff --git a/packages/InputDevices/res/values-ml-rIN/strings.xml b/packages/InputDevices/res/values-ml-rIN/strings.xml
index 4f846a0..b42bdb7 100644
--- a/packages/InputDevices/res/values-ml-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ml-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ഇംഗ്ലീഷ് (യു.എസ്), അന്തർദ്ദേശീയ ശൈലി"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ഇംഗ്ലീഷ് (യു.എസ്), കോൽമാക് ശൈലി"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ഇംഗ്ലീഷ് (യു.എസ്), ദ്വരോക്ക് ശൈലി"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ജര്‍‌മ്മന്‍‌"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ഫ്രഞ്ച്"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ഫ്രഞ്ച് (കാനഡ)"</string>
diff --git a/packages/InputDevices/res/values-mn-rMN/strings.xml b/packages/InputDevices/res/values-mn-rMN/strings.xml
index a28fd2a..f1354fe 100644
--- a/packages/InputDevices/res/values-mn-rMN/strings.xml
+++ b/packages/InputDevices/res/values-mn-rMN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англи (АНУ), Олон улсын стиль"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англи (АНУ), Колемак стиль"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англи (АНУ), Дворак стиль"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Aнгли хэл (АНУ), Aжилчин загвар"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Герман"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Франц"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Франц (Канад)"</string>
diff --git a/packages/InputDevices/res/values-mr-rIN/strings.xml b/packages/InputDevices/res/values-mr-rIN/strings.xml
index 119ae6c..9ffcc70 100644
--- a/packages/InputDevices/res/values-mr-rIN/strings.xml
+++ b/packages/InputDevices/res/values-mr-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"इंग्रजी (यूएस), आंतरराष्‍ट्रीय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"इंग्रजी (यूएस), कोलमॅक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"इंग्रजी (यूएस), ड्वोरॅक शैली"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"इंग्रजी (यूएस), कामगार शैली"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रेंच"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (कॅनडा)"</string>
diff --git a/packages/InputDevices/res/values-ms-rMY/strings.xml b/packages/InputDevices/res/values-ms-rMY/strings.xml
index a1a6d00..04983e3 100644
--- a/packages/InputDevices/res/values-ms-rMY/strings.xml
+++ b/packages/InputDevices/res/values-ms-rMY/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Bahasa Inggeris (AS), gaya A/bangsa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Bahasa Inggeris (AS), gaya Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Bahasa Inggeris (AS), gaya Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Bahasa Inggeris (AS), gaya Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Bahasa Jerman"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Bahasa Perancis"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Bahasa Perancis (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-my-rMM/strings.xml b/packages/InputDevices/res/values-my-rMM/strings.xml
index 91bdc55..f3762b4 100644
--- a/packages/InputDevices/res/values-my-rMM/strings.xml
+++ b/packages/InputDevices/res/values-my-rMM/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"အင်္ဂလိပ်(ယူအက်စ်)၊အပြည်ပြည်ဆိုင်ရာပုံစံ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"အင်္ဂလိပ်(ယူအက်စ်)၊Colemak ပုံစံ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"အင်္ဂလိပ် (ယူအက်စ်)၊Dvorak ပုံစံ"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"အင်္ဂလိပ် (ယူအက်စ်)၊ အလုပ်လုပ်သူ ပုံစံ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ဂျာမန်"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ပြင်သစ်"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ပြင်သစ် (ကနေဒါ)"</string>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index ad4b704..378cdc1 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ne-rNP/strings.xml b/packages/InputDevices/res/values-ne-rNP/strings.xml
index 4e5c2b5..571d717 100644
--- a/packages/InputDevices/res/values-ne-rNP/strings.xml
+++ b/packages/InputDevices/res/values-ne-rNP/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अङ्ग्रेजी (अमेरिकी), अन्तर्राष्ट्रिय शैली"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अङ्ग्रेजी (अमेरिकी), कोलमाक शैली"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अङ्ग्रेजी (अमेरिकी), डेभोर्याक शैली"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रान्सेली"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (क्यानाडा)"</string>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index c57251e..31ade05 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engels (VS), internationaal"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engels (VS), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engels (VS), Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engels (VS), Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Duits"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Frans"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Frans (Canada)"</string>
diff --git a/packages/InputDevices/res/values-pa-rIN/strings.xml b/packages/InputDevices/res/values-pa-rIN/strings.xml
index a885088..437352c 100644
--- a/packages/InputDevices/res/values-pa-rIN/strings.xml
+++ b/packages/InputDevices/res/values-pa-rIN/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਅੰਤਰਰਾਸ਼ਟਰੀ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਕੋਲਮਾਰਕ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ਅੰਗ੍ਰੇਜ਼ੀ (ਅਮਰੀਕਾ), ਵੋਰਕ ਸਟਾਈਲ"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ਅੰਗਰੇਜ਼ੀ (US), ਵਰਕਮੈਨ ਸਟਾਈਲ"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ਜਰਮਨ"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ਫਰਾਂਸੀਸੀ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ਫ੍ਰੈਂਚ (ਕੈਨੇਡਾ)"</string>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 39fb3ec..7175705 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index e9a0a38..15c2d37 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
index 3ac3b84..b806fc2 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), est. Internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index e9a0a38..15c2d37 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index c2392b1..ccfe568 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 70ecf6e..7d3038e 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string>
diff --git a/packages/InputDevices/res/values-si-rLK/strings.xml b/packages/InputDevices/res/values-si-rLK/strings.xml
index 67563c4..945a097 100644
--- a/packages/InputDevices/res/values-si-rLK/strings.xml
+++ b/packages/InputDevices/res/values-si-rLK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ඉංග්‍රීසි (US), අන්තර්ජාතික ආකාරය"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ඉංග්‍රීසි (US), Colemak ආකාරය"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ඉංග්‍රීසි (US), Dvorak ආකාරය"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ඉංග්‍රීසි (එ.ජ.), වර්ක්මන් ආකාරය"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ජර්මානු"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ප්‍රංශ"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ප්‍රංශ (කැනඩාව)"</string>
diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml
index d2ee0cf..2e76024f 100644
--- a/packages/InputDevices/res/values-sk/strings.xml
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), medzinárodné"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), štýl Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), štýl Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglické (USA), štýl Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemecké"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francúzske"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francúzske (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index 38542ef..f9e4610 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sq-rAL/strings.xml b/packages/InputDevices/res/values-sq-rAL/strings.xml
index 2092926..8a9000d 100644
--- a/packages/InputDevices/res/values-sq-rAL/strings.xml
+++ b/packages/InputDevices/res/values-sq-rAL/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglisht (SHBA), stili \"ndërkombëtar\""</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglisht (SHBA), stili \"colemak\""</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglisht (SHBA), stili \"dvorak\""</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglisht (SHBA), stili \"workman\""</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"gjermanisht"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"frëngjisht"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"frëngjisht (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index dd500e6..a216bc3 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index c2406c0..23f2c98 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index f71a696..720d427 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-ta-rIN/strings.xml b/packages/InputDevices/res/values-ta-rIN/strings.xml
index 30d653d..43d0bd1 100644
--- a/packages/InputDevices/res/values-ta-rIN/strings.xml
+++ b/packages/InputDevices/res/values-ta-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ஆங்கிலம் (யூஎஸ்), சர்வதேச நடை"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ஆங்கிலம் (யூஎஸ்), கோல்மாக் நடை"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ஆங்கிலம் (யூஎஸ்), டிவாரக் நடை"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ஜெர்மன்"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ஃபிரெஞ்ச்"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ஃபிரெஞ்ச் (கனடா)"</string>
diff --git a/packages/InputDevices/res/values-te-rIN/strings.xml b/packages/InputDevices/res/values-te-rIN/strings.xml
index cc1b14d..d214ae6 100644
--- a/packages/InputDevices/res/values-te-rIN/strings.xml
+++ b/packages/InputDevices/res/values-te-rIN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ఇంగ్లీష్ (US), అంతర్జాతీయ శైలి"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ఇంగ్లీష్ (US), కొల్‌మాక్ శైలి"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ఇంగ్లీష్ (US), ద్వోరక్ శైలి"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"జర్మన్"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ఫ్రెంచ్"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ఫ్రెంచ్ (కెనడా)"</string>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
index 487b051..50dc1e4 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"อังกฤษ (อเมริกัน), รูปแบบนานาชาติ"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"อังกฤษ (อเมริกัน), รูปแบบ Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"อังกฤษ (อเมริกัน), รูปแบบ Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"อังกฤษ (อเมริกัน), รูปแบบ Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"เยอรมัน"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"ฝรั่งเศส"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ฝรั่งเศส (แคนาดา)"</string>
diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml
index d7920ed..4bd857d 100644
--- a/packages/InputDevices/res/values-tl/strings.xml
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ingles (US), istilong International"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ingles (US), istilong Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ingles (US), istilong Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"English (US), Workman style"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"French (Canada)"</string>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index c0c70be..3dd0d3b 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 43a3fe6..c818001 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англійська (США), міжнародна"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"англійська (США), розкладка Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"англійська (США), розкладка Дворака"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"англійська (США), розкладка Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"німецька"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"французька"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французька (Канада)"</string>
diff --git a/packages/InputDevices/res/values-ur-rPK/strings.xml b/packages/InputDevices/res/values-ur-rPK/strings.xml
index eee1eb2..2c5a8b8 100644
--- a/packages/InputDevices/res/values-ur-rPK/strings.xml
+++ b/packages/InputDevices/res/values-ur-rPK/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگریزی (امریکہ)، انٹرنیشنل سٹائل"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگریزی (امریکہ)، کول مارک سٹائل"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگریزی (امریکہ)، ڈوراک سٹائل"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"جرمن"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسیسی"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسیسی (کینیڈا)"</string>
diff --git a/packages/InputDevices/res/values-uz-rUZ/strings.xml b/packages/InputDevices/res/values-uz-rUZ/strings.xml
index 3b6772b..ee20328 100644
--- a/packages/InputDevices/res/values-uz-rUZ/strings.xml
+++ b/packages/InputDevices/res/values-uz-rUZ/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglizcha (AQSH), xalqaro uslubda"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglizcha (AQSH), Kolemak uslubida"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglizcha (AQSH), Dvorak uslubida"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Nemischa"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransuzcha"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransuzcha (Kanada)"</string>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
index a90c1cd..3ca715b 100644
--- a/packages/InputDevices/res/values-vi/strings.xml
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Tiếng Anh (Mỹ), kiểu Quốc tế"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Tiếng Anh (Mỹ), kiểu Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Tiếng Anh (Mỹ), kiểu Dvorak"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Tiếng Anh (Mỹ), kiểu Workman"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tiếng Đức"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Tiếng Pháp"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Tiếng Pháp (Canada)"</string>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index c050ebd..2ba2667 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml
index ff5570e..9385df8 100644
--- a/packages/InputDevices/res/values-zh-rHK/strings.xml
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英文 (美國),Workman 樣式"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml
index 859983d..89ef558 100644
--- a/packages/InputDevices/res/values-zh-rTW/strings.xml
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -8,6 +8,7 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英文 (美國),國際樣式"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英文 (美國),Colemak 樣式"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英文 (美國),Dvorak 樣式"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英文 (美國),Workman 樣式"</string>
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德文"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"法文"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法文 (加拿大)"</string>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 9f30f7a..80899d0 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -8,6 +8,8 @@
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string>
     <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string>
+    <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) -->
+    <skip />
     <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string>
     <string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string>
     <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string>
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 968961a..5644c9a 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -21,6 +21,9 @@
     <!-- US English (Dvorak style) keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_english_us_dvorak_label">English (US), Dvorak style</string>
 
+    <!-- US English (Workman style) keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_english_us_workman_label">English (US), Workman style</string>
+
     <!-- German keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_german_label">German</string>
 
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 6f7253c..a302162 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -20,6 +20,10 @@
             android:label="@string/keyboard_layout_english_us_dvorak_label"
             android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
 
+    <keyboard-layout android:name="keyboard_layout_english_us_workman"
+            android:label="@string/keyboard_layout_english_us_workman_label"
+            android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+
     <keyboard-layout android:name="keyboard_layout_german"
             android:label="@string/keyboard_layout_german_label"
             android:keyboardLayout="@raw/keyboard_layout_german" />
diff --git a/packages/Keyguard/res/values-ne-rNP/strings.xml b/packages/Keyguard/res/values-ne-rNP/strings.xml
index d18d0f2..4a5907e0 100644
--- a/packages/Keyguard/res/values-ne-rNP/strings.xml
+++ b/packages/Keyguard/res/values-ne-rNP/strings.xml
@@ -82,22 +82,22 @@
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="74089475965050805">"तपाईँले तपाईँको अनलक ढाँचा गलत तरिकाले <xliff:g id="NUMBER_0">%d</xliff:g> पटक खिच्नु भएको छ। \n\n <xliff:g id="NUMBER_1">%d</xliff:g> सेकेन्डमा फेरि कोसिस गर्नुहोस्।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="8774056606869646621">"तपाईंले गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g>पटक।  <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो ट्याब्लेट रिसेट हुनेछ जसले आफ्नो सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="1843331751334128428">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। पछि <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो फोन  रिसेट हुनेछ जसले सम्पूर्ण डेटा मेटाउनेछ।।"</string>
-    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो ट्याब्लेट रिेसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
+    <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="258925501999698032">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो ट्याब्लेट रिेसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_now_wiping" product="default" msgid="7154028908459817066">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। यो फोन रिसेट गरिनेछ जसले सम्पूर्ण डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="6159955099372112688">"तपाईंले गलत तरिकाले  ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक।  <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="6945823186629369880">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, यो प्रयोगकर्ता हटाइनेछ जसले  सबै प्रयोगकर्ता डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="3963486905355778734">"तपाईंले गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ ।"</string>
     <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="7729009752252111673">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। यो प्रयोगकर्ता हटाइनेछ जसले सम्पूर्ण प्रयोगकर्ता डेटा मेट्नेछ।"</string>
-    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
+    <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="4621778507387853694">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि, काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="6853071165802933545">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER_0">%d</xliff:g> पटक। <xliff:g id="NUMBER_1">%d</xliff:g> थप असफल प्रयासहरूपछि , काम प्रोफाइल हटाइनेछ जसले सबै प्रोफाइल डेटा मेट्नेछ।"</string>
-    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"तपाईं गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण  प्रोफाइल डेटा मेट्नेछ ।"</string>
+    <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4686386497449912146">"तपाईँ गलत तरिकाले ट्याब्लेट अनलक गर्ने प्रयास गर्नु भएको छ<xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण  प्रोफाइल डेटा मेट्नेछ ।"</string>
     <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4951507352869831265">"तपाईंले गलत तरिकाले फोन अनलक गर्ने प्रयास गर्नु भएको छ <xliff:g id="NUMBER">%d</xliff:g> पटक। काम प्रोफाइल हटाइनेछ जसले सम्पूर्ण प्रोफाइल डेटा मेट्नेछ।"</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="3253575572118914370">"तपाईंले गलत तरिकाले आफ्नो अनलक ढाँचा <xliff:g id="NUMBER_0">%d</xliff:g> पटक कोर्नुभयो। <xliff:g id="NUMBER_1">%d</xliff:g> विफल प्रयत्नहरू पछि, तपाईंलाई आफ्नो ट्याब्लेट इमेल खाता प्रयोग गरेर अनलक गर्न सोधिने छ।\n\n फेरि प्रयास गर्नुहोस् <xliff:g id="NUMBER_2">%d</xliff:g> सेकेन्डहरूमा।"</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="1437638152015574839">"तपाईँले आफ्नो अनलक ढाँचा गलत रूपमा <xliff:g id="NUMBER_0">%d</xliff:g> पटक तान्नु भएको छ। <xliff:g id="NUMBER_1">%d</xliff:g> धेरै असफल प्रयासहरूपछि, तपाईँलाई एउटा इमेल खाताको प्रयोग गरेर तपाईँको फोन अनलक गर्न सोधिने छ।\n\n फेरि <xliff:g id="NUMBER_2">%d</xliff:g> सेकेन्डमा प्रयास गर्नुहोस्।"</string>
     <string name="kg_password_wrong_pin_code_pukked" msgid="30531039455764924">"SIM PIN कोड गलत छ। अब तपाईंले अाफ्नो उपकरण खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्दर।"</string>
     <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="6721575017538162249">
-      <item quantity="other"> गलत SIM PIN कोड, तपाईं सँग <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
-      <item quantity="one">SIM PIN कोड गलत छ, तपाईंले अाफ्नो यन्त्र खोल्नलाई तपाईंको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईंसँग <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
+      <item quantity="other"> गलत SIM PIN कोड, तपाईँ सँग <xliff:g id="NUMBER_1">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
+      <item quantity="one">SIM PIN कोड गलत छ, तपाईँले अाफ्नो यन्त्र खोल्नलाई तपाईँको वाहकसँग सम्पर्क गर्नै पर्न अघि तपाईँसँग <xliff:g id="NUMBER_0">%d</xliff:g> पटक प्रयास बाँकी छ।</item>
     </plurals>
     <string name="kg_password_wrong_puk_code_dead" msgid="7077536808291316208">"SIM प्रयोग बिहिन छ। तपाईंको वाहकलाई सम्पर्क गर्नुहोस्।"</string>
     <plurals name="kg_password_wrong_puk_code" formatted="false" msgid="7576227366999858780">
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
new file mode 100644
index 0000000..11e937b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -0,0 +1,161 @@
+package com.android.mtp;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Database for MTP objects.
+ * The object handle which is identifier for object in MTP protocol is not stable over sessions.
+ * When we resume the process, we need to remap our document ID with MTP's object handle.
+ * The database object remembers the map of document ID and fullpath, and helps to remap object
+ * handle and document ID by comparing fullpath.
+ * TODO: Remove @VisibleForTesting annotation when we start to use this class.
+ */
+@VisibleForTesting
+class MtpDatabase {
+    private static final int VERSION = 1;
+    private static final String NAME = "mtp";
+
+    private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments";
+
+    static final String COLUMN_DEVICE_ID = "deviceId";
+    static final String COLUMN_STORAGE_ID = "storageId";
+    static final String COLUMN_OBJECT_HANDLE = "objectHandle";
+    static final String COLUMN_FULL_PATH = "fullPath";
+
+    private static class OpenHelper extends SQLiteOpenHelper {
+        private static final String CREATE_TABLE_QUERY =
+                "CREATE TABLE " + TABLE_MTP_DOCUMENTS + " (" +
+                DocumentsContract.Document.COLUMN_DOCUMENT_ID +
+                    " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
+                COLUMN_STORAGE_ID + " INTEGER NOT NULL," +
+                COLUMN_OBJECT_HANDLE + " INTEGER," +
+                COLUMN_FULL_PATH + " TEXT NOT NULL," +
+                DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," +
+                DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
+                DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," +
+                DocumentsContract.Document.COLUMN_LAST_MODIFIED + " INTEGER," +
+                DocumentsContract.Document.COLUMN_ICON + " INTEGER," +
+                DocumentsContract.Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
+                DocumentsContract.Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+
+        public OpenHelper(Context context) {
+            super(context, NAME, null, VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL(CREATE_TABLE_QUERY);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private final SQLiteDatabase database;
+
+    @VisibleForTesting
+    MtpDatabase(Context context) {
+        final OpenHelper helper = new OpenHelper(context);
+        database = helper.getWritableDatabase();
+    }
+
+    @VisibleForTesting
+    static void deleteDatabase(Context context) {
+        SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
+    }
+
+    @VisibleForTesting
+    Cursor queryChildDocuments(String[] columnNames) {
+        return database.query(TABLE_MTP_DOCUMENTS, columnNames, null, null, null, null, null);
+    }
+
+    @VisibleForTesting
+    void putRootDocument(MtpRoot root) throws Exception {
+        database.beginTransaction();
+        try {
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_DEVICE_ID, root.mDeviceId);
+            values.put(COLUMN_STORAGE_ID, root.mStorageId);
+            values.putNull(COLUMN_OBJECT_HANDLE);
+            values.put(
+                    COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription));
+            values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
+            values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
+            values.putNull(Document.COLUMN_SUMMARY);
+            values.putNull(Document.COLUMN_LAST_MODIFIED);
+            values.putNull(Document.COLUMN_ICON);
+            values.put(Document.COLUMN_FLAGS, 0);
+            values.put(Document.COLUMN_SIZE,
+                    (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
+            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
+                throw new Exception("Failed to add root document.");
+            }
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    @VisibleForTesting
+    void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception {
+        database.beginTransaction();
+        try {
+            final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
+
+            int flag = 0;
+            if (info.getProtectionStatus() == 0) {
+                flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
+                        DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
+                if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
+                    flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
+                }
+            }
+            if (info.getThumbCompressedSize() > 0) {
+                flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
+            }
+
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_DEVICE_ID, deviceId);
+            values.put(COLUMN_STORAGE_ID, info.getStorageId());
+            values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
+            values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName()));
+            values.put(
+                    Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat()));
+            values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
+            values.putNull(Document.COLUMN_SUMMARY);
+            values.put(
+                    Document.COLUMN_LAST_MODIFIED,
+                    info.getDateModified() != 0 ? info.getDateModified() : null);
+            values.putNull(Document.COLUMN_ICON);
+            values.put(Document.COLUMN_FLAGS, flag);
+            values.put(Document.COLUMN_SIZE, info.getCompressedSize());
+            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
+                throw new Exception("Failed to add document.");
+            }
+            database.setTransactionSuccessful();
+        } finally {
+            database.endTransaction();
+        }
+    }
+
+    @VisibleForTesting
+    private String escape(String s) throws UnsupportedEncodingException {
+        return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
+    }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
new file mode 100644
index 0000000..7ce32542
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -0,0 +1,121 @@
+package com.android.mtp;
+
+
+import android.database.Cursor;
+import android.mtp.MtpConstants;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class MtpDatabaseTest extends AndroidTestCase {
+    private final String[] COLUMN_NAMES = new String[] {
+        DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+        MtpDatabase.COLUMN_DEVICE_ID,
+        MtpDatabase.COLUMN_STORAGE_ID,
+        MtpDatabase.COLUMN_OBJECT_HANDLE,
+        MtpDatabase.COLUMN_FULL_PATH,
+        DocumentsContract.Document.COLUMN_MIME_TYPE,
+        DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+        DocumentsContract.Document.COLUMN_SUMMARY,
+        DocumentsContract.Document.COLUMN_LAST_MODIFIED,
+        DocumentsContract.Document.COLUMN_ICON,
+        DocumentsContract.Document.COLUMN_FLAGS,
+        DocumentsContract.Document.COLUMN_SIZE
+    };
+
+    @Override
+    public void tearDown() {
+        MtpDatabase.deleteDatabase(getContext());
+    }
+
+    public void testPutRootDocument() throws Exception {
+        final MtpDatabase database = new MtpDatabase(getContext());
+        final MtpRoot root = new MtpRoot(
+                0,
+                1,
+                "Device A",
+                "Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(root);
+
+        final MtpRoot duplicatedNameRoot = new MtpRoot(
+                0,
+                2,
+                "Device A",
+                "Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(duplicatedNameRoot);
+
+        final MtpRoot strangeNameRoot = new MtpRoot(
+                0,
+                3,
+                "Device A",
+                "/@#%&<>Storage",
+                1000,
+                2000,
+                "");
+        database.putRootDocument(strangeNameRoot);
+
+        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
+        assertEquals(3, cursor.getCount());
+
+        cursor.moveToNext();
+        assertEquals("documentId", 1, cursor.getInt(0));
+        assertEquals("deviceId", 0, cursor.getInt(1));
+        assertEquals("storageId", 1, cursor.getInt(2));
+        assertTrue("objectHandle", cursor.isNull(3));
+        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+        assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5));
+        assertEquals("displayName", "Storage", cursor.getString(6));
+        assertTrue("summary", cursor.isNull(7));
+        assertTrue("lastModified", cursor.isNull(8));
+        assertTrue("icon", cursor.isNull(9));
+        assertEquals("flag", 0, cursor.getInt(10));
+        assertEquals("size", 1000, cursor.getInt(11));
+
+        cursor.moveToNext();
+        assertEquals("documentId", 2, cursor.getInt(0));
+        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
+
+        cursor.moveToNext();
+        assertEquals("documentId", 3, cursor.getInt(0));
+        assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4));
+    }
+
+    public void testPutDocument() throws Exception {
+        final MtpDatabase database = new MtpDatabase(getContext());
+        final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
+        builder.setObjectHandle(100);
+        builder.setName("test.txt");
+        builder.setStorageId(5);
+        builder.setFormat(MtpConstants.FORMAT_TEXT);
+        builder.setCompressedSize(1000);
+        database.putDocument(0, "/0/Storage", builder.build());
+
+        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToNext();
+        assertEquals("documentId", 1, cursor.getInt(0));
+        assertEquals("deviceId", 0, cursor.getInt(1));
+        assertEquals("storageId", 5, cursor.getInt(2));
+        assertEquals("objectHandle", 100, cursor.getInt(3));
+        assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4));
+        assertEquals("mimeType", "text/plain", cursor.getString(5));
+        assertEquals("displayName", "test.txt", cursor.getString(6));
+        assertTrue("summary", cursor.isNull(7));
+        assertTrue("lastModified", cursor.isNull(8));
+        assertTrue("icon", cursor.isNull(9));
+        assertEquals(
+                "flag",
+                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
+                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
+                cursor.getInt(10));
+        assertEquals("size", 1000, cursor.getInt(11));
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9b1f103..c44a7960 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -225,4 +225,10 @@
 
     <!-- [CHAR LIMIT=NONE] Label of a running process that represents another user -->
     <string name="running_process_item_user_label">User: <xliff:g id="user_name">%1$s</xliff:g></string>
+
+    <!-- Launch defaults preference summary with some set [CHAR LIMIT=40] -->
+    <string name="launch_defaults_some">Some defaults set</string>
+    <!-- Launch defaults preference summary with none set [CHAR LIMIT=40] -->
+    <string name="launch_defaults_none">No defaults set</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
new file mode 100644
index 0000000..344bf62
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.applications;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.usb.IUsbManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppUtils {
+    private static final String TAG = "AppUtils";
+
+    public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry,
+            IUsbManager usbManager, PackageManager pm, Context context) {
+        String packageName = appEntry.info.packageName;
+        boolean hasPreferred = hasPreferredActivities(pm, packageName)
+                || hasUsbDefaults(usbManager, packageName);
+        int status = pm.getIntentVerificationStatus(packageName, UserHandle.myUserId());
+        // consider a visible current link-handling state to be any explicitly designated behavior
+        boolean hasDomainURLsPreference =
+                status != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        return context.getString(hasPreferred || hasDomainURLsPreference
+                ? R.string.launch_defaults_some
+                : R.string.launch_defaults_none);
+    }
+
+    public static boolean hasUsbDefaults(IUsbManager usbManager, String packageName) {
+        try {
+            if (usbManager != null) {
+                return usbManager.hasDefaults(packageName, UserHandle.myUserId());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "mUsbManager.hasDefaults", e);
+        }
+        return false;
+    }
+
+    public static boolean hasPreferredActivities(PackageManager pm, String packageName) {
+        // Get list of preferred activities
+        List<ComponentName> prefActList = new ArrayList<>();
+        // Intent list cannot be null. so pass empty list
+        List<IntentFilter> intentList = new ArrayList<>();
+        pm.getPreferredActivities(intentList, prefActList, packageName);
+        Log.d(TAG, "Have " + prefActList.size() + " number of activities in preferred list");
+        return prefActList.size() > 0;
+    }
+
+}
diff --git a/packages/Shell/res/values-sv/strings.xml b/packages/Shell/res/values-sv/strings.xml
index 47ee2f7..8b72938 100644
--- a/packages/Shell/res/values-sv/strings.xml
+++ b/packages/Shell/res/values-sv/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"Skal"</string>
     <string name="bugreport_finished_title" msgid="2293711546892863898">"Felrapporten har skapats"</string>
-    <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Dra till vänster om du vill dela felrapporten"</string>
+    <string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Svep åt vänster om du vill dela felrapporten"</string>
     <string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryck om du vill dela felrapporten"</string>
     <string name="bugreport_confirm" msgid="5130698467795669780">"Felrapporter innehåller data från systemets olika loggfiler, inklusive personliga och privata uppgifter. Dela bara felrapporter med personer du litar på."</string>
     <string name="bugreport_confirm_repeat" msgid="4926842460688645058">"Visa det här meddelandet nästa gång"</string>
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 314b3c4..0cc2a67 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -3,7 +3,7 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) \
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) $(call all-Iaidl-files-under, src) \
     src/com/android/systemui/EventLogTags.logtags
 
 LOCAL_STATIC_JAVA_LIBRARIES := Keyguard
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bbb099e..5d622a0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -199,6 +199,11 @@
                     android:value="com.android.settings.category.system" />
         </activity>
 
+        <!-- Service used by secondary users to register themselves with the system user. -->
+        <service android:name=".recents.RecentsSystemUserService"
+            android:exported="false"
+            android:permission="com.android.systemui.permission.SELF" />
+
         <!-- Alternate Recents -->
         <activity android:name=".recents.RecentsActivity"
                   android:label="@string/accessibility_desc_recent_apps"
@@ -215,17 +220,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name=".recents.RecentsUserEventProxyReceiver"
-                  android:exported="false">
-            <intent-filter>
-                <action android:name="com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER" />
-                <action android:name="com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER" />
-            </intent-filter>
-        </receiver>
-
         <!-- Callback for dismissing screenshot notification after a share target is picked -->
         <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
                   android:process=":screenshot"
diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
new file mode 100644
index 0000000..962b00e
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingTop="20dp"
+    android:paddingStart="7dp"
+    android:paddingEnd="7dp">
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="80dp"
+        android:orientation="vertical">
+        <ImageView
+            android:id="@+id/tile_icon"
+            android:layout_gravity="center"
+            android:layout_width="@dimen/qs_tile_icon_size"
+            android:layout_height="@dimen/qs_tile_icon_size" />
+        <TextView
+            android:id="@+id/tile_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|bottom"
+            android:paddingTop="10dp"
+            android:gravity="center"
+            android:textSize="@dimen/qs_tile_text_size" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_add_tiles_list.xml b/packages/SystemUI/res/layout/qs_add_tiles_list.xml
new file mode 100644
index 0000000..312c207
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_add_tiles_list.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ListView
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:paddingTop="10dp"
+            android:id="@+id/empty_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="@string/no_tiles_add" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/tile_listing.xml b/packages/SystemUI/res/layout/tile_listing.xml
new file mode 100644
index 0000000..9ab62ca
--- /dev/null
+++ b/packages/SystemUI/res/layout/tile_listing.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ** Copyright 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.
+ -->
+
+ <LinearLayout
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+     android:background="@drawable/qs_background_primary"
+     android:paddingBottom="20dp"
+     android:orientation="vertical">
+
+     <LinearLayout
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:background="@drawable/notification_header_bg"
+         android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+         android:paddingTop="10dp"
+         android:paddingBottom="10dp"
+         android:orientation="horizontal">
+         <ImageView
+             android:id="@android:id/icon"
+             android:layout_width="36dp"
+             android:layout_height="36dp" />
+         <TextView
+             android:id="@android:id/title"
+             android:paddingStart="10dp"
+             android:textColor="@android:color/white"
+             android:textAppearance="?android:attr/textAppearanceSmall"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_gravity="center_vertical" />
+     </LinearLayout>
+
+     <GridLayout
+         android:id="@+id/tile_grid"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:columnCount="4" />
+
+ </LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index f50abf7..41ed64c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Herrangskik Kitsinstellings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Wys helderheid in Kitsinstellings"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimenteel"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Skakel Bluetooth aan?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Jy moet Bluetooth aanskakel om jou sleutelbord aan jou tablet te koppel."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Skakel aan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index e507e64..673710d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ፈጣን ቅንብሮችን ዳግም ያደራጁ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"በፈጣን ቅንብሮች ውስጥ ብሩህነትን አሳይ"</string>
     <string name="experimental" msgid="6198182315536726162">"የሙከራ"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ብሉቱዝ ይብራ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"የቁልፍ ሰሌዳዎን ከእርስዎ ጡባዊ ጋር ለማገናኘት በመጀመሪያ ብሉቱዝን ማብራት አለብዎት።"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"አብራ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 1098af1..c2c7e65 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -444,10 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"إعادة ترتيب الإعدادات السريعة"</string>
     <string name="show_brightness" msgid="6613930842805942519">"عرض السطوع في الإعدادات السريعة"</string>
     <string name="experimental" msgid="6198182315536726162">"إعدادات تجريبية"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"تشغيل البلوتوث؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"لتوصيل لوحة المفاتيح بالجهاز اللوحي، يلزمك تشغيل بلوتوث أولاً."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"تشغيل"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index 4b5309c..a949dbb 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Sürətli Ayarları yenidən tənzimləyin"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Sürətli ayarlarda parlaqlılığı göstərin"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivləşsin?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Tabletinizlə klaviaturaya bağlanmaq üçün ilk olaraq Bluetooth\'u aktivləşdirməlisiniz."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivləşdirin"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 70cf7f8..a59f4c5 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Пренареждане на бързите настройки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показване на яркостта от бързите настройки"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментални"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се включи ли Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"За да свържете клавиатурата с таблета си, първо трябва да включите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Включване"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index fd61c8b..f4adc14 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"দ্রুত সেটিংসে পুনরায় সাজান"</string>
     <string name="show_brightness" msgid="6613930842805942519">"দ্রুত সেটিংসে উজ্জ্বলতা দেখান"</string>
     <string name="experimental" msgid="6198182315536726162">"পরীক্ষামূলক"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth চালু করবেন?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"আপনার ট্যাবলেটের সাথে আপনার কীবোর্ড সংযুক্ত করতে, আপনাকে প্রথমে Bluetooth চালু করতে হবে।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"চালু করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 8eac5d9..00f96a7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -439,13 +439,10 @@
     <string name="activity_not_found" msgid="348423244327799974">"L\'aplicació no està instal·lada al dispositiu"</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Mostra els segons del rellotge"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra els segons del rellotge a la barra d\'estat. Això pot afectar la durada de la bateria."</string>
-    <string name="qs_rearrange" msgid="8060918697551068765">"Torna a ordenar la Configuració ràpida"</string>
-    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a la Configuració ràpida"</string>
+    <string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza Configuració ràpida"</string>
+    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a Configuració ràpida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vols activar el Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activa"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e1b0256..e429a06 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -444,10 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Změnit uspořádání Rychlého nastavení"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Zobrazit jas v Rychlém nastavení"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentální"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnout Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Chcete-li klávesnici připojit k tabletu, nejdříve musíte zapnout Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Zapnout"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 745bf5a..e4436c8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Slå til"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 796cce2..b113083 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Schnelleinstellungen neu anordnen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Helligkeit in den Schnelleinstellungen anzeigen"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentell"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivieren?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Zum Verbinden von Tastatur und Tablet muss Bluetooth aktiviert sein."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivieren"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 1dcfb7b..27a019c 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Αναδιάταξη Γρήγορων ρυθμίσεων"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Εμφάνιση φωτεινότητας στις Γρήγορες ρυθμίσεις"</string>
     <string name="experimental" msgid="6198182315536726162">"Σε πειραματικό στάδιο"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ενεργοποίηση Bluetooth;"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Για να συνδέσετε το πληκτρολόγιο με το tablet σας, θα πρέπει πρώτα να ενεργοποιήσετε το Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ενεργοποίηση"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index d991f16..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index d991f16..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index d991f16..b1f71a2 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Turn on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 4eef099..bca484a 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar la Configuración rápida"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar el brillo en la Configuración rápida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar el teclado con la tablet, primero debes activar Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 5dfb878..3c89db8 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Ajustes rápidos"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Ajustes rápidos"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para poder conectar tu teclado a tu tablet, debes activar el Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index d3d5120..01e5848 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Korralda kiirseaded ümber"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Kuva kiirseadetes heledus"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentaalne"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Kas lülitada Bluetooth sisse?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviatuuri ühendamiseks tahvelarvutiga peate esmalt Bluetoothi sisse lülitama."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Lülita sisse"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index d48373c..613b44a 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Berrantolatu ezarpen bizkorrak"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Erakutsi distira Ezarpen bizkorretan"</string>
     <string name="experimental" msgid="6198182315536726162">"Esperimentala"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth eginbidea aktibatu nahi duzu?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Teklatua tabletara konektatzeko, Bluetooth eginbidea aktibatu behar duzu."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktibatu"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index f9e2b22..9967225 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ترتیب مجدد در تنظیمات سریع"</string>
     <string name="show_brightness" msgid="6613930842805942519">"نمایش روشنایی در تنظیمات سریع"</string>
     <string name="experimental" msgid="6198182315536726162">"آزمایشی"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوتوث روشن شود؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"برای مرتبط کردن صفحه‌کلید با رایانه لوحی، ابتدا باید بلوتوث را روشن کنید."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"روشن کردن"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 7816337..2d138cb 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Järjestä pika-asetukset uudelleen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Näytä kirkkaus pika-asetuksissa"</string>
     <string name="experimental" msgid="6198182315536726162">"Kokeellinen"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Otetaanko Bluetooth käyttöön?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Jotta voit yhdistää näppäimistön tablettiisi, sinun on ensin otettava Bluetooth käyttöön."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ota käyttöön"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 66c435c..979ee35 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser les paramètres rapides"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans les paramètres rapides"</string>
     <string name="experimental" msgid="6198182315536726162">"Fonctions expérimentales"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter votre clavier à votre tablette, vous devez d\'abord activer la connectivité Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 5b39201..9ab6613 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser la fenêtre de configuration rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans fenêtre de configuration rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Paramètres expérimentaux"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer le Bluetooth ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter un clavier à votre tablette, vous devez avoir activé le Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activer"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index e5329ed..bc44150 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Configuración rápida"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Configuración rápida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Queres activar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teu teclado co tablet, primeiro tes que activar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index 38594be..ba8c517 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ઝડપી સેટિંગ્સને ફરીથી ગોઠવો"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ઝડપી સેટિંગ્સમાં તેજ બતાવો"</string>
     <string name="experimental" msgid="6198182315536726162">"પ્રાયોગિક"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ચાલુ કરવુ છે?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"તમારા ટેબ્લેટ સાથે કીબોર્ડ કનેક્ટ કરવા માટે, તમારે પહેલાં Bluetooth ચાલુ કરવાની જરૂર પડશે."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ચાલુ કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2abf37b..669ce5a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"त्वरित सेटिंग को पुन: व्यवस्थित करें"</string>
     <string name="show_brightness" msgid="6613930842805942519">"त्वरित सेटिंग में स्क्रीन की रोशनी दिखाएं"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटूथ चालू करें?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"अपने कीबोर्ड को अपने टैबलेट से कनेक्ट करने के लिए, आपको पहले ब्लूटूथ चालू करना होगा."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"चालू करें"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 30f22b7..e928cbc 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -441,10 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Promijeni raspored Brzih postavki"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Prikaži svjetlinu u Brzim postavkama"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentalno"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite li uključiti Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Da biste povezali tipkovnicu s tabletom, morate uključiti Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Uključi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e1a57f9..a347ede 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Gyorsbeállítások átrendezése"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Fényerő megjelenítése a gyorsbeállításokban"</string>
     <string name="experimental" msgid="6198182315536726162">"Kísérleti"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Engedélyezi a Bluetooth-kapcsolatot?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ha a billentyűzetet csatlakoztatni szeretné táblagépéhez, először engedélyeznie kell a Bluetooth-kapcsolatot."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Bekapcsolás"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 6124ef2..18f6c88 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Վերադասավորել Արագ կարգավորումները"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ցույց տալ պայծառությունն Արագ կարգավորումներում"</string>
     <string name="experimental" msgid="6198182315536726162">"Փորձնական"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Միացնե՞լ Bluetooth-ը:"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ստեղնաշարը ձեր պլանշետին միացնելու համար նախ անհրաժեշտ է միացնել Bluetooth-ը:"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Միացնել"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 2ce7a5b..afcac63 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Atur Ulang Setelan Cepat"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tampilkan kecerahan di Setelan Cepat"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Aktifkan Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menghubungkan keyboard dengan tablet, terlebih dahulu aktifkan Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktifkan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index cb95cde..48d0d1a 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Endurraða flýtistillingum"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Sýna birtustig í flýtistillingum"</string>
     <string name="experimental" msgid="6198182315536726162">"Tilraunastillingar"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Kveikja á Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Til að geta tengt lyklaborðið við spjaldtölvuna þarftu fyrst að kveikja á Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Kveikja"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 1e0cec7..dea0330 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Riorganizza Impostazioni rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostra luminosità in Impostazioni rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Sperimentali"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Attivare il Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connettere la tastiera al tablet, devi prima attivare il Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Attiva"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 88c6fbd..772a3fa 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
     <string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
     <string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"‏האם להפעיל את ה-Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"הפעל"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 6006f64..6e570e3 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"クイック設定を並べ替え"</string>
     <string name="show_brightness" msgid="6613930842805942519">"クイック設定に明るさ調整バーを表示する"</string>
     <string name="experimental" msgid="6198182315536726162">"試験運用版"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"BluetoothをONにしますか?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"タブレットでキーボードに接続するには、最初にBluetoothをONにする必要があります。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ONにする"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index f74a434..6f26123 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"სწრაფი პარამეტრების გადაწყობა"</string>
     <string name="show_brightness" msgid="6613930842805942519">"სიკაშკაშის ჩვენება სწრაფ პარამეტრებში"</string>
     <string name="experimental" msgid="6198182315536726162">"ექსპერიმენტული"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"გსურთ Bluetooth-ის ჩართვა?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"კლავიატურის ტაბლეტთან დასაკავშირებლად, ჯერ უნდა ჩართოთ Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ჩართვა"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 4483303..2668de4 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Жылдам параметрлерді қайта реттеу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Жылдам параметрлерде жарықтықты көрсету"</string>
     <string name="experimental" msgid="6198182315536726162">"Эксперименттік"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth функциясын қосу керек пе?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Пернетақтаны планшетке қосу үшін алдымен Bluetooth функциясын қосу керек."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Қосу"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 9b17ffc..08d761f 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"រៀបចំការកំណត់រហ័សឡើងវិញ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"បង្ហាញកម្រិតពន្លឺនៅក្នុងការកំណត់រហ័ស"</string>
     <string name="experimental" msgid="6198182315536726162">"ពិសោធន៍"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"បើកប៊្លូធូសឬ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ដើម្បីភ្ជាប់ក្តារចុចរបស់អ្នកជាមួយនឹងថេប្លេតរបស់អ្នក អ្នកត្រូវតែបើកប៊្លូធូសជាមុនសិន។"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"បើក"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 7401340..2cf1697 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳನ್ನು ಮರುಹೊಂದಿಸಿ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳಲ್ಲಿ ಪ್ರಖರತೆಯನ್ನು ತೋರಿಸಿ"</string>
     <string name="experimental" msgid="6198182315536726162">"ಪ್ರಾಯೋಗಿಕ"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡುವುದೇ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು, ನೀವು ಮೊದಲು ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ಆನ್ ಮಾಡು"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 1f7fec7..4e8be28 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"빠른 설정 재정렬"</string>
     <string name="show_brightness" msgid="6613930842805942519">"빠른 설정에서 밝기 표시"</string>
     <string name="experimental" msgid="6198182315536726162">"베타"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"블루투스를 켜시겠습니까?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"키보드를 태블릿에 연결하려면 먼저 블루투스를 켜야 합니다."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"사용"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 00ce218..fc6897f 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ыкчам жөндөөлөрдү кайра коюу"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ыкчам жөндөөлөрдөн жарык деңгээлин көрсөтүү"</string>
     <string name="experimental" msgid="6198182315536726162">"Сынамык"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth күйгүзүлсүнбү?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Баскычтобуңузду планшетиңизге туташтыруу үчүн, адегенде Bluetooth\'ту күйгүзүшүңүз керек."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Күйгүзүү"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 92dbb4d..6bb2043 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ຈັດ​ວາງ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ​ຄືນ​ໃໝ່"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ສະ​ແດງ​ຄວາມ​ແຈ້ງ​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</string>
     <string name="experimental" msgid="6198182315536726162">"ຍັງຢູ່ໃນການທົດລອງ"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ເປີດ​ໃຊ້ Bluetooth ບໍ່?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ເພື່ອ​ເຊື່ອມ​ຕໍ່​ແປ້ນ​ພິມ​ຂອງ​ທ່ານ​ກັບ​ແທັບ​ເລັດ​ຂອງ​ທ່ານ, ກ່ອນ​ອື່ນ​ໝົດ​ທ່ານ​ຕ້ອງ​ເປີດ Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ເປີດ​"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 304afe4..8883cb6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Pertvarkyti sparčiuosius nustatymus"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Rodyti skaistį sparčiuosiuose nustatymuose"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentinė versija"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Įjungti „Bluetooth“?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Kad galėtumėte prijungti klaviatūrą prie planšetinio kompiuterio, pirmiausia turite įjungti „Bluetooth“."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Įjungti"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a1a437d..47ca06a 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -441,10 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Pārkārtot ātros iestatījumus"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Rādīt spilgtumu ātrajos iestatījumos"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentāli"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vai ieslēgt Bluetooth savienojumu?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Lai pievienotu tastatūru planšetdatoram, vispirms ir jāieslēdz Bluetooth savienojums."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ieslēgt"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 808943b..891ffd8 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуредете ги Брзи поставки"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажете ја осветленоста во Брзи поставки"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се вклучи Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"За да ја поврзете тастатурата со таблетот, најпрво треба да вклучите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Вклучи"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 7724f34..64d4184 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ദ്രുത ക്രമീകരണം പുനഃസജ്ജീകരിക്കുക"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ദ്രുത ക്രമീകരണത്തിൽ തെളിച്ചം കാണിക്കുക"</string>
     <string name="experimental" msgid="6198182315536726162">"പരീക്ഷണാത്മകം!"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ഓണാക്കണോ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"നിങ്ങളുടെ ടാബ്‌ലെറ്റുമായി കീബോർഡ് കണക്റ്റുചെയ്യുന്നതിന്, ആദ്യം Bluetooth ഓണാക്കേണ്ടതുണ്ട്."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ഓണാക്കുക"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index cecf5af..740ee0d 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -438,10 +438,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Түргэн тохиргоог дахин засварлах"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Түргэн тохиргоонд гэрэлтүүлэг харах"</string>
     <string name="experimental" msgid="6198182315536726162">"Туршилтын"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth-г асаах уу?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Компьютерийн гараа таблетад холбохын тулд эхлээд Bluetooth-г асаана уу."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Асаах"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index b253849..00d7bc8 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिंग्जची पुनर्रचना करा"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिंग्जमध्‍ये चमक दर्शवा"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रायोगिक"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटुथ सुरू करायचे?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"आपला कीबोर्ड आपल्या टॅब्लेटसह कनेक्ट करण्यासाठी, आपल्याला प्रथम ब्लूटुथ चालू करणे आवश्यक आहे."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"चालू करा"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 5a94581..10865dd 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Susun Semula Tetapan Pantas"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tunjukkan kecerahan dalam Tetapan Pantas"</string>
     <string name="experimental" msgid="6198182315536726162">"Percubaan"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Hidupkan Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menyambungkan papan kekunci anda dengan tablet, anda perlu menghidupkan Bluetooth terlebih dahulu."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Hidupkan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 855df4d..5cb6087 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"အမြန် ဆက်တင်များကို ပြန်စီစဉ်ရန်"</string>
     <string name="show_brightness" msgid="6613930842805942519">"အမြန် ဆက်တင်များထဲက တောက်ပမှုကို ပြရန်"</string>
     <string name="experimental" msgid="6198182315536726162">"စမ်းသပ်ရေး"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ဘလူးတုသ် ဖွင့်ရမလား။"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ကီးဘုတ်ကို တပ်ဘလက်နှင့် ချိတ်ဆက်ရန်၊ ပမထဦးစွာ ဘလူးတုသ်ကို ဖွင့်ပါ။"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ဖွင့်ပါ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index d662464..39b404a 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Omorganiser hurtiginnstillingene"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i hurtiginnstillingene"</string>
     <string name="experimental" msgid="6198182315536726162">"På forsøksstadiet"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå på Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"For å koble tastaturet til nettbrettet ditt må du først slå på Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Slå på"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 8333fb0..2a312f3 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -380,7 +380,7 @@
     <string name="monitoring_description_app_personal" msgid="484599052118316268">"तपाईँ <xliff:g id="APPLICATION">%1$s</xliff:g> सँग जडित हुनुहुन्छ जसले इ-मेल, अनुप्रयोगहरू र वेबसाइट लगायतका तपाईँको निजी नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।"</string>
     <string name="monitoring_description_app_work" msgid="1754325860918060897">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि, आफ्नो प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
     <string name="monitoring_description_app_personal_work" msgid="4946600443852045903">"तपाईँको कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थापन गरिन्छ। यो <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> सँग जोडिएको छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधि अनुगमन गर्न सक्छ।\n\nतपाईँ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> सँग पनि जडित हुनुहुन्छ, जसले तपाईँको व्यक्तिगत नेटवर्क गतिविधि अनुगमन गर्न सक्छ।"</string>
-    <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"तपाईंको उपकरण <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थित गरिन्छ।\n\nतपाईंको प्रशासकले तपाईँको यन्त्र र त्यसको स्थान जानकारीमार्फत सेटिङहरू,  कर्पोरेट पहुँच, अनुप्रयोगहरू, तपाईँको यन्त्रसँग सम्बद्ध डेटा  र तपाईँको यन्त्रको स्थान जानकारीको अनुगमन र व्यवस्थापन गर्न सक्छ।\n\nतपाईं <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जडित हुनुहुन्छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि तपाईको प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
+    <string name="monitoring_description_vpn_app_device_owned" msgid="4970443827043261703">"तपाईँको उपकरण <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारा व्यवस्थित गरिन्छ।\n\nतपाईँको प्रशासकले तपाईँको यन्त्र र त्यसको स्थान जानकारीमार्फत सेटिङहरू,  कर्पोरेट पहुँच, अनुप्रयोगहरू, तपाईँको यन्त्रसँग सम्बद्ध डेटा  र तपाईँको यन्त्रको स्थान जानकारीको अनुगमन र व्यवस्थापन गर्न सक्छ।\n\nतपाईँ <xliff:g id="APPLICATION">%2$s</xliff:g> सँग जडित हुनुहुन्छ जसले इमेल, अनुप्रयोगहरू, र वेबसाइटहरू लगायतका तपाईँका नेटवर्क गतिविधिका अनुगमन गर्न सक्छ।\n\nथप जानकारीको लागि तपाईको प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
     <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"तपाईँले नखोले सम्म उपकरण बन्द रहनेछ"</string>
     <string name="hidden_notifications_title" msgid="7139628534207443290">"छिटो सूचनाहरू प्राप्त गर्नुहोस्"</string>
     <string name="hidden_notifications_text" msgid="2326409389088668981">"तपाईँले अनलक गर्नअघि तिनीहरूलाई हेर्नुहोस्"</string>
@@ -393,12 +393,12 @@
     <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string>
     <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string>
-    <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईं अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
-    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईं अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
+    <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string>
+    <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
     <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"लुकाउनुहुन्छ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string>
-    <string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"यो तपाईं सेटिङ् मा यो बारी अर्को समय देखापर्नेछ।"</string>
+    <string name="quick_settings_reset_confirmation_message" msgid="2235970126803317374">"यो तपाईँ सेटिङ् मा यो बारी अर्को समय देखापर्नेछ।"</string>
     <string name="quick_settings_reset_confirmation_button" msgid="2660339101868367515">"लुकाउनुहोस्"</string>
     <string name="volumeui_prompt_message" msgid="918680947433389110">"<xliff:g id="APP_NAME">%1$s</xliff:g> भोल्यूम संवाद बन्न चाहन्छ।"</string>
     <string name="volumeui_prompt_allow" msgid="7954396902482228786">"अनुमति दिनुहोस्"</string>
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
     <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
     <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्नुहुन्छ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"सक्रिय पार्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 3bca7c4..6bbb340 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Snelle instellingen opnieuw indelen"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Helderheid weergeven in Snelle instellingen"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimenteel"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth inschakelen?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Als je je toetsenbord wilt verbinden met je tablet, moet je eerst Bluetooth inschakelen."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Inschakelen"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index d431999..61b68c1 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਕ੍ਰਮ ਦਿਓ"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਮਕ ਦਿਖਾਓ"</string>
     <string name="experimental" msgid="6198182315536726162">"ਪ੍ਰਯੋਗਾਤਮਿਕ"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ਚਾਲੂ ਕਰੋ?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ਆਪਣੇ ਟੈਬਲੇਟ ਨਾਲ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਕਨੈਕਟ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਪਹਿਲਾਂ Bluetooth ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ।"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ਚਾਲੂ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index e1a57be..caa3081 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Uporządkuj Szybkie ustawienia"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Pokaż jasność w Szybkich ustawieniach"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperymentalne"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Włączyć Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Aby połączyć klawiaturę z tabletem, musisz najpierw włączyć Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Włącz"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index e94318b..b82eb32 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index a41ee93..c137908 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar as Definições rápidas"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar luminosidade nas Definições rápidas"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Pretende ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para ligar o teclado ao tablet, tem de ativar primeiro o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index e94318b..b82eb32 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
     <string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Ativar"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 64560b9..b5819ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -441,10 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Rearanjați Setările rapide"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Afișați luminozitatea în Setările rapide"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentale"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Activați Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Activați"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 7487393..0806684 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -444,10 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Изменить порядок Быстрых настроек"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Добавить яркость в Быстрые настройки"</string>
     <string name="experimental" msgid="6198182315536726162">"Экспериментальная функция"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Подключение по Bluetooth"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Чтобы подключить клавиатуру к планшету, включите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Включить"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index c54f0ff..39147ff 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"ඉක්මන් සැකසීම් යළි පිළිවෙළට සකසන්න"</string>
     <string name="show_brightness" msgid="6613930842805942519">"ඉක්මන් සැකසීම්වල දීප්තිය පෙන්වන්න"</string>
     <string name="experimental" msgid="6198182315536726162">"පරීක්ෂණාත්මක"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"බ්ලූටූත් ක්‍රියාත්මක කරන්නද?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"ඔබේ යතුරු පුවරුව ඔබේ ටැබ්ලට් පරිගණකයට සම්බන්ධ කිරීමට, ඔබ පළමුව බ්ලූටූත් ක්‍රියාත්මක කළ යුතුය."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ක්‍රියාත්මක කරන්න"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index adde8be..86c8fe2 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -444,10 +444,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Zmeniť usporiadanie Rýchlych nastavení"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Zobraziť jas v Rýchlych nastaveniach"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentálne"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnúť Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ak chcete klávesnicu pripojiť k tabletu, najprv musíte zapnúť Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Zapnúť"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 2542807..c5dd188 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Preuredi hitre nastavitve"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Prikaz svetlosti v hitrih nastavitvah"</string>
     <string name="experimental" msgid="6198182315536726162">"Poskusno"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite vklopiti Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Če želite povezati tipkovnico in tablični računalnik, vklopite Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Vklop"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index a710477..fcd65f7 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Risistemo Cilësimet e shpejta"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Shfaq ndriçimin te Cilësimet e shpejta"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentale"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Të aktivizohet \"bluetooth-i\"?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Për të lidhur tastierën me tabletin, në fillim duhet të aktivizosh \"bluetooth-in\"."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivizo"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index c171a60..2c55a5c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -441,10 +441,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Преуреди Брза подешавања"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Прикажи осветљеност у Брзим подешавањима"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Желите ли да укључите Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Да бисте повезали тастатуру са таблетом, прво морате да укључите Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Укључи"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index fae5ab9..e8b1765 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -314,9 +314,9 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Mindre brådskande aviseringar nedan"</string>
     <string name="notification_tap_again" msgid="8524949573675922138">"Tryck igen för att öppna"</string>
     <string name="keyguard_unlock" msgid="8043466894212841998">"Svep uppåt om du vill låsa upp"</string>
-    <string name="phone_hint" msgid="4872890986869209950">"Dra från ikonen och öppna telefonen"</string>
-    <string name="voice_hint" msgid="8939888732119726665">"Dra från ikonen och öppna röstassistenten"</string>
-    <string name="camera_hint" msgid="7939688436797157483">"Dra från ikonen och öppna kameran"</string>
+    <string name="phone_hint" msgid="4872890986869209950">"Svep från ikonen och öppna telefonen"</string>
+    <string name="voice_hint" msgid="8939888732119726665">"Svep från ikonen och öppna röstassistenten"</string>
+    <string name="camera_hint" msgid="7939688436797157483">"Svep från ikonen och öppna kameran"</string>
     <string name="interruption_level_none_with_warning" msgid="5114872171614161084">"Total tystnad. Även skärmläsningsprogram tystas."</string>
     <string name="interruption_level_none" msgid="6000083681244492992">"Helt tyst"</string>
     <string name="interruption_level_priority" msgid="6426766465363855505">"Bara prioriterade"</string>
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ordna snabbinställningarna"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Visa ljusstyrka i snabbinställningarna"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimentella"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vill du aktivera Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Om du vill ansluta tangentbordet till surfplattan måste du först aktivera Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aktivera"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 5b46a7e..bbe271d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Panga Upya Mipangilio ya Haraka"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Onyesha unga\'avu katika Mipangilio ya Haraka"</string>
     <string name="experimental" msgid="6198182315536726162">"Ya majaribio"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Je, ungependa kuwasha Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ili uunganishe Kibodi yako kwenye kompyuta yako kibao, lazima kwanza uwashe Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Washa"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 477b715..08bef13 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"விரைவு அமைப்புகளை மறுவரிசைப்படுத்து"</string>
     <string name="show_brightness" msgid="6613930842805942519">"விரைவு அமைப்புகளில் ஒளிர்வுப் பட்டியைக் காட்டு"</string>
     <string name="experimental" msgid="6198182315536726162">"சோதனை முயற்சி"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"புளூடூத்தை இயக்கவா?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"உங்கள் டேப்லெட்டுடன் விசைப்பலகையை இணைக்க, முதலில் புளூடூத்தை இயக்க வேண்டும்."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"இயக்கு"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index f718669a..46bc704 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"శీఘ్ర సెట్టింగ్‌ల ఏర్పాటు క్రమం మార్చు"</string>
     <string name="show_brightness" msgid="6613930842805942519">"శీఘ్ర సెట్టింగ్‌ల్లో ప్రకాశం చూపు"</string>
     <string name="experimental" msgid="6198182315536726162">"ప్రయోగాత్మకం"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"బ్లూటూత్ ఆన్ చేయాలా?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"మీ కీబోర్డ్‌ను మీ టాబ్లెట్‌తో కనెక్ట్ చేయడానికి, మీరు ముందుగా బ్లూటూత్ ఆన్ చేయాలి."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"ఆన్ చేయి"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ca4000a..f7e7be0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"จัดเรียงการตั้งค่าด่วนใหม่"</string>
     <string name="show_brightness" msgid="6613930842805942519">"แสดงความสว่างในการตั้งค่าด่วน"</string>
     <string name="experimental" msgid="6198182315536726162">"ทดสอบ"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"เปิดบลูทูธไหม"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"หากต้องการเชื่อมต่อแป้นพิมพ์กับแท็บเล็ต คุณต้องเปิดบลูทูธก่อน"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"เปิด"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 5e8bb7a..92a6cdc 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Ayusing Muli ang Mga Mabilisang Setting"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Ipakita ang liwanag sa Mga Mabilisang Setting"</string>
     <string name="experimental" msgid="6198182315536726162">"Pang-eksperimento"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"I-on ang Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Upang ikonekta ang iyong keyboard sa iyong tablet, kailangan mo munang i-on ang Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"I-on"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index dbca4c7..2b9d6da 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Hızlı Ayarlar\'ı Yeniden Düzenle"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Hızlı Ayarlar\'da parlaklığı göster"</string>
     <string name="experimental" msgid="6198182315536726162">"Deneysel"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth açılsın mı?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klavyenizi tabletinize bağlamak için önce Bluetooth\'u açmanız gerekir."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Aç"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 8f8793c..c040439 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Упорядкувати швидкі налаштування"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Показувати панель яскравості у швидких налаштуваннях"</string>
     <string name="experimental" msgid="6198182315536726162">"Експериментальні налаштування"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Увімкнути Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Щоб під’єднати клавіатуру до планшета, спершу потрібно ввімкнути Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Увімкнути"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index 086f9e2..b0a5336 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"فوری ترتیبات کو دوبارہ ترتیب دیں"</string>
     <string name="show_brightness" msgid="6613930842805942519">"فوری ترتیبات میں چمکیلا پن دکھائیں"</string>
     <string name="experimental" msgid="6198182315536726162">"تجرباتی"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوٹوتھ آن کریں؟"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"اپنے کی بورڈ کو اپنے ٹیبلٹ کے ساتھ منسلک کرنے کیلئے پہلے آپ کو اپنا بلو ٹوتھ آن کرنا ہو گا۔"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"آن کریں"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index b1a556a..9b9e2cd 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Tezkor sozlamalarni qayta tartiblash"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Tezkor sozlamalarda yorqinlikni ko‘rsatish"</string>
     <string name="experimental" msgid="6198182315536726162">"Tajribaviy"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth yoqilsinmi?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviaturani planshetingizga ulash uchun Bluetooth xizmatini yoqishingiz kerak."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Yoqish"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index e22909c..d4ed7a5 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Sắp xếp lại Cài đặt nhanh"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Hiển thị độ sáng trong Cài đặt nhanh"</string>
     <string name="experimental" msgid="6198182315536726162">"Thử nghiệm"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Bật Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Để kết nối bàn phím với máy tính bảng, trước tiên, bạn phải bật Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Bật"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 9d32567..05416ca 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速设置"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速设置中显示亮度栏"</string>
     <string name="experimental" msgid="6198182315536726162">"实验性"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要开启蓝牙吗?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"要将您的键盘连接到平板电脑,您必须先开启蓝牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"开启"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index d80eb72..ba4d3cb 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定顯示亮度"</string>
     <string name="experimental" msgid="6198182315536726162">"實驗版"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙嗎?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連接至平板電腦,請先開啟藍牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"開啟"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index e7a4450..6e0c852 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -442,10 +442,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
     <string name="show_brightness" msgid="6613930842805942519">"在快速設定中顯示亮度"</string>
     <string name="experimental" msgid="6198182315536726162">"實驗性"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙功能嗎?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連線到平板電腦,您必須先開啟藍牙。"</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"開啟"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 06dc0c9..147f6b6 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -440,10 +440,7 @@
     <string name="qs_rearrange" msgid="8060918697551068765">"Hlela kabusha izilungiselelo ezisheshayo"</string>
     <string name="show_brightness" msgid="6613930842805942519">"Bonisa ukukhanya kuzilungiselelo ezisheshayo"</string>
     <string name="experimental" msgid="6198182315536726162">"Okokulinga"</string>
-    <!-- no translation found for enable_bluetooth_title (5027037706500635269) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_message (9106595990708985385) -->
-    <skip />
-    <!-- no translation found for enable_bluetooth_confirmation_ok (6258074250948309715) -->
-    <skip />
+    <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vula i-Bluetooth?"</string>
+    <string name="enable_bluetooth_message" msgid="9106595990708985385">"Ukuze uxhume ikhibhodi yakho nethebhulethi yakho, kufanele uqale ngokuvula i-Bluetooth."</string>
+    <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"Vula"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6fe7e6a..3a5680d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1166,6 +1166,7 @@
     <string name="qs_customize" translatable="false">Allow long-press customize in Quick Settings</string>
     <string name="qs_customize_info" translatable="false">Info</string>
     <string name="qs_customize_remove" translatable="false">Remove</string>
+    <string name="no_tiles_add" translatable="false">No tiles to add</string>
 
     <!-- Dialog title asking if Bluetooth should be enabled [CHAR LIMIT=NONE] -->
     <string name="enable_bluetooth_title">Turn on Bluetooth?</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8f634e1..8241ddf 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -36,7 +36,6 @@
     <style name="RecentsTheme.Wallpaper">
         <!-- Wallpaper -->
         <item name="android:windowBackground">@color/transparent</item>
-        <item name="android:windowResizingBackground">@color/transparent</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:windowShowWallpaper">true</item>
     </style>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index a43d520..96ee397 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -23,6 +23,7 @@
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.content.ContentResolver;
@@ -230,17 +231,19 @@
         }
 
         CachedBluetoothDevice device = getPairedKeyboard();
-        if ((mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH)
-                && device != null) {
-            // If we're just coming out of tablet mode or BT just turned on,
-            // then we want to go ahead and automatically connect to the
-            // keyboard. We want to avoid this in other cases because we might
-            // be spuriously called after the user has manually disconnected
-            // the keyboard, meaning we shouldn't try to automtically connect
-            // it again.
-            mState = STATE_PAIRED;
-            device.connect(false);
-            return;
+        if (mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) {
+            if (device != null) {
+                // If we're just coming out of tablet mode or BT just turned on,
+                // then we want to go ahead and automatically connect to the
+                // keyboard. We want to avoid this in other cases because we might
+                // be spuriously called after the user has manually disconnected
+                // the keyboard, meaning we shouldn't try to automtically connect
+                // it again.
+                mState = STATE_PAIRED;
+                device.connect(false);
+                return;
+            }
+            mCachedDeviceManager.clearNonBondedDevices();
         }
 
         device = getDiscoveredKeyboard();
@@ -459,21 +462,36 @@
     }
 
     private final class KeyboardScanCallback extends ScanCallback {
+
+        private boolean isDeviceDiscoverable(ScanResult result) {
+            final ScanRecord scanRecord = result.getScanRecord();
+            final int flags = scanRecord.getAdvertiseFlags();
+            final int BT_DISCOVERABLE_MASK = 0x03;
+
+            return (flags & BT_DISCOVERABLE_MASK) != 0;
+        }
+
         @Override
         public void onBatchScanResults(List<ScanResult> results) {
             if (DEBUG) {
                 Slog.d(TAG, "onBatchScanResults(" + results.size() + ")");
             }
-            if (!results.isEmpty()) {
-                BluetoothDevice bestDevice = results.get(0).getDevice();
-                int bestRssi = results.get(0).getRssi();
-                final int N = results.size();
-                for (int i = 0; i < N; i++) {
-                    ScanResult r = results.get(i);
-                    if (r.getRssi() > bestRssi) {
-                        bestDevice = r.getDevice();
-                    }
+
+            BluetoothDevice bestDevice = null;
+            int bestRssi = Integer.MIN_VALUE;
+
+            for (ScanResult result : results) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onBatchScanResults: considering " + result);
                 }
+
+                if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) {
+                    bestDevice = result.getDevice();
+                    bestRssi = result.getRssi();
+                }
+            }
+
+            if (bestDevice != null) {
                 mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget();
             }
         }
@@ -491,8 +509,14 @@
             if (DEBUG) {
                 Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")");
             }
-            mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED,
-                    result.getDevice()).sendToTarget();
+
+            if (isDeviceDiscoverable(result)) {
+                mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED,
+                        result.getDevice()).sendToTarget();
+            } else if (DEBUG) {
+                Slog.d(TAG, "onScanResult: device " + result.getDevice() +
+                       " is not discoverable, ignoring");
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 8e98f10..61cb224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -137,7 +137,7 @@
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
 
-    protected final void refreshState() {
+    public final void refreshState() {
         refreshState(null);
     }
 
@@ -360,6 +360,19 @@
         }
     }
 
+    public static class DrawableIcon extends Icon {
+        protected final Drawable mDrawable;
+
+        public DrawableIcon(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            return mDrawable;
+        }
+    }
+
     public static class ResourceIcon extends Icon {
         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 12a099d..1336eec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -72,7 +72,8 @@
         mLargeCellHeight = mAllowDual ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_height)
                 : mCellHeight;
         mLargeCellWidth = mAllowDual ? (int) (mLargeCellHeight * TILE_ASPECT) : mCellWidth;
-        mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+        mDualTileUnderlap = mAllowDual
+                ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical) : 0;
         if (mColumns != columns) {
             mColumns = columns;
             postInvalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
index 3491cb6..3f85982 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
@@ -49,7 +49,7 @@
     }
 
     @Override
-    protected QSTile<?> createTile(String tileSpec) {
+    public QSTile<?> createTile(String tileSpec) {
         QSTile<?> tile = super.createTile(tileSpec);
         tile.setTileSpec(tileSpec);
         return tile;
@@ -113,6 +113,11 @@
         return mTiles;
     }
 
+    public void addTile(String spec) {
+        mTiles.add(spec);
+        super.onTuningChanged(TILES_SETTING, null);
+    }
+
     public void replace(String oldTile, String newTile) {
         if (oldTile.equals(newTile)) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
index 0e15f2b..3135408 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
@@ -43,7 +43,7 @@
     }
 
     private void setHovering(boolean hovering) {
-        setAlpha(hovering ? .5f : 1);
+        setAlpha(hovering ? .3f : 1);
     }
 
     @Override
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 fe8d78b..b5a885c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -18,18 +18,31 @@
 import android.animation.Animator;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnDismissListener;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.*;
+import android.view.ContextThemeWrapper;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.Toolbar;
 import android.widget.Toolbar.OnMenuItemClickListener;
+
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTile.Host.Callback;
 import com.android.systemui.qs.customize.DropButton.OnDropListener;
+import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -44,7 +57,8 @@
  * *someday* do fancy animations to get into/out of it.
  */
 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener, Animator.AnimatorListener {
+        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
+        OnCancelListener, OnDismissListener {
 
     private static final int MENU_SAVE = Menu.FIRST;
     private static final int MENU_RESET = Menu.FIRST + 1;
@@ -61,6 +75,7 @@
     private DropButton mInfoButton;
     private DropButton mRemoveButton;
     private FloatingActionButton mFab;
+    private SystemUIDialog mDialog;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -162,6 +177,14 @@
     }
 
     @Override
+    public void onTileSelected(String spec) {
+        if (mDialog != null) {
+            mHost.addTile(spec);
+            mDialog.dismiss();
+        }
+    }
+
+    @Override
     public void onTilesChanged() {
         mQsPanel.setTiles(mHost.getTiles());
     }
@@ -193,12 +216,41 @@
     @Override
     public void onClick(View v) {
         if (mFab == v) {
-            SystemUIDialog dialog = new SystemUIDialog(mContext);
-            dialog.show();
+            mDialog = new SystemUIDialog(mContext,
+                    android.R.style.Theme_Material_Dialog);
+            View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
+            ListView listView = (ListView) view.findViewById(android.R.id.list);
+            TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+            adapter.setListener(this);
+            listView.setDivider(null);
+            listView.setDividerHeight(0);
+            listView.setAdapter(adapter);
+            listView.setEmptyView(view.findViewById(R.id.empty_text));
+            mDialog.setView(view);
+            mDialog.setOnDismissListener(this);
+            mDialog.setOnCancelListener(this);
+            mDialog.show();
+            // Too lazy to figure out what this will be now, but it should probably be something
+            // besides just a dialog.
+            // For now, just make it big.
+            WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            mDialog.getWindow().setAttributes(params);
         }
     }
 
     @Override
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
     public void onAnimationEnd(Animator animation) {
         if (!isShown) {
             mPhoneStatusBar.getStatusBarWindow().removeView(this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
new file mode 100644
index 0000000..579f58d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.Icon;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.QSPagingSwitch;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class TileAdapter extends BaseAdapter {
+
+    private static final String TAG = "TileAdapter";
+
+    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+    private final Context mContext;
+
+    private TileSelectedListener mListener;
+    private ArrayList<String> mCurrentTiles;
+
+    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        mContext = context;
+        addSystemTiles(currentTiles, host);
+        // TODO: Live?
+    }
+
+    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        try {
+            ArrayList<String> tileSpecs = new ArrayList<>();
+            for (QSTile<?> tile : currentTiles) {
+                tileSpecs.add(tile.getTileSpec());
+            }
+            mCurrentTiles = tileSpecs;
+            final TileGroup group = new TileGroup("com.android.settings", mContext);
+            // TODO: Pull this list from a more authoritative place.
+            String[] possibleTiles = QSPagingSwitch.QS_PAGE_TILES.split(",");
+            for (int i = 0; i < possibleTiles.length; i++) {
+                final String spec = possibleTiles[i];
+                if (spec.startsWith("q")) {
+                    // Quick tiles can't be customized.
+                    continue;
+                }
+                if (tileSpecs.contains(spec)) {
+                    continue;
+                }
+                final QSTile<?> tile = host.createTile(spec);
+                // Bad, bad, very bad.
+                tile.setListening(true);
+                tile.clearState();
+                tile.refreshState();
+                tile.setListening(false);
+                new Handler(host.getLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
+                    }
+                });
+            }
+            // Error: Badness (10000).
+            // Serialize this work after the host's looper's queue is empty.
+            new Handler(host.getLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    new Handler(Looper.getMainLooper()).post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (group.mTiles.size() > 0) {
+                                mGroups.add(group);
+                                notifyDataSetChanged();
+                            }
+                            new QueryTilesTask().execute();
+                        }
+                    });
+                }
+            });
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't load system tiles", e);
+        }
+    }
+
+    public void setListener(TileSelectedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public int getCount() {
+        return mGroups.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mGroups.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+    }
+
+    private static class TileGroup {
+        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+        private CharSequence mLabel;
+        private Drawable mIcon;
+
+        public TileGroup(String pkg, Context context) throws NameNotFoundException {
+            PackageManager pm = context.getPackageManager();
+            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+            mLabel = info.loadLabel(pm);
+            mIcon = info.loadIcon(pm);
+            Log.d(TAG, "Added " + mLabel);
+        }
+
+        private void addTile(String spec, Drawable icon, String label) {
+            TileInfo info = new TileInfo();
+            info.label = label;
+            info.drawable = icon;
+            info.spec = spec;
+            mTiles.add(info);
+        }
+
+        private void addTile(String spec, Icon icon, String label, Context context) {
+            addTile(spec, icon.getDrawable(context), label);
+        }
+
+        private View getView(Context context, View convertView, ViewGroup parent,
+                final TileSelectedListener listener) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
+                        false);
+            }
+            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
+            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
+            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
+            final int N = mTiles.size();
+            if (grid.getChildCount() != N) {
+                grid.removeAllViews();
+            }
+            for (int i = 0; i < N; i++) {
+                if (grid.getChildCount() <= i) {
+                    grid.addView(createTile(context));
+                }
+                View view = grid.getChildAt(i);
+                final TileInfo tileInfo = mTiles.get(i);
+                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
+                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
+                view.setClickable(true);
+                view.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        listener.onTileSelected(tileInfo.spec);
+                    }
+                });
+            }
+            return convertView;
+        }
+
+        private View createTile(Context context) {
+            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+        }
+    }
+
+    private static class TileInfo {
+        private String spec;
+        private Drawable drawable;
+        private String label;
+    }
+
+    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+        // TODO: Become non-prototype and an API.
+        private static final String TILE_ACTION = "android.intent.action.QS_TILE";
+
+        @Override
+        protected Collection<TileGroup> doInBackground(Void... params) {
+            HashMap<String, TileGroup> pkgMap = new HashMap<>();
+            PackageManager pm = mContext.getPackageManager();
+            // TODO: Handle userness.
+            List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+            for (ResolveInfo info : services) {
+                String packageName = info.serviceInfo.packageName;
+                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+                String spec = CustomTile.PREFIX + componentName.flattenToShortString() + ")";
+                if (mCurrentTiles.contains(spec)) {
+                    continue;
+                }
+                try {
+                    TileGroup group = pkgMap.get(packageName);
+                    if (group == null) {
+                        group = new TileGroup(packageName, mContext);
+                        pkgMap.put(packageName, group);
+                    }
+                    Drawable icon = info.serviceInfo.loadIcon(pm);
+                    CharSequence label = info.serviceInfo.loadLabel(pm);
+                    group.addTile(spec, icon, label != null ? label.toString() : "null");
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
+                }
+            }
+            return pkgMap.values();
+        }
+
+        @Override
+        protected void onPostExecute(Collection<TileGroup> result) {
+            mGroups.addAll(result);
+            notifyDataSetChanged();
+        }
+    }
+
+    public interface TileSelectedListener {
+        void onTileSelected(String spec);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
new file mode 100644
index 0000000..cf76ed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class CustomTile extends QSTile<QSTile.State> {
+    public static final String PREFIX = "custom(";
+
+    private final ComponentName mComponent;
+
+    private CustomTile(Host host, String action) {
+        super(host);
+        mComponent = ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(Host host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad intent tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty intent tile spec action");
+        }
+        return new CustomTile(host, action);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleUserSwitch(int newUserId) {
+        super.handleUserSwitch(newUserId);
+    }
+
+    @Override
+    protected void handleClick() {
+        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+    }
+
+    @Override
+    protected void handleLongClick() {
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        // TODO: Actual things.
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            state.visible = true;
+            state.icon = new DrawableIcon(info.loadIcon(pm));
+            state.label = info.loadLabel(pm).toString();
+            state.contentDescription = state.label;
+        } catch (Exception e) {
+            state.visible = false;
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_INTENT;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 9781664..cdb6b93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -39,6 +39,8 @@
             public static final boolean EnableDismissAll = false;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
+            // This disables the search bar integration
+            public static final boolean DisableSearchBar = true;
             // This disables the bitmap and icon caches
             public static final boolean DisableBackgroundCache = false;
             // Enables the simulated task affiliations
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 100644
index 0000000..79eca30d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the system user to callback to the secondary users in
+ * response to UI events coming in from the system user's SystemUI.
+ */
+oneway interface IRecentsNonSystemUserCallbacks {
+    void preloadRecents();
+    void cancelPreloadingRecents();
+    void showRecents(boolean triggeredFromAltTab);
+    void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
+    void toggleRecents();
+    void onConfigurationChanged();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 100644
index 0000000..6b49195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the non-system user to register itself for callbacks and to
+ * callback to the system user to update internal state.
+ */
+oneway interface IRecentsSystemUserCallbacks {
+    void registerNonSystemUserCallbacks(IBinder nonSystemUserCallbacks, int userId);
+
+    void updateRecentsVisibility(boolean visible);
+    void startScreenPinning();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index aee3623..c216f97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,908 +16,398 @@
 
 package com.android.systemui.recents;
 
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ITaskStackListener;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.os.AsyncTask;
 import android.os.Handler;
-import android.os.SystemClock;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.MutableBoolean;
+import android.util.Log;
 import android.view.Display;
-import android.view.LayoutInflater;
 import android.view.View;
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewHeader;
-import com.android.systemui.recents.views.TaskViewTransform;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.util.ArrayList;
 
-/**
- * Annotation for a method that is only called from the system user's SystemUI process and will be
- * proxied to the current user.
- */
-@interface ProxyFromSystemToCurrentUser {}
-/**
- * Annotation for a method that may be called from any user's SystemUI process and will be proxied
- * to the system user.
- */
-@interface ProxyFromAnyToSystemUser {}
 
-/** A proxy implementation for the recents component */
+/**
+ * An implementation of the SystemUI recents component, which supports both system and secondary
+ * users.
+ */
 public class Recents extends SystemUI
-        implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
+        implements RecentsComponent {
+
+    private final static String TAG = "Recents";
+    private final static boolean DEBUG = false;
 
     public final static int EVENT_BUS_PRIORITY = 1;
+    public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
 
-    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
-    final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
-    final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
+    private static SystemServicesProxy sSystemServicesProxy;
+    private static RecentsTaskLoader sTaskLoader;
 
-    // Owner proxy events
-    final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
-            "action_notify_recents_visibility_change";
-    final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
-            "action_screen_pinning_request";
+    private Handler mHandler;
+    private RecentsImpl mImpl;
 
-    final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
-    final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
-    final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+    // Only For system user, this is the callbacks instance we return to each secondary user
+    private RecentsSystemUser mSystemUserCallbacks;
 
-    final static int sMinToggleDelay = 350;
+    // Only for secondary users, this is the callbacks instance provided by the system user to make
+    // calls back
+    private IRecentsSystemUserCallbacks mCallbacksToSystemUser;
 
-    public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
-    public final static String sRecentsPackage = "com.android.systemui";
-    public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
+    // The set of runnables to run after binding to the system user's service.
+    private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
 
-    /**
-     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
-     * task stacks and update recents accordingly.
-     */
-    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
-        Handler mHandler;
-
-        public TaskStackListenerImpl(Handler handler) {
-            mHandler = handler;
-        }
-
+    // Only for secondary users, this is the death handler for the binder from the system user
+    private final IBinder.DeathRecipient mCallbacksToSystemUserDeathRcpt = new IBinder.DeathRecipient() {
         @Override
-        public void onTaskStackChanged() {
-            // Debounce any task stack changes
-            mHandler.removeCallbacks(this);
-            mHandler.post(this);
-        }
+        public void binderDied() {
+            mCallbacksToSystemUser = null;
 
-        /** Preloads the next task */
-        public void run() {
-            // TODO: Temporarily skip this if multi stack is enabled
-            /*
-            RecentsConfiguration config = RecentsConfiguration.getInstance();
-            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
-                RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-                SystemServicesProxy ssp = loader.getSystemServicesProxy();
-                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
-
-                // Load the next task only if we aren't svelte
-                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, true);
-                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-                // This callback is made when a new activity is launched and the old one is paused
-                // so ignore the current activity and try and preload the thumbnail for the
-                // previous one.
-                if (runningTaskInfo != null) {
-                    launchOpts.runningTaskId = runningTaskInfo.id;
+            // Retry after a fixed duration
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    registerWithSystemUser();
                 }
-                launchOpts.numVisibleTasks = 2;
-                launchOpts.numVisibleTaskThumbnails = 2;
-                launchOpts.onlyLoadForCache = true;
-                launchOpts.onlyLoadPausedActivities = true;
-                loader.loadTasks(mContext, plan, launchOpts);
-            }
-            */
+            }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
         }
-    }
+    };
 
-    /**
-     * A proxy for Recents events which happens strictly for the owner.
-     */
-    class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
+    // Only for secondary users, this is the service connection we use to connect to the system user
+    private final ServiceConnection mServiceConnectionToSystemUser = new ServiceConnection() {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
-                    visibilityChanged(context,
-                            intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
-                    break;
-                case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
-                    onStartScreenPinning(context);
-                    break;
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (service != null) {
+                mCallbacksToSystemUser = IRecentsSystemUserCallbacks.Stub.asInterface(
+                        service);
+
+                // Listen for system user's death, so that we can reconnect later
+                try {
+                    service.linkToDeath(mCallbacksToSystemUserDeathRcpt, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Lost connection to (System) SystemUI", e);
+                }
+
+                // Run each of the queued runnables
+                runAndFlushOnConnectRunnables();
             }
+
+            // Unbind ourselves now that we've registered our callbacks.  The
+            // binder to the system user are still valid at this point.
+            mContext.unbindService(this);
         }
-    }
 
-    static RecentsTaskLoadPlan sInstanceLoadPlan;
-    static Recents sInstance;
-
-    SystemServicesProxy mSystemServicesProxy;
-    Handler mHandler;
-    TaskStackListenerImpl mTaskStackListener;
-    RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
-    RecentsAppWidgetHost mAppWidgetHost;
-    boolean mBootCompleted;
-    boolean mStartAnimationTriggered;
-    boolean mCanReuseTaskStackViews = true;
-
-    // Task launching
-    RecentsConfiguration mConfig;
-    Rect mSearchBarBounds = new Rect();
-    Rect mTaskStackBounds = new Rect();
-    Rect mLastTaskViewBounds = new Rect();
-    TaskViewTransform mTmpTransform = new TaskViewTransform();
-    int mStatusBarHeight;
-    int mNavBarHeight;
-    int mNavBarWidth;
-    int mTaskBarHeight;
-
-    // Header (for transition)
-    TaskViewHeader mHeaderBar;
-    final Object mHeaderBarLock = new Object();
-    TaskStackView mDummyStackView;
-
-    // Variables to keep track of if we need to start recents after binding
-    boolean mTriggeredFromAltTab;
-    long mLastToggleTime;
-
-    Bitmap mThumbnailTransitionBitmapCache;
-    Task mThumbnailTransitionBitmapCacheKey;
-
-    public Recents() {
-    }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // Do nothing
+        }
+    };
 
     /**
-     * Gets the singleton instance and starts it if needed. On the primary user on the device, this
-     * component gets started as a normal {@link SystemUI} component. On a secondary user, this
-     * lifecycle doesn't exist, so we need to start it manually here if needed.
+     * Returns the callbacks interface that non-system users can call.
      */
-    public static Recents getInstanceAndStartIfNeeded(Context ctx) {
-        if (sInstance == null) {
-            sInstance = new Recents();
-            sInstance.mContext = ctx;
-            sInstance.start();
-            sInstance.onBootCompleted();
-        }
-        return sInstance;
+    public IBinder getSystemUserCallbacks() {
+        return mSystemUserCallbacks;
     }
 
-    /** Creates a new broadcast intent */
-    static Intent createLocalBroadcastIntent(Context context, String action) {
-        Intent intent = new Intent(action);
-        intent.setPackage(context.getPackageName());
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                Intent.FLAG_RECEIVER_FOREGROUND);
-        return intent;
+    public static RecentsTaskLoader getTaskLoader() {
+        return sTaskLoader;
     }
 
-    /** Initializes the Recents. */
-    @ProxyFromSystemToCurrentUser
+    public static SystemServicesProxy getSystemServices() {
+        return sSystemServicesProxy;
+    }
+
     @Override
     public void start() {
-        if (sInstance == null) {
-            sInstance = this;
-        }
-        Resources res = mContext.getResources();
-        RecentsTaskLoader.initialize(mContext);
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mSystemServicesProxy = new SystemServicesProxy(mContext);
+        sSystemServicesProxy = new SystemServicesProxy(mContext);
+        sTaskLoader = new RecentsTaskLoader(mContext);
         mHandler = new Handler();
-        mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+        mImpl = new RecentsImpl(mContext);
 
-        // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl(mHandler);
-        mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+        // Register with the event bus
+        EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+        EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
 
-        // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
-        // instances of RecentsComponent needs to notify the owner when the visibility
-        // changes.
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
-            filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
-            mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
-                    null, mHandler);
+        // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
+        // the system user's Recents component to pass events (like show/hide/toggleRecents) to the
+        // secondary user, and vice versa (like visibility change, screen pinning).
+        final int processUser = sSystemServicesProxy.getProcessUser();
+        if (sSystemServicesProxy.isSystemUser(processUser)) {
+            // For the system user, initialize an instance of the interface that we can pass to the
+            // secondary user
+            mSystemUserCallbacks = new RecentsSystemUser(mContext, mImpl);
+        } else {
+            // For the secondary user, bind to the primary user's service to get a persistent
+            // interface to register its implementation and to later update its state
+            registerWithSystemUser();
         }
-
-        // Initialize the static configuration resources
-        mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy);
-        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
-        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
-        mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
-        mDummyStackView = new TaskStackView(mContext, new TaskStack());
-        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
-                null, false);
-        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
-
-        // When we start, preload the data associated with the previous recent tasks.
-        // We can use a new plan since the caches will be the same.
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
-        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
-        launchOpts.onlyLoadForCache = true;
-        loader.loadTasks(mContext, plan, launchOpts);
         putComponent(Recents.class, this);
     }
 
     @Override
     public void onBootCompleted() {
-        mBootCompleted = true;
-        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+        mImpl.onBootCompleted();
     }
 
-    /** Shows the Recents. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Shows the Recents.
+     */
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            showRecentsInternal(triggeredFromAltTab);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.showRecents(triggeredFromAltTab);
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.showRecents(triggeredFromAltTab);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void showRecentsInternal(boolean triggeredFromAltTab) {
-        mTriggeredFromAltTab = triggeredFromAltTab;
-
-        try {
-            showRecentsActivity();
-        } catch (ActivityNotFoundException e) {
-            Console.logRawError("Failed to launch RecentAppsIntent", e);
-        }
-    }
-
-    /** Hides the Recents. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Hides the Recents.
+     */
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (mBootCompleted) {
-            // Defer to the activity to handle hiding recents, if it handles it, then it must still
-            // be visible
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
-            intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-
-    /** Toggles the Recents activity. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Toggles the Recents activity.
+     */
     @Override
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            toggleRecentsInternal();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.toggleRecents();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.toggleRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
-    void toggleRecentsInternal() {
-        mTriggeredFromAltTab = false;
-
-        try {
-            toggleRecentsActivity();
-        } catch (ActivityNotFoundException e) {
-            Console.logRawError("Failed to launch RecentAppsIntent", e);
-        }
-    }
-
-    /** Preloads info for the Recents activity. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Preloads info for the Recents activity.
+     */
     @Override
     public void preloadRecents() {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            preloadRecentsInternal();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.preloadRecents();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-
-    void preloadRecentsInternal() {
-        // Preload only the raw task list into a new load plan (which will be consumed by the
-        // RecentsActivity) only if there is a task to animate to.
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean topTaskHome = new MutableBoolean(true);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        sInstanceLoadPlan = loader.createLoadPlan(mContext);
-        if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
-            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
-            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
-            TaskStack stack = sInstanceLoadPlan.getTaskStack();
-            if (stack.getTaskCount() > 0) {
-                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView,
-                        topTaskHome.value);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.preloadRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
             }
         }
     }
 
     @Override
     public void cancelPreloadingRecents() {
-        // Do nothing
-    }
-
-    void showRelativeAffiliatedTask(boolean showNextTask) {
-        // Return early if there is no focused stack
-        int focusedStackId = mSystemServicesProxy.getFocusedStack();
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, true /* isTopTaskHome */);
-        TaskStack focusedStack = plan.getTaskStack();
-
-        // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
-
-        ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
-        // Return early if there is no running task (can't determine affiliated tasks in this case)
-        if (runningTask == null) return;
-        // Return early if the running task is in the home stack (optimization)
-        if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
-
-        // Find the task in the recents list
-        ArrayList<Task> tasks = focusedStack.getTasks();
-        Task toTask = null;
-        ActivityOptions launchOpts = null;
-        int taskCount = tasks.size();
-        int numAffiliatedTasks = 0;
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == runningTask.id) {
-                TaskGrouping group = task.group;
-                Task.TaskKey toTaskKey;
-                if (showNextTask) {
-                    toTaskKey = group.getNextTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_next_affiliated_task_target,
-                            R.anim.recents_launch_next_affiliated_task_source);
-                } else {
-                    toTaskKey = group.getPrevTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_prev_affiliated_task_target,
-                            R.anim.recents_launch_prev_affiliated_task_source);
-                }
-                if (toTaskKey != null) {
-                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
-                }
-                numAffiliatedTasks = group.getTaskCount();
-                break;
-            }
-        }
-
-        // Return early if there is no next task
-        if (toTask == null) {
-            if (numAffiliatedTasks > 1) {
-                if (showNextTask) {
-                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_next_affiliated_task_bounce));
-                } else {
-                    mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_prev_affiliated_task_bounce));
-                }
-            }
-            return;
-        }
-
-        // Keep track of actually launched affiliated tasks
-        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
-
-        // Launch the task
-        if (toTask.isActive) {
-            // Bring an active task to the foreground
-            mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.cancelPreloadingRecents();
         } else {
-            mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
-                    toTask.activityLabel, launchOpts);
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.cancelPreloadingRecents();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+                }
+            }
         }
     }
 
     @Override
     public void showNextAffiliatedTask() {
-        // Keep track of when the affiliated task is triggered
-        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
-        showRelativeAffiliatedTask(true);
+        mImpl.showNextAffiliatedTask();
     }
 
     @Override
     public void showPrevAffiliatedTask() {
-        // Keep track of when the affiliated task is triggered
-        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
-        showRelativeAffiliatedTask(false);
+        mImpl.showPrevAffiliatedTask();
     }
 
-    /** Updates on configuration change. */
-    @ProxyFromSystemToCurrentUser
+    /**
+     * Updates on configuration change.
+     */
     public void onConfigurationChanged(Configuration newConfig) {
-        if (mSystemServicesProxy.isForegroundUserSystem()) {
-            configurationChanged();
+        int currentUser = sSystemServicesProxy.getCurrentUser();
+        if (sSystemServicesProxy.isSystemUser(currentUser)) {
+            mImpl.onConfigurationChanged();
         } else {
-            Intent intent = createLocalBroadcastIntent(mContext,
-                    RecentsUserEventProxyReceiver.ACTION_PROXY_CONFIG_CHANGE_TO_USER);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-        }
-    }
-    void configurationChanged() {
-        // Don't reuse task stack views if the configuration changes
-        mCanReuseTaskStackViews = false;
-        mConfig.updateOnConfigurationChange();
-    }
-
-    /**
-     * Prepares the header bar layout for the next transition, if the task view bounds has changed
-     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
-     *
-     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
-     *                               is not already bound (can be expensive)
-     */
-    void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
-        Rect windowRect = mSystemServicesProxy.getWindowRect();
-
-        // Update the configuration for the current state
-        mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect());
-
-        if (tryAndBindSearchWidget) {
-            // Try and pre-emptively bind the search widget on startup to ensure that we
-            // have the right thumbnail bounds to animate to.
-            // Note: We have to reload the widget id before we get the task stack bounds below
-            if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
-                mConfig.getSearchBarBounds(windowRect,
-                        mStatusBarHeight, mSearchBarBounds);
-            }
-        }
-        Rect systemInsets = new Rect(0, mStatusBarHeight,
-                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
-                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
-        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
-                mSearchBarBounds, mTaskStackBounds);
-
-        // Rebind the header bar and draw it for the transition
-        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
-        Rect taskStackBounds = new Rect(mTaskStackBounds);
-        algo.setSystemInsets(systemInsets);
-        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
-        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
-        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
-            mLastTaskViewBounds.set(taskViewBounds);
-
-            int taskViewWidth = taskViewBounds.width();
-            synchronized (mHeaderBarLock) {
-                mHeaderBar.measure(
-                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
-                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
-                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
-            }
-        }
-    }
-
-    /** Toggles the recents activity */
-    void toggleRecentsActivity() {
-        // If the user has toggled it too quickly, then just eat up the event here (it's better than
-        // showing a janky screenshot).
-        // NOTE: Ideally, the screenshot mechanism would take the window transform into account
-        if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
-            return;
-        }
-
-        // If Recents is the front most activity, then we should just communicate with it directly
-        // to launch the first task or dismiss itself
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean isTopTaskHome = new MutableBoolean(true);
-        if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
-            // Notify recents to toggle itself
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
-            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-            mLastToggleTime = SystemClock.elapsedRealtime();
-            return;
-        } else {
-            // Otherwise, start the recents activity
-            showRecentsActivity(topTask, isTopTaskHome.value);
-        }
-    }
-
-    /** Shows the recents activity if it is not already running */
-    void showRecentsActivity() {
-        // Check if the top task is in the home stack, and start the recents activity
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean isTopTaskHome = new MutableBoolean(true);
-        if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
-            showRecentsActivity(topTask, isTopTaskHome.value);
-        }
-    }
-
-    /**
-     * Creates the activity options for a unknown state->recents transition.
-     */
-    ActivityOptions getUnknownTransitionActivityOptions() {
-        mStartAnimationTriggered = false;
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_unknown_enter,
-                R.anim.recents_from_unknown_exit,
-                mHandler, this);
-    }
-
-    /**
-     * Creates the activity options for a home->recents transition.
-     */
-    ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
-        mStartAnimationTriggered = false;
-        if (fromSearchHome) {
-            return ActivityOptions.makeCustomAnimation(mContext,
-                    R.anim.recents_from_search_launcher_enter,
-                    R.anim.recents_from_search_launcher_exit,
-                    mHandler, this);
-        }
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_launcher_enter,
-                R.anim.recents_from_launcher_exit,
-                mHandler, this);
-    }
-
-    /**
-     * Creates the activity options for an app->recents transition.
-     */
-    ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
-            TaskStack stack, TaskStackView stackView) {
-
-        // Update the destination rect
-        Task toTask = new Task();
-        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
-        Rect toTaskRect = toTransform.rect;
-        Bitmap thumbnail;
-        if (mThumbnailTransitionBitmapCacheKey != null
-                && mThumbnailTransitionBitmapCacheKey.key != null
-                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
-            thumbnail = mThumbnailTransitionBitmapCache;
-            mThumbnailTransitionBitmapCacheKey = null;
-            mThumbnailTransitionBitmapCache = null;
-        } else {
-            preloadIcon(topTask);
-            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
-        }
-        if (thumbnail != null) {
-            mStartAnimationTriggered = false;
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
-                    toTaskRect.height(), mHandler, this);
-        }
-
-        // If both the screenshot and thumbnail fails, then just fall back to the default transition
-        return getUnknownTransitionActivityOptions();
-    }
-
-    /**
-     * Preloads the icon of a task.
-     */
-    void preloadIcon(ActivityManager.RunningTaskInfo task) {
-
-        // Ensure that we load the running task's icon
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.runningTaskId = task.id;
-        launchOpts.loadThumbnails = false;
-        launchOpts.onlyLoadForCache = true;
-        RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
-    }
-
-    /**
-     * Caches the header thumbnail used for a window animation asynchronously into
-     * {@link #mThumbnailTransitionBitmapCache}.
-     */
-    void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
-            TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
-        preloadIcon(topTask);
-
-        // Update the header bar if necessary
-        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
-        // Update the destination rect
-        mDummyStackView.updateMinMaxScrollForStack(stack);
-        final Task toTask = new Task();
-        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                topTask.id, toTask);
-        new AsyncTask<Void, Void, Bitmap>() {
-            @Override
-            protected Bitmap doInBackground(Void... params) {
-                return drawThumbnailTransitionBitmap(toTask, toTransform);
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                mThumbnailTransitionBitmapCache = bitmap;
-                mThumbnailTransitionBitmapCacheKey = toTask;
-            }
-        }.execute();
-    }
-
-    /**
-     * Draws the header of a task used for the window animation into a bitmap.
-     */
-    Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
-        if (toTransform != null && toTask.key != null) {
-            Bitmap thumbnail;
-            synchronized (mHeaderBarLock) {
-                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
-                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
-                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
-                        Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                    thumbnail.eraseColor(0xFFff0000);
-                } else {
-                    Canvas c = new Canvas(thumbnail);
-                    c.scale(toTransform.scale, toTransform.scale);
-                    mHeaderBar.rebindToTask(toTask);
-                    mHeaderBar.draw(c);
-                    c.setBitmap(null);
-                }
-            }
-            return thumbnail.createAshmemBitmap();
-        }
-        return null;
-    }
-
-    /** Returns the transition rect for the given task id. */
-    TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
-            int runningTaskId, Task runningTaskOut) {
-        // Find the running task in the TaskStack
-        Task task = null;
-        ArrayList<Task> tasks = stack.getTasks();
-        if (runningTaskId != -1) {
-            // Otherwise, try and find the task with the
-            int taskCount = tasks.size();
-            for (int i = taskCount - 1; i >= 0; i--) {
-                Task t = tasks.get(i);
-                if (t.key.id == runningTaskId) {
-                    task = t;
-                    runningTaskOut.copyFrom(t);
-                    break;
-                }
-            }
-        }
-        if (task == null) {
-            // If no task is specified or we can not find the task just use the front most one
-            task = tasks.get(tasks.size() - 1);
-            runningTaskOut.copyFrom(task);
-        }
-
-        // Get the transform for the running task
-        stackView.getScroller().setStackScrollToInitialState();
-        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
-                stackView.getScroller().getStackScroll(), mTmpTransform, null);
-        return mTmpTransform;
-    }
-
-    /** Shows the recents activity */
-    void showRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-
-        // Update the header bar if necessary
-        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
-        if (sInstanceLoadPlan == null) {
-            // Create a new load plan if onPreloadRecents() was never triggered
-            sInstanceLoadPlan = loader.createLoadPlan(mContext);
-        }
-
-        if (!sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
-        }
-        TaskStack stack = sInstanceLoadPlan.getTaskStack();
-
-        // Prepare the dummy stack for the transition
-        mDummyStackView.updateMinMaxScrollForStack(stack);
-        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
-                mDummyStackView.computeStackVisibilityReport();
-        boolean hasRecentTasks = stack.getTaskCount() > 0;
-        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
-        if (useThumbnailTransition) {
-            // Try starting with a thumbnail transition
-            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
-                    mDummyStackView);
-            if (opts != null) {
-                startRecentsActivity(topTask, opts, false /* fromHome */,
-                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
-            } else {
-                // Fall through below to the non-thumbnail transition
-                useThumbnailTransition = false;
-            }
-        }
-
-        if (!useThumbnailTransition) {
-            // If there is no thumbnail transition, but is launching from home into recents, then
-            // use a quick home transition and do the animation from home
-            if (hasRecentTasks) {
-                String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
-                String searchWidgetPackage =
-                        Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
-
-                // Determine whether we are coming from a search owned home activity
-                boolean fromSearchHome = (homeActivityPackage != null) &&
-                        homeActivityPackage.equals(searchWidgetPackage);
-                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
-                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
-                        false /* fromThumbnail */, stackVr);
-            } else {
-                // Otherwise we do the normal fade from an unknown source
-                ActivityOptions opts = getUnknownTransitionActivityOptions();
-                startRecentsActivity(topTask, opts, true /* fromHome */,
-                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
-            }
-        }
-        mLastToggleTime = SystemClock.elapsedRealtime();
-    }
-
-    /** Starts the recents activity */
-    void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
-              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
-              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
-        // Update the configuration based on the launch options
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        launchState.launchedFromHome = fromSearchHome || fromHome;
-        launchState.launchedFromSearchHome = fromSearchHome;
-        launchState.launchedFromAppWithThumbnail = fromThumbnail;
-        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
-        launchState.launchedWithAltTab = mTriggeredFromAltTab;
-        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
-        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
-        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
-        launchState.launchedHasConfigurationChanged = false;
-
-        Intent intent = new Intent(sToggleRecentsAction);
-        intent.setClassName(sRecentsPackage, sRecentsActivity);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        if (opts != null) {
-            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
-        } else {
-            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-        }
-        mCanReuseTaskStackViews = true;
-    }
-
-    /** Notifies the callbacks that the visibility of Recents has changed. */
-    @ProxyFromAnyToSystemUser
-    public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
-            boolean visible) {
-        if (ssp.isForegroundUserSystem()) {
-            visibilityChanged(context, visible);
-        } else {
-            Intent intent = createLocalBroadcastIntent(context,
-                    ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
-            intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
-            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-        }
-    }
-    static void visibilityChanged(Context context, boolean visible) {
-        // For the primary user, the context for the SystemUI component is the SystemUIApplication
-        SystemUIApplication app = (SystemUIApplication)
-                getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
-        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
-        if (statusBar != null) {
-            statusBar.updateRecentsVisibility(visible);
-        }
-    }
-
-    /** Notifies the status bar to trigger screen pinning. */
-    @ProxyFromAnyToSystemUser
-    public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
-        if (ssp.isForegroundUserSystem()) {
-            onStartScreenPinning(context);
-        } else {
-            Intent intent = createLocalBroadcastIntent(context,
-                    ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
-            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-        }
-    }
-    static void onStartScreenPinning(Context context) {
-        // For the primary user, the context for the SystemUI component is the SystemUIApplication
-        SystemUIApplication app = (SystemUIApplication)
-                getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
-        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
-        if (statusBar != null) {
-            statusBar.showScreenPinningRequest(false);
-        }
-    }
-
-    /**
-     * Returns the preloaded load plan and invalidates it.
-     */
-    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
-        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
-        sInstanceLoadPlan = null;
-        return plan;
-    }
-
-    /**** OnAnimationStartedListener Implementation ****/
-
-    @Override
-    public void onAnimationStarted() {
-        // Notify recents to start the enter animation
-        if (!mStartAnimationTriggered) {
-            // There can be a race condition between the start animation callback and
-            // the start of the new activity (where we register the receiver that listens
-            // to this broadcast, so we add our own receiver and if that gets called, then
-            // we know the activity has not yet started and we can retry sending the broadcast.
-            BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (getResultCode() == Activity.RESULT_OK) {
-                        mStartAnimationTriggered = true;
-                        return;
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onConfigurationChanged();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
                     }
-
-                    // Schedule for the broadcast to be sent again after some time
-                    mHandler.postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            onAnimationStarted();
-                        }
-                    }, 25);
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
                 }
-            };
-
-            // Send the broadcast to notify Recents that the animation has started
-            Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
-            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
-                    fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
+            }
         }
     }
+
+    /**
+     * Handle Recents activity visibility changed.
+     */
+    public final void onBusEvent(final RecentsVisibilityChangedEvent event) {
+        int processUser = event.systemServicesProxy.getProcessUser();
+        if (event.systemServicesProxy.isSystemUser(processUser)) {
+            mImpl.onVisibilityChanged(event.applicationContext, event.visible);
+        } else {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mCallbacksToSystemUser.updateRecentsVisibility(event.visible);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Handle screen pinning request.
+     */
+    public final void onBusEvent(final ScreenPinningRequestEvent event) {
+        int processUser = event.systemServicesProxy.getProcessUser();
+        if (event.systemServicesProxy.isSystemUser(processUser)) {
+            mImpl.onStartScreenPinning(event.applicationContext);
+        } else {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mCallbacksToSystemUser.startScreenPinning();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Attempts to register with the system user.
+     */
+    private void registerWithSystemUser() {
+        final int processUser = sSystemServicesProxy.getProcessUser();
+        postToSystemUser(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mCallbacksToSystemUser.registerNonSystemUserCallbacks(mImpl, processUser);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to register", e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Runs the runnable in the system user's Recents context, connecting to the service if
+     * necessary.
+     */
+    private void postToSystemUser(final Runnable onConnectRunnable) {
+        mOnConnectRunnables.add(onConnectRunnable);
+        if (mCallbacksToSystemUser == null) {
+            Intent systemUserServiceIntent = new Intent();
+            systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
+            boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
+                    mServiceConnectionToSystemUser, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+            if (!bound) {
+                // Retry after a fixed duration
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        registerWithSystemUser();
+                    }
+                }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
+            }
+        } else {
+            runAndFlushOnConnectRunnables();
+        }
+    }
+
+    /**
+     * Runs all the queued runnables after a service connection is made.
+     */
+    private void runAndFlushOnConnectRunnables() {
+        for (Runnable r : mOnConnectRunnables) {
+            r.run();
+        }
+        mOnConnectRunnables.clear();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index e647c1f..c416967 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -39,9 +39,15 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.Console;
@@ -68,6 +74,7 @@
     RecentsConfiguration mConfig;
     RecentsPackageMonitor mPackageMonitor;
     long mLastTabKeyEventTime;
+    boolean mFinishedOnStartup;
 
     // Top level views
     RecentsView mRecentsView;
@@ -121,36 +128,6 @@
     }
 
     /**
-     * Broadcast receiver to handle messages from AlternateRecentsComponent.
-     */
-    final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) {
-                if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
-                    // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
-                    dismissRecentsToFocusedTaskOrHome(false);
-                } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
-                    // Otherwise, dismiss Recents to Home
-                    dismissRecentsToHome(true);
-                } else {
-                    // Do nothing
-                }
-            } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
-                // If we are toggling Recents, then first unfilter any filtered stacks first
-                dismissRecentsToFocusedTaskOrHome(true);
-            } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) {
-                // Trigger the enter animation
-                onEnterAnimationTriggered();
-                // Notify the fallback receiver that we have successfully got the broadcast
-                // See AlternateRecentsComponent.onAnimationStarted()
-                setResultCode(Activity.RESULT_OK);
-            }
-        }
-    };
-
-    /**
      * Broadcast receiver to handle messages from the system
      */
     final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
@@ -162,7 +139,7 @@
                 dismissRecentsToHomeIfVisible(false);
             } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
                 // When the search activity changes, update the search widget view
-                SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                SystemServicesProxy ssp = Recents.getSystemServices();
                 mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(context, mAppWidgetHost);
                 refreshSearchWidgetView();
             }
@@ -173,8 +150,8 @@
     void updateRecentsTasks() {
         // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
         // reconstructing the task stack
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
         if (plan == null) {
             plan = loader.createLoadPlan(this);
         }
@@ -229,15 +206,19 @@
                 mEmptyView = mEmptyViewStub.inflate();
             }
             mEmptyView.setVisibility(View.VISIBLE);
-            mRecentsView.setSearchBarVisibility(View.GONE);
+            if (!Constants.DebugFlags.App.DisableSearchBar) {
+                mRecentsView.setSearchBarVisibility(View.GONE);
+            }
         } else {
             if (mEmptyView != null) {
                 mEmptyView.setVisibility(View.GONE);
             }
-            if (mRecentsView.hasValidSearchBar()) {
-                mRecentsView.setSearchBarVisibility(View.VISIBLE);
-            } else {
-                refreshSearchWidgetView();
+            if (!Constants.DebugFlags.App.DisableSearchBar) {
+                if (mRecentsView.hasValidSearchBar()) {
+                    mRecentsView.setSearchBarVisibility(View.VISIBLE);
+                } else {
+                    refreshSearchWidgetView();
+                }
             }
         }
 
@@ -266,7 +247,7 @@
     /** Dismisses recents if we are already visible and the intent is to toggle the recents view */
     boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
         RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // If we currently have filtered stacks, then unfilter those first
             if (checkFilteredStackState &&
@@ -296,7 +277,6 @@
                     null, mFinishLaunchHomeRunnable, null);
             mRecentsView.startExitToHomeAnimation(
                     new ViewAnimation.TaskViewExitContext(exitTrigger));
-            mScrimViews.startExitRecentsAnimation();
         } else {
             mFinishLaunchHomeRunnable.run();
         }
@@ -310,7 +290,7 @@
 
     /** Dismisses Recents directly to Home if we currently aren't transitioning. */
     boolean dismissRecentsToHomeIfVisible(boolean animated) {
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // Return to Home
             dismissRecentsToHome(animated);
@@ -323,20 +303,27 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mFinishedOnStartup = false;
+
+        // In the case that the activity starts up before the Recents component has initialized
+        // (usually when debugging/pushing the SysUI apk), just finish this activity.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (ssp == null) {
+            mFinishedOnStartup = true;
+            finish();
+            return;
+        }
 
         // Register this activity with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
-        // For the non-primary user, ensure that the SystemServicesProxy and configuration is
-        // initialized
-        RecentsTaskLoader.initialize(this);
-        SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-        mConfig = RecentsConfiguration.initialize(this, ssp);
-        mConfig.update(this, ssp, ssp.getWindowRect());
-        mPackageMonitor = new RecentsPackageMonitor();
-
         // Initialize the widget host (the host id is static and does not change)
-        mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+        mConfig = RecentsConfiguration.getInstance();
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
+        }
+        mPackageMonitor = new RecentsPackageMonitor();
+        mPackageMonitor.register(this);
 
         // Set the Recents layout
         setContentView(R.layout.recents);
@@ -349,12 +336,16 @@
         mScrimViews = new SystemBarScrimViews(this);
 
         // Bind the search app widget when we first start up
-        mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
+        }
 
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+        }
         registerReceiver(mSystemBroadcastReceiver, filter);
     }
 
@@ -367,21 +358,6 @@
     @Override
     protected void onStart() {
         super.onStart();
-        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
-        MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.notifyVisibilityChanged(this, ssp, true);
-
-        // Register the broadcast receiver to handle messages from our service
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
-        filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
-        filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
-        registerReceiver(mServiceBroadcastReceiver, filter);
-
-        // Register any broadcast receivers for the task loader
-        mPackageMonitor.register(this);
 
         // Update the recent tasks
         updateRecentsTasks();
@@ -389,15 +365,22 @@
         // If this is a new instance from a configuration change, then we have to manually trigger
         // the enter animation state, or if recents was relaunched by AM, without going through
         // the normal mechanisms
+        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
                 !launchState.launchedFromAppWithThumbnail;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
-            onEnterAnimationTriggered();
+            EventBus.getDefault().send(new EnterRecentsWindowAnimationStartedEvent());
         }
 
         if (!launchState.launchedHasConfigurationChanged) {
             mRecentsView.disableLayersForOneFrame();
         }
+
+        // Notify that recents is now visible
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
+
+        MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
     }
 
     @Override
@@ -412,19 +395,10 @@
     @Override
     protected void onStop() {
         super.onStop();
-        MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.notifyVisibilityChanged(this, ssp, false);
 
-        // Notify the views that we are no longer visible
-        mRecentsView.onRecentsHidden();
-
-        // Unregister the RecentsService receiver
-        unregisterReceiver(mServiceBroadcastReceiver);
-
-        // Unregister any broadcast receivers for the task loader
-        mPackageMonitor.unregister();
+        // Notify that recents is now hidden
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
 
         // Workaround for b/22542869, if the RecentsActivity is started again, but without going
         // through SystemUI, we need to reset the config launch flags to ensure that we do not
@@ -436,45 +410,48 @@
         launchState.launchedToTaskId = -1;
         launchState.launchedWithAltTab = false;
         launchState.launchedHasConfigurationChanged = false;
+
+        MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
 
+        // In the case that the activity finished on startup, just skip the unregistration below
+        if (mFinishedOnStartup) {
+            return;
+        }
+
         // Unregister the system broadcast receivers
         unregisterReceiver(mSystemBroadcastReceiver);
 
+        // Unregister any broadcast receivers for the task loader
+        mPackageMonitor.unregister();
+
         // Stop listening for widget package changes if there was one bound
-        mAppWidgetHost.stopListening();
+        if (!Constants.DebugFlags.App.DisableSearchBar) {
+            mAppWidgetHost.stopListening();
+        }
+
         EventBus.getDefault().unregister(this);
     }
 
-    public void onEnterAnimationTriggered() {
-        // Try and start the enter animation (or restart it on configuration changed)
-        ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
-        ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
-        mRecentsView.startEnterRecentsAnimation(ctx);
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY);
+    }
 
-        if (mSearchWidgetInfo != null) {
-            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    // Start listening for widget package changes if there is one bound
-                    if (mAppWidgetHost != null) {
-                        mAppWidgetHost.startListening();
-                    }
-                }
-            });
-        }
-
-        // Animate the SystemUI scrim views
-        mScrimViews.startEnterRecentsAnimation();
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        EventBus.getDefault().unregister(mScrimViews);
     }
 
     @Override
     public void onTrimMemory(int level) {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
         if (loader != null) {
             loader.onTrimMemory(level);
         }
@@ -519,7 +496,7 @@
 
     @Override
     public void onUserInteraction() {
-        mRecentsView.onUserInteraction();
+        EventBus.getDefault().send(new UserInteractionEvent());
     }
 
     @Override
@@ -540,12 +517,6 @@
     /**** RecentsView.RecentsViewCallbacks Implementation ****/
 
     @Override
-    public void onExitToHomeAnimationTriggered() {
-        // Animate the SystemUI scrim views out
-        mScrimViews.startExitRecentsAnimation();
-    }
-
-    @Override
     public void onTaskViewClicked() {
     }
 
@@ -561,30 +532,54 @@
     }
 
     @Override
-    public void onScreenPinningRequest() {
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-        SystemServicesProxy ssp = loader.getSystemServicesProxy();
-        Recents.startScreenPinning(this, ssp);
-
-        MetricsLogger.count(this, "overview_screen_pinned", 1);
-    }
-
-    @Override
     public void runAfterPause(Runnable r) {
         mAfterPauseRunnable = r;
     }
 
     /**** EventBus events ****/
 
+    public final void onBusEvent(ToggleRecentsEvent event) {
+        dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
+    }
+
+    public final void onBusEvent(HideRecentsEvent event) {
+        if (event.triggeredFromAltTab) {
+            // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
+            dismissRecentsToFocusedTaskOrHome(false /* checkFilteredStackState */);
+        } else if (event.triggeredFromHomeKey) {
+            // Otherwise, dismiss Recents to Home
+            dismissRecentsToHome(true /* checkFilteredStackState */);
+        } else {
+            // Do nothing
+        }
+    }
+
+    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
+        // Try and start the enter animation (or restart it on configuration changed)
+        ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
+        ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
+        mRecentsView.startEnterRecentsAnimation(ctx);
+        if (mSearchWidgetInfo != null) {
+            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                @Override
+                public void run() {
+                    // Start listening for widget package changes if there is one bound
+                    if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) {
+                        mAppWidgetHost.startListening();
+                    }
+                }
+            });
+        }
+    }
+
     public final void onBusEvent(AppWidgetProviderChangedEvent event) {
         refreshSearchWidgetView();
     }
 
     public final void onBusEvent(ShowApplicationInfoEvent event) {
         // Create a new task stack with the application info details activity
-        Intent baseIntent = event.task.key.baseIntent;
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
+                Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null));
         intent.setComponent(intent.resolveActivity(getPackageManager()));
         TaskStackBuilder.create(this)
                 .addNextIntentWithParentStack(intent).startActivities(null,
@@ -596,11 +591,12 @@
 
     public final void onBusEvent(DismissTaskEvent event) {
         // Remove any stored data from the loader
-        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
         loader.deleteTaskData(event.task, false);
 
         // Remove the task from activity manager
-        loader.getSystemServicesProxy().removeTask(event.task.key.id);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.removeTask(event.task.key.id);
     }
 
     public final void onBusEvent(ResizeTaskEvent event) {
@@ -619,9 +615,13 @@
         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
     }
 
+    public final void onBusEvent(ScreenPinningRequestEvent event) {
+        MetricsLogger.count(this, "overview_screen_pinned", 1);
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
-            SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+            SystemServicesProxy ssp = Recents.getSystemServices();
             int searchWidgetId = ssp.getSearchAppWidgetId(this);
             mSearchWidgetHostView = (RecentsAppWidgetHostView) mAppWidgetHost.createView(
                     this, searchWidgetId, mSearchWidgetInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 37439e7..d8f4023 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -22,7 +22,6 @@
 import android.provider.Settings;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 
 /**
  * Application resources that can be retrieved from the application context and are not specifically
@@ -112,8 +111,7 @@
     void update(Context context, SystemServicesProxy ssp, Rect windowRect) {
         // Only update resources that can change after the first load, either through developer
         // settings or via multi window
-        lockToAppEnabled = ssp.getSystemSetting(context,
-                Settings.System.LOCK_TO_APP_ENABLED) != 0;
+        lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
         hasDockedTasks = ssp.hasDockedTask();
 
         // Recompute some values based on the given state, since we can not rely on the resource
@@ -144,8 +142,10 @@
         return mLaunchState;
     }
 
-    /** Called when the configuration has changed, and we want to reset any configuration specific
-     * members. */
+    /**
+     * Called when the configuration has changed, and we want to reset any configuration specific
+     * members.
+     */
     public void updateOnConfigurationChange() {
         mLaunchState.updateOnConfigurationChange();
     }
@@ -165,7 +165,8 @@
         } else {
             // In portrait, the search bar appears on the top (which already has the inset)
             int swInset = getInsetToSmallestWidth(windowBounds.right - windowBounds.left);
-            taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom,
+            int top = searchBarBounds.isEmpty() ? topInset : 0;
+            taskStackBounds.set(windowBounds.left + swInset, searchBarBounds.bottom + top,
                     windowBounds.right - swInset, windowBounds.bottom);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
new file mode 100644
index 0000000..07c7897
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ITaskStackListener;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.MutableBoolean;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.ForegroundThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskGrouping;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewHeader;
+import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.util.ArrayList;
+
+/**
+ * An implementation of the Recents component for the current user.  For secondary users, this can
+ * be called remotely from the system user.
+ */
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
+        implements ActivityOptions.OnAnimationStartedListener {
+
+    private final static String TAG = "RecentsImpl";
+    private final static boolean DEBUG = false;
+
+    private final static int sMinToggleDelay = 350;
+
+    public final static String RECENTS_PACKAGE = "com.android.systemui";
+    public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
+
+
+    /**
+     * An implementation of ITaskStackListener, that allows us to listen for changes to the system
+     * task stacks and update recents accordingly.
+     */
+    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+        Handler mHandler;
+
+        public TaskStackListenerImpl(Handler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void onTaskStackChanged() {
+            // Debounce any task stack changes
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+
+        /** Preloads the next task */
+        public void run() {
+            // TODO: Temporarily skip this if multi stack is enabled
+            /*
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+                RecentsTaskLoader loader = Recents.getTaskLoader();
+                SystemServicesProxy ssp = Recents.getSystemServices();
+                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+
+                // Load the next task only if we aren't svelte
+                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+                loader.preloadTasks(plan, true);
+                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+                // This callback is made when a new activity is launched and the old one is paused
+                // so ignore the current activity and try and preload the thumbnail for the
+                // previous one.
+                if (runningTaskInfo != null) {
+                    launchOpts.runningTaskId = runningTaskInfo.id;
+                }
+                launchOpts.numVisibleTasks = 2;
+                launchOpts.numVisibleTaskThumbnails = 2;
+                launchOpts.onlyLoadForCache = true;
+                launchOpts.onlyLoadPausedActivities = true;
+                loader.loadTasks(mContext, plan, launchOpts);
+            }
+            */
+        }
+    }
+
+    private static RecentsTaskLoadPlan sInstanceLoadPlan;
+
+    Context mContext;
+    Handler mHandler;
+    TaskStackListenerImpl mTaskStackListener;
+    RecentsAppWidgetHost mAppWidgetHost;
+    boolean mBootCompleted;
+    boolean mStartAnimationTriggered;
+    boolean mCanReuseTaskStackViews = true;
+
+    // Task launching
+    RecentsConfiguration mConfig;
+    Rect mSearchBarBounds = new Rect();
+    Rect mTaskStackBounds = new Rect();
+    Rect mLastTaskViewBounds = new Rect();
+    TaskViewTransform mTmpTransform = new TaskViewTransform();
+    int mStatusBarHeight;
+    int mNavBarHeight;
+    int mNavBarWidth;
+    int mTaskBarHeight;
+
+    // Header (for transition)
+    TaskViewHeader mHeaderBar;
+    final Object mHeaderBarLock = new Object();
+    TaskStackView mDummyStackView;
+
+    // Variables to keep track of if we need to start recents after binding
+    boolean mTriggeredFromAltTab;
+    long mLastToggleTime;
+
+    Bitmap mThumbnailTransitionBitmapCache;
+    Task mThumbnailTransitionBitmapCacheKey;
+
+
+    public RecentsImpl(Context context) {
+        mContext = context;
+        mHandler = new Handler();
+        mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+        Resources res = mContext.getResources();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        // Initialize the static foreground thread
+        ForegroundThread.get();
+
+        // Register the task stack listener
+        mTaskStackListener = new TaskStackListenerImpl(mHandler);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.registerTaskStackListener(mTaskStackListener);
+
+        // Initialize the static configuration resources
+        mConfig = RecentsConfiguration.initialize(mContext, ssp);
+        mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+        mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+        mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+        mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
+        mDummyStackView = new TaskStackView(mContext, new TaskStack());
+        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+                null, false);
+        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+
+        // When we start, preload the data associated with the previous recent tasks.
+        // We can use a new plan since the caches will be the same.
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+        launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
+        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
+        launchOpts.onlyLoadForCache = true;
+        loader.loadTasks(mContext, plan, launchOpts);
+    }
+
+    public void onBootCompleted() {
+        mBootCompleted = true;
+        reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        // Don't reuse task stack views if the configuration changes
+        mCanReuseTaskStackViews = false;
+        mConfig.updateOnConfigurationChange();
+    }
+
+    /**
+     * This is only called from the system user's Recents.  Secondary users will instead proxy their
+     * visibility change events through to the system user via
+     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
+     */
+    public void onVisibilityChanged(Context context, boolean visible) {
+        SystemUIApplication app = (SystemUIApplication) context;
+        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+        if (statusBar != null) {
+            statusBar.updateRecentsVisibility(visible);
+        }
+    }
+
+    /**
+     * This is only called from the system user's Recents.  Secondary users will instead proxy their
+     * visibility change events through to the system user via
+     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
+     */
+    public void onStartScreenPinning(Context context) {
+        SystemUIApplication app = (SystemUIApplication) context;
+        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+        if (statusBar != null) {
+            statusBar.showScreenPinningRequest(false);
+        }
+    }
+
+    @Override
+    public void showRecents(boolean triggeredFromAltTab) {
+        mTriggeredFromAltTab = triggeredFromAltTab;
+
+        try {
+            // Check if the top task is in the home stack, and start the recents activity
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+            MutableBoolean isTopTaskHome = new MutableBoolean(true);
+            if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
+                startRecentsActivity(topTask, isTopTaskHome.value);
+            }
+        } catch (ActivityNotFoundException e) {
+            Console.logRawError("Failed to launch RecentAppsIntent", e);
+        }
+    }
+
+    @Override
+    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        if (mBootCompleted) {
+            // Defer to the activity to handle hiding recents, if it handles it, then it must still
+            // be visible
+            EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
+                    triggeredFromHomeKey));
+        }
+    }
+
+    @Override
+    public void toggleRecents() {
+        mTriggeredFromAltTab = false;
+
+        try {
+            // If the user has toggled it too quickly, then just eat up the event here (it's better
+            // than showing a janky screenshot).
+            // NOTE: Ideally, the screenshot mechanism would take the window transform into account
+            if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+                return;
+            }
+
+            // If Recents is the front most activity, then we should just communicate with it
+            // directly to launch the first task or dismiss itself
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+            MutableBoolean isTopTaskHome = new MutableBoolean(true);
+            if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
+                // Notify recents to toggle itself
+                EventBus.getDefault().post(new ToggleRecentsEvent());
+                mLastToggleTime = SystemClock.elapsedRealtime();
+                return;
+            } else {
+                // Otherwise, start the recents activity
+                startRecentsActivity(topTask, isTopTaskHome.value);
+            }
+        } catch (ActivityNotFoundException e) {
+            Console.logRawError("Failed to launch RecentAppsIntent", e);
+        }
+    }
+
+    @Override
+    public void preloadRecents() {
+        // Preload only the raw task list into a new load plan (which will be consumed by the
+        // RecentsActivity) only if there is a task to animate to.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
+        MutableBoolean topTaskHome = new MutableBoolean(true);
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        sInstanceLoadPlan = loader.createLoadPlan(mContext);
+        if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
+            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
+            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+            TaskStack stack = sInstanceLoadPlan.getTaskStack();
+            if (stack.getTaskCount() > 0) {
+                // We try and draw the thumbnail transition bitmap in parallel before
+                // toggle/show recents is called
+                preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
+            }
+        }
+    }
+
+    @Override
+    public void cancelPreloadingRecents() {
+        // Do nothing
+    }
+
+    public void showRelativeAffiliatedTask(boolean showNextTask) {
+        // Return early if there is no focused stack
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        int focusedStackId = ssp.getFocusedStack();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+        loader.preloadTasks(plan, true /* isTopTaskHome */);
+        TaskStack focusedStack = plan.getTaskStack();
+
+        // Return early if there are no tasks in the focused stack
+        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+
+        ActivityManager.RunningTaskInfo runningTask = ssp.getTopMostTask();
+        // Return early if there is no running task (can't determine affiliated tasks in this case)
+        if (runningTask == null) return;
+        // Return early if the running task is in the home stack (optimization)
+        if (ssp.isInHomeStack(runningTask.id)) return;
+
+        // Find the task in the recents list
+        ArrayList<Task> tasks = focusedStack.getTasks();
+        Task toTask = null;
+        ActivityOptions launchOpts = null;
+        int taskCount = tasks.size();
+        int numAffiliatedTasks = 0;
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.key.id == runningTask.id) {
+                TaskGrouping group = task.group;
+                Task.TaskKey toTaskKey;
+                if (showNextTask) {
+                    toTaskKey = group.getNextTaskInGroup(task);
+                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                            R.anim.recents_launch_next_affiliated_task_target,
+                            R.anim.recents_launch_next_affiliated_task_source);
+                } else {
+                    toTaskKey = group.getPrevTaskInGroup(task);
+                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                            R.anim.recents_launch_prev_affiliated_task_target,
+                            R.anim.recents_launch_prev_affiliated_task_source);
+                }
+                if (toTaskKey != null) {
+                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
+                }
+                numAffiliatedTasks = group.getTaskCount();
+                break;
+            }
+        }
+
+        // Return early if there is no next task
+        if (toTask == null) {
+            if (numAffiliatedTasks > 1) {
+                if (showNextTask) {
+                    ssp.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_next_affiliated_task_bounce));
+                } else {
+                    ssp.startInPlaceAnimationOnFrontMostApplication(
+                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                    R.anim.recents_launch_prev_affiliated_task_bounce));
+                }
+            }
+            return;
+        }
+
+        // Keep track of actually launched affiliated tasks
+        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
+
+        // Launch the task
+        if (toTask.isActive) {
+            // Bring an active task to the foreground
+            ssp.moveTaskToFront(toTask.key.id, launchOpts);
+        } else {
+            ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
+        }
+    }
+
+    public void showNextAffiliatedTask() {
+        // Keep track of when the affiliated task is triggered
+        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
+        showRelativeAffiliatedTask(true);
+    }
+
+    public void showPrevAffiliatedTask() {
+        // Keep track of when the affiliated task is triggered
+        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
+        showRelativeAffiliatedTask(false);
+    }
+
+    /**
+     * Returns the preloaded load plan and invalidates it.
+     */
+    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
+        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
+        sInstanceLoadPlan = null;
+        return plan;
+    }
+
+    /**
+     * Prepares the header bar layout for the next transition, if the task view bounds has changed
+     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
+     *
+     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
+     *                               is not already bound (can be expensive)
+     */
+    private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect windowRect = ssp.getWindowRect();
+
+        // Update the configuration for the current state
+        mConfig.update(mContext, ssp, ssp.getWindowRect());
+
+        if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) {
+            // Try and pre-emptively bind the search widget on startup to ensure that we
+            // have the right thumbnail bounds to animate to.
+            // Note: We have to reload the widget id before we get the task stack bounds below
+            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+                mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
+            }
+        }
+        Rect systemInsets = new Rect(0, mStatusBarHeight,
+                (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
+                (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
+        mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+                mSearchBarBounds, mTaskStackBounds);
+
+        // Rebind the header bar and draw it for the transition
+        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+        Rect taskStackBounds = new Rect(mTaskStackBounds);
+        algo.setSystemInsets(systemInsets);
+        algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
+        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
+        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
+            mLastTaskViewBounds.set(taskViewBounds);
+
+            int taskViewWidth = taskViewBounds.width();
+            synchronized (mHeaderBarLock) {
+                mHeaderBar.measure(
+                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
+                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
+                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
+            }
+        }
+    }
+
+    /**
+     * Preloads the icon of a task.
+     */
+    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
+        // Ensure that we load the running task's icon
+        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+        launchOpts.runningTaskId = task.id;
+        launchOpts.loadThumbnails = false;
+        launchOpts.onlyLoadForCache = true;
+        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+    }
+
+    /**
+     * Caches the header thumbnail used for a window animation asynchronously into
+     * {@link #mThumbnailTransitionBitmapCache}.
+     */
+    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
+            TaskStack stack, TaskStackView stackView) {
+        preloadIcon(topTask);
+
+        // Update the header bar if necessary
+        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+        // Update the destination rect
+        mDummyStackView.updateMinMaxScrollForStack(stack);
+        final Task toTask = new Task();
+        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                topTask.id, toTask);
+        ForegroundThread.getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mThumbnailTransitionBitmapCache = transitionBitmap;
+                        mThumbnailTransitionBitmapCacheKey = toTask;
+                    }
+                });
+            }
+        });
+    }
+
+    /**
+     * Creates the activity options for a unknown state->recents transition.
+     */
+    private ActivityOptions getUnknownTransitionActivityOptions() {
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_unknown_enter,
+                R.anim.recents_from_unknown_exit,
+                mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for a home->recents transition.
+     */
+    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
+        if (fromSearchHome) {
+            return ActivityOptions.makeCustomAnimation(mContext,
+                    R.anim.recents_from_search_launcher_enter,
+                    R.anim.recents_from_search_launcher_exit,
+                    mHandler, this);
+        }
+        return ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.recents_from_launcher_enter,
+                R.anim.recents_from_launcher_exit,
+                mHandler, this);
+    }
+
+    /**
+     * Creates the activity options for an app->recents transition.
+     */
+    private ActivityOptions getThumbnailTransitionActivityOptions(
+            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+
+        // Update the destination rect
+        Task toTask = new Task();
+        TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                topTask.id, toTask);
+        RectF toTaskRect = toTransform.rect;
+        Bitmap thumbnail;
+        if (mThumbnailTransitionBitmapCacheKey != null
+                && mThumbnailTransitionBitmapCacheKey.key != null
+                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
+            thumbnail = mThumbnailTransitionBitmapCache;
+            mThumbnailTransitionBitmapCacheKey = null;
+            mThumbnailTransitionBitmapCache = null;
+        } else {
+            preloadIcon(topTask);
+            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+        }
+        if (thumbnail != null) {
+            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+                    thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
+                    (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this);
+        }
+
+        // If both the screenshot and thumbnail fails, then just fall back to the default transition
+        return getUnknownTransitionActivityOptions();
+    }
+
+    /**
+     * Returns the transition rect for the given task id.
+     */
+    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
+            TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
+        // Find the running task in the TaskStack
+        Task task = null;
+        ArrayList<Task> tasks = stack.getTasks();
+        if (runningTaskId != -1) {
+            // Otherwise, try and find the task with the
+            int taskCount = tasks.size();
+            for (int i = taskCount - 1; i >= 0; i--) {
+                Task t = tasks.get(i);
+                if (t.key.id == runningTaskId) {
+                    task = t;
+                    runningTaskOut.copyFrom(t);
+                    break;
+                }
+            }
+        }
+        if (task == null) {
+            // If no task is specified or we can not find the task just use the front most one
+            task = tasks.get(tasks.size() - 1);
+            runningTaskOut.copyFrom(task);
+        }
+
+        // Get the transform for the running task
+        stackView.getScroller().setStackScrollToInitialState();
+        mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+                stackView.getScroller().getStackScroll(), mTmpTransform, null);
+        return mTmpTransform;
+    }
+
+    /**
+     * Draws the header of a task used for the window animation into a bitmap.
+     */
+    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
+        if (toTransform != null && toTask.key != null) {
+            Bitmap thumbnail;
+            synchronized (mHeaderBarLock) {
+                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
+                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
+                        Bitmap.Config.ARGB_8888);
+                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                    thumbnail.eraseColor(0xFFff0000);
+                } else {
+                    Canvas c = new Canvas(thumbnail);
+                    c.scale(toTransform.scale, toTransform.scale);
+                    mHeaderBar.rebindToTask(toTask);
+                    mHeaderBar.draw(c);
+                    c.setBitmap(null);
+                }
+            }
+            return thumbnail.createAshmemBitmap();
+        }
+        return null;
+    }
+
+    /**
+     * Shows the recents activity
+     */
+    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+            boolean isTopTaskHome) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+
+        // Update the header bar if necessary
+        reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+        if (sInstanceLoadPlan == null) {
+            // Create a new load plan if onPreloadRecents() was never triggered
+            sInstanceLoadPlan = loader.createLoadPlan(mContext);
+        }
+
+        if (!sInstanceLoadPlan.hasTasks()) {
+            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+        }
+        TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+        // Prepare the dummy stack for the transition
+        mDummyStackView.updateMinMaxScrollForStack(stack);
+        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+                mDummyStackView.computeStackVisibilityReport();
+        boolean hasRecentTasks = stack.getTaskCount() > 0;
+        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
+
+        if (useThumbnailTransition) {
+            // Try starting with a thumbnail transition
+            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
+                    mDummyStackView);
+            if (opts != null) {
+                startRecentsActivity(topTask, opts, false /* fromHome */,
+                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
+            } else {
+                // Fall through below to the non-thumbnail transition
+                useThumbnailTransition = false;
+            }
+        }
+
+        if (!useThumbnailTransition) {
+            // If there is no thumbnail transition, but is launching from home into recents, then
+            // use a quick home transition and do the animation from home
+            if (!Constants.DebugFlags.App.DisableSearchBar && hasRecentTasks) {
+                String homeActivityPackage = ssp.getHomeActivityPackageName();
+                String searchWidgetPackage = Prefs.getString(mContext,
+                        Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+
+                // Determine whether we are coming from a search owned home activity
+                boolean fromSearchHome = (homeActivityPackage != null) &&
+                        homeActivityPackage.equals(searchWidgetPackage);
+                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
+                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
+                        false /* fromThumbnail */, stackVr);
+            } else {
+                // Otherwise we do the normal fade from an unknown source
+                ActivityOptions opts = getUnknownTransitionActivityOptions();
+                startRecentsActivity(topTask, opts, true /* fromHome */,
+                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
+            }
+        }
+        mLastToggleTime = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Starts the recents activity.
+     */
+    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
+              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+        mStartAnimationTriggered = false;
+
+        // Update the configuration based on the launch options
+        RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+        launchState.launchedFromHome = fromSearchHome || fromHome;
+        launchState.launchedFromSearchHome = fromSearchHome;
+        launchState.launchedFromAppWithThumbnail = fromThumbnail;
+        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+        launchState.launchedWithAltTab = mTriggeredFromAltTab;
+        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
+        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
+        launchState.launchedHasConfigurationChanged = false;
+
+        Intent intent = new Intent();
+        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+        if (opts != null) {
+            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+        } else {
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        }
+        mCanReuseTaskStackViews = true;
+    }
+
+    /**** OnAnimationStartedListener Implementation ****/
+
+    @Override
+    public void onAnimationStarted() {
+        // Notify recents to start the enter animation
+        if (!mStartAnimationTriggered) {
+            mStartAnimationTriggered = true;
+            EventBus.getDefault().post(new EnterRecentsWindowAnimationStartedEvent());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index d3c65d2..31ee8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -33,10 +30,12 @@
 import android.widget.Toast;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.views.RecentsView;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+
 /**
  * A helper for the dialogs that show when task debugging is on.
  */
@@ -80,7 +79,6 @@
     private View mResizeTaskDialogContent;
     private RecentsActivity mRecentsActivity;
     private RecentsView mRecentsView;
-    private SystemServicesProxy mSsp;
     private Rect[] mBounds = {new Rect(), new Rect(), new Rect(), new Rect()};
     private Task[] mTasks = {null, null, null, null};
 
@@ -93,7 +91,6 @@
     public RecentsResizeTaskDialog(FragmentManager mgr, RecentsActivity activity) {
         mFragmentManager = mgr;
         mRecentsActivity = activity;
-        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
     }
 
     /** Shows the resize-task dialog. */
@@ -144,7 +141,8 @@
 
     /** Helper function to place window(s) on the display according to an arrangement request. */
     private void placeTasks(int arrangement) {
-        Rect rect = mSsp.getDisplayRect();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect rect = ssp.getDisplayRect();
         for (int i = 0; i < mBounds.length; ++i) {
             mBounds[i].set(rect);
             if (i != 0) {
@@ -240,7 +238,7 @@
         // current app configuration.
         for (int i = additionalTasks; i >= 0; --i) {
             if (mTasks[i] != null) {
-                mSsp.setTaskResizeable(mTasks[i].key.id);
+                ssp.setTaskResizeable(mTasks[i].key.id);
             }
         }
 
@@ -278,8 +276,9 @@
 
         if (mTasks[0].key.stackId != DOCKED_STACK_ID) {
             int taskId = mTasks[0].key.id;
-            mSsp.setTaskResizeable(taskId);
-            mSsp.dockTask(taskId, createMode);
+            SystemServicesProxy ssp = Recents.getSystemServices();
+            ssp.setTaskResizeable(taskId);
+            ssp.dockTask(taskId, createMode);
             mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID);
         } else {
             Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
new file mode 100644
index 0000000..fb21500
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * An implementation of the system user's Recents interface to be called remotely by secondary
+ * users.
+ */
+public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
+
+    private static final String TAG = "RecentsSystemUser";
+
+    private Context mContext;
+    private RecentsImpl mImpl;
+    private final SparseArray<IRecentsNonSystemUserCallbacks> mNonSystemUserRecents =
+            new SparseArray<>();
+
+    public RecentsSystemUser(Context context, RecentsImpl impl) {
+        mContext = context;
+        mImpl = impl;
+    }
+
+    @Override
+    public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks, int userId) {
+        try {
+            final IRecentsNonSystemUserCallbacks callback =
+                    IRecentsNonSystemUserCallbacks.Stub.asInterface(nonSystemUserCallbacks);
+            nonSystemUserCallbacks.linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    mNonSystemUserRecents.removeAt(mNonSystemUserRecents.indexOfValue(callback));
+                }
+            }, 0);
+            mNonSystemUserRecents.put(userId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register NonSystemUserCallbacks", e);
+        }
+    }
+
+    public IRecentsNonSystemUserCallbacks getNonSystemUserRecentsForUser(int userId) {
+        return mNonSystemUserRecents.get(userId);
+    }
+
+    @Override
+    public void updateRecentsVisibility(boolean visible) {
+        mImpl.onVisibilityChanged(mContext, visible);
+    }
+
+    @Override
+    public void startScreenPinning() {
+        mImpl.onStartScreenPinning(mContext);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
new file mode 100644
index 0000000..39d0d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import com.android.systemui.SystemUIApplication;
+
+/**
+ * A strictly system-user service that is started by the secondary user's Recents (with a limited
+ * lifespan), to get the interface that the secondary user's Recents can call through to the system
+ * user's Recents.
+ */
+public class RecentsSystemUserService extends Service {
+
+    private static final String TAG = "RecentsSystemUserService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        SystemUIApplication app = (SystemUIApplication) getApplication();
+        Recents recents = app.getComponent(Recents.class);
+        if (DEBUG) {
+            Log.d(TAG, "onBind: " + recents);
+        }
+        if (recents != null) {
+            return recents.getSystemUserCallbacks();
+        }
+        return null;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
deleted file mode 100644
index 5eefbc7..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * A proxy for Recents events which happens strictly for non-owner users.
- */
-public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
-    final public static String ACTION_PROXY_SHOW_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_HIDE_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_TOGGLE_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_PRELOAD_RECENTS_TO_USER =
-            "com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER";
-    final public static String ACTION_PROXY_CONFIG_CHANGE_TO_USER =
-            "com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Recents recents = Recents.getInstanceAndStartIfNeeded(context);
-        switch (intent.getAction()) {
-            case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
-                boolean triggeredFromAltTab = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
-                recents.showRecentsInternal(triggeredFromAltTab);
-                break;
-            }
-            case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
-                boolean triggeredFromAltTab = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
-                boolean triggeredFromHome = intent.getBooleanExtra(
-                        Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
-                recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome);
-                break;
-            }
-            case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
-                recents.toggleRecentsInternal();
-                break;
-            case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
-                recents.preloadRecentsInternal();
-                break;
-            case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
-                recents.configurationChanged();
-                break;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index ef543d0..fec0fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -208,7 +208,7 @@
      *
      * Events should not be edited by subscribers.
      */
-    public static class Event {
+    public static class Event implements Cloneable {
         // Indicates that this event's dispatch should be traced and logged to logcat
         boolean trace;
         // Indicates that this event must be posted on the EventBus's looper thread before invocation
@@ -218,6 +218,14 @@
 
         // Only accessible from derived events
         protected Event() {}
+
+        @Override
+        protected Object clone() throws CloneNotSupportedException {
+            Event evt = (Event) super.clone();
+            // When cloning an event, reset the cancelled-dispatch state
+            evt.cancelled = false;
+            return evt;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
index 6769716..5f3e830 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
 
 /**
- * The String LRU cache.
+ * This is sent when the task animation when dismissing Recents starts.
  */
-class StringLruCache extends KeyStoreLruCache<String> {
-    public StringLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
+public class DismissRecentsToHomeAnimationStarted extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
index 6769716..f187178 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationStartedEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
 
 /**
- * The String LRU cache.
+ * This is sent when the window animation into Recents starts.
  */
-class StringLruCache extends KeyStoreLruCache<String> {
-    public StringLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
+public class EnterRecentsWindowAnimationStartedEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
new file mode 100644
index 0000000..bf9b421
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the user taps on the Home button or finishes alt-tabbing to hide the Recents
+ * activity.
+ */
+public class HideRecentsEvent extends EventBus.Event {
+
+    public final boolean triggeredFromAltTab;
+    public final boolean triggeredFromHomeKey;
+
+    public HideRecentsEvent(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        this.triggeredFromAltTab = triggeredFromAltTab;
+        this.triggeredFromHomeKey = triggeredFromHomeKey;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
copy to packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
index 6769716..49655b4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
 
 /**
- * The String LRU cache.
+ * This is sent when the user taps on the Overview button to toggle the Recents activity.
  */
-class StringLruCache extends KeyStoreLruCache<String> {
-    public StringLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
+public class ToggleRecentsEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
new file mode 100644
index 0000000..898d1fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when the visibility of the RecentsActivity for the current user changes.
+ */
+public class RecentsVisibilityChangedEvent extends EventBus.Event {
+
+    public final Context applicationContext;
+    public final SystemServicesProxy systemServicesProxy;
+    public final boolean visible;
+
+    public RecentsVisibilityChangedEvent(Context context, SystemServicesProxy systemServicesProxy,
+            boolean visible) {
+        this.applicationContext = context.getApplicationContext();
+        this.systemServicesProxy = systemServicesProxy;
+        this.visible = visible;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
new file mode 100644
index 0000000..5cb4ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when we want to start screen pinning.
+ */
+public class ScreenPinningRequestEvent extends EventBus.Event {
+
+    public final Context applicationContext;
+    public final SystemServicesProxy systemServicesProxy;
+
+    public ScreenPinningRequestEvent(Context context, SystemServicesProxy systemServicesProxy) {
+        this.applicationContext = context.getApplicationContext();
+        this.systemServicesProxy = systemServicesProxy;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
rename to packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
index 6769716..6e6cd84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/StringLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.model;
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
 
 /**
- * The String LRU cache.
+ * This is sent whenever the user interacts with the activity.
  */
-class StringLruCache extends KeyStoreLruCache<String> {
-    public StringLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
+public class UserInteractionEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
new file mode 100644
index 0000000..8dc2983
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
+ * foreground thread for each process.
+ */
+public final class ForegroundThread extends HandlerThread {
+    private static ForegroundThread sInstance;
+    private static Handler sHandler;
+
+    private ForegroundThread() {
+        super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new ForegroundThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+        }
+    }
+
+    public static ForegroundThread get() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (ForegroundThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 568d2b1..a51e475 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents.misc;
 
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
@@ -49,12 +46,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MutableBoolean;
@@ -63,10 +59,11 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import com.android.internal.app.AssistUtils;
+import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsImpl;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -74,6 +71,9 @@
 import java.util.List;
 import java.util.Random;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
  * a point of injection when testing UI.
@@ -82,12 +82,8 @@
     final static String TAG = "SystemServicesProxy";
 
     final static BitmapFactory.Options sBitmapOptions;
-    final static HandlerThread sBgThread;
 
     static {
-        sBgThread = new HandlerThread("Recents-SystemServicesProxy",
-                android.os.Process.THREAD_PRIORITY_BACKGROUND);
-        sBgThread.start();
         sBitmapOptions = new BitmapFactory.Options();
         sBitmapOptions.inMutable = true;
     }
@@ -100,12 +96,11 @@
     IPackageManager mIpm;
     AssistUtils mAssistUtils;
     WindowManager mWm;
+    UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
     ComponentName mAssistComponent;
 
-    Handler mBgThreadHandler;
-
     Bitmap mDummyIcon;
     int mDummyThumbnailWidth;
     int mDummyThumbnailHeight;
@@ -122,9 +117,9 @@
         mIpm = AppGlobals.getPackageManager();
         mAssistUtils = new AssistUtils(context);
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mUm = UserManager.get(context);
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mBgThreadHandler = new Handler(sBgThread.getLooper());
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -244,8 +239,8 @@
             ComponentName topActivity = topTask.topActivity;
 
             // Check if the front most activity is recents
-            if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
-                    topActivity.getClassName().equals(Recents.sRecentsActivity)) {
+            if (topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
+                    topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY)) {
                 if (isHomeTopMost != null) {
                     isHomeTopMost.value = false;
                 }
@@ -415,7 +410,7 @@
         if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
 
         // Remove the task.
-        mBgThreadHandler.post(new Runnable() {
+        BackgroundThread.getHandler().post(new Runnable() {
             @Override
             public void run() {
                 mAm.removeTask(taskId);
@@ -552,12 +547,27 @@
     }
 
     /**
-     * Returns whether the foreground user is the owner.
+     * Returns whether the provided {@param userId} represents the system user.
      */
-    public boolean isForegroundUserSystem() {
-        if (mAm == null) return false;
+    public boolean isSystemUser(int userId) {
+        return userId == UserHandle.USER_SYSTEM;
+    }
 
-        return mAm.getCurrentUser() == UserHandle.USER_SYSTEM;
+    /**
+     * Returns the current user id.
+     */
+    public int getCurrentUser() {
+        if (mAm == null) return 0;
+
+        return mAm.getCurrentUser();
+    }
+
+    /**
+     * Returns the processes user id.
+     */
+    public int getProcessUser() {
+        if (mUm == null) return 0;
+        return mUm.getUserHandle();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 93c5ee7..2bf2ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -18,13 +18,10 @@
 
 import android.animation.Animator;
 import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.View;
 import android.view.ViewParent;
 
-import java.util.ArrayList;
-
 /* Common code */
 public class Utilities {
 
@@ -45,93 +42,19 @@
     }
 
     /** Scales a rect about its centroid */
-    public static void scaleRectAboutCenter(Rect r, float scale) {
+    public static void scaleRectAboutCenter(RectF r, float scale) {
         if (scale != 1.0f) {
-            int cx = r.centerX();
-            int cy = r.centerY();
+            float cx = r.centerX();
+            float cy = r.centerY();
             r.offset(-cx, -cy);
-            r.left = (int) (r.left * scale + 0.5f);
-            r.top = (int) (r.top * scale + 0.5f);
-            r.right = (int) (r.right * scale + 0.5f);
-            r.bottom = (int) (r.bottom * scale + 0.5f);
+            r.left *= scale;
+            r.top *= scale;
+            r.right *= scale;
+            r.bottom *= scale;
             r.offset(cx, cy);
         }
     }
 
-    /** Maps a coorindate in a descendant view into the parent. */
-    public static float mapCoordInDescendentToSelf(View descendant, View root,
-            float[] coord, boolean includeRootScroll) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != root && v != null) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(root);
-
-        float scale = 1.0f;
-        int count = ancestorChain.size();
-        for (int i = 0; i < count; i++) {
-            View v0 = ancestorChain.get(i);
-            // For TextViews, scroll has a meaning which relates to the text position
-            // which is very strange... ignore the scroll.
-            if (v0 != descendant || includeRootScroll) {
-                pt[0] -= v0.getScrollX();
-                pt[1] -= v0.getScrollY();
-            }
-
-            v0.getMatrix().mapPoints(pt);
-            pt[0] += v0.getLeft();
-            pt[1] += v0.getTop();
-            scale *= v0.getScaleX();
-        }
-
-        coord[0] = pt[0];
-        coord[1] = pt[1];
-        return scale;
-    }
-
-    /** Maps a coordinate in the root to a descendent. */
-    public static float mapCoordInSelfToDescendent(View descendant, View root,
-            float[] coord, Matrix tmpInverseMatrix) {
-        ArrayList<View> ancestorChain = new ArrayList<View>();
-
-        float[] pt = {coord[0], coord[1]};
-
-        View v = descendant;
-        while(v != root) {
-            ancestorChain.add(v);
-            v = (View) v.getParent();
-        }
-        ancestorChain.add(root);
-
-        float scale = 1.0f;
-        int count = ancestorChain.size();
-        tmpInverseMatrix.set(Matrix.IDENTITY_MATRIX);
-        for (int i = count - 1; i >= 0; i--) {
-            View ancestor = ancestorChain.get(i);
-            View next = i > 0 ? ancestorChain.get(i-1) : null;
-
-            pt[0] += ancestor.getScrollX();
-            pt[1] += ancestor.getScrollY();
-
-            if (next != null) {
-                pt[0] -= next.getLeft();
-                pt[1] -= next.getTop();
-                next.getMatrix().invert(tmpInverseMatrix);
-                tmpInverseMatrix.mapPoints(pt);
-                scale *= next.getScaleX();
-            }
-        }
-
-        coord[0] = pt[0];
-        coord[1] = pt[1];
-        return scale;
-    }
-
     /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
     public static float computeContrastBetweenColors(int bg, int fg) {
         float bgR = Color.red(bg) / 255f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
deleted file mode 100644
index 624a8ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.graphics.Bitmap;
-
-/**
- * The Bitmap LRU cache.
- */
-class BitmapLruCache extends KeyStoreLruCache<Bitmap> {
-    public BitmapLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
deleted file mode 100644
index 01a515b..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.graphics.drawable.Drawable;
-
-/**
- * The Drawable LRU cache.
- */
-class DrawableLruCache extends KeyStoreLruCache<Drawable> {
-    public DrawableLruCache(int cacheSize) {
-        super(cacheSize);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
deleted file mode 100644
index 97e0916..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.util.LruCache;
-
-import java.util.HashMap;
-
-/**
- * An LRU cache that internally support querying the keys as well as values.  We use this to keep
- * track of the task metadata to determine when to invalidate the cache when tasks have been
- * updated. Generally, this cache will return the last known cache value for the requested task
- * key.
- */
-public class KeyStoreLruCache<V> {
-    // We keep a set of keys that are associated with the LRU cache, so that we can find out
-    // information about the Task that was previously in the cache.
-    HashMap<Integer, Task.TaskKey> mTaskKeys = new HashMap<Integer, Task.TaskKey>();
-    // The cache implementation, mapping task id -> value
-    LruCache<Integer, V> mCache;
-
-    public KeyStoreLruCache(int cacheSize) {
-        mCache = new LruCache<Integer, V>(cacheSize) {
-
-            @Override
-            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
-                mTaskKeys.remove(taskId);
-            }
-        };
-    }
-
-    /** Gets a specific entry in the cache. */
-    final V get(Task.TaskKey key) {
-        return mCache.get(key.id);
-    }
-
-    /**
-     * Returns the value only if the Task has not updated since the last time it was in the cache.
-     */
-    final V getAndInvalidateIfModified(Task.TaskKey key) {
-        Task.TaskKey lastKey = mTaskKeys.get(key.id);
-        if (lastKey != null && (lastKey.lastActiveTime < key.lastActiveTime)) {
-            // The task has updated (been made active since the last time it was put into the
-            // LRU cache) so invalidate that item in the cache
-            remove(key);
-            return null;
-        }
-        // Either the task does not exist in the cache, or the last active time is the same as
-        // the key specified, so return what is in the cache
-        return mCache.get(key.id);
-    }
-
-    /** Puts an entry in the cache for a specific key. */
-    final void put(Task.TaskKey key, V value) {
-        mCache.put(key.id, value);
-        mTaskKeys.put(key.id, key);
-    }
-
-    /** Removes a cache entry for a specific key. */
-    final void remove(Task.TaskKey key) {
-        mCache.remove(key.id);
-        mTaskKeys.remove(key.id);
-    }
-
-    /** Removes all the entries in the cache. */
-    final void evictAll() {
-        mCache.evictAll();
-        mTaskKeys.clear();
-    }
-
-    /** Returns the size of the cache. */
-    final int size() {
-        return mCache.size();
-    }
-
-    /** Trims the cache to a specific size */
-    final void trimToSize(int cacheSize) {
-        mCache.resize(cacheSize);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 8f9a293..d9057b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -16,17 +16,12 @@
 
 package com.android.systemui.recents.model;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.os.Looper;
 import android.os.UserHandle;
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.util.HashSet;
-import java.util.List;
 
 /**
  * The package monitor listens for changes from PackageManager to update the contents of the
@@ -38,8 +33,9 @@
     public void register(Context context) {
         try {
             // We register for events from all users, but will cross-reference them with
-            // packages for the current user and any profiles they have
-            register(context, Looper.getMainLooper(), UserHandle.ALL, true);
+            // packages for the current user and any profiles they have.  Ensure that events are
+            // handled in a background thread.
+            register(context, BackgroundThread.get().getLooper(), UserHandle.ALL, true);
         } catch (IllegalStateException e) {
             e.printStackTrace();
         }
@@ -57,9 +53,9 @@
 
     @Override
     public void onPackageRemoved(String packageName, int uid) {
-        // Notify callbacks that a package has changed
+        // Notify callbacks on the main thread that a package has changed
         final int eventUserId = getChangingUserId();
-        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
+        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 
     @Override
@@ -70,39 +66,8 @@
 
     @Override
     public void onPackageModified(String packageName) {
-        // Notify callbacks that a package has changed
+        // Notify callbacks on the main thread that a package has changed
         final int eventUserId = getChangingUserId();
-        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-
-    /**
-     * Computes the components that have been removed as a result of a change in the specified
-     * package.
-     */
-    public HashSet<ComponentName> computeComponentsRemoved(List<Task.TaskKey> taskKeys,
-            String packageName, int userId) {
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        HashSet<ComponentName> existingComponents = new HashSet<ComponentName>();
-        HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
-        for (Task.TaskKey t : taskKeys) {
-            // Skip if this doesn't apply to the current user
-            if (t.userId != userId) continue;
-
-            ComponentName cn = t.baseIntent.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (existingComponents.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
-                if (ssp.getActivityInfo(cn, userId) != null) {
-                    existingComponents.add(cn);
-                } else {
-                    removedComponents.add(cn);
-                }
-            }
-        }
-        return removedComponents;
+        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6ef7253..8de8e15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -23,12 +23,12 @@
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.util.Log;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 
 
@@ -41,8 +41,10 @@
  *      options specified, such that we can transition into the Recents activity seamlessly
  */
 public class RecentsTaskLoadPlan {
-    static String TAG = "RecentsTaskLoadPlan";
-    static boolean DEBUG = false;
+    private static String TAG = "RecentsTaskLoadPlan";
+    private static boolean DEBUG = false;
+
+    private static int INVALID_TASK_ID = -1;
 
     /** The set of conditions to load tasks. */
     public static class Options {
@@ -57,25 +59,22 @@
 
     Context mContext;
     RecentsConfiguration mConfig;
-    SystemServicesProxy mSystemServicesProxy;
 
     List<ActivityManager.RecentTaskInfo> mRawTasks;
     TaskStack mStack;
-    HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
-            new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
 
     /** Package level ctor */
-    RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
+    RecentsTaskLoadPlan(Context context, RecentsConfiguration config) {
         mContext = context;
         mConfig = config;
-        mSystemServicesProxy = ssp;
     }
 
     /**
      * An optimization to preload the raw list of tasks.
      */
     public synchronized void preloadRawTasks(boolean isTopTaskHome) {
-        mRawTasks = mSystemServicesProxy.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
                 UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
         Collections.reverse(mRawTasks);
 
@@ -87,12 +86,10 @@
      * have a list of all the recent tasks with their metadata, not including icons or
      * thumbnails which were not cached and have to be loaded.
      */
-    synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
+    public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
         if (DEBUG) Log.d(TAG, "preloadPlan");
 
-        // This activity info cache will be used for both preloadPlan() and executePlan()
-        mActivityInfoCache.clear();
-
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
         ArrayList<Task> stackTasks = new ArrayList<>();
         if (mRawTasks == null) {
@@ -106,30 +103,14 @@
             Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
                     t.userId, t.firstActiveTime, t.lastActiveTime);
 
-            // Get an existing activity info handle if possible
-            Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
-            ActivityInfoHandle infoHandle;
-            boolean hadCachedActivityInfo = false;
-            if (mActivityInfoCache.containsKey(cnKey)) {
-                infoHandle = mActivityInfoCache.get(cnKey);
-                hadCachedActivityInfo = true;
-            } else {
-                infoHandle = new ActivityInfoHandle();
-            }
-
             // Load the label, icon, and color
             String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
-                    mSystemServicesProxy, infoHandle);
+                    ssp);
             String contentDescription = loader.getAndUpdateContentDescription(taskKey,
-                    activityLabel, mSystemServicesProxy, res);
-            Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
-                    mSystemServicesProxy, res, infoHandle, false);
-            int activityColor = loader.getActivityPrimaryColor(t.taskDescription, res);
-
-            // Update the activity info cache
-            if (!hadCachedActivityInfo && infoHandle.info != null) {
-                mActivityInfoCache.put(cnKey, infoHandle);
-            }
+                    activityLabel, ssp, res);
+            Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp,
+                    res, false);
+            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
 
             Bitmap icon = t.taskDescription != null
                     ? t.taskDescription.getInMemoryIcon()
@@ -139,11 +120,11 @@
                     : null;
 
             // Add the task to the stack
-            Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
-                    t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription,
-                    activityIcon, activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled,
-                    icon, iconFilename);
-            task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
+            Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
+                    t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
+                    activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
+                    iconFilename);
+            task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
 
             stackTasks.add(task);
@@ -158,12 +139,13 @@
     /**
      * Called to apply the actual loading based on the specified conditions.
      */
-    synchronized void executePlan(Options opts, RecentsTaskLoader loader,
+    public synchronized void executePlan(Options opts, RecentsTaskLoader loader,
             TaskResourceLoadQueue loadQueue) {
         if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
                 ", # thumbnails: " + opts.numVisibleTaskThumbnails +
                 ", running task id: " + opts.runningTaskId);
 
+        SystemServicesProxy ssp = Recents.getSystemServices();
         Resources res = mContext.getResources();
 
         // Iterate through each of the tasks and load them according to the load conditions.
@@ -174,17 +156,6 @@
             Task task = tasks.get(i);
             Task.TaskKey taskKey = task.key;
 
-            // Get an existing activity info handle if possible
-            Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
-            ActivityInfoHandle infoHandle;
-            boolean hadCachedActivityInfo = false;
-            if (mActivityInfoCache.containsKey(cnKey)) {
-                infoHandle = mActivityInfoCache.get(cnKey);
-                hadCachedActivityInfo = true;
-            } else {
-                infoHandle = new ActivityInfoHandle();
-            }
-
             boolean isRunningTask = (task.key.id == opts.runningTaskId);
             boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
             boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
@@ -197,26 +168,20 @@
             if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
                 if (task.activityIcon == null) {
                     if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
-                    task.activityIcon = loader.getAndUpdateActivityIcon(taskKey,
-                            t.taskDescription, mSystemServicesProxy, res, infoHandle, true);
+                    task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
+                            ssp, res, true);
                 }
             }
             if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
                 if (task.thumbnail == null || isRunningTask) {
                     if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
                     if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) {
-                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                                mSystemServicesProxy, true);
+                        task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true);
                     } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) {
                         loadQueue.addTask(task);
                     }
                 }
             }
-
-            // Update the activity info cache
-            if (!hadCachedActivityInfo && infoHandle.info != null) {
-                mActivityInfoCache.put(cnKey, infoHandle);
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 39bef81..ea97b71 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
@@ -27,20 +28,21 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.util.Log;
+import android.util.LruCache;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
+import java.util.Map;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 
-/** Handle to an ActivityInfo */
-class ActivityInfoHandle {
-    ActivityInfo info;
-}
-
-/** A bitmap load queue */
+/**
+ * A Task load queue
+ */
 class TaskResourceLoadQueue {
     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
 
@@ -78,8 +80,10 @@
     }
 }
 
-/* Task resource loader */
-class TaskResourceLoader implements Runnable {
+/**
+ * Task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
     static String TAG = "TaskResourceLoader";
     static boolean DEBUG = false;
 
@@ -88,10 +92,9 @@
     Handler mLoadThreadHandler;
     Handler mMainThreadHandler;
 
-    SystemServicesProxy mSystemServicesProxy;
     TaskResourceLoadQueue mLoadQueue;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
+    TaskKeyLruCache<Drawable> mApplicationIconCache;
+    TaskKeyLruCache<Bitmap> mThumbnailCache;
     Bitmap mDefaultThumbnail;
     BitmapDrawable mDefaultApplicationIcon;
 
@@ -99,9 +102,9 @@
     boolean mWaitingOnLoadQueue;
 
     /** Constructor, creates a new loading thread that loads task resources in the background */
-    public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
-                              BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
-                              BitmapDrawable defaultApplicationIcon) {
+    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+            TaskKeyLruCache<Drawable> applicationIconCache, TaskKeyLruCache<Bitmap> thumbnailCache,
+            Bitmap defaultThumbnail, BitmapDrawable defaultApplicationIcon) {
         mLoadQueue = loadQueue;
         mApplicationIconCache = applicationIconCache;
         mThumbnailCache = thumbnailCache;
@@ -119,7 +122,6 @@
     void start(Context context) {
         mContext = context;
         mCancelled = false;
-        mSystemServicesProxy = new SystemServicesProxy(context);
         // Notify the load thread to start loading
         synchronized(mLoadThread) {
             mLoadThread.notifyAll();
@@ -130,7 +132,6 @@
     void stop() {
         // Mark as cancelled for the thread to pick up
         mCancelled = true;
-        mSystemServicesProxy = null;
         // If we are waiting for the load queue for more tasks, then we can just reset the
         // Context now, since nothing is using it
         if (mWaitingOnLoadQueue) {
@@ -155,7 +156,7 @@
                 }
             } else {
                 RecentsConfiguration config = RecentsConfiguration.getInstance();
-                SystemServicesProxy ssp = mSystemServicesProxy;
+                SystemServicesProxy ssp = Recents.getSystemServices();
                 // If we've stopped the loader, then fall through to the above logic to wait on
                 // the load thread
                 if (ssp != null) {
@@ -172,7 +173,7 @@
 
                             if (cachedIcon == null) {
                                 ActivityInfo info = ssp.getActivityInfo(
-                                        t.key.baseIntent.getComponent(), t.key.userId);
+                                        t.key.getComponent(), t.key.userId);
                                 if (info != null) {
                                     if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
                                     cachedIcon = ssp.getActivityIcon(info, t.key.userId);
@@ -246,201 +247,67 @@
     }
 }
 
-/* Recents task loader
- * NOTE: We should not hold any references to non-application Context from a static instance */
+/**
+ * Recents task loader
+ */
 public class RecentsTaskLoader {
+
     private static final String TAG = "RecentsTaskLoader";
+    private static final boolean DEBUG = false;
 
-    static RecentsTaskLoader sInstance;
-    static int INVALID_TASK_ID = -1;
+    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
+    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
+    // below, this is per-package so we can't invalidate the items in the cache based on the last
+    // active time.  Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
+    // package in the cache has been updated, so that we may remove it.
+    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
+    private final TaskKeyLruCache<Drawable> mApplicationIconCache;
+    private final TaskKeyLruCache<Bitmap> mThumbnailCache;
+    private final TaskKeyLruCache<String> mActivityLabelCache;
+    private final TaskKeyLruCache<String> mContentDescriptionCache;
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final BackgroundTaskLoader mLoader;
 
-    SystemServicesProxy mSystemServicesProxy;
-    DrawableLruCache mApplicationIconCache;
-    BitmapLruCache mThumbnailCache;
-    StringLruCache mActivityLabelCache;
-    StringLruCache mContentDescriptionCache;
-    TaskResourceLoadQueue mLoadQueue;
-    TaskResourceLoader mLoader;
+    private final int mMaxThumbnailCacheSize;
+    private final int mMaxIconCacheSize;
+    private int mNumVisibleTasksLoaded;
+    private int mNumVisibleThumbnailsLoaded;
 
-    int mMaxThumbnailCacheSize;
-    int mMaxIconCacheSize;
-    int mNumVisibleTasksLoaded;
-    int mNumVisibleThumbnailsLoaded;
-
+    int mDefaultTaskBarBackgroundColor;
     BitmapDrawable mDefaultApplicationIcon;
     Bitmap mDefaultThumbnail;
 
-    /** Private Constructor */
-    private RecentsTaskLoader(Context context) {
-        mMaxThumbnailCacheSize = context.getResources().getInteger(
-                R.integer.config_recents_max_thumbnail_count);
-        mMaxIconCacheSize = context.getResources().getInteger(
-                R.integer.config_recents_max_icon_count);
+    public RecentsTaskLoader(Context context) {
+        Resources res = context.getResources();
+        mDefaultTaskBarBackgroundColor =
+                res.getColor(R.color.recents_task_bar_default_background_color);
+        mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
+        mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
         int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxIconCacheSize;
         int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
                 mMaxThumbnailCacheSize;
 
         // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-        icon.eraseColor(0x00000000);
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        icon.eraseColor(0);
         mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
         mDefaultThumbnail.setHasAlpha(false);
         mDefaultThumbnail.eraseColor(0xFFffffff);
         mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
 
         // Initialize the proxy, cache and loaders
-        mSystemServicesProxy = new SystemServicesProxy(context);
+        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
         mLoadQueue = new TaskResourceLoadQueue();
-        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
-        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
-        mActivityLabelCache = new StringLruCache(100);
-        mContentDescriptionCache = new StringLruCache(100);
-        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
+        mApplicationIconCache = new TaskKeyLruCache<>(iconCacheSize);
+        mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
+        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks);
+        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks);
+        mActivityInfoCache = new LruCache(numRecentTasks);
+        mLoader = new BackgroundTaskLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
                 mDefaultThumbnail, mDefaultApplicationIcon);
     }
 
-    /** Initializes the recents task loader */
-    public static RecentsTaskLoader initialize(Context context) {
-        if (sInstance == null) {
-            sInstance = new RecentsTaskLoader(context);
-        }
-        return sInstance;
-    }
-
-    /** Returns the current recents task loader */
-    public static RecentsTaskLoader getInstance() {
-        return sInstance;
-    }
-
-    /** Returns the system services proxy */
-    public SystemServicesProxy getSystemServicesProxy() {
-        return mSystemServicesProxy;
-    }
-
-    /** Returns the activity label using as many cached values as we can. */
-    public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
-            ActivityManager.TaskDescription td, SystemServicesProxy ssp,
-            ActivityInfoHandle infoHandle) {
-        // Return the task description label if it exists
-        if (td != null && td.getLabel() != null) {
-            return td.getLabel();
-        }
-        // Return the cached activity label if it exists
-        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // All short paths failed, load the label from the activity info and cache it
-        if (infoHandle.info == null) {
-            infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
-                    taskKey.userId);
-        }
-        if (infoHandle.info != null) {
-            label = ssp.getActivityLabel(infoHandle.info);
-            mActivityLabelCache.put(taskKey, label);
-            return label;
-        } else {
-            Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent()
-                    + " u=" + taskKey.userId);
-        }
-        // If the activity info does not exist or fails to load, return an empty label for now,
-        // but do not cache it
-        return "";
-    }
-
-    /** Returns the content description using as many cached values as we can. */
-    public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
-            SystemServicesProxy ssp, Resources res) {
-        // Return the cached content description if it exists
-        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // If the given activity label is empty, don't compute or cache the content description
-        if (activityLabel.isEmpty()) {
-            return "";
-        }
-
-        label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
-        if (label != null) {
-            mContentDescriptionCache.put(taskKey, label);
-            return label;
-        } else {
-            Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent()
-                    + " u=" + taskKey.userId);
-        }
-        // If the content description does not exist, return an empty label for now, but do not
-        // cache it
-        return "";
-    }
-
-    /** Returns the activity icon using as many cached values as we can. */
-    public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
-            ActivityManager.TaskDescription td, SystemServicesProxy ssp,
-            Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
-        // Return the cached activity icon if it exists
-        Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
-        if (icon != null) {
-            return icon;
-        }
-
-        if (loadIfNotCached) {
-            // Return and cache the task description icon if it exists
-            Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
-                    td.getIconFilename(), ssp, res);
-            if (tdDrawable != null) {
-                mApplicationIconCache.put(taskKey, tdDrawable);
-                return tdDrawable;
-            }
-
-            // Load the icon from the activity info and cache it
-            if (infoHandle.info == null) {
-                infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
-                        taskKey.userId);
-            }
-            if (infoHandle.info != null) {
-                icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
-                if (icon != null) {
-                    mApplicationIconCache.put(taskKey, icon);
-                    return icon;
-                }
-            }
-        }
-        // We couldn't load any icon
-        return null;
-    }
-
-    /** Returns the bitmap using as many cached values as we can. */
-    public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
-            boolean loadIfNotCached) {
-        // Return the cached thumbnail if it exists
-        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (thumbnail != null) {
-            return thumbnail;
-        }
-
-        RecentsConfiguration config = RecentsConfiguration.getInstance();
-        if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING && loadIfNotCached) {
-            // Load the thumbnail from the system
-            thumbnail = ssp.getTaskThumbnail(taskKey.id);
-            if (thumbnail != null) {
-                mThumbnailCache.put(taskKey, thumbnail);
-                return thumbnail;
-            }
-        }
-        // We couldn't load any thumbnail
-        return null;
-    }
-
-    /** Returns the activity's primary color. */
-    public int getActivityPrimaryColor(ActivityManager.TaskDescription td, Resources res) {
-        if (td != null && td.getPrimaryColor() != 0) {
-            return td.getPrimaryColor();
-        }
-        return res.getColor(R.color.recents_task_bar_default_background_color);
-    }
-
     /** Returns the size of the app icon cache. */
     public int getApplicationIconCacheSize() {
         return mMaxIconCacheSize;
@@ -454,7 +321,7 @@
     /** Creates a new plan for loading the recent tasks. */
     public RecentsTaskLoadPlan createLoadPlan(Context context) {
         RecentsConfiguration config = RecentsConfiguration.getInstance();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config);
         return plan;
     }
 
@@ -505,17 +372,12 @@
         mLoadQueue.removeTask(t);
         mThumbnailCache.remove(t.key);
         mApplicationIconCache.remove(t.key);
+        mActivityInfoCache.remove(t.key.getComponent());
         if (notifyTaskDataUnloaded) {
             t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon);
         }
     }
 
-    /** Stops the task loader and clears all pending tasks */
-    void stopLoader() {
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
     /**
      * Handles signals from the system, trimming memory when requested to prevent us from running
      * out of memory.
@@ -542,18 +404,23 @@
                 // We are leaving recents, so trim the data a bit
                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 2));
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                 // We are going to be low on memory
                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 4));
                 break;
             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                 // We are low on memory, so release everything
                 mThumbnailCache.evictAll();
                 mApplicationIconCache.evictAll();
+                mActivityInfoCache.evictAll();
                 // The cache is small, only clear the label cache when we are critical
                 mActivityLabelCache.evictAll();
                 mContentDescriptionCache.evictAll();
@@ -562,4 +429,165 @@
                 break;
         }
     }
+
+    /**
+     * Returns the cached task label if the task key is not expired, updating the cache if it is.
+     */
+    String getAndUpdateActivityLabel(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+                                     SystemServicesProxy ssp) {
+        // Return the task description label if it exists
+        if (td != null && td.getLabel() != null) {
+            return td.getLabel();
+        }
+        // Return the cached activity label if it exists
+        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+        if (activityInfo != null) {
+            label = ssp.getActivityLabel(activityInfo);
+            mActivityLabelCache.put(taskKey, label);
+            return label;
+        }
+        // If the activity info does not exist or fails to load, return an empty label for now,
+        // but do not cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task content description if the task key is not expired, updating the
+     * cache if it is.
+     */
+    String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
+            SystemServicesProxy ssp, Resources res) {
+        // Return the cached content description if it exists
+        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // If the given activity label is empty, don't compute or cache the content description
+        if (activityLabel.isEmpty()) {
+            return "";
+        }
+
+        label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
+        if (label != null) {
+            mContentDescriptionCache.put(taskKey, label);
+            return label;
+        }
+        // If the content description does not exist, return an empty label for now, but do not
+        // cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
+     */
+    Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
+            SystemServicesProxy ssp, Resources res, boolean loadIfNotCached) {
+        // Return the cached activity icon if it exists
+        Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
+        if (icon != null) {
+            return icon;
+        }
+
+        if (loadIfNotCached) {
+            // Return and cache the task description icon if it exists
+            icon = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
+                    td.getIconFilename(), ssp, res);
+            if (icon != null) {
+                mApplicationIconCache.put(taskKey, icon);
+                return icon;
+            }
+
+            // Load the icon from the activity info and cache it
+            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp);
+            if (activityInfo != null) {
+                icon = ssp.getActivityIcon(activityInfo, taskKey.userId);
+                if (icon != null) {
+                    mApplicationIconCache.put(taskKey, icon);
+                    return icon;
+                }
+            }
+        }
+        // We couldn't load any icon
+        return null;
+    }
+
+    /**
+     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
+     */
+    Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
+            boolean loadIfNotCached) {
+        // Return the cached thumbnail if it exists
+        Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+        if (thumbnail != null) {
+            return thumbnail;
+        }
+
+        if (loadIfNotCached) {
+            RecentsConfiguration config = RecentsConfiguration.getInstance();
+            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
+                // Load the thumbnail from the system
+                thumbnail = ssp.getTaskThumbnail(taskKey.id);
+                if (thumbnail != null) {
+                    mThumbnailCache.put(taskKey, thumbnail);
+                    return thumbnail;
+                }
+            }
+        }
+        // We couldn't load any thumbnail
+        return null;
+    }
+
+    /**
+     * Returns the task's primary color.
+     */
+    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getPrimaryColor() != 0) {
+            return td.getPrimaryColor();
+        }
+        return mDefaultTaskBarBackgroundColor;
+    }
+
+    /**
+     * Returns the activity info for the given task key, retrieving one from the system if the
+     * task key is expired.
+     */
+    private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey, SystemServicesProxy ssp) {
+        ComponentName cn = taskKey.getComponent();
+        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
+        if (activityInfo == null) {
+            activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
+            mActivityInfoCache.put(cn, activityInfo);
+        }
+        return activityInfo;
+    }
+
+    /**
+     * Stops the task loader and clears all queued, pending task loads.
+     */
+    private void stopLoader() {
+        mLoader.stop();
+        mLoadQueue.clearTasks();
+    }
+
+    /**** Event Bus Events ****/
+
+    public final void onBusEvent(PackagesChangedEvent event) {
+        // Remove all the cached activity infos for this package.  The other caches do not need to
+        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+        // cached contents are requested
+        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+        for (ComponentName cn : activityInfoCache.keySet()) {
+            if (cn.getPackageName().equals(event.packageName)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Removing activity info from cache: " + cn);
+                }
+                mActivityInfoCache.remove(cn);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index c14adf4..0793180 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -38,38 +38,11 @@
         public void onTaskDataUnloaded();
 
         /* Notifies when a task's stack id has changed. */
-        public void onMultiStackDebugTaskStackIdChanged();
-    }
-
-    /** The ComponentNameKey represents the unique primary key for a component
-     * belonging to a specified user. */
-    public static class ComponentNameKey {
-        final ComponentName component;
-        final int userId;
-
-        public ComponentNameKey(ComponentName cn, int user) {
-            component = cn;
-            userId = user;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(component, userId);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof ComponentNameKey)) {
-                return false;
-            }
-            return component.equals(((ComponentNameKey) o).component) &&
-                    userId == ((ComponentNameKey) o).userId;
-        }
+        public void onTaskStackIdChanged();
     }
 
     /* The Task Key represents the unique primary key for the task */
     public static class TaskKey {
-        final ComponentNameKey mComponentNameKey;
         public final int id;
         public int stackId;
         public final Intent baseIntent;
@@ -79,7 +52,6 @@
 
         public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
                 long lastActiveTime) {
-            mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId);
             this.id = id;
             this.stackId = stackId;
             this.baseIntent = intent;
@@ -88,9 +60,8 @@
             this.lastActiveTime = lastActiveTime;
         }
 
-        /** Returns the component name key for this task. */
-        public ComponentNameKey getComponentNameKey() {
-            return mComponentNameKey;
+        public ComponentName getComponent() {
+            return this.baseIntent.getComponent();
         }
 
         @Override
@@ -113,7 +84,7 @@
                     + "s: " + stackId + ", "
                     + "u: " + userId + ", "
                     + "lat: " + lastActiveTime + ", "
-                    + baseIntent.getComponent().getPackageName();
+                    + getComponent().getPackageName();
         }
     }
 
@@ -134,7 +105,8 @@
     public boolean lockToTaskEnabled;
     public Bitmap icon;
     public String iconFilename;
-    TaskCallbacks mCb;
+
+    private TaskCallbacks mCb;
 
     public Task() {
         // Do nothing
@@ -194,7 +166,7 @@
     public void setStackId(int stackId) {
         key.stackId = stackId;
         if (mCb != null) {
-            mCb.onMultiStackDebugTaskStackIdChanged();
+            mCb.onTaskStackIdChanged();
         }
     }
 
@@ -229,7 +201,7 @@
         if (group != null) {
             groupAffiliation = Integer.toString(group.affiliation);
         }
-        return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
+        return "Task (" + groupAffiliation + "): " + key.getComponent().getPackageName() +
                 " [" + super.toString() + "]";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
new file mode 100644
index 0000000..6d11f14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.util.LruCache;
+import android.util.SparseArray;
+
+/**
+ * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
+ * recently referenced key/values will be evicted as more values than the given cache size are
+ * inserted.
+ *
+ * In addition, this also allows the caller to invalidate cached values for keys that have since
+ * changed.
+ */
+public class TaskKeyLruCache<V> {
+
+    private final SparseArray<Task.TaskKey> mKeys = new SparseArray<>();
+    private final LruCache<Integer, V> mCache;
+
+    public TaskKeyLruCache(int cacheSize) {
+        mCache = new LruCache<Integer, V>(cacheSize) {
+
+            @Override
+            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
+                mKeys.remove(taskId);
+            }
+        };
+    }
+
+    /**
+     * Gets a specific entry in the cache with the specified key, regardless of whether the cached
+     * value is valid or not.
+     */
+    final V get(Task.TaskKey key) {
+        return mCache.get(key.id);
+    }
+
+    /**
+     * Returns the value only if the key is valid (has not been updated since the last time it was
+     * in the cache)
+     */
+    final V getAndInvalidateIfModified(Task.TaskKey key) {
+        Task.TaskKey lastKey = mKeys.get(key.id);
+        if (lastKey != null) {
+            if ((lastKey.stackId != key.stackId) || (lastKey.lastActiveTime < key.lastActiveTime)) {
+                // The task has updated (been made active since the last time it was put into the
+                // LRU cache) or the stack id for the task has changed, invalidate that cache item
+                remove(key);
+                return null;
+            }
+        }
+        // Either the task does not exist in the cache, or the last active time is the same as
+        // the key specified, so return what is in the cache
+        return mCache.get(key.id);
+    }
+
+    /** Puts an entry in the cache for a specific key. */
+    final void put(Task.TaskKey key, V value) {
+        mKeys.put(key.id, key);
+        mCache.put(key.id, value);
+    }
+
+    /** Removes a cache entry for a specific key. */
+    final void remove(Task.TaskKey key) {
+        mKeys.remove(key.id);
+        mCache.remove(key.id);
+    }
+
+    /** Removes all the entries in the cache. */
+    final void evictAll() {
+        mCache.evictAll();
+        mKeys.clear();
+    }
+
+    /** Trims the cache to a specific size */
+    final void trimToSize(int cacheSize) {
+        mCache.resize(cacheSize);
+    }
+}
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 6598112..b3937c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.model;
 
 import android.animation.ObjectAnimator;
-import android.app.ActivityManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -25,13 +25,16 @@
 import android.graphics.drawable.ColorDrawable;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.NamedCounter;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
 
@@ -424,8 +427,8 @@
         boolean filtered = mTaskList.setFilter(new TaskFilter() {
             @Override
             public boolean acceptTask(Task at, int i) {
-                return t.key.baseIntent.getComponent().getPackageName().equals(
-                        at.key.baseIntent.getComponent().getPackageName());
+                return t.key.getComponent().getPackageName().equals(
+                        at.key.getComponent().getPackageName());
             }
         });
         if (filtered && mCb != null) {
@@ -490,7 +493,7 @@
             int groupCountDown = Constants.DebugFlags.App.TaskAffiliationsGroupCount;
             for (int i = 0; i < taskCount; i++) {
                 Task t = tasks.get(i);
-                String packageName = t.key.baseIntent.getComponent().getPackageName();
+                String packageName = t.key.getComponent().getPackageName();
                 packageName = "pkg";
                 TaskGrouping group;
                 if (packageName.equals(prevPackage) && groupCountDown > 0) {
@@ -577,6 +580,37 @@
         }
     }
 
+    /**
+     * Computes the components of tasks in this stack that have been removed as a result of a change
+     * in the specified package.
+     */
+    public HashSet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        HashSet<ComponentName> existingComponents = new HashSet<>();
+        HashSet<ComponentName> removedComponents = new HashSet<>();
+        ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
+        for (Task.TaskKey t : taskKeys) {
+            // Skip if this doesn't apply to the current user
+            if (t.userId != userId) continue;
+
+            ComponentName cn = t.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                if (existingComponents.contains(cn)) {
+                    // If we know that the component still exists in the package, then skip
+                    continue;
+                }
+                if (ssp.getActivityInfo(cn, userId) != null) {
+                    existingComponents.add(cn);
+                } else {
+                    removedComponents.add(cn);
+                }
+            }
+        }
+        return removedComponents;
+    }
+
     @Override
     public String toString() {
         String str = "Tasks:\n";
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 a5fd3eb..7f618e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -44,22 +42,26 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 /**
  * This view is the the top level layout that contains TaskStacks (which are laid out according
  * to their SpaceNode bounds.
@@ -75,8 +77,6 @@
         public void onTaskViewClicked();
         public void onTaskLaunchFailed();
         public void onAllTaskViewsDismissed();
-        public void onExitToHomeAnimationTriggered();
-        public void onScreenPinningRequest();
         public void runAfterPause(Runnable r);
     }
 
@@ -261,7 +261,7 @@
         ctx.postAnimationTrigger.decrement();
 
         // Notify of the exit animation
-        mCb.onExitToHomeAnimationTriggered();
+        EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
     }
 
     /** Adds the search bar */
@@ -404,14 +404,6 @@
         return super.verifyDrawable(who);
     }
 
-    /** Notifies each task view of the user interaction. */
-    public void onUserInteraction() {
-        // Get the first stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onUserInteraction();
-        }
-    }
-
     /** Focuses the next task in the first stack view */
     public void focusNextTask(boolean forward) {
         // Get the first stack view
@@ -602,8 +594,8 @@
 
         final int left = pts[0] + offsetX;
         final int top = pts[1] + offsetY;
-        final Rect rect = new Rect(left, top, left + transform.rect.width(),
-                top + transform.rect.height());
+        final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
+                top + (int) transform.rect.height());
 
         return new AppTransitionAnimationSpec(taskId, b, rect);
     }
@@ -630,15 +622,14 @@
             // and then offset to the expected transform rect, but bound this to just
             // outside the display rect (to ensure we don't animate from too far away)
             sourceView = stackView;
-            offsetX = transform.rect.left;
+            offsetX = (int) transform.rect.left;
             offsetY = getMeasuredHeight();
         } else {
             sourceView = tv.mThumbnailView;
         }
 
         // Compute the thumbnail to scale up from
-        final SystemServicesProxy ssp =
-                RecentsTaskLoader.getInstance().getSystemServicesProxy();
+        final SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityOptions opts = null;
         ActivityOptions.OnAnimationStartedListener animStartedListener = null;
         if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
@@ -652,7 +643,8 @@
                             postDelayed(new Runnable() {
                                 @Override
                                 public void run() {
-                                    mCb.onScreenPinningRequest();
+                                    EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                            getContext(), ssp));
                                 }
                             }, 350);
                             mTriggered = true;
@@ -664,7 +656,7 @@
                     animStartedListener, destinationStack);
             opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
                     Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
-                    offsetX, offsetY, transform.rect.width(), transform.rect.height(),
+                    offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
                     sourceView.getHandler(), animStartedListener);
         } else {
             opts = ActivityOptions.makeBasic();
@@ -684,7 +676,8 @@
                     if (ssp.startActivityFromRecents(getContext(), task.key.id,
                             task.activityLabel, launchOpts)) {
                         if (screenPinningRequested) {
-                            mCb.onScreenPinningRequest();
+                            EventBus.getDefault().send(new ScreenPinningRequestEvent(
+                                    getContext(), ssp));
                         }
                     } else {
                         // Dismiss the task and return the user to home if we fail to
@@ -742,14 +735,6 @@
         MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
     }
 
-    /** Final callback after Recents is finally hidden. */
-    public void onRecentsHidden() {
-        // Notify each task stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onRecentsHidden();
-        }
-    }
-
     @Override
     public void onTaskStackFilterTriggered() {
         // Hide the search bar
@@ -814,7 +799,7 @@
 
                 // Dock the new task if we are hovering over a valid dock state
                 if (event.dockState != TaskStack.DockState.NONE) {
-                    SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                    SystemServicesProxy ssp = Recents.getSystemServices();
                     ssp.setTaskResizeable(event.task.key.id);
                     ssp.dockTask(event.task.key.id, event.dockState.createMode);
                     launchTask(event.task, null, INVALID_STACK_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 76399f4..cf4c9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -34,14 +34,14 @@
  */
 class DockRegion {
     public static TaskStack.DockState[] PHONE_LANDSCAPE = {
-            TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT
+            TaskStack.DockState.LEFT
     };
     public static TaskStack.DockState[] PHONE_PORTRAIT = {
             // We only allow docking to the top for now
             TaskStack.DockState.TOP
     };
     public static TaskStack.DockState[] TABLET_LANDSCAPE = {
-            TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT
+            TaskStack.DockState.LEFT
     };
     public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
 }
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 b28cc21..5fbea00 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,8 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -74,10 +76,12 @@
                 View.VISIBLE : View.INVISIBLE);
     }
 
+    /**** EventBus events ****/
+
     /**
      * Starts animating the scrim views when entering Recents.
      */
-    public void startEnterRecentsAnimation() {
+    public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) {
         RecentsActivityLaunchState launchState = mConfig.getLaunchState();
         int transitionEnterFromAppDelay = mContext.getResources().getInteger(
                 R.integer.recents_enter_from_app_transition_duration);
@@ -124,7 +128,7 @@
      * Starts animating the scrim views when leaving Recents (either via launching a task, or
      * going home).
      */
-    public void startExitRecentsAnimation() {
+    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         int taskViewExitToAppDuration = mContext.getResources().getInteger(
                 R.integer.recents_task_exit_to_app_duration);
         if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
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 f637407..5928854 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,15 +16,12 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
 import android.animation.ValueAnimator;
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -33,16 +30,18 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -53,6 +52,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
 
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
@@ -88,11 +89,11 @@
     boolean mStartEnterAnimationRequestedAfterLayout;
     boolean mStartEnterAnimationCompleted;
     ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
+
     Rect mTaskStackBounds = new Rect();
     int[] mTmpVisibleRange = new int[2];
-    float[] mTmpCoord = new float[2];
-    Matrix mTmpMatrix = new Matrix();
     Rect mTmpRect = new Rect();
+    RectF mTmpTaskRect = new RectF();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -331,8 +332,7 @@
     /** Synchronizes the views with the model */
     boolean synchronizeStackViewsWithModel() {
         if (mStackViewsDirty) {
-            RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-            SystemServicesProxy ssp = loader.getSystemServicesProxy();
+            SystemServicesProxy ssp = Recents.getSystemServices();
 
             // Get all the task transforms
             ArrayList<Task> tasks = mStack.getTasks();
@@ -412,23 +412,24 @@
         return false;
     }
 
-    /** Updates the clip for each of the task views. */
+    /**
+     * Updates the clip for each of the task views from back to front.
+     */
     void clipTaskViews() {
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
+        TaskView tmpTv = null;
         int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount - 1; i++) {
+        for (int i = 0; i < taskViewCount; i++) {
             TaskView tv = taskViews.get(i);
-            TaskView nextTv = null;
-            TaskView tmpTv = null;
+            TaskView frontTv = null;
             int clipBottom = 0;
-            if (tv.shouldClipViewInStack()) {
+            if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
                 // Find the next view to clip against
-                int nextIndex = i;
-                while (nextIndex < (taskViewCount - 1)) {
-                    tmpTv = taskViews.get(++nextIndex);
-                    if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
-                        nextTv = tmpTv;
+                for (int j = i + 1; j < taskViewCount; j++) {
+                    tmpTv = taskViews.get(j);
+                    if (tmpTv.shouldClipViewInStack()) {
+                        frontTv = tmpTv;
                         break;
                     }
                 }
@@ -436,23 +437,23 @@
                 // Clip against the next view, this is just an approximation since we are
                 // stacked and we can make assumptions about the visibility of the this
                 // task relative to the ones in front of it.
-                if (nextTv != null) {
-                    // Map the top edge of next task view into the local space of the current
-                    // task view to find the clip amount in local space
-                    mTmpCoord[0] = mTmpCoord[1] = 0;
-                    Utilities.mapCoordInDescendentToSelf(nextTv, this, mTmpCoord, false);
-                    Utilities.mapCoordInSelfToDescendent(tv, this, mTmpCoord, mTmpMatrix);
-                    clipBottom = (int) Math.floor(tv.getMeasuredHeight() - mTmpCoord[1]
-                            - nextTv.getPaddingTop() - 1);
+                if (frontTv != null) {
+                    mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+                    mTmpTaskRect.offset(0, tv.getTranslationY());
+                    Utilities.scaleRectAboutCenter(mTmpTaskRect, tv.getScaleX());
+                    float taskBottom = mTmpTaskRect.bottom;
+                    mTmpTaskRect.set(mLayoutAlgorithm.mTaskRect);
+                    mTmpTaskRect.offset(0, frontTv.getTranslationY());
+                    Utilities.scaleRectAboutCenter(mTmpTaskRect, frontTv.getScaleX());
+                    float frontTaskTop = mTmpTaskRect.top;
+                    if (frontTaskTop < taskBottom) {
+                        // Map the stack view space coordinate (the rects) to view space
+                        clipBottom = (int) ((taskBottom - frontTaskTop) / tv.getScaleX()) - 1;
+                    }
                 }
             }
             tv.getViewBounds().setClipBottom(clipBottom);
         }
-        if (taskViewCount > 0) {
-            // The front most task should never be clipped
-            TaskView tv = taskViews.get(taskViewCount - 1);
-            tv.getViewBounds().setClipBottom(0);
-        }
         mStackViewsClipDirty = false;
     }
 
@@ -877,8 +878,7 @@
                     // Poke the dozer to restart the trigger after the animation completes
                     mUIDozeTrigger.poke();
 
-                    RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-                    SystemServicesProxy ssp = loader.getSystemServicesProxy();
+                    SystemServicesProxy ssp = Recents.getSystemServices();
                     List<TaskView> taskViews = getTaskViews();
                     int taskViewCount = taskViews.size();
                     if (taskViewCount > 0) {
@@ -954,21 +954,10 @@
         }
     }
 
-    /** Final callback after Recents is finally hidden. */
-    void onRecentsHidden() {
-        reset();
-    }
-
     public boolean isTransformedTouchPointInView(float x, float y, View child) {
         return isTransformedTouchPointInView(x, y, child, null);
     }
 
-    /** Pokes the dozer on user interaction. */
-    void onUserInteraction() {
-        // Poke the doze trigger if it is dozing
-        mUIDozeTrigger.poke();
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         mLayersDisabled = false;
@@ -1149,7 +1138,7 @@
         }
 
         // Report that this tasks's data is no longer being used
-        RecentsTaskLoader.getInstance().unloadTaskData(task);
+        Recents.getTaskLoader().unloadTaskData(task);
 
         // Detach the view from the hierarchy
         detachViewFromParent(tv);
@@ -1173,7 +1162,7 @@
         tv.onTaskBound(task);
 
         // Load the task data
-        RecentsTaskLoader.getInstance().loadTaskData(task);
+        Recents.getTaskLoader().loadTaskData(task);
 
         // If the doze trigger has already fired, then update the state for this task view
         tv.setNoUserInteractionState();
@@ -1262,14 +1251,14 @@
 
     public final void onBusEvent(PackagesChangedEvent event) {
         // Compute which components need to be removed
-        HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved(
-                mStack.getTaskKeys(), event.packageName, event.userId);
+        HashSet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
+                event.packageName, event.userId);
 
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task t = tasks.get(i);
-            if (removedComponents.contains(t.key.baseIntent.getComponent())) {
+            if (removedComponents.contains(t.key.getComponent())) {
                 TaskView tv = getChildViewForTask(t);
                 if (tv != null) {
                     // For visible children, defer removing the task until after the animation
@@ -1316,4 +1305,15 @@
             }
         }
     }
+
+    public final void onBusEvent(UserInteractionEvent event) {
+        // Poke the doze trigger on user interaction
+        mUIDozeTrigger.poke();
+    }
+
+    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+        if (!event.visible) {
+            reset();
+        }
+    }
 }
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 4322f1a..1274318 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -26,8 +26,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 
 import java.util.List;
@@ -395,8 +395,7 @@
 
         // The user intentionally tapped on the background, which is like a tap on the "desktop".
         // Hide recents and transition to the launcher.
-        Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext());
-        recents.hideRecents(false /* altTab */, true /* homeKey */);
+        EventBus.getDefault().send(new HideRecentsEvent(false, true));
     }
 
     /** Handles generic motion events */
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 d227d55..bab4da7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -47,9 +47,7 @@
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
@@ -761,7 +759,7 @@
     }
 
     @Override
-    public void onMultiStackDebugTaskStackIdChanged() {
+    public void onTaskStackIdChanged() {
         mHeaderView.rebindToTask(mTask);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 949d515..e1e07ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -48,13 +48,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 
 
@@ -62,8 +61,6 @@
 public class TaskViewHeader extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
 
-    RecentsConfiguration mConfig;
-    private SystemServicesProxy mSsp;
     Task mTask;
 
     // Header views
@@ -111,8 +108,6 @@
 
     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mConfig = RecentsConfiguration.getInstance();
-        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
         setWillNotDraw(false);
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
@@ -266,8 +261,9 @@
 
     /** Updates the resize task bar button. */
     void updateResizeTaskBarIcon(Task t) {
-        Rect display = mSsp.getWindowRect();
-        Rect taskRect = mSsp.getTaskBounds(t.key.id);
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        Rect display = ssp.getWindowRect();
+        Rect taskRect = ssp.getTaskBounds(t.key.id);
         int resId = R.drawable.star;
         if (display.equals(taskRect) || taskRect.isEmpty()) {
             resId = R.drawable.vector_drawable_place_fullscreen;
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 f8f7052..ec50eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.views;
 
 import android.animation.ValueAnimator;
-import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.view.animation.Interpolator;
@@ -35,7 +35,7 @@
 
     // This is a window-space rect that is purely used for coordinating the animation of an app
     // window into Recents.
-    public Rect rect = new Rect();
+    public RectF rect = new RectF();
 
     public TaskViewTransform() {
         // Do nothing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index f8ddc73..96b919e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -246,7 +246,7 @@
         }
     }
 
-    protected QSTile<?> createTile(String tileSpec) {
+    public QSTile<?> createTile(String tileSpec) {
         if (tileSpec.equals("wifi")) return new WifiTile(this, false);
         else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
         else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
@@ -272,6 +272,7 @@
         else if (tileSpec.equals("qlock")) return new QLockTile(this);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
+        else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index d701b3c..116237d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -30,7 +30,11 @@
     private final Context mContext;
 
     public SystemUIDialog(Context context) {
-        super(context, R.style.Theme_SystemUI_Dialog);
+        this(context, R.style.Theme_SystemUI_Dialog);
+    }
+
+    public SystemUIDialog(Context context, int theme) {
+        super(context, theme);
         mContext = context;
 
         getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 3ac2a94..4ce0933 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -211,7 +211,7 @@
         }
 
         @Override
-        protected QSTile<?> createTile(String tileSpec) {
+        public QSTile<?> createTile(String tileSpec) {
             return new DraggableTile(this, tileSpec);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f156607..a03e7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -141,6 +141,7 @@
     public void onConfigurationChanged() {
         mEndNowButton.setText(mContext.getString(R.string.volume_zen_end_now));
         mSpTexts.update();
+        Util.setText(mEndNowButton, mContext.getString(R.string.volume_zen_end_now));
     }
 
 }
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index d6d17cb..fdc2543 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -22,7 +22,9 @@
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 
 LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, src) \
     $(call all-java-files-under, ../src) \
     $(call all-proto-files-under, ../src) \
     src/com/android/systemui/EventLogTags.logtags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 120000
index 0000000..0ea3e91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 120000
index 0000000..b1a0963
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
\ No newline at end of file
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 3759c91..be7071e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -64,6 +64,10 @@
     case RS_TYPE_FLOAT_32:                                                              \
         len = _env->GetArrayLength((jfloatArray)data);                                  \
         ptr = _env->GetFloatArrayElements((jfloatArray)data, flag);                     \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 4;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -89,6 +93,10 @@
     case RS_TYPE_FLOAT_64:                                                              \
         len = _env->GetArrayLength((jdoubleArray)data);                                 \
         ptr = _env->GetDoubleArrayElements((jdoubleArray)data, flag);                   \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 8;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -115,6 +123,10 @@
     case RS_TYPE_UNSIGNED_8:                                                            \
         len = _env->GetArrayLength((jbyteArray)data);                                   \
         ptr = _env->GetByteArrayElements((jbyteArray)data, flag);                       \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 1;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -141,6 +153,10 @@
     case RS_TYPE_UNSIGNED_16:                                                           \
         len = _env->GetArrayLength((jshortArray)data);                                  \
         ptr = _env->GetShortArrayElements((jshortArray)data, flag);                     \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 2;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -167,6 +183,10 @@
     case RS_TYPE_UNSIGNED_32:                                                           \
         len = _env->GetArrayLength((jintArray)data);                                    \
         ptr = _env->GetIntArrayElements((jintArray)data, flag);                         \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 4;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -193,6 +213,10 @@
     case RS_TYPE_UNSIGNED_64:                                                           \
         len = _env->GetArrayLength((jlongArray)data);                                   \
         ptr = _env->GetLongArrayElements((jlongArray)data, flag);                       \
+        if (ptr == nullptr) {                                                           \
+            ALOGE("Failed to get Java array elements.");                                \
+            return;                                                                     \
+        }                                                                               \
         typeBytes = 8;                                                                  \
         if (usePadding) {                                                               \
             srcPtr = ptr;                                                               \
@@ -332,16 +356,40 @@
 
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+  if (jFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: fieldIDs.");
+      return ret;
+  }
+
   jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
   jsize values_length = _env->GetArrayLength(valueArray);
+  if (jValues == nullptr) {
+      ALOGE("Failed to get Java array elements: values.");
+      return ret;
+  }
+
   jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
   jsize sizes_length = _env->GetArrayLength(sizeArray);
+  if (jSizes == nullptr) {
+      ALOGE("Failed to get Java array elements: sizes.");
+      return ret;
+  }
+
   jlong* jDepClosures =
       _env->GetLongArrayElements(depClosureArray, nullptr);
   jsize depClosures_length = _env->GetArrayLength(depClosureArray);
+  if (jDepClosures == nullptr) {
+      ALOGE("Failed to get Java array elements: depClosures.");
+      return ret;
+  }
+
   jlong* jDepFieldIDs =
       _env->GetLongArrayElements(depFieldIDArray, nullptr);
   jsize depFieldIDs_length = _env->GetArrayLength(depFieldIDArray);
+  if (jDepFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: depFieldIDs.");
+      return ret;
+  }
 
   size_t numValues, numDependencies;
   RsScriptFieldID* fieldIDs;
@@ -435,12 +483,31 @@
 
   jbyte* jParams = _env->GetByteArrayElements(paramArray, nullptr);
   jsize jParamLength = _env->GetArrayLength(paramArray);
+  if (jParams == nullptr) {
+      ALOGE("Failed to get Java array elements: params.");
+      return ret;
+  }
+
   jlong* jFieldIDs = _env->GetLongArrayElements(fieldIDArray, nullptr);
   jsize fieldIDs_length = _env->GetArrayLength(fieldIDArray);
+  if (jFieldIDs == nullptr) {
+      ALOGE("Failed to get Java array elements: fieldIDs.");
+      return ret;
+  }
+
   jlong* jValues = _env->GetLongArrayElements(valueArray, nullptr);
   jsize values_length = _env->GetArrayLength(valueArray);
+  if (jValues == nullptr) {
+      ALOGE("Failed to get Java array elements: values.");
+      return ret;
+  }
+
   jint* jSizes = _env->GetIntArrayElements(sizeArray, nullptr);
   jsize sizes_length = _env->GetArrayLength(sizeArray);
+  if (jSizes == nullptr) {
+      ALOGE("Failed to get Java array elements: sizes.");
+      return ret;
+  }
 
   size_t numValues;
   RsScriptFieldID* fieldIDs;
@@ -515,6 +582,10 @@
 
   jlong* jClosures = _env->GetLongArrayElements(closureArray, nullptr);
   jsize numClosures = _env->GetArrayLength(closureArray);
+  if (jClosures == nullptr) {
+      ALOGE("Failed to get Java array elements: closures.");
+      return ret;
+  }
 
   RsClosure* closures;
 
@@ -720,6 +791,11 @@
     }
     jint len = _env->GetArrayLength(str);
     jbyte * cptr = (jbyte *) _env->GetPrimitiveArrayCritical(str, 0);
+    if (cptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
+
     rsAssignName((RsContext)con, (void *)obj, (const char *)cptr, len);
     _env->ReleasePrimitiveArrayCritical(str, cptr, JNI_ABORT);
 }
@@ -916,6 +992,10 @@
         ALOGD("nContextGetMessage, con(%p), len(%i)", (RsContext)con, len);
     }
     jint *ptr = _env->GetIntArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
     size_t receiveLen;
     uint32_t subID;
     int id = rsContextGetMessage((RsContext)con,
@@ -936,6 +1016,10 @@
         ALOGD("nContextPeekMessage, con(%p)", (RsContext)con);
     }
     jint *auxDataPtr = _env->GetIntArrayElements(auxData, nullptr);
+    if (auxDataPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
     size_t receiveLen;
     uint32_t subID;
     int id = rsContextPeekMessage((RsContext)con, &receiveLen, sizeof(receiveLen),
@@ -970,6 +1054,10 @@
     if (data) {
         len = _env->GetArrayLength(data);
         ptr = _env->GetIntArrayElements(data, nullptr);
+        if (ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
     }
     if (kLogApi) {
         ALOGD("nContextSendMessage, con(%p), id(%i), len(%i)", (RsContext)con, id, len);
@@ -1004,7 +1092,15 @@
     }
 
     jlong *jIds = _env->GetLongArrayElements(_ids, nullptr);
+    if (jIds == nullptr) {
+        ALOGE("Failed to get Java array elements: ids");
+        return 0;
+    }
     jint *jArraySizes = _env->GetIntArrayElements(_arraySizes, nullptr);
+    if (jArraySizes == nullptr) {
+        ALOGE("Failed to get Java array elements: arraySizes");
+        return 0;
+    }
 
     RsElement *ids = (RsElement*)malloc(fieldCount * sizeof(RsElement));
     uint32_t *arraySizes = (uint32_t *)malloc(fieldCount * sizeof(uint32_t));
@@ -1311,6 +1407,10 @@
               sizeBytes);
     }
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsAllocationElementData((RsContext)con, (RsAllocation)alloc,
                             xoff, yoff, zoff,
                             lod, ptr, sizeBytes, compIdx);
@@ -1449,6 +1549,10 @@
               sizeBytes);
     }
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsAllocationElementRead((RsContext)con, (RsAllocation)alloc,
                             xoff, yoff, zoff,
                             lod, ptr, sizeBytes, compIdx);
@@ -1775,6 +1879,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptSetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
@@ -1787,6 +1895,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptGetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, 0);
 }
@@ -1800,8 +1912,16 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     jint dimsLen = _env->GetArrayLength(dims) * sizeof(int);
     jint *dimsPtr = _env->GetIntArrayElements(dims, nullptr);
+    if (dimsPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptSetVarVE((RsContext)con, (RsScript)script, slot, ptr, len, (RsElement)elem,
                      (const uint32_t*) dimsPtr, dimsLen);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
@@ -1819,6 +1939,10 @@
     jint length = _env->GetArrayLength(timeZone);
     jbyte* timeZone_ptr;
     timeZone_ptr = (jbyte *) _env->GetPrimitiveArrayCritical(timeZone, (jboolean *)0);
+    if (timeZone_ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
 
     rsScriptSetTimeZone((RsContext)con, (RsScript)script, (const char *)timeZone_ptr, length);
 
@@ -1844,6 +1968,10 @@
     }
     jint len = _env->GetArrayLength(data);
     jbyte *ptr = _env->GetByteArrayElements(data, nullptr);
+    if (ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return;
+    }
     rsScriptInvokeV((RsContext)con, (RsScript)script, slot, ptr, len);
     _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
 }
@@ -1870,8 +1998,12 @@
             return;
         }
 
-        // TODO (b/20760800): Check in_ptr is not null
         in_ptr = _env->GetLongArrayElements(ains, nullptr);
+        if (in_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
+
         if (sizeof(RsAllocation) == sizeof(jlong)) {
             in_allocs = (RsAllocation*)in_ptr;
 
@@ -1897,6 +2029,10 @@
     if (params != nullptr) {
         param_len = _env->GetArrayLength(params);
         param_ptr = _env->GetByteArrayElements(params, nullptr);
+        if (param_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
     }
 
     RsScriptCall sc, *sca = nullptr;
@@ -1908,6 +2044,10 @@
     if (limits != nullptr) {
         limit_len = _env->GetArrayLength(limits);
         limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
 
         assert(limit_len == 6);
         UNUSED(limit_len);  // As the assert might not be compiled.
@@ -1966,6 +2106,10 @@
     if (limits != nullptr) {
         limit_len = _env->GetArrayLength(limits);
         limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            ALOGE("Failed to get Java array elements");
+            return;
+        }
 
         // We expect to be passed an array [x1, x2] which specifies
         // the sub-range for a 1-dimensional reduction.
@@ -2037,6 +2181,10 @@
     }
     script_ptr = (jbyte *)
         _env->GetPrimitiveArrayCritical(scriptRef, (jboolean *)0);
+    if (script_ptr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return ret;
+    }
 
     //rsScriptCSetText((RsContext)con, (const char *)script_ptr, length);
 
@@ -2104,6 +2252,10 @@
 
     jint kernelsLen = _env->GetArrayLength(_kernels);
     jlong *jKernelsPtr = _env->GetLongArrayElements(_kernels, nullptr);
+    if (jKernelsPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: kernels");
+        return 0;
+    }
     RsScriptKernelID* kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
     for(int i = 0; i < kernelsLen; ++i) {
         kernelsPtr[i] = (RsScriptKernelID)jKernelsPtr[i];
@@ -2111,6 +2263,10 @@
 
     jint srcLen = _env->GetArrayLength(_src);
     jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
+    if (jSrcPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: src");
+        return 0;
+    }
     RsScriptKernelID* srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
     for(int i = 0; i < srcLen; ++i) {
         srcPtr[i] = (RsScriptKernelID)jSrcPtr[i];
@@ -2118,6 +2274,10 @@
 
     jint dstkLen = _env->GetArrayLength(_dstk);
     jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
+    if (jDstkPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: dstk");
+        return 0;
+    }
     RsScriptKernelID* dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
     for(int i = 0; i < dstkLen; ++i) {
         dstkPtr[i] = (RsScriptKernelID)jDstkPtr[i];
@@ -2125,6 +2285,10 @@
 
     jint dstfLen = _env->GetArrayLength(_dstf);
     jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
+    if (jDstfPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: dstf");
+        return 0;
+    }
     RsScriptKernelID* dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
     for(int i = 0; i < dstfLen; ++i) {
         dstfPtr[i] = (RsScriptKernelID)jDstfPtr[i];
@@ -2132,6 +2296,10 @@
 
     jint typesLen = _env->GetArrayLength(_types);
     jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
+    if (jTypesPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: types");
+        return 0;
+    }
     RsType* typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
     for(int i = 0; i < typesLen; ++i) {
         typesPtr[i] = (RsType)jTypesPtr[i];
@@ -2244,6 +2412,10 @@
     AutoJavaStringToUTF8 shaderUTF(_env, shader);
     jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
     jint paramLen = _env->GetArrayLength(params);
+    if (jParamPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
 
     int texCount = _env->GetArrayLength(texNames);
     AutoJavaStringArrayToUTF8 names(_env, texNames, texCount);
@@ -2277,6 +2449,10 @@
     AutoJavaStringToUTF8 shaderUTF(_env, shader);
     jlong *jParamPtr = _env->GetLongArrayElements(params, nullptr);
     jint paramLen = _env->GetArrayLength(params);
+    if (jParamPtr == nullptr) {
+        ALOGE("Failed to get Java array elements");
+        return 0;
+    }
 
     if (kLogApi) {
         ALOGD("nProgramVertexCreate, con(%p), paramLen(%i)", (RsContext)con, paramLen);
@@ -2392,6 +2568,10 @@
 
     jint vtxLen = _env->GetArrayLength(_vtx);
     jlong *jVtxPtr = _env->GetLongArrayElements(_vtx, nullptr);
+    if (jVtxPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: vtx");
+        return 0;
+    }
     RsAllocation* vtxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * vtxLen);
     for(int i = 0; i < vtxLen; ++i) {
         vtxPtr[i] = (RsAllocation)(uintptr_t)jVtxPtr[i];
@@ -2399,6 +2579,10 @@
 
     jint idxLen = _env->GetArrayLength(_idx);
     jlong *jIdxPtr = _env->GetLongArrayElements(_idx, nullptr);
+    if (jIdxPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: idx");
+        return 0;
+    }
     RsAllocation* idxPtr = (RsAllocation*) malloc(sizeof(RsAllocation) * idxLen);
     for(int i = 0; i < idxLen; ++i) {
         idxPtr[i] = (RsAllocation)(uintptr_t)jIdxPtr[i];
@@ -2406,6 +2590,10 @@
 
     jint primLen = _env->GetArrayLength(_prim);
     jint *primPtr = _env->GetIntArrayElements(_prim, nullptr);
+    if (primPtr == nullptr) {
+        ALOGE("Failed to get Java array elements: prim");
+        return 0;
+    }
 
     jlong id = (jlong)(uintptr_t)rsMeshCreate((RsContext)con,
                                (RsAllocation *)vtxPtr, vtxLen,
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 465118c2..df6b1d6 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -355,7 +355,8 @@
 
         final ServiceMap smap = getServiceMap(r.userId);
         boolean addToStarting = false;
-        if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
+        if (!callerFg && r.app == null
+                && mAm.mUserController.hasStartedUserState(r.userId)) {
             ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
             if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
                 // If this is not coming from a foreground caller, then we may want
@@ -1401,7 +1402,7 @@
 
         // Make sure that the user who owns this service is started.  If not,
         // we don't want to allow it to run.
-        if (mAm.mStartedUsers.get(r.userId) == null) {
+        if (!mAm.mUserController.hasStartedUserState(r.userId)) {
             String msg = "Unable to launch app "
                     + r.appInfo.packageName + "/"
                     + r.appInfo.uid + " for service "
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb1b6b8..c728b39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -79,7 +79,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
-import android.util.SparseIntArray;
 import android.view.Display;
 
 import com.android.internal.R;
@@ -205,7 +204,6 @@
 import android.os.IBinder;
 import android.os.IPermissionController;
 import android.os.IProcessInfoService;
-import android.os.IRemoteCallback;
 import android.os.IUserManager;
 import android.os.Looper;
 import android.os.Message;
@@ -370,17 +368,10 @@
     // How long we wait until we timeout on key dispatching during instrumentation.
     static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
 
-    // Amount of time we wait for observers to handle a user switch before
-    // giving up on them and unfreezing the screen.
-    static final int USER_SWITCH_TIMEOUT = 2*1000;
-
     // This is the amount of time an app needs to be running a foreground service before
     // we will consider it to be doing interaction for usage stats.
     static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
 
-    // Maximum number of users we allow to be running at a time.
-    static final int MAX_RUNNING_USERS = 3;
-
     // How long to wait in getAssistContextExtras for the activity and foreground services
     // to respond with the result.
     static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -505,6 +496,8 @@
      */
     String mDeviceOwnerName;
 
+    final UserController mUserController;
+
     public class PendingAssistExtras extends Binder implements Runnable {
         public final ActivityRecord activity;
         public final Bundle extras;
@@ -707,32 +700,6 @@
     final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     /**
-     * Which users have been started, so are allowed to run code.
-     */
-    final SparseArray<UserState> mStartedUsers = new SparseArray<>();
-
-    /**
-     * LRU list of history of current users.  Most recently current is at the end.
-     */
-    final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
-
-    /**
-     * Constant array of the users that are currently started.
-     */
-    int[] mStartedUserArray = new int[] { 0 };
-
-    /**
-     * Registered observers of the user switching mechanics.
-     */
-    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
-            = new RemoteCallbackList<IUserSwitchObserver>();
-
-    /**
-     * Currently active user switch.
-     */
-    Object mCurUserSwitchCallback;
-
-    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1300,19 +1267,6 @@
 
     final ActivityThread mSystemThread;
 
-    // Holds the current foreground user's id
-    int mCurrentUserId = 0;
-    // Holds the target user's id during a user switch
-    int mTargetUserId = UserHandle.USER_NULL;
-    // If there are multiple profiles for the current user, their ids are here
-    // Currently only the primary user can have managed profiles
-    int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
-
-    /**
-     * Mapping from each known user ID to the profile group ID it is associated with.
-     */
-    SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
-
     private UserManagerService mUserManager;
 
     private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -1442,7 +1396,7 @@
                     boolean isBackground = (UserHandle.getAppId(proc.uid)
                             >= Process.FIRST_APPLICATION_UID
                             && proc.pid != MY_PID);
-                    for (int userId : mCurrentProfileIds) {
+                    for (int userId : mUserController.mCurrentProfileIds) {
                         isBackground &= (proc.userId != userId);
                     }
                     if (isBackground && !showBackground) {
@@ -1829,15 +1783,15 @@
                 break;
             }
             case REPORT_USER_SWITCH_MSG: {
-                dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case CONTINUE_USER_SWITCH_MSG: {
-                continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case USER_SWITCH_TIMEOUT_MSG: {
-                timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
                 break;
             }
             case IMMERSIVE_MODE_LOCK_MSG: {
@@ -1866,7 +1820,7 @@
             }
             case START_PROFILES_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    startProfilesLocked();
+                    mUserController.startProfilesLocked();
                 }
                 break;
             }
@@ -2057,14 +2011,14 @@
                 }
             } break;
             case FOREGROUND_PROFILE_CHANGED_MSG: {
-                dispatchForegroundProfileChanged(msg.arg1);
+                mUserController.dispatchForegroundProfileChanged(msg.arg1);
             } break;
             case REPORT_TIME_TRACKER_MSG: {
                 AppTimeTracker tracker = (AppTimeTracker)msg.obj;
                 tracker.deliverResult(mContext);
             } break;
             case REPORT_USER_SWITCH_COMPLETE_MSG: {
-                dispatchUserSwitchComplete(msg.arg1);
+                mUserController.dispatchUserSwitchComplete(msg.arg1);
             } break;
             case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
                 IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
@@ -2386,10 +2340,7 @@
 
         mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
 
-        // User 0 is the first and only user that runs at boot.
-        mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
-        mUserLru.add(UserHandle.USER_SYSTEM);
-        updateStartedUserArrayLocked();
+        mUserController = new UserController(this);
 
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
             ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -5566,16 +5517,6 @@
                 null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
     }
 
-    private void forceStopUserLocked(int userId, String reason) {
-        forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
-        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                | Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        broadcastIntentLocked(null, null, intent,
-                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-    }
 
     private final boolean killPackageProcessesLocked(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -5739,7 +5680,7 @@
 
     }
 
-    private final boolean forceStopPackageLocked(String packageName, int appId,
+    final boolean forceStopPackageLocked(String packageName, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, boolean uninstalling, int userId, String reason) {
         int i;
@@ -6460,32 +6401,18 @@
                     || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
                     SystemProperties.set("dev.bootcomplete", "1");
                 }
-                for (int i=0; i<mStartedUsers.size(); i++) {
-                    UserState uss = mStartedUsers.valueAt(i);
-                    if (uss.mState == UserState.STATE_BOOTING) {
-                        uss.mState = UserState.STATE_RUNNING;
-                        final int userId = mStartedUsers.keyAt(i);
-                        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-                        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-                        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) {
-                                        synchronized (ActivityManagerService.this) {
-                                            requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                                    true, false);
-                                        }
-                                    }
-                                },
-                                0, null, null,
-                                new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                                AppOpsManager.OP_NONE, null, true, false,
-                                MY_PID, Process.SYSTEM_UID, userId);
-                    }
-                }
+                mUserController.sendBootCompletedLocked(
+                        new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                synchronized (ActivityManagerService.this) {
+                                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),
+                                            true, false);
+                                }
+                            }
+                        });
                 scheduleStartProfilesLocked();
             }
         }
@@ -9492,7 +9419,7 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = unsafeConvertIncomingUser(userId);
+            int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
@@ -10338,7 +10265,9 @@
         int callingPid = Binder.getCallingPid();
         long ident = 0;
         boolean clearedIdentity = false;
-        userId = unsafeConvertIncomingUser(userId);
+        synchronized (this) {
+            userId = unsafeConvertIncomingUserLocked(userId);
+        }
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
             ident = Binder.clearCallingIdentity();
@@ -11005,8 +10934,9 @@
 
     @Override
     public boolean isAssistDataAllowedOnCurrentActivity() {
-        int userId = mCurrentUserId;
+        int userId;
         synchronized (this) {
+            userId = mUserController.mCurrentUserId;
             ActivityRecord activity = getFocusedStack().topActivity();
             if (activity == null) {
                 return false;
@@ -11929,7 +11859,7 @@
 
             // Make sure we have the current profile info, since it is needed for
             // security checks.
-            updateCurrentProfileIdsLocked();
+            mUserController.updateCurrentProfileIdsLocked();
 
             mRecentTasks.clear();
             mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
@@ -12036,18 +11966,20 @@
 
         retrieveSettings();
         loadResourcesOnSystemReady();
-
+        final int currentUserId;
         synchronized (this) {
+            currentUserId = mUserController.mCurrentUserId;
             readGrantedUriPermissionsLocked();
         }
 
         if (goingCallback != null) goingCallback.run();
 
+
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
-                Integer.toString(mCurrentUserId), mCurrentUserId);
+                Integer.toString(currentUserId), currentUserId);
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
-                Integer.toString(mCurrentUserId), mCurrentUserId);
-        mSystemServiceManager.startUser(mCurrentUserId);
+                Integer.toString(currentUserId), currentUserId);
+        mSystemServiceManager.startUser(currentUserId);
 
         synchronized (this) {
             if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -12075,7 +12007,7 @@
 
             // Start up initial activity.
             mBooting = true;
-            startHomeActivityLocked(mCurrentUserId, "systemReady");
+            startHomeActivityLocked(currentUserId, "systemReady");
 
             try {
                 if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
@@ -12096,13 +12028,14 @@
                 Intent intent = new Intent(Intent.ACTION_USER_STARTED);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
                         null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                        null, false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
+                        null, false, false, MY_PID, Process.SYSTEM_UID,
+                        currentUserId);
                 intent = new Intent(Intent.ACTION_USER_STARTING);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
                         null, new IIntentReceiver.Stub() {
                             @Override
@@ -12119,7 +12052,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeTopActivitiesLocked();
-            sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
+            sendUserSwitchBroadcastsLocked(-1, currentUserId);
         }
     }
 
@@ -12318,7 +12251,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mCurrentProfileIds) {
+        for (int userId : mUserController.mCurrentProfileIds) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -13775,38 +13708,7 @@
         if (dumpPackage == null) {
             pw.println();
             needSep = false;
-            pw.println("  mStartedUsers:");
-            for (int i=0; i<mStartedUsers.size(); i++) {
-                UserState uss = mStartedUsers.valueAt(i);
-                pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-                        pw.print(": "); uss.dump("", pw);
-            }
-            pw.print("  mStartedUserArray: [");
-            for (int i=0; i<mStartedUserArray.length; i++) {
-                if (i > 0) pw.print(", ");
-                pw.print(mStartedUserArray[i]);
-            }
-            pw.println("]");
-            pw.print("  mUserLru: [");
-            for (int i=0; i<mUserLru.size(); i++) {
-                if (i > 0) pw.print(", ");
-                pw.print(mUserLru.get(i));
-            }
-            pw.println("]");
-            if (dumpAll) {
-                pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
-            }
-            synchronized (mUserProfileGroupIdsSelfLocked) {
-                if (mUserProfileGroupIdsSelfLocked.size() > 0) {
-                    pw.println("  mUserProfileGroupIds:");
-                    for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
-                        pw.print("    User #");
-                        pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
-                        pw.print(" -> profile #");
-                        pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
-                    }
-                }
-            }
+            mUserController.dump(pw, dumpAll);
         }
         if (mHomeProcess != null && (dumpPackage == null
                 || mHomeProcess.pkgList.containsKey(dumpPackage))) {
@@ -16098,9 +16000,9 @@
                 requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
     }
 
-    int unsafeConvertIncomingUser(int userId) {
+    int unsafeConvertIncomingUserLocked(int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? mCurrentUserId : userId;
+                ? mUserController.mCurrentUserId : userId;
     }
 
     int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
@@ -16116,7 +16018,7 @@
         // the value the caller will receive and someone else changing it.
         // We assume that USER_CURRENT_OR_SELF will use the current user; later
         // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUser(userId);
+        int targetUserId = unsafeConvertIncomingUserLocked(userId);
 
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
             final boolean allow;
@@ -16137,14 +16039,7 @@
             } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                synchronized (mUserProfileGroupIdsSelfLocked) {
-                    int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
-                            UserInfo.NO_PROFILE_GROUP_ID);
-                    int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
-                            UserInfo.NO_PROFILE_GROUP_ID);
-                    allow = callingProfile != UserInfo.NO_PROFILE_GROUP_ID
-                            && callingProfile == targetProfile;
-                }
+                allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -16763,7 +16658,7 @@
         return receivers;
     }
 
-    private final int broadcastIntentLocked(ProcessRecord callerApp,
+    final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, Intent intent, String resolvedType,
             IIntentReceiver resultTo, int resultCode, String resultData,
             Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle options,
@@ -17080,7 +16975,7 @@
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
-            users = mStartedUserArray;
+            users = mUserController.getStartedUserArrayLocked();
         } else {
             // Caller wants broadcast to go to one specific user.
             users = new int[] {userId};
@@ -17667,6 +17562,12 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+    void updateUserConfigurationLocked() {
+        Configuration configuration = new Configuration(mConfiguration);
+        Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
+                mUserController.mCurrentUserId);
+        updateConfigurationLocked(configuration, null, false);
+    }
 
     boolean updateConfigurationLocked(Configuration values,
             ActivityRecord starting, boolean initLocale) {
@@ -17712,7 +17613,8 @@
                 newConfig.seq = mConfigurationSeq;
                 mConfiguration = newConfig;
                 Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
-                mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId);
+                mUsageStatsService.reportConfigurationChange(newConfig,
+                        mUserController.mCurrentUserId);
                 //mUsageStatsService.noteStartConfig(newConfig);
 
                 final Configuration configCopy = new Configuration(mConfiguration);
@@ -19196,7 +19098,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mStartedUsers.get(app.userId);
+            UserState userState = mUserController.getStartedUserState(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -20076,44 +19978,18 @@
      */
     @Override
     public boolean startUserInBackground(final int userId) {
-        return startUser(userId, /* foreground */ false);
+        return mUserController.startUser(userId, /* foreground */ false);
     }
 
     /**
      * Start user, if its not already running, and bring it to foreground.
      */
     boolean startUserInForeground(final int userId, Dialog dlg) {
-        boolean result = startUser(userId, /* foreground */ true);
+        boolean result = mUserController.startUser(userId, /* foreground */ true);
         dlg.dismiss();
         return result;
     }
 
-    /**
-     * Refreshes the list of users related to the current user when either a
-     * user switch happens or when a new related user is started in the
-     * background.
-     */
-    private void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
-        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
-        for (int i = 0; i < currentProfileIds.length; i++) {
-            currentProfileIds[i] = profiles.get(i).id;
-        }
-        mCurrentProfileIds = currentProfileIds;
-
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
-            for (int i = 0; i < users.size(); i++) {
-                UserInfo user = users.get(i);
-                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
-                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
-                }
-            }
-        }
-    }
-
     private Set<Integer> getProfileIdsLocked(int userId) {
         Set<Integer> userIds = new HashSet<Integer>();
         final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
@@ -20139,7 +20015,7 @@
                 return false;
             }
             userName = userInfo.name;
-            mTargetUserId = userId;
+            mUserController.mTargetUserId = userId;
         }
         mUiHandler.removeMessages(START_USER_SWITCH_MSG);
         mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
@@ -20153,186 +20029,6 @@
         d.show();
     }
 
-    private boolean startUser(final int userId, final boolean foreground) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: switchUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-
-        if (DEBUG_MU) Slog.i(TAG_MU, "starting userid:" + userId + " fore:" + foreground);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (this) {
-                final int oldUserId = mCurrentUserId;
-                if (oldUserId == userId) {
-                    return true;
-                }
-
-                mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
-                        "startUser", false);
-
-                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
-                if (userInfo == null) {
-                    Slog.w(TAG, "No user info for user #" + userId);
-                    return false;
-                }
-                if (foreground && userInfo.isManagedProfile()) {
-                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
-                    return false;
-                }
-
-                if (foreground) {
-                    mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
-                            R.anim.screen_user_enter);
-                }
-
-                boolean needStart = false;
-
-                // If the user we are switching to is not currently started, then
-                // we need to start it now.
-                if (mStartedUsers.get(userId) == null) {
-                    mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                }
-
-                final Integer userIdInt = Integer.valueOf(userId);
-                mUserLru.remove(userIdInt);
-                mUserLru.add(userIdInt);
-
-                if (foreground) {
-                    mCurrentUserId = userId;
-                    updateUserConfigurationLocked();
-                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
-                    updateCurrentProfileIdsLocked();
-                    mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
-                    // Once the internal notion of the active user has switched, we lock the device
-                    // with the option to show the user switcher on the keyguard.
-                    mWindowManager.lockNow(null);
-                } else {
-                    final Integer currentUserIdInt = Integer.valueOf(mCurrentUserId);
-                    updateCurrentProfileIdsLocked();
-                    mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
-                    mUserLru.remove(currentUserIdInt);
-                    mUserLru.add(currentUserIdInt);
-                }
-
-                final UserState uss = mStartedUsers.get(userId);
-
-                // Make sure user is in the started state.  If it is currently
-                // stopping, we need to knock that off.
-                if (uss.mState == UserState.STATE_STOPPING) {
-                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
-                    // so we can just fairly silently bring the user back from
-                    // the almost-dead.
-                    uss.mState = UserState.STATE_RUNNING;
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                } else if (uss.mState == UserState.STATE_SHUTDOWN) {
-                    // This means ACTION_SHUTDOWN has been sent, so we will
-                    // need to treat this as a new boot of the user.
-                    uss.mState = UserState.STATE_BOOTING;
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                }
-
-                if (uss.mState == UserState.STATE_BOOTING) {
-                    // Booting up a new user, need to tell system services about it.
-                    // Note that this is on the same handler as scheduling of broadcasts,
-                    // which is important because it needs to go first.
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
-                }
-
-                if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
-                            oldUserId));
-                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
-                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
-                            oldUserId, userId, uss));
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
-                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
-                }
-
-                if (needStart) {
-                    // Send USER_STARTED broadcast
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    broadcastIntentLocked(null, null, intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, Process.SYSTEM_UID, userId);
-                }
-
-                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
-                    if (userId != UserHandle.USER_SYSTEM) {
-                        Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
-                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                        broadcastIntentLocked(null, null, intent, null,
-                                new IIntentReceiver.Stub() {
-                                    public void performReceive(Intent intent, int resultCode,
-                                            String data, Bundle extras, boolean ordered,
-                                            boolean sticky, int sendingUser) {
-                                        onUserInitialized(uss, foreground, oldUserId, userId);
-                                    }
-                                }, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, MY_PID, Process.SYSTEM_UID, userId);
-                        uss.initializing = true;
-                    } else {
-                        getUserManagerLocked().makeInitialized(userInfo.id);
-                    }
-                }
-
-                if (foreground) {
-                    if (!uss.initializing) {
-                        moveUserToForegroundLocked(uss, oldUserId, userId);
-                    }
-                } else {
-                    mStackSupervisor.startBackgroundUserLocked(userId, uss);
-                }
-
-                if (needStart) {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    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) throws RemoteException {
-                                }
-                            }, 0, null, null,
-                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        return true;
-    }
-
-    void dispatchForegroundProfileChanged(int userId) {
-        final int N = mUserSwitchObservers.beginBroadcast();
-        for (int i = 0; i < N; i++) {
-            try {
-                mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
-            } catch (RemoteException e) {
-                // Ignore
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
     void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -20381,150 +20077,6 @@
         }
     }
 
-    void dispatchUserSwitch(final UserState uss, final int oldUserId,
-            final int newUserId) {
-        final int N = mUserSwitchObservers.beginBroadcast();
-        if (N > 0) {
-            final IRemoteCallback callback = new IRemoteCallback.Stub() {
-                int mCount = 0;
-                @Override
-                public void sendResult(Bundle data) throws RemoteException {
-                    synchronized (ActivityManagerService.this) {
-                        if (mCurUserSwitchCallback == this) {
-                            mCount++;
-                            if (mCount == N) {
-                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-                            }
-                        }
-                    }
-                }
-            };
-            synchronized (this) {
-                uss.switching = true;
-                mCurUserSwitchCallback = callback;
-            }
-            for (int i=0; i<N; i++) {
-                try {
-                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
-                            newUserId, callback);
-                } catch (RemoteException e) {
-                }
-            }
-        } else {
-            synchronized (this) {
-                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
-    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
-        synchronized (this) {
-            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
-            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-        }
-    }
-
-    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
-        mCurUserSwitchCallback = null;
-        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
-                oldUserId, newUserId, uss));
-    }
-
-    void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
-        synchronized (this) {
-            if (foreground) {
-                moveUserToForegroundLocked(uss, oldUserId, newUserId);
-            }
-        }
-
-        completeSwitchAndInitialize(uss, newUserId, true, false);
-    }
-
-    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
-        boolean homeInFront = mStackSupervisor.switchUserLocked(newUserId, uss);
-        if (homeInFront) {
-            startHomeActivityLocked(newUserId, "moveUserToFroreground");
-        } else {
-            mStackSupervisor.resumeTopActivitiesLocked();
-        }
-        EventLogTags.writeAmSwitchUser(newUserId);
-        getUserManagerLocked().onUserForeground(newUserId);
-        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
-    }
-
-    private void updateUserConfigurationLocked() {
-        Configuration configuration = new Configuration(mConfiguration);
-        Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
-                mCurrentUserId);
-        updateConfigurationLocked(configuration, null, false);
-    }
-
-    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
-        completeSwitchAndInitialize(uss, newUserId, false, true);
-    }
-
-    void completeSwitchAndInitialize(UserState uss, int newUserId,
-            boolean clearInitializing, boolean clearSwitching) {
-        boolean unfrozen = false;
-        synchronized (this) {
-            if (clearInitializing) {
-                uss.initializing = false;
-                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
-            }
-            if (clearSwitching) {
-                uss.switching = false;
-            }
-            if (!uss.switching && !uss.initializing) {
-                mWindowManager.stopFreezingScreen();
-                unfrozen = true;
-            }
-        }
-        if (unfrozen) {
-            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
-                    newUserId, 0));
-        }
-        stopGuestUserIfBackground();
-    }
-
-    /** Called on handler thread */
-    void dispatchUserSwitchComplete(int userId) {
-        final int observerCount = mUserSwitchObservers.beginBroadcast();
-        for (int i = 0; i < observerCount; i++) {
-            try {
-                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
-            } catch (RemoteException e) {
-            }
-        }
-        mUserSwitchObservers.finishBroadcast();
-    }
-
-    /**
-     * Stops the guest user if it has gone to the background.
-     */
-    private void stopGuestUserIfBackground() {
-        synchronized (this) {
-            final int num = mUserLru.size();
-            for (int i = 0; i < num; i++) {
-                Integer oldUserId = mUserLru.get(i);
-                UserState oldUss = mStartedUsers.get(oldUserId);
-                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
-                        || oldUss.mState == UserState.STATE_STOPPING
-                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
-                    continue;
-                }
-                UserInfo userInfo = mUserManager.getUserInfo(oldUserId);
-                if (userInfo.isGuest()) {
-                    // This is a user to be stopped.
-                    stopUserLocked(oldUserId, null);
-                    break;
-                }
-            }
-        }
-    }
-
     void scheduleStartProfilesLocked() {
         if (!mHandler.hasMessages(START_PROFILES_MSG)) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20532,229 +20084,9 @@
         }
     }
 
-    void startProfilesLocked() {
-        if (DEBUG_MU) Slog.i(TAG_MU, "startProfilesLocked");
-        List<UserInfo> profiles = getUserManagerLocked().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
-        List<UserInfo> toStart = new ArrayList<UserInfo>(profiles.size());
-        for (UserInfo user : profiles) {
-            if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != mCurrentUserId) {
-                toStart.add(user);
-            }
-        }
-        final int n = toStart.size();
-        int i = 0;
-        for (; i < n && i < (MAX_RUNNING_USERS - 1); ++i) {
-            startUserInBackground(toStart.get(i).id);
-        }
-        if (i < n) {
-            Slog.w(TAG_MU, "More profiles than MAX_RUNNING_USERS");
-        }
-    }
-
-    void finishUserBoot(UserState uss) {
-        synchronized (this) {
-            if (uss.mState == UserState.STATE_BOOTING
-                    && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
-                uss.mState = UserState.STATE_RUNNING;
-                final int userId = uss.mHandle.getIdentifier();
-                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
-                broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null,
-                        new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
-                        AppOpsManager.OP_NONE, null, true, false, MY_PID, Process.SYSTEM_UID,
-                        userId);
-            }
-        }
-    }
-
-    void finishUserSwitch(UserState uss) {
-        synchronized (this) {
-            finishUserBoot(uss);
-
-            startProfilesLocked();
-
-            int num = mUserLru.size();
-            int i = 0;
-            while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
-                Integer oldUserId = mUserLru.get(i);
-                UserState oldUss = mStartedUsers.get(oldUserId);
-                if (oldUss == null) {
-                    // Shouldn't happen, but be sane if it does.
-                    mUserLru.remove(i);
-                    num--;
-                    continue;
-                }
-                if (oldUss.mState == UserState.STATE_STOPPING
-                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
-                    // This user is already stopping, doesn't count.
-                    num--;
-                    i++;
-                    continue;
-                }
-                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
-                    // Owner/System user and current user can't be stopped. We count it as running
-                    // when it is not a pure system user.
-                    if (UserInfo.isSystemOnly(oldUserId)) {
-                        num--;
-                    }
-                    i++;
-                    continue;
-                }
-                // This is a user to be stopped.
-                stopUserLocked(oldUserId, null);
-                num--;
-                i++;
-            }
-        }
-    }
-
     @Override
     public int stopUser(final int userId, final IStopUserCallback callback) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: switchUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
-            throw new IllegalArgumentException("Can't stop system user " + userId);
-        }
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
-        synchronized (this) {
-            return stopUserLocked(userId, callback);
-        }
-    }
-
-    private int stopUserLocked(final int userId, final IStopUserCallback callback) {
-        if (DEBUG_MU) Slog.i(TAG_MU, "stopUserLocked userId=" + userId);
-        if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
-            return ActivityManager.USER_OP_IS_CURRENT;
-        }
-
-        final UserState uss = mStartedUsers.get(userId);
-        if (uss == null) {
-            // User is not started, nothing to do...  but we do need to
-            // callback if requested.
-            if (callback != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callback.userStopped(userId);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                });
-            }
-            return ActivityManager.USER_OP_SUCCESS;
-        }
-
-        if (callback != null) {
-            uss.mStopCallbacks.add(callback);
-        }
-
-        if (uss.mState != UserState.STATE_STOPPING
-                && uss.mState != UserState.STATE_SHUTDOWN) {
-            uss.mState = UserState.STATE_STOPPING;
-            updateStartedUserArrayLocked();
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                // We are going to broadcast ACTION_USER_STOPPING and then
-                // once that is done send a final ACTION_SHUTDOWN and then
-                // stop the user.
-                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
-                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 (ActivityManagerService.this) {
-                            if (uss.mState != UserState.STATE_STOPPING) {
-                                // Whoops, we are being started back up.  Abort, abort!
-                                return;
-                            }
-                            uss.mState = UserState.STATE_SHUTDOWN;
-                        }
-                        mBatteryStatsService.noteEvent(
-                                BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
-                                Integer.toString(userId), userId);
-                        mSystemServiceManager.stopUser(userId);
-                        broadcastIntentLocked(null, null, shutdownIntent,
-                                null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, true, false, MY_PID, Process.SYSTEM_UID, userId);
-                    }
-                };
-                // Kick things off.
-                broadcastIntentLocked(null, null, stoppingIntent,
-                        null, stoppingReceiver, 0, null, null,
-                        new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                        null, true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        return ActivityManager.USER_OP_SUCCESS;
-    }
-
-    void finishUserStop(UserState uss) {
-        final int userId = uss.mHandle.getIdentifier();
-        boolean stopped;
-        ArrayList<IStopUserCallback> callbacks;
-        synchronized (this) {
-            callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
-            if (mStartedUsers.get(userId) != uss) {
-                stopped = false;
-            } else if (uss.mState != UserState.STATE_SHUTDOWN) {
-                stopped = false;
-            } else {
-                stopped = true;
-                // User can no longer run.
-                mStartedUsers.remove(userId);
-                mUserLru.remove(Integer.valueOf(userId));
-                updateStartedUserArrayLocked();
-
-                // Clean up all state and processes associated with the user.
-                // Kill all the processes for the user.
-                forceStopUserLocked(userId, "finish user");
-            }
-        }
-
-        for (int i=0; i<callbacks.size(); i++) {
-            try {
-                if (stopped) callbacks.get(i).userStopped(userId);
-                else callbacks.get(i).userStopAborted(userId);
-            } catch (RemoteException e) {
-            }
-        }
-
-        if (stopped) {
-            mSystemServiceManager.cleanupUser(userId);
-            synchronized (this) {
-                mStackSupervisor.removeUserLocked(userId);
-            }
-        }
+        return mUserController.stopUser(userId, callback);
     }
 
     void onUserRemovedLocked(int userId) {
@@ -20763,25 +20095,7 @@
 
     @Override
     public UserInfo getCurrentUser() {
-        if ((checkCallingPermission(INTERACT_ACROSS_USERS)
-                != PackageManager.PERMISSION_GRANTED) && (
-                checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED)) {
-            String msg = "Permission Denial: getCurrentUser() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        synchronized (this) {
-            int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
-            return getUserManagerLocked().getUserInfo(userId);
-        }
-    }
-
-    int getCurrentUserIdLocked() {
-        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        return mUserController.getCurrentUser();
     }
 
     @Override
@@ -20801,7 +20115,7 @@
     }
 
     boolean isUserRunningLocked(int userId, boolean orStopped) {
-        UserState state = mStartedUsers.get(userId);
+        UserState state = mUserController.getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -20824,50 +20138,18 @@
             throw new SecurityException(msg);
         }
         synchronized (this) {
-            return mStartedUserArray;
-        }
-    }
-
-    private void updateStartedUserArrayLocked() {
-        int num = 0;
-        for (int i=0; i<mStartedUsers.size();  i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            // This list does not include stopping users.
-            if (uss.mState != UserState.STATE_STOPPING
-                    && uss.mState != UserState.STATE_SHUTDOWN) {
-                num++;
-            }
-        }
-        mStartedUserArray = new int[num];
-        num = 0;
-        for (int i=0; i<mStartedUsers.size();  i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            if (uss.mState != UserState.STATE_STOPPING
-                    && uss.mState != UserState.STATE_SHUTDOWN) {
-                mStartedUserArray[num] = mStartedUsers.keyAt(i);
-                num++;
-            }
+            return mUserController.getStartedUserArrayLocked();
         }
     }
 
     @Override
     public void registerUserSwitchObserver(IUserSwitchObserver observer) {
-        if (checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: registerUserSwitchObserver() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + INTERACT_ACROSS_USERS_FULL;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-
-        mUserSwitchObservers.register(observer);
+        mUserController.registerUserSwitchObserver(observer);
     }
 
     @Override
     public void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
-        mUserSwitchObservers.unregister(observer);
+        mUserController.unregisterUserSwitchObserver(observer);
     }
 
     int[] getUsersLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1721470..8bf1d22 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -366,7 +366,7 @@
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = activityContainer.mStackId;
-        mCurrentUser = mService.mCurrentUserId;
+        mCurrentUser = mService.mUserController.mCurrentUserId;
         mRecentTasks = recentTasks;
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
@@ -1819,7 +1819,7 @@
         // Make sure that the user who owns this activity is started.  If not,
         // we will just leave it as is because someone should be bringing
         // another user's activities to the top of the stack.
-        if (mService.mStartedUsers.get(next.userId) == null) {
+        if (!mService.mUserController.hasStartedUserState(next.userId)) {
             Slog.w(TAG, "Skipping resume of top activity " + next
                     + ": user " + next.userId + " is stopped");
             if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e13ca39..8dec77c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -97,7 +97,6 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -2684,7 +2683,7 @@
             // Complete user switch
             if (startingUsers != null) {
                 for (int i = 0; i < startingUsers.size(); i++) {
-                    mService.finishUserSwitch(startingUsers.get(i));
+                    mService.mUserController.finishUserSwitch(startingUsers.get(i));
                 }
             }
             // Complete starting up of background users
@@ -2692,7 +2691,7 @@
                 startingUsers = new ArrayList<UserState>(mStartingBackgroundUsers);
                 mStartingBackgroundUsers.clear();
                 for (int i = 0; i < startingUsers.size(); i++) {
-                    mService.finishUserBoot(startingUsers.get(i));
+                    mService.mUserController.finishUserBoot(startingUsers.get(i));
                 }
             }
         }
@@ -3747,10 +3746,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
-            if (mService.mCurrentProfileIds[i] == userId) return true;
-        }
-        return false;
+        return mService.mUserController.isCurrentProfileLocked(userId);
     }
 
     /** Checks whether the activity should be shown for current user. */
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 5b46799..6ed880e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.getCurrentUserIdLocked();
+                    userId = owner.mUserController.getCurrentUserIdLocked();
                 }
                 switch (key.type) {
                     case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
new file mode 100644
index 0000000..ff74d83
--- /dev/null
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -0,0 +1,862 @@
+/*
+ * 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.am;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.USER_OP_IS_CURRENT;
+import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
+import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
+import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
+import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IStopUserCallback;
+import android.app.IUserSwitchObserver;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.UserManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ */
+final class UserController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
+    // Maximum number of users we allow to be running at a time.
+    static final int MAX_RUNNING_USERS = 3;
+
+    // Amount of time we wait for observers to handle a user switch before
+    // giving up on them and unfreezing the screen.
+    static final int USER_SWITCH_TIMEOUT = 2 * 1000;
+
+    private final ActivityManagerService mService;
+    private final Handler mHandler;
+
+    // Holds the current foreground user's id
+    int mCurrentUserId = 0;
+    // Holds the target user's id during a user switch
+    int mTargetUserId = UserHandle.USER_NULL;
+
+    /**
+     * Which users have been started, so are allowed to run code.
+     */
+    private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+    /**
+     * LRU list of history of current users.  Most recently current is at the end.
+     */
+    private final ArrayList<Integer> mUserLru = new ArrayList<>();
+
+    /**
+     * Constant array of the users that are currently started.
+     */
+    private int[] mStartedUserArray = new int[] { 0 };
+
+    // If there are multiple profiles for the current user, their ids are here
+    // Currently only the primary user can have managed profiles
+    int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+
+    /**
+     * Mapping from each known user ID to the profile group ID it is associated with.
+     */
+    private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+
+    /**
+     * Registered observers of the user switching mechanics.
+     */
+    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+            = new RemoteCallbackList<>();
+
+    /**
+     * Currently active user switch.
+     */
+    Object mCurUserSwitchCallback;
+
+    UserController(ActivityManagerService service) {
+        mService = service;
+        mHandler = mService.mHandler;
+        // User 0 is the first and only user that runs at boot.
+        mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
+        mUserLru.add(UserHandle.USER_SYSTEM);
+        updateStartedUserArrayLocked();
+    }
+
+    void finishUserSwitch(UserState uss) {
+        synchronized (mService) {
+            finishUserBoot(uss);
+
+            startProfilesLocked();
+
+            int num = mUserLru.size();
+            int i = 0;
+            while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
+                Integer oldUserId = mUserLru.get(i);
+                UserState oldUss = mStartedUsers.get(oldUserId);
+                if (oldUss == null) {
+                    // Shouldn't happen, but be sane if it does.
+                    mUserLru.remove(i);
+                    num--;
+                    continue;
+                }
+                if (oldUss.mState == UserState.STATE_STOPPING
+                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
+                    // This user is already stopping, doesn't count.
+                    num--;
+                    i++;
+                    continue;
+                }
+                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
+                    // Owner/System user and current user can't be stopped. We count it as running
+                    // when it is not a pure system user.
+                    if (UserInfo.isSystemOnly(oldUserId)) {
+                        num--;
+                    }
+                    i++;
+                    continue;
+                }
+                // This is a user to be stopped.
+                stopUserLocked(oldUserId, null);
+                num--;
+                i++;
+            }
+        }
+    }
+
+    void finishUserBoot(UserState uss) {
+        synchronized (mService) {
+            if (uss.mState == UserState.STATE_BOOTING
+                    && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
+                uss.mState = UserState.STATE_RUNNING;
+                final int userId = uss.mHandle.getIdentifier();
+                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                mService.broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                        AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+                        Process.SYSTEM_UID, userId);
+            }
+        }
+    }
+
+    int stopUser(final int userId, final IStopUserCallback callback) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
+            throw new IllegalArgumentException("Can't stop system user " + userId);
+        }
+        mService.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
+                userId);
+        synchronized (mService) {
+            return stopUserLocked(userId, callback);
+        }
+    }
+
+    private int stopUserLocked(final int userId, final IStopUserCallback callback) {
+        if (DEBUG_MU) Slog.i(TAG, "stopUserLocked userId=" + userId);
+        if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
+            return USER_OP_IS_CURRENT;
+        }
+
+        final UserState uss = mStartedUsers.get(userId);
+        if (uss == null) {
+            // User is not started, nothing to do...  but we do need to
+            // callback if requested.
+            if (callback != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            callback.userStopped(userId);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                });
+            }
+            return USER_OP_SUCCESS;
+        }
+
+        if (callback != null) {
+            uss.mStopCallbacks.add(callback);
+        }
+
+        if (uss.mState != UserState.STATE_STOPPING
+                && uss.mState != UserState.STATE_SHUTDOWN) {
+            uss.mState = UserState.STATE_STOPPING;
+            updateStartedUserArrayLocked();
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                // We are going to broadcast ACTION_USER_STOPPING and then
+                // once that is done send a final ACTION_SHUTDOWN and then
+                // stop the user.
+                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
+                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.mState != UserState.STATE_STOPPING) {
+                                // Whoops, we are being started back up.  Abort, abort!
+                                return;
+                            }
+                            uss.mState = 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, ActivityManagerService.MY_PID,
+                                android.os.Process.SYSTEM_UID, userId);
+                    }
+                };
+                // Kick things off.
+                mService.broadcastIntentLocked(null, null, stoppingIntent,
+                        null, stoppingReceiver, 0, null, null,
+                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                        UserHandle.USER_ALL);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        return USER_OP_SUCCESS;
+    }
+
+    void finishUserStop(UserState uss) {
+        final int userId = uss.mHandle.getIdentifier();
+        boolean stopped;
+        ArrayList<IStopUserCallback> callbacks;
+        synchronized (mService) {
+            callbacks = new ArrayList<>(uss.mStopCallbacks);
+            if (mStartedUsers.get(userId) != uss) {
+                stopped = false;
+            } else if (uss.mState != UserState.STATE_SHUTDOWN) {
+                stopped = false;
+            } else {
+                stopped = true;
+                // User can no longer run.
+                mStartedUsers.remove(userId);
+                mUserLru.remove(Integer.valueOf(userId));
+                updateStartedUserArrayLocked();
+
+                // Clean up all state and processes associated with the user.
+                // Kill all the processes for the user.
+                forceStopUserLocked(userId, "finish user");
+            }
+        }
+
+        for (int i = 0; i < callbacks.size(); i++) {
+            try {
+                if (stopped) callbacks.get(i).userStopped(userId);
+                else callbacks.get(i).userStopAborted(userId);
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (stopped) {
+            mService.mSystemServiceManager.cleanupUser(userId);
+            synchronized (mService) {
+                mService.mStackSupervisor.removeUserLocked(userId);
+            }
+        }
+    }
+
+    private void forceStopUserLocked(int userId, String reason) {
+        mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+                userId, reason);
+        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        mService.broadcastIntentLocked(null, null, intent,
+                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                UserHandle.USER_ALL);
+    }
+
+
+    /**
+     * Stops the guest user if it has gone to the background.
+     */
+    private void stopGuestUserIfBackground() {
+        synchronized (mService) {
+            final int num = mUserLru.size();
+            for (int i = 0; i < num; i++) {
+                Integer oldUserId = mUserLru.get(i);
+                UserState oldUss = mStartedUsers.get(oldUserId);
+                if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
+                        || oldUss.mState == UserState.STATE_STOPPING
+                        || oldUss.mState == UserState.STATE_SHUTDOWN) {
+                    continue;
+                }
+                UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+                if (userInfo.isGuest()) {
+                    // This is a user to be stopped.
+                    stopUserLocked(oldUserId, null);
+                    break;
+                }
+            }
+        }
+    }
+
+    void startProfilesLocked() {
+        if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
+        List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+                mCurrentUserId, false /* enabledOnly */);
+        List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
+        for (UserInfo user : profiles) {
+            if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
+                    && user.id != mCurrentUserId) {
+                profilesToStart.add(user);
+            }
+        }
+        final int profilesToStartSize = profilesToStart.size();
+        int i = 0;
+        for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+            startUser(profilesToStart.get(i).id, /* foreground= */ false);
+        }
+        if (i < profilesToStartSize) {
+            Slog.w(TAG, "More profiles than MAX_RUNNING_USERS");
+        }
+    }
+
+    private UserManagerService getUserManagerLocked() {
+        return mService.getUserManagerLocked();
+    }
+
+    boolean startUser(final int userId, final boolean foreground) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        if (DEBUG_MU) Slog.i(TAG, "starting userid:" + userId + " fore:" + foreground);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                final int oldUserId = mCurrentUserId;
+                if (oldUserId == userId) {
+                    return true;
+                }
+
+                mService.mStackSupervisor.setLockTaskModeLocked(null,
+                        ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
+
+                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                if (userInfo == null) {
+                    Slog.w(TAG, "No user info for user #" + userId);
+                    return false;
+                }
+                if (foreground && userInfo.isManagedProfile()) {
+                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+                    return false;
+                }
+
+                if (foreground) {
+                    mService.mWindowManager.startFreezingScreen(
+                            R.anim.screen_user_exit, R.anim.screen_user_enter);
+                }
+
+                boolean needStart = false;
+
+                // If the user we are switching to is not currently started, then
+                // we need to start it now.
+                if (mStartedUsers.get(userId) == null) {
+                    mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                final Integer userIdInt = userId;
+                mUserLru.remove(userIdInt);
+                mUserLru.add(userIdInt);
+
+                if (foreground) {
+                    mCurrentUserId = userId;
+                    mService.updateUserConfigurationLocked();
+                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+                    updateCurrentProfileIdsLocked();
+                    mService.mWindowManager.setCurrentUser(userId, mCurrentProfileIds);
+                    // Once the internal notion of the active user has switched, we lock the device
+                    // with the option to show the user switcher on the keyguard.
+                    mService.mWindowManager.lockNow(null);
+                } else {
+                    final Integer currentUserIdInt = mCurrentUserId;
+                    updateCurrentProfileIdsLocked();
+                    mService.mWindowManager.setCurrentProfileIds(mCurrentProfileIds);
+                    mUserLru.remove(currentUserIdInt);
+                    mUserLru.add(currentUserIdInt);
+                }
+
+                final UserState uss = mStartedUsers.get(userId);
+
+                // Make sure user is in the started state.  If it is currently
+                // stopping, we need to knock that off.
+                if (uss.mState == UserState.STATE_STOPPING) {
+                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                    // so we can just fairly silently bring the user back from
+                    // the almost-dead.
+                    uss.mState = UserState.STATE_RUNNING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                } else if (uss.mState == UserState.STATE_SHUTDOWN) {
+                    // This means ACTION_SHUTDOWN has been sent, so we will
+                    // need to treat this as a new boot of the user.
+                    uss.mState = UserState.STATE_BOOTING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                if (uss.mState == UserState.STATE_BOOTING) {
+                    // Booting up a new user, need to tell system services about it.
+                    // Note that this is on the same handler as scheduling of broadcasts,
+                    // which is important because it needs to go first.
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+                }
+
+                if (foreground) {
+                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                            oldUserId));
+                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                            oldUserId, userId, uss));
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+                }
+
+                if (needStart) {
+                    // Send USER_STARTED broadcast
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    mService.broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                            userId);
+                }
+
+                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                    if (userId != UserHandle.USER_SYSTEM) {
+                        Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                        mService.broadcastIntentLocked(null, null, intent, null,
+                                new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        onUserInitialized(uss, foreground, oldUserId, userId);
+                                    }
+                                }, 0, null, null, null, AppOpsManager.OP_NONE,
+                                null, true, false, ActivityManagerService.MY_PID,
+                                Process.SYSTEM_UID, userId);
+                        uss.initializing = true;
+                    } else {
+                        getUserManagerLocked().makeInitialized(userInfo.id);
+                    }
+                }
+
+                if (foreground) {
+                    if (!uss.initializing) {
+                        moveUserToForegroundLocked(uss, oldUserId, userId);
+                    }
+                } else {
+                    mService.mStackSupervisor.startBackgroundUserLocked(userId, uss);
+                }
+
+                if (needStart) {
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    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) throws RemoteException {
+                                }
+                            }, 0, null, null,
+                            new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                            null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
+                            UserHandle.USER_ALL);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return true;
+    }
+
+    void dispatchForegroundProfileChanged(int userId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        for (int i = 0; i < observerCount; i++) {
+            try {
+                mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    /** Called on handler thread */
+    void dispatchUserSwitchComplete(int userId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        for (int i = 0; i < observerCount; i++) {
+            try {
+                mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
+            } catch (RemoteException e) {
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+        synchronized (mService) {
+            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+        }
+    }
+
+    void dispatchUserSwitch(final UserState uss, final int oldUserId,
+            final int newUserId) {
+        final int observerCount = mUserSwitchObservers.beginBroadcast();
+        if (observerCount > 0) {
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                int mCount = 0;
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    synchronized (mService) {
+                        if (mCurUserSwitchCallback == this) {
+                            mCount++;
+                            if (mCount == observerCount) {
+                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                            }
+                        }
+                    }
+                }
+            };
+            synchronized (mService) {
+                uss.switching = true;
+                mCurUserSwitchCallback = callback;
+            }
+            for (int i = 0; i < observerCount; i++) {
+                try {
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
+                            newUserId, callback);
+                } catch (RemoteException e) {
+                }
+            }
+        } else {
+            synchronized (mService) {
+                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+        mCurUserSwitchCallback = null;
+        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+        mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+                oldUserId, newUserId, uss));
+    }
+
+    void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
+        completeSwitchAndInitialize(uss, newUserId, false, true);
+    }
+
+    void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
+        synchronized (mService) {
+            if (foreground) {
+                moveUserToForegroundLocked(uss, oldUserId, newUserId);
+            }
+        }
+        completeSwitchAndInitialize(uss, newUserId, true, false);
+    }
+
+    void completeSwitchAndInitialize(UserState uss, int newUserId,
+            boolean clearInitializing, boolean clearSwitching) {
+        boolean unfrozen = false;
+        synchronized (mService) {
+            if (clearInitializing) {
+                uss.initializing = false;
+                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+            }
+            if (clearSwitching) {
+                uss.switching = false;
+            }
+            if (!uss.switching && !uss.initializing) {
+                mService.mWindowManager.stopFreezingScreen();
+                unfrozen = true;
+            }
+        }
+        if (unfrozen) {
+            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
+                    newUserId, 0));
+        }
+        stopGuestUserIfBackground();
+    }
+
+    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
+        boolean homeInFront = mService.mStackSupervisor.switchUserLocked(newUserId, uss);
+        if (homeInFront) {
+            mService.startHomeActivityLocked(newUserId, "moveUserToForeground");
+        } else {
+            mService.mStackSupervisor.resumeTopActivitiesLocked();
+        }
+        EventLogTags.writeAmSwitchUser(newUserId);
+        getUserManagerLocked().onUserForeground(newUserId);
+        mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+    }
+
+    void registerUserSwitchObserver(IUserSwitchObserver observer) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            final String msg = "Permission Denial: registerUserSwitchObserver() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        mUserSwitchObservers.register(observer);
+    }
+
+    void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
+        mUserSwitchObservers.unregister(observer);
+    }
+
+    UserState getStartedUserState(int userId) {
+        return mStartedUsers.get(userId);
+    }
+
+    boolean hasStartedUserState(int userId) {
+        return mStartedUsers.get(userId) != null;
+    }
+
+    private void updateStartedUserArrayLocked() {
+        int num = 0;
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            // This list does not include stopping users.
+            if (uss.mState != UserState.STATE_STOPPING
+                    && uss.mState != UserState.STATE_SHUTDOWN) {
+                num++;
+            }
+        }
+        mStartedUserArray = new int[num];
+        num = 0;
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            if (uss.mState != UserState.STATE_STOPPING
+                    && uss.mState != UserState.STATE_SHUTDOWN) {
+                mStartedUserArray[num] = mStartedUsers.keyAt(i);
+                num++;
+            }
+        }
+    }
+
+    void sendBootCompletedLocked(IIntentReceiver resultTo) {
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            if (uss.mState == UserState.STATE_BOOTING) {
+                uss.mState = UserState.STATE_RUNNING;
+                final int userId = mStartedUsers.keyAt(i);
+                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                mService.broadcastIntentLocked(null, null, intent, null,
+                        resultTo, 0, null, null,
+                        new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                        AppOpsManager.OP_NONE, null, true, false,
+                        ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+            }
+        }
+    }
+
+    /**
+     * Refreshes the list of users related to the current user when either a
+     * user switch happens or when a new related user is started in the
+     * background.
+     */
+    void updateCurrentProfileIdsLocked() {
+        final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+                false /* enabledOnly */);
+        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
+        for (int i = 0; i < currentProfileIds.length; i++) {
+            currentProfileIds[i] = profiles.get(i).id;
+        }
+        mCurrentProfileIds = currentProfileIds;
+
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            mUserProfileGroupIdsSelfLocked.clear();
+            final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+            for (int i = 0; i < users.size(); i++) {
+                UserInfo user = users.get(i);
+                if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                }
+            }
+        }
+    }
+
+    int[] getStartedUserArrayLocked() {
+        return mStartedUserArray;
+    }
+
+    UserInfo getCurrentUser() {
+        if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) && (
+                mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                        != PackageManager.PERMISSION_GRANTED)) {
+            String msg = "Permission Denial: getCurrentUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (mService) {
+            return getCurrentUserLocked();
+        }
+    }
+
+    UserInfo getCurrentUserLocked() {
+        int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        return getUserManagerLocked().getUserInfo(userId);
+    }
+
+    int getCurrentUserIdLocked() {
+        return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+    }
+
+    boolean isSameProfileGroup(int callingUserId, int targetUserId) {
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+                    UserInfo.NO_PROFILE_GROUP_ID);
+            int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+                    UserInfo.NO_PROFILE_GROUP_ID);
+            return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
+                    && callingProfile == targetProfile;
+        }
+    }
+
+    boolean isCurrentProfileLocked(int userId) {
+        return ArrayUtils.contains(mCurrentProfileIds, userId);
+    }
+
+    void dump(PrintWriter pw, boolean dumpAll) {
+        pw.println("  mStartedUsers:");
+        for (int i = 0; i < mStartedUsers.size(); i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
+            pw.print(": "); uss.dump("", pw);
+        }
+        pw.print("  mStartedUserArray: [");
+        for (int i = 0; i < mStartedUserArray.length; i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(mStartedUserArray[i]);
+        }
+        pw.println("]");
+        pw.print("  mUserLru: [");
+        for (int i = 0; i < mUserLru.size(); i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(mUserLru.get(i));
+        }
+        pw.println("]");
+        if (dumpAll) {
+            pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
+        }
+        synchronized (mUserProfileGroupIdsSelfLocked) {
+            if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+                pw.println("  mUserProfileGroupIds:");
+                for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                    pw.print("    User #");
+                    pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                    pw.print(" -> profile #");
+                    pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                }
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 4f61db9..8abc8fc 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -586,6 +586,8 @@
                     grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
                     grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
                             userId);
+                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
+                            userId);
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 496653e..b3e7f5f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4811,18 +4811,13 @@
             // First try to add the "always" resolution(s) for the current user, if any
             if (alwaysList.size() > 0) {
                 result.addAll(alwaysList);
-            // if there is an "always" for the parent user, add it.
-            } else if (xpDomainInfo != null && xpDomainInfo.bestDomainVerificationStatus
-                    == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-                result.add(xpDomainInfo.resolveInfo);
             } else {
                 // Add all undefined apps as we want them to appear in the disambiguation dialog.
                 result.addAll(undefinedList);
+                // Maybe add one for the other profile.
                 if (xpDomainInfo != null && (
                         xpDomainInfo.bestDomainVerificationStatus
-                        == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
-                        || xpDomainInfo.bestDomainVerificationStatus
-                        == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK)) {
+                        != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
                     result.add(xpDomainInfo.resolveInfo);
                 }
                 includeBrowser = true;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index d148a4f..8b99305 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2275,10 +2275,8 @@
                 //
                 // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
                 // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
-                //   system/core/logd/LogStatistics.cpp
+                //   frameworks/base/libs/packagelistparser
                 //   system/core/run-as/run-as.c
-                //   system/core/sdcard/sdcard.c
-                //   external/libselinux/src/android.c:package_info_init()
                 //
                 sb.setLength(0);
                 sb.append(ai.packageName);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e354029..b38b9ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3016,7 +3016,7 @@
 
         // Display task switcher for ALT-TAB.
         if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
-            if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
+            if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
                 final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                 if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                     mRecentAppsHeldModifiers = shiftlessModifiers;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 7abc048..2365a45 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -778,11 +778,17 @@
                         int unscaledThumbHeight = (int) (thumbHeight / scale);
                         mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
                     } else {
-                        // In landscape, we scale the height and clip to the top/left square
-                        scale = thumbHeight / (appHeight - contentInsets.top);
+                        // In landscape, we scale the height and clip to the top/left square. We
+                        // only scale the part that is not covered by status bar and the nav bar.
+                        scale = thumbHeight / (appHeight - contentInsets.top
+                                - contentInsets.bottom);
                         scaledTopDecor = (int) (scale * contentInsets.top);
                         int unscaledThumbWidth = (int) (thumbWidth / scale);
                         mTmpFromClipRect.right = mTmpFromClipRect.left + unscaledThumbWidth;
+                        // This removes the navigation bar from the first frame, so it better
+                        // matches the thumbnail. We need to do this explicitly in landscape,
+                        // because in portrait we already crop vertically.
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.bottom - contentInsets.bottom;
                     }
 
                     mNextAppTransitionInsets.set(contentInsets);
@@ -836,11 +842,16 @@
                     int unscaledThumbHeight = (int) (thumbHeight / scale);
                     mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
                 } else {
-                    // In landscape, we scale the height and clip to the top/left square
-                    scale = thumbHeight / (appHeight - contentInsets.top);
+                    // In landscape, we scale the height and clip to the top/left square. We only
+                    // scale the part that is not covered by status bar and the nav bar.
+                    scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom);
                     scaledTopDecor = (int) (scale * contentInsets.top);
                     int unscaledThumbWidth = (int) (thumbWidth / scale);
                     mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth;
+                    // This removes the navigation bar from the last frame, so it better matches the
+                    // thumbnail. We need to do this explicitly in landscape, because in portrait we
+                    // already crop vertically.
+                    mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom;
                 }
 
                 mNextAppTransitionInsets.set(contentInsets);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2828cd0..bf7063f 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -367,6 +367,7 @@
         return false;
     }
 
+    // This must be called while inside a transaction.
     boolean showAllWindowsLocked() {
         boolean isAnimating = false;
         final int NW = mAllAppWinAnimators.size();
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 7c2da2d..be3e922 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -56,7 +56,7 @@
             int screenOffset, int maskThickness) {
         mScreenSize = new Point();
         display.getSize(mScreenSize);
-        if (mScreenSize.x != mScreenSize.y) {
+        if (mScreenSize.x != mScreenSize.y + screenOffset) {
             Slog.w(TAG, "Screen dimensions of displayId = " + display.getDisplayId() +
                     "are not equal, circularMask will not be drawn.");
             mDimensionsUnequal = true;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3521682..f5e97e5 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -265,21 +265,27 @@
     }
 
     void broadcastDragEndedLw() {
+        final int myPid = Process.myPid();
+
         if (WindowManagerService.DEBUG_DRAG) {
             Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
         }
-        DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
-                0, 0, null, null, null, null, mDragResult);
-        for (WindowState ws: mNotifiedWindows) {
+        for (WindowState ws : mNotifiedWindows) {
+            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                    0, 0, null, null, null, null, mDragResult);
             try {
                 ws.mClient.dispatchDragEvent(evt);
             } catch (RemoteException e) {
                 Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
             }
+            // if the current window is in the same process,
+            // the dispatch has already recycled the event
+            if (myPid != ws.mSession.mPid) {
+                evt.recycle();
+            }
         }
         mNotifiedWindows.clear();
         mDragInProgress = false;
-        evt.recycle();
     }
 
     void endDragLw() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ecc1f2c..1754123 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1808,8 +1808,6 @@
                     + Debug.getCallers(3));
         }
         if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
-            if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
-                WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);
             if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
                     mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
                 Slog.v(TAG, "Showing " + this
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 247562f..0c004b2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1147,7 +1147,16 @@
             for (int j = 0; j < windowsCount; j++) {
                 appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
             }
-            mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
+            SurfaceControl.openTransaction();
+            try {
+                mService.mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
+            }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
             int topOpeningLayer = 0;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f563839..d2e4de3 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -144,28 +144,6 @@
             = "android.intent.action.ANY_DATA_STATE";
 
     /**
-     * Broadcast Action: Occurs when a data connection connects to a provisioning apn
-     * and is broadcast by the low level data connection code.
-     * The intent will have the following extra values:</p>
-     * <dl>
-     *   <dt>apn</dt><dd>A string that is the APN associated with this connection.</dd>
-     *   <dt>apnType</dt><dd>A string array of APN types associated with this connection.
-     *      The APN type {@code *} is a special type that means this APN services all types.</dd>
-     *   <dt>linkProperties</dt><dd>{@code LinkProperties} for this APN.</dd>
-     *   <dt>linkCapabilities</dt><dd>The {@code LinkCapabilities} for this APN.</dd>
-     *   <dt>iface</dt><dd>A string that is the name of the interface.</dd>
-     * </dl>
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN
-            = "android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN";
-
-    /**
      * Broadcast Action: An attempt to establish a data connection has failed.
      * The intent will have the following extra values:</p>
      * <dl>
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 84f4385..d864f66 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -124,17 +124,18 @@
             }
 
             for (const ResourceEntry* entry : sortedEntries) {
-                ResourceId id = {
-                        package->id ? package->id.value() : uint8_t(0),
-                        type->id ? type->id.value() : uint8_t(0),
-                        entry->id ? entry->id.value() : uint16_t(0)
-                };
+                ResourceId id(package->id ? package->id.value() : uint8_t(0),
+                              type->id ? type->id.value() : uint8_t(0),
+                              entry->id ? entry->id.value() : uint16_t(0));
+                ResourceName name(package->name, type->type, entry->name);
 
-                ResourceName name = { package->name, type->type, entry->name };
                 std::cout << "    spec resource " << id << " " << name;
-                if (entry->publicStatus.isPublic) {
-                    std::cout << " PUBLIC";
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPublic: std::cout << " PUBLIC"; break;
+                case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break;
+                default: break;
                 }
+
                 std::cout << std::endl;
 
                 PrintVisitor visitor;
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index 84a4125..cdf1b6a 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -77,6 +77,18 @@
     return output;
 }
 
+bool JavaClassGenerator::skipSymbol(SymbolState state) {
+    switch (mOptions.types) {
+    case JavaClassGeneratorOptions::SymbolTypes::kAll:
+        return false;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
+        return state == SymbolState::kUndefined;
+    case JavaClassGeneratorOptions::SymbolTypes::kPublic:
+        return state != SymbolState::kPublic;
+    }
+    return true;
+}
+
 void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate,
                                            const std::u16string& entryName,
                                            const Styleable* styleable,
@@ -121,7 +133,7 @@
         // We may reference IDs from other packages, so prefix the entry name with
         // the package.
         const ResourceNameRef& itemName = sortedAttributes[i].second;
-        if (packageNameToGenerate != itemName.package) {
+        if (!itemName.package.empty() && packageNameToGenerate != itemName.package) {
             *out << "_" << transform(itemName.package);
         }
         *out << "_" << transform(itemName.entry) << " = " << i << ";\n";
@@ -137,7 +149,11 @@
     std::u16string unmangledPackage;
     std::u16string unmangledName;
     for (const auto& entry : type->entries) {
-        ResourceId id = { package->id.value(), type->id.value(), entry->id.value() };
+        if (skipSymbol(entry->symbolStatus.state)) {
+            continue;
+        }
+
+        ResourceId id(package->id.value(), type->id.value(), entry->id.value());
         assert(id.isValid());
 
         unmangledName = entry->name;
@@ -157,7 +173,7 @@
         }
 
         if (!isValidSymbol(unmangledName)) {
-            ResourceNameRef resourceName = { packageNameToGenerate, type->type, unmangledName };
+            ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
             std::stringstream err;
             err << "invalid symbol name '" << resourceName << "'";
             mError = err.str();
@@ -177,13 +193,24 @@
 }
 
 bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
-    generateHeader(packageNameToGenerate, out);
+    return generate(packageNameToGenerate, packageNameToGenerate, out);
+}
+
+bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
+                                  const StringPiece16& outPackageName, std::ostream* out) {
+    generateHeader(outPackageName, out);
 
     *out << "public final class R {\n";
 
     for (const auto& package : mTable->packages) {
         for (const auto& type : package->types) {
-            *out << "    public static final class " << type->type << " {\n";
+            StringPiece16 typeStr;
+            if (type->type == ResourceType::kAttrPrivate) {
+                typeStr = toString(ResourceType::kAttr);
+            } else {
+                typeStr = toString(type->type);
+            }
+            *out << "    public static final class " << typeStr << " {\n";
             if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) {
                 return false;
             }
@@ -196,4 +223,6 @@
     return true;
 }
 
+
+
 } // namespace aapt
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index 682bacf..e53a765 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -33,6 +33,17 @@
      * on resource entries. Default is true.
      */
     bool useFinal = true;
+
+    enum class SymbolTypes {
+        kAll,
+        kPublicPrivate,
+        kPublic,
+    };
+
+    /*
+     *
+     */
+    SymbolTypes types = SymbolTypes::kAll;
 };
 
 /*
@@ -49,7 +60,11 @@
      * We need to generate these symbols in a separate file.
      * Returns true on success.
      */
-    bool generate(const StringPiece16& package, std::ostream* out);
+    bool generate(const StringPiece16& packageNameToGenerate, std::ostream* out);
+
+    bool generate(const StringPiece16& packageNameToGenerate,
+                  const StringPiece16& outputPackageName,
+                  std::ostream* out);
 
     const std::string& getError() const;
 
@@ -64,6 +79,8 @@
                            const Styleable* styleable,
                            std::ostream* out);
 
+    bool skipSymbol(SymbolState state);
+
     ResourceTable* mTable;
     JavaClassGeneratorOptions mOptions;
     std::string mError;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index 48fcf8c..becf99b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -65,6 +65,87 @@
               output.find("public static final int hey_dude_cool_attr = 0;"));
 }
 
+TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/com.foo$two", ResourceId(0x01020001))
+            .build();
+
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", u"com.android.internal", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("package com.android.internal;"));
+    EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+    EXPECT_EQ(std::string::npos, output.find("two"));
+    EXPECT_EQ(std::string::npos, output.find("com_foo$two"));
+}
+
+TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:^attr-private/one", ResourceId(0x01010000))
+            .build();
+
+    JavaClassGenerator generator(table.get(), {});
+    std::stringstream out;
+    ASSERT_TRUE(generator.generate(u"android", &out));
+
+    std::string output = out.str();
+    EXPECT_NE(std::string::npos, output.find("public static final class attr"));
+    EXPECT_EQ(std::string::npos, output.find("public static final class ^attr-private"));
+}
+
+TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
+    StdErrDiagnostics diag;
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"android", 0x01)
+            .addSimple(u"@android:id/one", ResourceId(0x01020000))
+            .addSimple(u"@android:id/two", ResourceId(0x01020001))
+            .addSimple(u"@android:id/three", ResourceId(0x01020002))
+            .build();
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
+                                      SymbolState::kPublic, &diag));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
+                                      SymbolState::kPrivate, &diag));
+
+    JavaClassGeneratorOptions options;
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_EQ(std::string::npos, output.find("two"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_EQ(std::string::npos, output.find("three"));
+    }
+
+    options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+    {
+        JavaClassGenerator generator(table.get(), options);
+        std::stringstream out;
+        ASSERT_TRUE(generator.generate(u"android", &out));
+        std::string output = out.str();
+        EXPECT_NE(std::string::npos, output.find("public static final int one = 0x01020000;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int two = 0x01020001;"));
+        EXPECT_NE(std::string::npos, output.find("public static final int three = 0x01020002;"));
+    }
+}
+
 /*
  * TODO(adamlesinski): Re-enable this once we get merging working again.
  * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 63629f0..bfef9d0 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -18,10 +18,11 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "util/Util.h"
 #include "ValueVisitor.h"
 #include "XmlPullParser.h"
 
+#include "util/Util.h"
+
 #include <sstream>
 
 namespace aapt {
@@ -184,7 +185,7 @@
     ResourceName name;
     Source source;
     ResourceId id;
-    bool markPublic = false;
+    SymbolState symbolState = SymbolState::kUndefined;
     std::unique_ptr<Value> value;
     std::list<ParsedResource> childResources;
 };
@@ -192,8 +193,10 @@
 // Recursively adds resources to the ResourceTable.
 static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
                                 IDiagnostics* diag, ParsedResource* res) {
-    if (res->markPublic && !table->markPublic(res->name, res->id, res->source, diag)) {
-        return false;
+    if (res->symbolState != SymbolState::kUndefined) {
+        if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+            return false;
+        }
     }
 
     if (!res->value) {
@@ -318,6 +321,8 @@
             result = parseAttr(parser, &parsedResource);
         } else if (elementName == u"public") {
             result = parsePublic(parser, &parsedResource);
+        } else if (elementName == u"java-symbol" || elementName == u"symbol") {
+            result = parseSymbol(parser, &parsedResource);
         } else {
             mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber()))
                         << "unknown resource type '" << elementName << "'");
@@ -506,7 +511,29 @@
         outResource->value = util::make_unique<Id>();
     }
 
-    outResource->markPublic = true;
+    outResource->symbolState = SymbolState::kPublic;
+    return true;
+}
+
+bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+    const Source source = mSource.withLine(parser->getLineNumber());
+
+    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    if (!maybeType) {
+        mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
+                     "'type' attribute");
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(maybeType.value());
+    if (!parsedType) {
+        mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value()
+                     << "' in <" << parser->getElementName() << ">");
+        return false;
+    }
+
+    outResource->name.type = *parsedType;
+    outResource->symbolState = SymbolState::kPrivate;
     return true;
 }
 
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5ccd47f..34c68d7 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -77,6 +77,7 @@
     bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
     bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
     bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
     bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
     bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
     Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index e32fb5e..84674e8 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -220,6 +220,16 @@
                            kValidNameMangledChars, diag);
 }
 
+bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
+                                            const ResourceId id,
+                                            const ConfigDescription& config,
+                                            const Source& source,
+                                            std::unique_ptr<Value> value,
+                                            IDiagnostics* diag) {
+    return addResourceImpl(name, id, config, source, std::move(value),
+                           kValidNameMangledChars, diag);
+}
+
 bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
                                     const ConfigDescription& config, const Source& source,
                                     std::unique_ptr<Value> value, const char16_t* validChars,
@@ -305,19 +315,25 @@
     return true;
 }
 
-bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
-                               const Source& source, IDiagnostics* diag) {
-    return markPublicImpl(name, resId, source, kValidNameChars, diag);
+bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+                                   const Source& source, SymbolState state, IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
 }
 
-bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                           const Source& source, IDiagnostics* diag) {
-    return markPublicImpl(name, resId, source, kValidNameMangledChars, diag);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                               const Source& source, SymbolState state,
+                                               IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
 }
 
-bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                                   const Source& source, const char16_t* validChars,
-                                   IDiagnostics* diag) {
+bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                                       const Source& source, SymbolState state,
+                                       const char16_t* validChars, IDiagnostics* diag) {
+    if (state == SymbolState::kUndefined) {
+        // Nothing to do.
+        return true;
+    }
+
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
         diag->error(DiagMessage(source)
@@ -371,9 +387,18 @@
         return false;
     }
 
-    type->publicStatus.isPublic = true;
-    entry->publicStatus.isPublic = true;
-    entry->publicStatus.source = source;
+    // Only mark the type state as public, it doesn't care about being private.
+    if (state == SymbolState::kPublic) {
+        type->symbolStatus.state = SymbolState::kPublic;
+    }
+
+    // Downgrading to a private symbol from a public one is not allowed.
+    if (entry->symbolStatus.state != SymbolState::kPublic) {
+        if (entry->symbolStatus.state != state) {
+            entry->symbolStatus.state = state;
+            entry->symbolStatus.source = source;
+        }
+    }
 
     if (resId.isValid()) {
         package->id = resId.packageId();
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 60fed2f..be90936 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -31,11 +31,17 @@
 
 namespace aapt {
 
+enum class SymbolState {
+    kUndefined,
+    kPublic,
+    kPrivate
+};
+
 /**
  * The Public status of a resource.
  */
-struct Public {
-    bool isPublic = false;
+struct Symbol {
+    SymbolState state = SymbolState::kUndefined;
     Source source;
     std::u16string comment;
 };
@@ -71,7 +77,7 @@
      * Whether this resource is public (and must maintain the same
      * entry ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * The resource's values for each configuration.
@@ -100,7 +106,7 @@
      * Whether this type is public (and must maintain the same
      * type ID across builds).
      */
-    Public publicStatus;
+    Symbol symbolStatus;
 
     /**
      * List of resources for this type.
@@ -171,10 +177,15 @@
                                  const Source& source, std::unique_ptr<Value> value,
                                  IDiagnostics* diag);
 
-    bool markPublic(const ResourceNameRef& name, const ResourceId resId, const Source& source,
-                    IDiagnostics* diag);
-    bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                const Source& source, IDiagnostics* diag);
+    bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
+                                 const ConfigDescription& config,
+                                 const Source& source, std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
+
+    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
+                        SymbolState state, IDiagnostics* diag);
+    bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
+                                    const Source& source, SymbolState state, IDiagnostics* diag);
     struct SearchResult {
         ResourceTablePackage* package;
         ResourceTableType* type;
@@ -217,8 +228,9 @@
                          const ConfigDescription& config, const Source& source,
                          std::unique_ptr<Value> value, const char16_t* validChars,
                          IDiagnostics* diag);
-    bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
-                        const Source& source, const char16_t* validChars, IDiagnostics* diag);
+    bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
+                            const Source& source, SymbolState state, const char16_t* validChars,
+                            IDiagnostics* diag);
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index af0afef..38cda09 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -134,7 +134,14 @@
 
 struct Public_entry {
     uint16_t entryId;
-    uint16_t res0;
+
+    enum : uint16_t {
+        kUndefined = 0,
+        kPublic = 1,
+        kPrivate = 2,
+    };
+
+    uint16_t state;
     android::ResStringPool_ref key;
     android::ResStringPool_ref source;
     uint32_t sourceLine;
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 75cbd5d..095552a 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -300,7 +300,7 @@
 
         T* result = buffer->nextBlock<T>();
         ResTable_entry* outEntry = (ResTable_entry*)(result);
-        if (entry->entry->publicStatus.isPublic) {
+        if (entry->entry->symbolStatus.state == SymbolState::kPublic) {
             outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
         }
 
@@ -455,7 +455,7 @@
 
             // Populate the config masks for this entry.
 
-            if (entry->publicStatus.isPublic) {
+            if (entry->symbolStatus.state == SymbolState::kPublic) {
                 configMasks[entry->id.value()] |=
                         util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
             }
@@ -480,17 +480,31 @@
         publicHeader->typeId = type->id.value();
 
         for (ResourceEntry* entry : *sortedEntries) {
-            if (entry->publicStatus.isPublic) {
+            if (entry->symbolStatus.state != SymbolState::kUndefined) {
                 // Write the public status of this entry.
                 Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
                 publicEntry->entryId = util::hostToDevice32(entry->id.value());
                 publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
                         entry->name).getIndex());
                 publicEntry->source.index = util::hostToDevice32(mSourcePool->makeRef(
-                        util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
-                if (entry->publicStatus.source.line) {
+                        util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
+                if (entry->symbolStatus.source.line) {
                     publicEntry->sourceLine = util::hostToDevice32(
-                            entry->publicStatus.source.line.value());
+                            entry->symbolStatus.source.line.value());
+                }
+
+                switch (entry->symbolStatus.state) {
+                case SymbolState::kPrivate:
+                    publicEntry->state = Public_entry::kPrivate;
+                    break;
+
+                case SymbolState::kPublic:
+                    publicEntry->state = Public_entry::kPublic;
+                    break;
+
+                default:
+                    assert(false && "should not serialize any other state");
+                    break;
                 }
 
                 // Don't hostToDevice until the last step.
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index fa321a0..b84f2e0 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -52,7 +52,7 @@
     bool staticLib = false;
     bool verbose = false;
     bool outputToDirectory = false;
-    Maybe<std::string> privateSymbols;
+    Maybe<std::u16string> privateSymbols;
 };
 
 struct LinkContext : public IAaptContext {
@@ -328,13 +328,14 @@
         return true;
     }
 
-    bool writeJavaFile(ResourceTable* table, const StringPiece16& package) {
+    bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
+                       const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
         if (!mOptions.generateJavaClassPath) {
             return true;
         }
 
         std::string outPath = mOptions.generateJavaClassPath.value();
-        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package)));
+        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
         file::mkdirs(outPath);
         file::appendPath(&outPath, "R.java");
 
@@ -344,13 +345,8 @@
             return false;
         }
 
-        JavaClassGeneratorOptions javaOptions;
-        if (mOptions.staticLib) {
-            javaOptions.useFinal = false;
-        }
-
         JavaClassGenerator generator(table, javaOptions);
-        if (!generator.generate(mContext.getCompilationPackage(), &fout)) {
+        if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
             mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
             return false;
         }
@@ -652,8 +648,34 @@
         }
 
         if (mOptions.generateJavaClassPath) {
-            if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) {
-                return 1;
+            JavaClassGeneratorOptions options;
+            if (mOptions.staticLib) {
+                options.useFinal = false;
+            }
+
+            if (mOptions.privateSymbols) {
+                // If we defined a private symbols package, we only emit Public symbols
+                // to the original package, and private and public symbols to the private package.
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
+
+                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mOptions.privateSymbols.value(), options)) {
+                    return 1;
+                }
+
+            } else {
+                // Emit Everything.
+
+                if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
+                                   mContext.getCompilationPackage(), options)) {
+                    return 1;
+                }
             }
         }
 
@@ -680,6 +702,7 @@
 
 int link(const std::vector<StringPiece>& args) {
     LinkOptions options;
+    Maybe<std::string> privateSymbolsPackage;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -698,13 +721,17 @@
             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
                           "private symbols. If not specified, public and private symbols will "
-                          "use the application's package name", &options.privateSymbols)
+                          "use the application's package name", &privateSymbolsPackage)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
         return 1;
     }
 
+    if (privateSymbolsPackage) {
+        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
+    }
+
     LinkCommand cmd = { options };
     return cmd.run(flags.getArgs());
 }
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index db20bcb..5a2f5f0 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -61,7 +61,7 @@
             continue;
         }
 
-        if (!type->publicStatus.isPublic) {
+        if (type->symbolStatus.state != SymbolState::kPublic) {
             // No public attributes, so we can safely leave these private attributes where they are.
             return true;
         }
@@ -71,7 +71,7 @@
 
         moveIf(type->entries, std::back_inserter(privAttrType->entries),
                [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
-                   return !entry->publicStatus.isPublic;
+                   return entry->symbolStatus.state != SymbolState::kPublic;
                });
         break;
     }
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 8173c30..a2f8d19 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -31,10 +31,12 @@
             .addSimple(u"@android:attr/publicB")
             .addSimple(u"@android:attr/privateB")
             .build();
-    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicA"),
-                                  ResourceId(0x01010000), {}, context->getDiagnostics()));
-    ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicB"),
-                                      ResourceId(0x01010002), {}, context->getDiagnostics()));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
+                                      ResourceId(0x01010000), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
+                                      ResourceId(0x01010002), {}, SymbolState::kPublic,
+                                      context->getDiagnostics()));
 
     PrivateAttributeMover mover;
     ASSERT_TRUE(mover.consume(context.get(), table.get()));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index c0356e5..b4fb996 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -249,10 +249,13 @@
     for (auto& package : table->packages) {
         for (auto& type : package->types) {
             for (auto& entry : type->entries) {
-                // A public entry with no values will not be encoded properly.
-                if (entry->publicStatus.isPublic && entry->values.empty()) {
-                    context->getDiagnostics()->error(DiagMessage(entry->publicStatus.source)
-                                                     << "No value for public resource");
+                // Symbol state information may be lost if there is no value for the resource.
+                if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) {
+                    context->getDiagnostics()->error(
+                            DiagMessage(entry->symbolStatus.source)
+                            << "no definition for declared symbol '"
+                            << ResourceNameRef(package->name, type->type, entry->name)
+                            << "'");
                     error = true;
                 }
 
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 0d63b97..db52546 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -69,8 +69,8 @@
 
     for (auto& srcType : srcPackage->types) {
         ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type);
-        if (srcType->publicStatus.isPublic) {
-            if (dstType->publicStatus.isPublic && dstType->id && srcType->id
+        if (srcType->symbolStatus.state == SymbolState::kPublic) {
+            if (dstType->symbolStatus.state == SymbolState::kPublic && dstType->id && srcType->id
                     && dstType->id.value() == srcType->id.value()) {
                 // Both types are public and have different IDs.
                 mContext->getDiagnostics()->error(DiagMessage(src)
@@ -81,7 +81,7 @@
                 continue;
             }
 
-            dstType->publicStatus = std::move(srcType->publicStatus);
+            dstType->symbolStatus = std::move(srcType->symbolStatus);
             dstType->id = srcType->id;
         }
 
@@ -94,20 +94,29 @@
                 dstEntry = dstType->findOrCreateEntry(srcEntry->name);
             }
 
-            if (srcEntry->publicStatus.isPublic) {
-                if (dstEntry->publicStatus.isPublic && dstEntry->id && srcEntry->id
-                        && dstEntry->id.value() != srcEntry->id.value()) {
-                    // Both entries are public and have different IDs.
-                    mContext->getDiagnostics()->error(DiagMessage(src)
-                                                      << "can not merge entry '"
-                                                      << srcEntry->name
-                                                      << "': conflicting public IDs");
-                    error = true;
-                    continue;
+            if (srcEntry->symbolStatus.state != SymbolState::kUndefined) {
+                if (srcEntry->symbolStatus.state == SymbolState::kPublic) {
+                    if (dstEntry->symbolStatus.state == SymbolState::kPublic &&
+                            dstEntry->id && srcEntry->id &&
+                            dstEntry->id.value() != srcEntry->id.value()) {
+                        // Both entries are public and have different IDs.
+                        mContext->getDiagnostics()->error(DiagMessage(src)
+                                                          << "can not merge entry '"
+                                                          << srcEntry->name
+                                                          << "': conflicting public IDs");
+                        error = true;
+                        continue;
+                    }
+
+                    if (srcEntry->id) {
+                        dstEntry->id = srcEntry->id;
+                    }
                 }
 
-                dstEntry->publicStatus = std::move(srcEntry->publicStatus);
-                dstEntry->id = srcEntry->id;
+                if (dstEntry->symbolStatus.state != SymbolState::kPublic &&
+                        dstEntry->symbolStatus.state != srcEntry->symbolStatus.state) {
+                    dstEntry->symbolStatus = std::move(srcEntry->symbolStatus);
+                }
             }
 
             for (ResourceConfigValue& srcValue : srcEntry->values) {
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0d8d8b5..0383c44 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -87,8 +87,9 @@
 
     ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
                                    const ConfigDescription& config, std::unique_ptr<Value> value) {
-        bool result = mTable->addResource(parseNameOrDie(name), id, config, {}, std::move(value),
-                                          &mDiagnostics);
+        ResourceName resName = parseNameOrDie(name);
+        bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+                                                      &mDiagnostics);
         assert(result);
         return *this;
     }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index ac91865..c7a715e 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -429,7 +429,19 @@
             source.line = util::deviceToHost32(entry->sourceLine);
         }
 
-        if (!mTable->markPublicAllowMangled(name, resId, source, mContext->getDiagnostics())) {
+        SymbolState state = SymbolState::kUndefined;
+        switch (util::deviceToHost16(entry->state)) {
+        case Public_entry::kPrivate:
+            state = SymbolState::kPrivate;
+            break;
+
+        case Public_entry::kPublic:
+            state = SymbolState::kPublic;
+            break;
+        }
+
+        if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
+                                                mContext->getDiagnostics())) {
             return false;
         }
 
@@ -564,8 +576,9 @@
         }
 
         if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->markPublicAllowMangled(name, resId, mSource.withLine(0),
-                                                mContext->getDiagnostics())) {
+            if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
+                                                    SymbolState::kPublic,
+                                                    mContext->getDiagnostics())) {
                 return false;
             }
         }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index ff8d6d4d..1970542 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1086,17 +1086,8 @@
     public WifiActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
         if (mService == null) return null;
         try {
-            WifiActivityEnergyInfo record;
-            if (!isEnhancedPowerReportingSupported()) {
-                return null;
-            }
             synchronized(this) {
-                record = mService.reportActivityInfo();
-                if (record != null && record.isValid()) {
-                    return record;
-                } else {
-                    return null;
-                }
+                return mService.reportActivityInfo();
             }
         } catch (RemoteException e) {
             Log.e(TAG, "getControllerActivityEnergyInfo: " + e);
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index a65f250..c26ca6e 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -709,6 +709,7 @@
         validateChannel();
         HotlistSettings settings = new HotlistSettings();
         settings.bssidInfos = bssidInfos;
+        settings.apLostThreshold = apLostThreshold;
         sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
     }