Merge "Frameworks/base: Change android.util.Log multiline logging"
diff --git a/api/current.txt b/api/current.txt
index e4962d8..36430bb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3538,6 +3538,7 @@
     method public deprecated void setTitleColor(int);
     method public void setVisible(boolean);
     method public final void setVolumeControlStream(int);
+    method public void setVrMode(boolean);
     method public boolean shouldShowRequestPermissionRationale(java.lang.String);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
@@ -8993,6 +8994,7 @@
     field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
     field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
     field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+    field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
     field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
     field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
     field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9556,6 +9558,7 @@
     field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+    field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -29311,6 +29314,7 @@
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
     field public static final java.lang.String IS_READ = "is_read";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String LIMIT_PARAM_KEY = "limit";
     field public static final int MISSED_TYPE = 3; // 0x3
     field public static final java.lang.String NEW = "new";
@@ -31581,6 +31585,7 @@
 
   public static final class VoicemailContract.Status implements android.provider.BaseColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int);
     field public static final java.lang.String CONFIGURATION_STATE = "configuration_state";
     field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2
     field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1
@@ -31597,6 +31602,9 @@
     field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id";
+    field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied";
+    field public static final java.lang.String QUOTA_TOTAL = "quota_total";
+    field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
@@ -31613,6 +31621,7 @@
     field public static final java.lang.String HAS_CONTENT = "has_content";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String MIME_TYPE = "mime_type";
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
@@ -33252,6 +33261,7 @@
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public final void setNotificationsShown(java.lang.String[]);
+    field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
     field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/api/system-current.txt b/api/system-current.txt
index 586ea06..1a15555 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3643,6 +3643,7 @@
     method public deprecated void setTitleColor(int);
     method public void setVisible(boolean);
     method public final void setVolumeControlStream(int);
+    method public void setVrMode(boolean);
     method public boolean shouldShowRequestPermissionRationale(java.lang.String);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
@@ -9261,6 +9262,7 @@
     field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
     field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
     field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+    field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
     field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
     field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
     field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9872,6 +9874,7 @@
     field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+    field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -31321,6 +31324,7 @@
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
     field public static final java.lang.String IS_READ = "is_read";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String LIMIT_PARAM_KEY = "limit";
     field public static final int MISSED_TYPE = 3; // 0x3
     field public static final java.lang.String NEW = "new";
@@ -33724,6 +33728,7 @@
 
   public static final class VoicemailContract.Status implements android.provider.BaseColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int);
     field public static final java.lang.String CONFIGURATION_STATE = "configuration_state";
     field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2
     field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1
@@ -33740,6 +33745,9 @@
     field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id";
+    field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied";
+    field public static final java.lang.String QUOTA_TOTAL = "quota_total";
+    field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
@@ -33756,6 +33764,7 @@
     field public static final java.lang.String HAS_CONTENT = "has_content";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String MIME_TYPE = "mime_type";
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
@@ -35400,6 +35409,7 @@
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void setOnNotificationPostedTrim(int);
     method public void unregisterAsSystemService() throws android.os.RemoteException;
+    field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
     field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/api/test-current.txt b/api/test-current.txt
index eccd8b0..3531e34 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3538,6 +3538,7 @@
     method public deprecated void setTitleColor(int);
     method public void setVisible(boolean);
     method public final void setVolumeControlStream(int);
+    method public void setVrMode(boolean);
     method public boolean shouldShowRequestPermissionRationale(java.lang.String);
     method public boolean shouldUpRecreateTask(android.content.Intent);
     method public boolean showAssist(android.os.Bundle);
@@ -8993,6 +8994,7 @@
     field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8
     field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000
     field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4
+    field public static final int FLAG_ENABLE_VR_MODE = 32768; // 0x8000
     field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20
     field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100
     field public static final int FLAG_FINISH_ON_TASK_LAUNCH = 2; // 0x2
@@ -9556,6 +9558,7 @@
     field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host";
     field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+    field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode";
     field public static final java.lang.String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview";
     field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
@@ -29313,6 +29316,7 @@
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
     field public static final java.lang.String IS_READ = "is_read";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String LIMIT_PARAM_KEY = "limit";
     field public static final int MISSED_TYPE = 3; // 0x3
     field public static final java.lang.String NEW = "new";
@@ -31583,6 +31587,7 @@
 
   public static final class VoicemailContract.Status implements android.provider.BaseColumns {
     method public static android.net.Uri buildSourceUri(java.lang.String);
+    method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int);
     field public static final java.lang.String CONFIGURATION_STATE = "configuration_state";
     field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2
     field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1
@@ -31599,6 +31604,9 @@
     field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
     field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id";
+    field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied";
+    field public static final java.lang.String QUOTA_TOTAL = "quota_total";
+    field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff
     field public static final java.lang.String SETTINGS_URI = "settings_uri";
     field public static final java.lang.String SOURCE_PACKAGE = "source_package";
     field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri";
@@ -31615,6 +31623,7 @@
     field public static final java.lang.String HAS_CONTENT = "has_content";
     field public static final java.lang.String IS_READ = "is_read";
     field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
+    field public static final java.lang.String LAST_MODIFIED = "last_modified";
     field public static final java.lang.String MIME_TYPE = "mime_type";
     field public static final java.lang.String NUMBER = "number";
     field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
@@ -33254,6 +33263,7 @@
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public final void setNotificationsShown(java.lang.String[]);
+    field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications";
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
     field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 72f8c77..3e6b595 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -31,9 +31,11 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.View.OnFocusChangeListener;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewHierarchyEncoder;
+import android.view.ViewParent;
 import android.view.Window;
 import android.widget.SpinnerAdapter;
 import java.lang.annotation.Retention;
@@ -1071,6 +1073,62 @@
     }
 
     /**
+     * Attempts to move focus to the ActionBar if it does not already contain the focus.
+     *
+     * @return {@code true} if focus changes or {@code false} if focus doesn't change.
+     * @hide
+     */
+    public boolean requestFocus() {
+        return false;
+    }
+
+    /**
+     * Common implementation for requestFocus that takes in the Toolbar and moves focus
+     * to the contents. This makes the ViewGroups containing the toolbar allow focus while it stays
+     * in the ActionBar and then prevents it again once it leaves.
+     *
+     * @param viewGroup The toolbar ViewGroup
+     * @return {@code true} if focus changes or {@code false} if focus doesn't change.
+     * @hide
+     */
+    protected boolean requestFocus(ViewGroup viewGroup) {
+        if (viewGroup != null && !viewGroup.hasFocus()) {
+            final ViewGroup toolbar = viewGroup.getTouchscreenBlocksFocus() ? viewGroup : null;
+            ViewParent parent = viewGroup.getParent();
+            ViewGroup container = null;
+            while (parent != null && parent instanceof ViewGroup) {
+                final ViewGroup vgParent = (ViewGroup) parent;
+                if (vgParent.getTouchscreenBlocksFocus()) {
+                    container = vgParent;
+                    break;
+                }
+                parent = vgParent.getParent();
+            }
+            if (container != null) {
+                container.setTouchscreenBlocksFocus(false);
+            }
+            if (toolbar != null) {
+                toolbar.setTouchscreenBlocksFocus(false);
+            }
+            viewGroup.requestFocus();
+            final View focused = viewGroup.findFocus();
+            if (focused != null) {
+                focused.setOnFocusChangeListener(new FollowOutOfActionBar(viewGroup,
+                        container, toolbar));
+            } else {
+                if (container != null) {
+                    container.setTouchscreenBlocksFocus(true);
+                }
+                if (toolbar != null) {
+                    toolbar.setTouchscreenBlocksFocus(true);
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Listener interface for ActionBar navigation events.
      *
      * @deprecated Action bar navigation modes are deprecated and not supported by inline
@@ -1388,4 +1446,43 @@
             encoder.addProperty("gravity", gravity);
         }
     }
+
+    /**
+     * Tracks the focused View until it leaves the ActionBar, then it resets the
+     * touchscreenBlocksFocus value.
+     */
+    private static class FollowOutOfActionBar implements OnFocusChangeListener, Runnable {
+        private final ViewGroup mFocusRoot;
+        private final ViewGroup mContainer;
+        private final ViewGroup mToolbar;
+
+        public FollowOutOfActionBar(ViewGroup focusRoot, ViewGroup container, ViewGroup toolbar) {
+            mContainer = container;
+            mToolbar = toolbar;
+            mFocusRoot = focusRoot;
+        }
+
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            if (!hasFocus) {
+                v.setOnFocusChangeListener(null);
+                final View focused = mFocusRoot.findFocus();
+                if (focused != null) {
+                    focused.setOnFocusChangeListener(this);
+                } else {
+                    mFocusRoot.post(this);
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            if (mContainer != null) {
+                mContainer.setTouchscreenBlocksFocus(true);
+            }
+            if (mToolbar != null) {
+                mToolbar.setTouchscreenBlocksFocus(true);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4ed2fd7..6d72059 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -816,6 +816,7 @@
     SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
 
     private boolean mHasCurrentPermissionsRequest;
+    private boolean mEatKeyUpEvent;
 
     /** Return the intent that started this activity. */
     public Intent getIntent() {
@@ -2827,9 +2828,25 @@
 
         // Let action bars open menus in response to the menu key prioritized over
         // the window handling it
-        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_MENU &&
                 mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
             return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+            // Capture the Alt-up and send focus to the ActionBar
+            final int action = event.getAction();
+            if (action == KeyEvent.ACTION_DOWN) {
+                if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
+                    final ActionBar actionBar = getActionBar();
+                    if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
+                        mEatKeyUpEvent = true;
+                        return true;
+                    }
+                }
+            } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
+                mEatKeyUpEvent = false;
+                return true;
+            }
         }
 
         Window win = getWindow();
@@ -6021,6 +6038,22 @@
     }
 
     /**
+     * Enable or disable virtual reality (VR) mode.
+     *
+     * <p>VR mode is a hint to Android system services to switch to modes optimized for
+     * high-performance stereoscopic rendering.</p>
+     *
+     * @param enabled {@code true} to enable this mode.
+     */
+    public void setVrMode(boolean enabled) {
+        try {
+            ActivityManagerNative.getDefault().setVrMode(mToken, enabled);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
      * Start an action mode of the default type {@link ActionMode#TYPE_PRIMARY}.
      *
      * @param callback Callback that will manage lifecycle events for this action mode
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 681ed5b..8637dde 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1237,6 +1237,18 @@
     public static final int RECENT_IGNORE_HOME_STACK_TASKS = 0x0008;
 
     /**
+     * Ignores all tasks that are on the docked stack.
+     * @hide
+     */
+    public static final int RECENT_INGORE_DOCKED_STACK_TASKS = 0x0010;
+
+    /**
+     * Ignores all tasks that are on the pinned stack.
+     * @hide
+     */
+    public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020;
+
+    /**
      * <p></p>Return a list of the tasks that the user has recently launched, with
      * the most recent being first and older ones after in order.
      *
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d724823..1b08273 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2766,6 +2766,14 @@
             reply.writeNoException();
             return true;
         }
+        case SET_VR_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final IBinder token = data.readStrongBinder();
+            final boolean enable = data.readInt() == 1;
+            setVrMode(token, enable);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5896,6 +5904,18 @@
         return res;
     }
 
+    public void setVrMode(IBinder token, boolean enabled) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(enabled ? 1 : 0);
+        mRemote.transact(SET_VR_MODE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     @Override
     public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 20f3495..c6cc452 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -31,6 +31,7 @@
 import android.view.ViewGroupOverlay;
 import android.view.ViewTreeObserver;
 import android.view.Window;
+import android.view.accessibility.AccessibilityEvent;
 
 import java.util.ArrayList;
 
@@ -500,6 +501,10 @@
     @Override
     protected void onTransitionsComplete() {
         moveSharedElementsFromOverlay();
+        final ViewGroup decorView = getDecor();
+        if (decorView != null) {
+            decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        }
     }
 
     private void sharedElementTransitionStarted() {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index f91a0be..0ecf223 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -552,6 +552,8 @@
 
     public void enterPictureInPictureMode(IBinder token) throws RemoteException;
 
+    public void setVrMode(IBinder token, boolean enabled) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -917,4 +919,5 @@
     int IN_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 353;
     int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
     int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
+    int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 356;
 }
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index bce5bf3..abe9822 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -55,6 +55,7 @@
     void setTopicImportance(String pkg, int uid, in Notification.Topic topic, int importance);
     int getTopicImportance(String pkg, int uid, in Notification.Topic topic);
     void setAppImportance(String pkg, int uid, int importance);
+    boolean doesAppUseTopics(String pkg, int uid);
 
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index cbce22c..6bf3fab 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -131,6 +131,18 @@
     public static final int HEADSET_CLIENT = 16;
 
     /**
+     * HID Profile
+     * @hide
+     */
+    public static final int HID = 17;
+
+    /**
+     * HDP Profile
+     * @hide
+     */
+    public static final int HDP = 18;
+
+    /**
      * Default priority for devices that we try to auto-connect to and
      * and allow incoming connections for the profile
      * @hide
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0cb0e9f..326735e 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -276,6 +276,12 @@
      */
     public static final int FLAG_RESUME_WHILE_PAUSING = 0x4000;
     /**
+     * Bit in {@link #flags} indicating that this activity should be run with VR mode enabled.
+     *
+     * {@see android.app.Activity#setVrMode(boolean)}.
+     */
+    public static final int FLAG_ENABLE_VR_MODE = 0x8000;
+    /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
      * android.R.attr#systemUserOnly attribute.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 40bcc7e..3235bcf 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1830,6 +1830,16 @@
     public static final String FEATURE_MIDI = "android.software.midi";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device implements a an optimized mode for virtual reality (VR) applications that handles
+     * stereoscopic rendering of notifications, and may potentially also include optimizations to
+     * reduce latency in the graphics, display, and sensor stacks.  Presence of this feature
+     * also indicates that the VrCore library is included on this device.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+    /**
      * Action to external storage service to clean out removed apps.
      * @hide
      */
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a01d34a..2ca9ab8a 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -37,6 +37,7 @@
     private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
     private static final String ENV_DOWNLOAD_CACHE = "DOWNLOAD_CACHE";
     private static final String ENV_OEM_ROOT = "OEM_ROOT";
+    private static final String ENV_ODM_ROOT = "ODM_ROOT";
     private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
 
     /** {@hide} */
@@ -56,6 +57,7 @@
     private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
     private static final File DIR_DOWNLOAD_CACHE = getDirectory(ENV_DOWNLOAD_CACHE, "/cache");
     private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
+    private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm");
     private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
 
     private static UserEnvironment sCurrentUser;
@@ -156,6 +158,16 @@
     }
 
     /**
+     * Return root directory of the "odm" partition holding ODM customizations,
+     * if any. If present, the partition is mounted read-only.
+     *
+     * @hide
+     */
+    public static File getOdmDirectory() {
+        return DIR_ODM_ROOT;
+    }
+
+    /**
      * Return root directory of the "vendor" partition that holds vendor-provided
      * software that should persist across simple reflashing of the "system" partition.
      * @hide
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 1d4d572..6a5d857c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -421,6 +421,13 @@
         public static final String ADD_FOR_ALL_USERS = "add_for_all_users";
 
         /**
+         * The date the row is last inserted, updated, or marked as deleted, in milliseconds
+         * since the epoch. Read only.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String LAST_MODIFIED = "last_modified";
+
+        /**
          * 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.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9508077..b883f9c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6133,6 +6133,13 @@
         public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
 
         /**
+         * A Long representing a bitmap of profiles that should be disabled when bluetooth starts.
+         * See {@link android.bluetooth.BluetoothProfile}.
+         * {@hide}
+         */
+        public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles";
+
+        /**
          * The policy for deciding when Wi-Fi should go to sleep (which will in
          * turn switch to using the mobile data as an Internet connection).
          * <p>
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 76eaea9..24683cb 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -245,6 +245,13 @@
         public static final String DELETED = "deleted";
 
         /**
+         * The date the row is last inserted, updated, or marked as deleted, in milliseconds
+         * since the epoch. Read only.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String LAST_MODIFIED = "last_modified";
+
+        /**
          * A convenience method to build voicemail URI specific to a source package by appending
          * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
          */
@@ -449,6 +456,26 @@
         public static final int NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING = 2;
 
         /**
+         * Amount of resource that is used by existing voicemail in the visual voicemail inbox,
+         * or {@link #QUOTA_UNAVAILABLE}. Unit is not specified.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String QUOTA_OCCUPIED = "quota_occupied";
+
+        /**
+         * Total resource in the visual voicemail inbox that can be used, or
+         * {@link #QUOTA_UNAVAILABLE}. Unit is not specified.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String QUOTA_TOTAL = "quota_total";
+
+        /**
+         * Value for {@link #QUOTA_OCCUPIED} and {@link #QUOTA_TOTAL} to indicate that no
+         * information is available.
+         */
+        public static final int QUOTA_UNAVAILABLE = -1;
+
+        /**
          * A convenience method to build status URI specific to a source package by appending
          * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
          */
@@ -488,6 +515,39 @@
         }
 
         /**
+         * A helper method to set the quota of a voicemail source. Unit is unspecified.
+         *
+         * @param context The context from the package calling the method. This will be the source.
+         * @param accountHandle The handle for the account the source is associated with.
+         * @param occupied See {@link Status#QUOTA_OCCUPIED}
+         * @param total See {@link Status#QUOTA_TOTAL}
+         */
+        public static void setQuota(Context context, PhoneAccountHandle accountHandle, int occupied,
+                int total) {
+            if (occupied == QUOTA_UNAVAILABLE && total == QUOTA_UNAVAILABLE) {
+                return;
+            }
+            ContentValues values = new ContentValues();
+            values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME,
+                    accountHandle.getComponentName().flattenToString());
+            values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId());
+            if (occupied != QUOTA_UNAVAILABLE) {
+                values.put(Status.QUOTA_OCCUPIED,occupied);
+            }
+            if (total != QUOTA_UNAVAILABLE) {
+                values.put(Status.QUOTA_TOTAL,total);
+            }
+
+            ContentResolver contentResolver = context.getContentResolver();
+            Uri statusUri = buildSourceUri(context.getPackageName());
+            if (isStatusPresent(contentResolver, statusUri)) {
+                contentResolver.update(statusUri, values, null, null);
+            } else {
+                contentResolver.insert(statusUri, values);
+            }
+        }
+
+        /**
          * Determines if a voicemail source exists in the status table.
          *
          * @param contentResolver A content resolver constructed from the appropriate context.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 232d4fe..1e62edc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -60,6 +60,16 @@
  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
  *     &lt;/intent-filter>
  * &lt;/service></pre>
+ * <p> Typically, while enabled in user settings, this service will be bound on boot or when a
+ * settings change occurs that could affect whether this service should run.  However, for some
+ * system usage modes, the you may instead specify that this service is instead bound and unbound
+ * in response to mode changes by including a category in the intent filter.  Currently
+ * supported categories are:
+ * <ul>
+ *   <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled
+ *   VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li>
+ * </ul>
+ * </p>
  */
 public abstract class NotificationListenerService extends Service {
     // TAG = "NotificationListenerService[MySubclass]"
@@ -162,6 +172,17 @@
             = "android.service.notification.NotificationListenerService";
 
     /**
+     * If this category is declared in the application manifest for a service of this type, this
+     * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather
+     * than the normal lifecycle for a notification service.
+     *
+     * {@see android.app.Activity#setVrMode(boolean)}
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
+    public static final String CATEGORY_VR_NOTIFICATIONS =
+        "android.intent.category.vr.notifications";
+
+    /**
      * Implement this method to learn about new notifications as they are posted by apps.
      *
      * @param sbn A data structure encapsulating the original {@link android.app.Notification}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index faf2640..0ed007d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -197,6 +197,10 @@
     // so the window should no longer be active.
     boolean mStopped = false;
 
+    // Set to true if the owner of this window is in ambient mode,
+    // which means it won't receive input events.
+    boolean mIsAmbientMode = false;
+
     // Set to true to stop input during an Activity Transition.
     boolean mPausedForTransition = false;
 
@@ -1032,6 +1036,10 @@
         }
     }
 
+    public void setIsAmbientMode(boolean ambient) {
+        mIsAmbientMode = ambient;
+    }
+
     void setWindowStopped(boolean stopped) {
         if (mStopped != stopped) {
             mStopped = stopped;
@@ -3685,7 +3693,7 @@
                 return true;
             } else if ((!mAttachInfo.mHasWindowFocus
                     && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped
-                    || (mPausedForTransition && !isBack(q.mEvent))) {
+                    || mIsAmbientMode || (mPausedForTransition && !isBack(q.mEvent))) {
                 // This is a focus event and the window doesn't currently have input focus or
                 // has stopped. This could be an event that came back from the previous stage
                 // but the window has lost focus or stopped in the meantime.
@@ -5511,6 +5519,8 @@
                 writer.println(mProcessInputEventsScheduled);
         writer.print(innerPrefix); writer.print("mTraversalScheduled=");
                 writer.print(mTraversalScheduled);
+        writer.print(innerPrefix); writer.print("mIsAmbientMode=");
+                writer.print(mIsAmbientMode);
         if (mTraversalScheduled) {
             writer.print(" (barrier="); writer.print(mTraversalBarrier); writer.println(")");
         } else {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2e884cc..251f4c8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1183,6 +1183,13 @@
         public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
 
         /**
+         * Flag to indicate that this child window should always be laid-out in the parent
+         * frame regardless of the current windowing mode configuration.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index f4c343a..19e290b 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 
 import com.android.internal.R;
@@ -1313,7 +1314,8 @@
             p.width = mLastWidth = mWidth;
         }
 
-        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
+                | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 
         // Used for debugging.
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 9d12803..4b6e7e4 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -29,10 +29,14 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowCallbackWrapper;
 import android.widget.SpinnerAdapter;
 import android.widget.Toolbar;
+
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.widget.DecorToolbar;
@@ -499,6 +503,12 @@
         }
     }
 
+    /** @hide */
+    @Override
+    public boolean requestFocus() {
+        return requestFocus(mDecorToolbar.getViewGroup());
+    }
+
     private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
         public ToolbarCallbackWrapper(Window.Callback wrapped) {
             super(wrapped);
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 05cfd81..c6bf1b4 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -18,6 +18,8 @@
 
 import android.animation.ValueAnimator;
 import android.content.res.TypedArray;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.widget.Toolbar;
 
@@ -950,6 +952,12 @@
         return false;
     }
 
+    /** @hide */
+    @Override
+    public boolean requestFocus() {
+        return requestFocus(mDecorToolbar.getViewGroup());
+    }
+
     /**
      * @hide
      */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 313987a..c03d471 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2370,7 +2370,7 @@
     <!-- Set initial MaxRetry value for operators -->
     <integer name="config_mdc_initial_max_retry">1</integer>
 
-    <!-- The OEM specified sensor type for the gesture to launch the camear app. -->
+    <!-- The OEM specified sensor type for the gesture to launch the camera app. -->
     <integer name="config_cameraLaunchGestureSensorType">-1</integer>
     <!-- The OEM specified sensor string type for the gesture to launch camera app, this value
          must match the value of config_cameraLaunchGestureSensorType in OEM's HAL -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4843879..a127d94 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3966,25 +3966,6 @@
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
     <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
 
-
-    <!-- [CHAR LIMIT=100] Notification importance slider title -->
-    <string name="notification_importance_title">Importance</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
-    <string name="notification_importance_blocked">Blocked: Never show these notifications</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance slider: low importance level description -->
-    <string name="notification_importance_low">Low: Silently show at the bottom of the notification list</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance slider: normal importance level description -->
-    <string name="notification_importance_default">Normal: Silently show these notifications</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance slider: high importance level description -->
-    <string name="notification_importance_high">High: Show at the top of the notifications list and make sound</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance slider: max importance level description -->
-    <string name="notification_importance_max">Urgent: Peek onto the screen and make sound</string>
-
     <!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes_summary">
         <item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f257f14..16c610e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2091,12 +2091,6 @@
   <java-symbol type="array" name="config_system_condition_providers" />
   <java-symbol type="string" name="muted_by" />
   <java-symbol type="string" name="zen_mode_alarm" />
-  <java-symbol type="string" name="notification_importance_blocked" />
-  <java-symbol type="string" name="notification_importance_low" />
-  <java-symbol type="string" name="notification_importance_default" />
-  <java-symbol type="string" name="notification_importance_high" />
-  <java-symbol type="string" name="notification_importance_max" />
-  <java-symbol type="string" name="notification_importance_title" />
 
   <java-symbol type="string" name="select_day" />
   <java-symbol type="string" name="select_year" />
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index bdc8f68..ad9559f 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -91,8 +91,7 @@
 
     MergingOpBatch(batchid_t batchId, BakedOpState* op)
             : BatchBase(batchId, op, true)
-            , mClipSideFlags(op->computedState.clipSideFlags)
-            , mClipRect(op->computedState.clipRect) {
+            , mClipSideFlags(op->computedState.clipSideFlags) {
     }
 
     /*
@@ -194,22 +193,17 @@
         mBounds.unionWith(op->computedState.clippedBounds);
         mOps.push_back(op);
 
-        const int newClipSideFlags = op->computedState.clipSideFlags;
-        mClipSideFlags |= newClipSideFlags;
-
-        const Rect& opClip = op->computedState.clipRect;
-        if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left;
-        if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top;
-        if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right;
-        if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom;
+        // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat
+        // check, and doesn't extend past a side of the clip that's in use by the merged batch.
+        // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect.
+        mClipSideFlags |= op->computedState.clipSideFlags;
     }
 
     int getClipSideFlags() const { return mClipSideFlags; }
-    const Rect& getClipRect() const { return mClipRect; }
+    const Rect& getClipRect() const { return mBounds; }
 
 private:
     int mClipSideFlags;
-    Rect mClipRect;
 };
 
 OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index 288f8af..b28e436 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -222,6 +222,46 @@
             << "Expect number of ops = 2 * loop count";
 }
 
+TEST(OpReorderer, clippedMerging) {
+    class ClippedMergingTestRenderer : public TestRendererBase {
+    public:
+        void onMergedBitmapOps(const MergedBakedOpList& opList) override {
+            EXPECT_EQ(0, mIndex);
+            mIndex += opList.count;
+            EXPECT_EQ(4u, opList.count);
+            EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip);
+            EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right,
+                    opList.clipSideFlags);
+        }
+    };
+    auto node = TestUtils::createNode(0, 0, 100, 100,
+            [](RenderProperties& props, TestCanvas& canvas) {
+        SkBitmap bitmap = TestUtils::createSkBitmap(20, 20);
+
+        // left side clipped (to inset left half)
+        canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op);
+        canvas.drawBitmap(bitmap, 0, 40, nullptr);
+
+        // top side clipped (to inset top half)
+        canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op);
+        canvas.drawBitmap(bitmap, 40, 0, nullptr);
+
+        // right side clipped (to inset right half)
+        canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op);
+        canvas.drawBitmap(bitmap, 80, 40, nullptr);
+
+        // bottom not clipped, just abutting (inset bottom half)
+        canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op);
+        canvas.drawBitmap(bitmap, 40, 70, nullptr);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            createSyncedNodeList(node), sLightCenter);
+    ClippedMergingTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
 TEST(OpReorderer, textMerging) {
     class TextMergingTestRenderer : public TestRendererBase {
     public:
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 63a519f..2c311a7 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -493,30 +493,6 @@
     }
 
     @VisibleForTesting
-    public File createFile(String rootId, String path, String mimeType, byte[] content)
-            throws FileNotFoundException, IOException {
-        Log.d(TAG, "Creating test file " + rootId + ":" + path);
-        StubDocument root = mRoots.get(rootId).document;
-        if (root == null) {
-            throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
-        }
-        final File file = new File(root.file, path.substring(1));
-        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
-            if (!file.mkdirs()) {
-                throw new FileNotFoundException("Couldn't create directory " + file.getPath());
-            }
-        } else {
-            if (!file.createNewFile()) {
-                throw new FileNotFoundException("Couldn't create file " + file.getPath());
-            }
-            try (final FileOutputStream fout = new FileOutputStream(file)) {
-                fout.write(content);
-            }
-        }
-        return file;
-    }
-
-    @VisibleForTesting
     public Uri createRegularFile(String rootId, String path, String mimeType, byte[] content)
             throws FileNotFoundException, IOException {
         final File file = createFile(rootId, path, mimeType, content);
@@ -560,6 +536,29 @@
         return found.file;
     }
 
+    private File createFile(String rootId, String path, String mimeType, byte[] content)
+            throws FileNotFoundException, IOException {
+        Log.d(TAG, "Creating test file " + rootId + ":" + path);
+        StubDocument root = mRoots.get(rootId).document;
+        if (root == null) {
+            throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
+        }
+        final File file = new File(root.file, path.substring(1));
+        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
+            if (!file.mkdirs()) {
+                throw new FileNotFoundException("Couldn't create directory " + file.getPath());
+            }
+        } else {
+            if (!file.createNewFile()) {
+                throw new FileNotFoundException("Couldn't create file " + file.getPath());
+            }
+            try (final FileOutputStream fout = new FileOutputStream(file)) {
+                fout.write(content);
+            }
+        }
+        return file;
+    }
+
     final static class RootInfo {
         public final String name;
         public final StubDocument document;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index cb49535e..ac47067 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -66,10 +66,13 @@
         database.beginTransaction();
         try {
             final ContentValues[] valuesList = new ContentValues[1];
+            final ContentValues[] extraValuesList = new ContentValues[1];
             valuesList[0] = new ContentValues();
-            MtpDatabase.getDeviceDocumentValues(valuesList[0], device);
+            extraValuesList[0] = new ContentValues();
+            MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device);
             final boolean changed = putDocuments(
                     valuesList,
+                    extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                     EMPTY_ARGS,
                     /* heuristic */ false,
@@ -88,7 +91,7 @@
      * @param roots List of root information.
      * @return If roots are added or removed from the database.
      */
-    synchronized boolean putRootDocuments(
+    synchronized boolean putStorageDocuments(
             String parentDocumentId, Resources resources, MtpRoot[] roots) {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
@@ -109,36 +112,21 @@
                     throw new Error("Unexpected map mode.");
             }
             final ContentValues[] valuesList = new ContentValues[roots.length];
+            final ContentValues[] extraValuesList = new ContentValues[roots.length];
             for (int i = 0; i < roots.length; i++) {
                 valuesList[i] = new ContentValues();
+                extraValuesList[i] = new ContentValues();
                 MtpDatabase.getStorageDocumentValues(
-                        valuesList[i], resources, parentDocumentId, roots[i]);
+                        valuesList[i], extraValuesList[i], resources, parentDocumentId, roots[i]);
             }
             final boolean changed = putDocuments(
                     valuesList,
+                    extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + "=?",
                     strings(parentDocumentId),
                     heuristic,
                     mapColumn);
-            final ContentValues values = new ContentValues();
-            int i = 0;
-            for (final MtpRoot root : roots) {
-                // Use the same value for the root ID and the corresponding document ID.
-                final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
-                // If it fails to insert/update documents, the document ID will be set with -1.
-                // In this case we don't insert/update root extra information neither.
-                if (documentId == null) {
-                    continue;
-                }
-                values.put(Root.COLUMN_ROOT_ID, documentId);
-                values.put(
-                        Root.COLUMN_FLAGS,
-                        Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
-                values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
-                values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
-                values.put(Root.COLUMN_MIME_TYPES, "");
-                database.replace(TABLE_ROOT_EXTRA, null, values);
-            }
+
             database.setTransactionSuccessful();
             return changed;
         } finally {
@@ -176,6 +164,7 @@
         }
         putDocuments(
                 valuesList,
+                null,
                 COLUMN_PARENT_DOCUMENT_ID + "=?",
                 strings(parentId),
                 heuristic,
@@ -257,6 +246,7 @@
      * rows. If the methods adds rows to database, it updates valueList with correct document ID.
      *
      * @param valuesList Values for documents to be stored in the database.
+     * @param rootExtraValuesList Values for root extra to be stored in the database.
      * @param selection SQL where closure to select rows that shares the same parent.
      * @param args Argument for selection SQL.
      * @param heuristic Whether the mapping mode is heuristic.
@@ -264,6 +254,7 @@
      */
     private boolean putDocuments(
             ContentValues[] valuesList,
+            @Nullable ContentValues[] rootExtraValuesList,
             String selection,
             String[] args,
             boolean heuristic,
@@ -272,7 +263,14 @@
         boolean added = false;
         database.beginTransaction();
         try {
-            for (final ContentValues values : valuesList) {
+            for (int i = 0; i < valuesList.length; i++) {
+                final ContentValues values = valuesList[i];
+                final ContentValues rootExtraValues;
+                if (rootExtraValuesList != null) {
+                    rootExtraValues = rootExtraValuesList[i];
+                } else {
+                    rootExtraValues = null;
+                }
                 final Cursor candidateCursor = database.query(
                         TABLE_DOCUMENTS,
                         strings(Document.COLUMN_DOCUMENT_ID),
@@ -290,25 +288,26 @@
                     final long rowId;
                     if (candidateCursor.getCount() == 0) {
                         rowId = database.insert(TABLE_DOCUMENTS, null, values);
-                        if (rowId == -1) {
-                            throw new SQLiteException("Failed to put a document into database.");
-                        }
                         added = true;
                     } else if (!heuristic) {
                         candidateCursor.moveToNext();
-                        final String documentId = candidateCursor.getString(0);
-                        rowId = database.update(
+                        rowId = candidateCursor.getLong(0);
+                        database.update(
                                 TABLE_DOCUMENTS,
                                 values,
                                 SELECTION_DOCUMENT_ID,
-                                strings(documentId));
+                                strings(rowId));
                     } else {
                         values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
-                        rowId = database.insert(TABLE_DOCUMENTS, null, values);
+                        rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values);
                     }
                     // Document ID is a primary integer key of the table. So the returned row
                     // IDs should be same with the document ID.
                     values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+                    if (rootExtraValues != null) {
+                        rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId);
+                        database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues);
+                    }
                 } finally {
                     candidateCursor.close();
                 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 10941eb..112914e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -23,6 +23,9 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -106,17 +109,93 @@
      * @return Database cursor.
      */
     Cursor queryRoots(String[] columnNames) {
-        final SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
-        builder.setTables(JOIN_ROOTS);
-        builder.setProjectionMap(COLUMN_MAP_ROOTS);
-        return builder.query(
-                mDatabase,
-                columnNames,
-                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?",
-                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_STORAGE),
+        final String selection =
+                COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?";
+        final Cursor deviceCursor = mDatabase.query(
+                TABLE_DOCUMENTS,
+                strings(COLUMN_DEVICE_ID),
+                selection,
+                strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_DEVICE),
+                COLUMN_DEVICE_ID,
                 null,
                 null,
                 null);
+
+        try {
+            final SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
+            builder.setTables(JOIN_ROOTS);
+            builder.setProjectionMap(COLUMN_MAP_ROOTS);
+            final MatrixCursor result = new MatrixCursor(columnNames);
+            final ContentValues values = new ContentValues();
+
+            while (deviceCursor.moveToNext()) {
+                final int deviceId = deviceCursor.getInt(0);
+                final Cursor storageCursor = builder.query(
+                        mDatabase,
+                        columnNames,
+                        selection + " AND " + COLUMN_DEVICE_ID + " = ?",
+                        strings(ROW_STATE_VALID,
+                                ROW_STATE_INVALIDATED,
+                                DOCUMENT_TYPE_STORAGE,
+                                deviceId),
+                        null,
+                        null,
+                        null);
+                try {
+                    values.clear();
+                    if (storageCursor.getCount() == 1) {
+                        storageCursor.moveToNext();
+                        DatabaseUtils.cursorRowToContentValues(storageCursor, values);
+                    } else {
+                        final Cursor cursor = builder.query(
+                                mDatabase,
+                                columnNames,
+                                selection + " AND " + COLUMN_DEVICE_ID + " = ?",
+                                strings(ROW_STATE_VALID,
+                                        ROW_STATE_INVALIDATED,
+                                        DOCUMENT_TYPE_DEVICE,
+                                        deviceId),
+                                null,
+                                null,
+                                null);
+                        try {
+                            cursor.moveToNext();
+                            DatabaseUtils.cursorRowToContentValues(cursor, values);
+                        } finally {
+                            cursor.close();
+                        }
+
+                        long capacityBytes = 0;
+                        long availableBytes = 0;
+                        int capacityIndex = cursor.getColumnIndex(Root.COLUMN_CAPACITY_BYTES);
+                        int availableIndex = cursor.getColumnIndex(Root.COLUMN_AVAILABLE_BYTES);
+                        while (storageCursor.moveToNext()) {
+                            // If requested columnNames does not include COLUMN_XXX_BYTES, we don't
+                            // calculate corresponding values.
+                            if (capacityIndex != -1) {
+                                capacityBytes += cursor.getLong(capacityIndex);
+                            }
+                            if (availableIndex != -1) {
+                                availableBytes += cursor.getLong(availableIndex);
+                            }
+                        }
+                        values.put(Root.COLUMN_CAPACITY_BYTES, capacityBytes);
+                        values.put(Root.COLUMN_AVAILABLE_BYTES, availableBytes);
+                    }
+                } finally {
+                    storageCursor.close();
+                }
+
+                final RowBuilder row = result.newRow();
+                for (final String key : values.keySet()) {
+                    row.add(key, values.get(key));
+                }
+            }
+
+            return result;
+        } finally {
+            deviceCursor.close();
+        }
     }
 
     /**
@@ -380,7 +459,10 @@
         context.deleteDatabase(DATABASE_NAME);
     }
 
-    static void getDeviceDocumentValues(ContentValues values, MtpDeviceRecord device) {
+    static void getDeviceDocumentValues(
+            ContentValues values,
+            ContentValues extraValues,
+            MtpDeviceRecord device) {
         values.clear();
         values.put(COLUMN_DEVICE_ID, device.deviceId);
         values.putNull(COLUMN_STORAGE_ID);
@@ -394,11 +476,15 @@
         values.putNull(Document.COLUMN_LAST_MODIFIED);
         values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
         values.put(Document.COLUMN_FLAGS, 0);
-        long size = 0;
-        for (final MtpRoot root : device.roots) {
-            size += root.mMaxCapacity - root.mFreeSpace;
-        }
-        values.put(Document.COLUMN_SIZE, size);
+        values.putNull(Document.COLUMN_SIZE);
+
+        extraValues.clear();
+        extraValues.put(
+                Root.COLUMN_FLAGS,
+                Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+        extraValues.putNull(Root.COLUMN_AVAILABLE_BYTES);
+        extraValues.putNull(Root.COLUMN_CAPACITY_BYTES);
+        extraValues.put(Root.COLUMN_MIME_TYPES, "");
     }
 
     /**
@@ -408,7 +494,11 @@
      * @param root Root to be converted {@link ContentValues}.
      */
     static void getStorageDocumentValues(
-            ContentValues values, Resources resources, String parentDocumentId, MtpRoot root) {
+            ContentValues values,
+            ContentValues extraValues,
+            Resources resources,
+            String parentDocumentId,
+            MtpRoot root) {
         values.clear();
         values.put(COLUMN_DEVICE_ID, root.mDeviceId);
         values.put(COLUMN_STORAGE_ID, root.mStorageId);
@@ -424,6 +514,13 @@
         values.put(Document.COLUMN_FLAGS, 0);
         values.put(Document.COLUMN_SIZE,
                 (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
+
+        extraValues.put(
+                Root.COLUMN_FLAGS,
+                Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+        extraValues.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
+        extraValues.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
+        extraValues.put(Root.COLUMN_MIME_TYPES, "");
     }
 
     /**
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index a233589..f252e0f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -126,14 +126,14 @@
             Document.COLUMN_LAST_MODIFIED + " INTEGER," +
             Document.COLUMN_ICON + " INTEGER," +
             Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
-            Document.COLUMN_SIZE + " INTEGER NOT NULL);";
+            Document.COLUMN_SIZE + " INTEGER);";
 
     static final String QUERY_CREATE_ROOT_EXTRA =
             "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" +
             Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," +
             Root.COLUMN_FLAGS + " INTEGER NOT NULL," +
-            Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," +
-            Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," +
+            Root.COLUMN_AVAILABLE_BYTES + " INTEGER," +
+            Root.COLUMN_CAPACITY_BYTES + " INTEGER," +
             Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);";
 
     /**
@@ -145,18 +145,26 @@
         COLUMN_MAP_ROOTS = new HashMap<>();
         COLUMN_MAP_ROOTS.put(Root.COLUMN_ROOT_ID, TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID);
         COLUMN_MAP_ROOTS.put(Root.COLUMN_FLAGS, TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS);
-        COLUMN_MAP_ROOTS.put(Root.COLUMN_ICON, TABLE_DOCUMENTS + "." + Document.COLUMN_ICON);
         COLUMN_MAP_ROOTS.put(
-                Root.COLUMN_TITLE, TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME);
-        COLUMN_MAP_ROOTS.put(Root.COLUMN_SUMMARY, TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY);
+                Root.COLUMN_ICON,
+                TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " + Root.COLUMN_ICON);
         COLUMN_MAP_ROOTS.put(
-                Root.COLUMN_DOCUMENT_ID, TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID);
+                Root.COLUMN_TITLE,
+                TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " + Root.COLUMN_TITLE);
+        COLUMN_MAP_ROOTS.put(
+                Root.COLUMN_SUMMARY,
+                TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " + Root.COLUMN_SUMMARY);
+        COLUMN_MAP_ROOTS.put(
+                Root.COLUMN_DOCUMENT_ID,
+                TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID +
+                " AS " + Root.COLUMN_DOCUMENT_ID);
         COLUMN_MAP_ROOTS.put(
                 Root.COLUMN_AVAILABLE_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES);
         COLUMN_MAP_ROOTS.put(
                 Root.COLUMN_CAPACITY_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES);
         COLUMN_MAP_ROOTS.put(
                 Root.COLUMN_MIME_TYPES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES);
+        COLUMN_MAP_ROOTS.put(COLUMN_DEVICE_ID, COLUMN_DEVICE_ID);
     }
 
     private static String createJoinFromClosure(
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index e6c2726..619ef54 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -127,7 +127,7 @@
                         continue;
                     }
                     mDatabase.getMapper().startAddingDocuments(documentId);
-                    if (mDatabase.getMapper().putRootDocuments(
+                    if (mDatabase.getMapper().putStorageDocuments(
                             documentId, mResources, device.roots)) {
                         changed = true;
                     }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 7bd9a17..f37a55c 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -41,7 +41,7 @@
     public void setUp() {
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", new TestResources(), new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", new TestResources(), new MtpRoot[] {
                 new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index b745175..1e1ea0a 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -74,9 +74,72 @@
         return cursor.getString(cursor.getColumnIndex(columnName));
     }
 
-    public void testPutRootDocuments() throws Exception {
+    public void testPutSingleStorageDocuments() throws Exception {
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device", true, new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
+                new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "")
+        });
+        mDatabase.getMapper().stopAddingDocuments("1");
+
+        {
+            final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
+            assertEquals(1, cursor.getCount());
+
+            cursor.moveToNext();
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
+            assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertTrue(isNull(cursor, COLUMN_SUMMARY));
+            assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
+            assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
+            assertEquals(0, getInt(cursor, COLUMN_FLAGS));
+            assertEquals(1000, getInt(cursor, COLUMN_SIZE));
+            assertEquals(
+                    MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
+
+            cursor.close();
+        }
+
+        {
+            final Cursor cursor = mDatabase.queryRoots(new String [] {
+                    Root.COLUMN_ROOT_ID,
+                    Root.COLUMN_FLAGS,
+                    Root.COLUMN_ICON,
+                    Root.COLUMN_TITLE,
+                    Root.COLUMN_SUMMARY,
+                    Root.COLUMN_DOCUMENT_ID,
+                    Root.COLUMN_AVAILABLE_BYTES,
+                    Root.COLUMN_CAPACITY_BYTES
+            });
+            assertEquals(1, cursor.getCount());
+
+            cursor.moveToNext();
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(
+                    Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE,
+                    getInt(cursor, Root.COLUMN_FLAGS));
+            assertEquals(R.drawable.ic_root_mtp, getInt(cursor, Root.COLUMN_ICON));
+            assertEquals("Device Storage", getString(cursor, Root.COLUMN_TITLE));
+            assertTrue(isNull(cursor, Root.COLUMN_SUMMARY));
+            assertEquals(2, getInt(cursor, Root.COLUMN_DOCUMENT_ID));
+            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
+            assertEquals(2000, getInt(cursor, Root.COLUMN_CAPACITY_BYTES));
+
+            cursor.close();
+        }
+    }
+
+    public void testPutStorageDocuments() throws Exception {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
                 new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
@@ -111,52 +174,6 @@
 
             cursor.close();
         }
-
-        {
-            final Cursor cursor = mDatabase.queryRoots(new String [] {
-                    Root.COLUMN_ROOT_ID,
-                    Root.COLUMN_FLAGS,
-                    Root.COLUMN_ICON,
-                    Root.COLUMN_TITLE,
-                    Root.COLUMN_SUMMARY,
-                    Root.COLUMN_DOCUMENT_ID,
-                    Root.COLUMN_AVAILABLE_BYTES,
-                    Root.COLUMN_CAPACITY_BYTES
-            });
-            assertEquals(3, cursor.getCount());
-
-            cursor.moveToNext();
-            assertEquals(1, cursor.getInt(0));
-            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
-            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
-            assertEquals("Device Storage", cursor.getString(3));
-            assertTrue(cursor.isNull(4));
-            assertEquals(1, cursor.getInt(5));
-            assertEquals(1000, cursor.getInt(6));
-            assertEquals(2000, cursor.getInt(7));
-
-            cursor.moveToNext();
-            assertEquals(2, cursor.getInt(0));
-            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
-            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
-            assertEquals("Device Storage", cursor.getString(3));
-            assertTrue(cursor.isNull(4));
-            assertEquals(2, cursor.getInt(5));
-            assertEquals(2000, cursor.getInt(6));
-            assertEquals(4000, cursor.getInt(7));
-
-            cursor.moveToNext();
-            assertEquals(3, cursor.getInt(0));
-            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
-            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
-            assertEquals("Device /@#%&<>Storage", cursor.getString(3));
-            assertTrue(cursor.isNull(4));
-            assertEquals(3, cursor.getInt(5));
-            assertEquals(3000, cursor.getInt(6));
-            assertEquals(6000, cursor.getInt(7));
-
-            cursor.close();
-        }
     }
 
     private MtpObjectInfo createDocument(int objectHandle, String name, int format, int size) {
@@ -245,13 +262,9 @@
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        final String[] rootColumns = new String[] {
-                Root.COLUMN_ROOT_ID,
-                Root.COLUMN_AVAILABLE_BYTES
-        };
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
         });
@@ -270,18 +283,6 @@
             cursor.close();
         }
 
-        {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
-            assertEquals(2, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.close();
-        }
-
         mDatabase.getMapper().clearMapping();
 
         {
@@ -298,20 +299,8 @@
             cursor.close();
         }
 
-        {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
-            assertEquals(2, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.close();
-        }
-
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
         });
@@ -334,21 +323,6 @@
             cursor.close();
         }
 
-        {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
-            assertEquals(3, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.close();
-        }
-
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
 
         {
@@ -364,18 +338,6 @@
             assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
-
-        {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
-            assertEquals(2, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.close();
-        }
     }
 
     public void testRestoreIdForChildDocuments() throws Exception {
@@ -461,26 +423,33 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        mDatabase.getMapper().startAddingDocuments("deviceDocIdA");
-        mDatabase.getMapper().startAddingDocuments("deviceDocIdB");
-        mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] {
-                new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0]));
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device A", "Storage", 0, 0, "")
         });
-        mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] {
-                new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
+        mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
+                new MtpRoot(1, 100, "Device B", "Storage", 0, 0, "")
         });
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Device A Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
-            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertEquals("Device B Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -488,36 +457,36 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID));
             assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID));
             assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
 
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocIdA");
-        mDatabase.getMapper().startAddingDocuments("deviceDocIdB");
-        mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
-        mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] {
                 new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocIdA");
-        mDatabase.getMapper().stopAddingDocuments("deviceDocIdB");
+        mDatabase.getMapper().stopAddingDocuments("1");
+        mDatabase.getMapper().stopAddingDocuments("2");
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -527,10 +496,10 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(5, getInt(cursor, Root.COLUMN_ROOT_ID));
             assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(6, getInt(cursor, Root.COLUMN_ROOT_ID));
             assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
@@ -591,29 +560,34 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device",  false,  new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -622,7 +596,7 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
             assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
@@ -634,19 +608,15 @@
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        final String[] rootColumns = new String[] {
-                Root.COLUMN_ROOT_ID,
-                Root.COLUMN_AVAILABLE_BYTES
-        };
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
@@ -665,33 +635,27 @@
             assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
-        {
-            final Cursor cursor = mDatabase.queryRoots(rootColumns);
-            assertEquals(2, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.moveToNext();
-            assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID));
-            assertEquals(2001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
-            cursor.close();
-        }
     }
 
     public void testReplaceExistingRoots() {
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device", true, new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
         // The client code should be able to replace existing rows with new information.
         // Add one.
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
         // Replace it.
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
         {
             final String[] columns = new String[] {
                     DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -701,7 +665,7 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -709,12 +673,14 @@
         {
             final String[] columns = new String[] {
                     Root.COLUMN_ROOT_ID,
+                    Root.COLUMN_TITLE,
                     Root.COLUMN_AVAILABLE_BYTES
             };
             final Cursor cursor = mDatabase.queryRoots(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals("Device Storage B", getString(cursor, Root.COLUMN_TITLE));
             assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
@@ -723,8 +689,13 @@
     public void testFailToReplaceExisitingUnmappedRoots() {
         // The client code should not be able to replace rows before resolving 'unmapped' rows.
         // Add one.
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device", true, new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
@@ -732,18 +703,19 @@
         assertEquals(1, oldCursor.getCount());
 
         // Add one.
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
         });
         // Add one more before resolving unmapped documents.
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
                 new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         // Because the roots shares the same name, the roots should have new IDs.
-        final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+        final Cursor newCursor = mDatabase.queryChildDocuments(
+                strings(Document.COLUMN_DOCUMENT_ID), "1");
         assertEquals(2, newCursor.getCount());
         oldCursor.moveToNext();
         newCursor.moveToNext();
@@ -755,9 +727,9 @@
         newCursor.close();
     }
 
-    public void testQueryDocument() {
+    public void testQueryDocuments() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -765,13 +737,61 @@
         final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
         assertEquals(1, cursor.getCount());
         cursor.moveToNext();
-        assertEquals("Device Storage A", cursor.getString(0));
+        assertEquals("Device Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
         cursor.close();
     }
 
+    public void testQueryRoots() {
+        // Add device document.
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(0, "Device", false, new MtpRoot[0]));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        // It the device does not have storages, it shows a device root.
+        {
+            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("Device", cursor.getString(0));
+            cursor.close();
+        }
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, "")
+        });
+        mDatabase.getMapper().stopAddingDocuments("1");
+
+        // It the device has single storage, it shows a storage root.
+        {
+            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("Device Storage A", cursor.getString(0));
+            cursor.close();
+        }
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] {
+                new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+                new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "")
+        });
+        mDatabase.getMapper().stopAddingDocuments("1");
+
+        // It the device has multiple storages, it shows a device root.
+        {
+            final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE));
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("Device", cursor.getString(0));
+            cursor.close();
+        }
+    }
+
     public void testGetParentId() throws FileNotFoundException {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -790,7 +810,7 @@
 
     public void testDeleteDocument() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
@@ -834,7 +854,7 @@
 
     public void testPutNewDocument() {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().stopAddingDocuments("deviceDocId");
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 884b1e2..71c4897 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -147,7 +147,7 @@
             mProvider.openDevice(0);
             mResolver.waitForNotification(ROOTS_URI, 1);
             final Cursor cursor = mProvider.queryRoots(null);
-            assertEquals(1, cursor.getCount());
+            assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("3", cursor.getString(0));
             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
@@ -176,7 +176,7 @@
     public void testQueryRoots_error() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mMtpManager.addValidDevice(
-                new MtpDeviceRecord(0, "Device", false /* unopened */, new MtpRoot[0]));
+                new MtpDeviceRecord(0, "Device A", false /* unopened */, new MtpRoot[0]));
         mMtpManager.addValidDevice(new MtpDeviceRecord(
                 1,
                 "Device",
@@ -197,7 +197,16 @@
             mResolver.waitForNotification(ROOTS_URI, 1);
 
             final Cursor cursor = mProvider.queryRoots(null);
-            assertEquals(1, cursor.getCount());
+            assertEquals(2, cursor.getCount());
+
+            cursor.moveToNext();
+            assertEquals("1", cursor.getString(0));
+            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
+            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
+            assertEquals("Device A", cursor.getString(3));
+            assertEquals("1", cursor.getString(4));
+            assertEquals(0, cursor.getInt(5));
+
             cursor.moveToNext();
             assertEquals("3", cursor.getString(0));
             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6680d88..51d8ca0 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -25,6 +25,7 @@
     <!-- Comma-separated list of bluetooth, wifi, and cell. -->
     <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string>
     <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string>
+    <string name="def_bluetooth_disabled_profiles" translatable="false">0</string>
     <bool name="def_auto_time">true</bool>
     <bool name="def_auto_time_zone">true</bool>
     <bool name="def_accelerometer_rotation">true</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index d4e428e..7338a9c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -116,6 +116,12 @@
             // cleaned up automatically when the user is deleted.
             File databaseFile = new File(
                     Environment.getUserSystemDirectory(userHandle), DATABASE_NAME);
+            // If databaseFile doesn't exist, database can be kept in memory. It's safe because the
+            // database will be migrated and disposed of immediately after onCreate finishes
+            if (!databaseFile.exists()) {
+                Log.i(TAG, "No previous database file exists - running in in-memory mode");
+                return null;
+            }
             return databaseFile.getPath();
         }
     }
@@ -130,8 +136,16 @@
         return mValidTables.contains(name);
     }
 
+    private boolean isInMemory() {
+        return getDatabaseName() == null;
+    }
+
     public void dropDatabase() {
         close();
+        // No need to remove files if db is in memory
+        if (isInMemory()) {
+            return;
+        }
         File databaseFile = mContext.getDatabasePath(getDatabaseName());
         if (databaseFile.exists()) {
             databaseFile.delete();
@@ -145,6 +159,10 @@
 
     public void backupDatabase() {
         close();
+        // No need to backup files if db is in memory
+        if (isInMemory()) {
+            return;
+        }
         File databaseFile = mContext.getDatabasePath(getDatabaseName());
         if (!databaseFile.exists()) {
             return;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7365e66..bcb459a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1890,7 +1890,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 123;
+            private static final int SETTINGS_VERSION = 124;
 
             private final int mUserId;
 
@@ -2060,6 +2060,16 @@
                     }
                     currentVersion = 123;
                 }
+
+                if (currentVersion == 123) {
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    String defaultDisabledProfiles = (getContext().getResources().getString(
+                            R.string.def_bluetooth_disabled_profiles));
+                    globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES,
+                            defaultDisabledProfiles, SettingsState.SYSTEM_PACKAGE_NAME);
+                    currentVersion = 124;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 // Return the current version.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 02ddae6..9546c8d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -139,6 +139,9 @@
     <!-- Adding Quick Settings tiles -->
     <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
 
+    <!-- Block notifications inline notifications -->
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index f15c97e..3cdee64 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -139,7 +139,9 @@
 
         </FrameLayout>
 
-        <RadioGroup android:layout_width="wrap_content"
+        <RadioGroup
+                    android:id="@+id/apply_to"
+                    android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:paddingTop="8dp">
             <RadioButton android:id="@+id/apply_to_topic"
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 6a9268a8..35e53f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -194,6 +194,8 @@
         int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
         List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
                 ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+                ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS |
+                ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE |
                 ActivityManager.RECENT_INCLUDE_PROFILES |
                 ActivityManager.RECENT_WITH_EXCLUDED, userId);
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 d06012e..6f003ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -352,28 +352,28 @@
             @Override
             public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
                 if (t.isAffiliatedTask()) {
-                    // If this task is affiliated with another parent in the stack, then the historical state of this
-                    // task depends on the state of the parent task
+                    // If this task is affiliated with another parent in the stack, then the
+                    // historical state of this task depends on the state of the parent task
                     Task parentTask = taskIdMap.get(t.affiliationTaskId);
                     if (parentTask != null) {
                         t = parentTask;
                     }
                 }
-                return !t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId);
+                return !t.isHistorical;
             }
         });
         mHistoryTaskList.setFilter(new TaskFilter() {
             @Override
             public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
                 if (t.isAffiliatedTask()) {
-                    // If this task is affiliated with another parent in the stack, then the historical state of this
-                    // task depends on the state of the parent task
+                    // If this task is affiliated with another parent in the stack, then the
+                    // historical state of this task depends on the state of the parent task
                     Task parentTask = taskIdMap.get(t.affiliationTaskId);
                     if (parentTask != null) {
                         t = parentTask;
                     }
                 }
-                return t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId);
+                return t.isHistorical;
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 57db80a..79236be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import android.annotation.IdRes;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
+import android.widget.RadioGroup;
 import android.widget.SeekBar;
 import android.widget.TextView;
 
@@ -101,24 +103,51 @@
                 ? new Notification.Topic(Notification.TOPIC_DEFAULT, mContext.getString(
                 com.android.internal.R.string.default_notification_topic_label))
                 : sbn.getNotification().getTopic();
+        boolean doesAppUseTopics = false;
+        try {
+            doesAppUseTopics = sINM.doesAppUseTopics(sbn.getPackageName(), sbn.getUid());
+        } catch (RemoteException e) {}
+        final boolean appUsesTopics = doesAppUseTopics;
 
         final RadioButton applyToTopic = (RadioButton) row.findViewById(R.id.apply_to_topic);
-        if (sbn.getNotification().getTopic() != null) {
-            applyToTopic.setVisibility(View.VISIBLE);
-            applyToTopic.setChecked(true);
-            applyToTopic.setText(mContext.getString(R.string.apply_to_topic, topic.getLabel()));
-            row.findViewById(R.id.apply_to_app).setVisibility(View.VISIBLE);
-        }
-
+        applyToTopic.setChecked(true);
+        final View applyToApp = row.findViewById(R.id.apply_to_app);
         final TextView topicSummary = ((TextView) row.findViewById(R.id.summary));
         final TextView topicTitle = ((TextView) row.findViewById(R.id.title));
-        SeekBar seekBar = (SeekBar) row.findViewById(R.id.seekbar);
+        final SeekBar seekBar = (SeekBar) row.findViewById(R.id.seekbar);
+        final RadioGroup applyTo = (RadioGroup) row.findViewById(R.id.apply_to);
+        applyTo.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
+                try {
+                    switch (checkedId) {
+                        case R.id.apply_to_topic:
+                            sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic,
+                                    seekBar.getProgress());
+                            break;
+                        default:
+                            sINM.setAppImportance(sbn.getPackageName(), sbn.getUid(),
+                                    seekBar.getProgress());
+                    }
+                } catch (RemoteException e) {
+                    // :(
+                }
+            }
+        });
+
         seekBar.setMax(4);
         seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
             @Override
             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                 updateTitleAndSummary(progress);
                 if (fromUser) {
+                    if (appUsesTopics) {
+                        applyToTopic.setVisibility(View.VISIBLE);
+
+                        applyToTopic.setText(
+                                mContext.getString(R.string.apply_to_topic, topic.getLabel()));
+                        applyToApp.setVisibility(View.VISIBLE);
+                    }
                     try {
                         if (applyToTopic.isChecked()) {
                             sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic,
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 86183af..4dc46ac 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -48,6 +48,13 @@
 
     static SystemConfig sInstance;
 
+    // permission flag, determines which types of configuration are allowed to be read
+    private static final int ALLOW_FEATURES = 0x01;
+    private static final int ALLOW_LIBS = 0x02;
+    private static final int ALLOW_PERMISSIONS = 0x04;
+    private static final int ALLOW_APP_CONFIGS = 0x08;
+    private static final int ALLOW_ALL = ~0;
+
     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
     int[] mGlobalGids;
 
@@ -161,21 +168,27 @@
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
-                Environment.getRootDirectory(), "etc", "sysconfig"), false);
+                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
         // Read configuration from the old permissions dir
         readPermissions(Environment.buildPath(
-                Environment.getRootDirectory(), "etc", "permissions"), false);
-        // Only read features from OEM config
+                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
+        // Allow ODM to customize system configs around libs, features and apps
+        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
         readPermissions(Environment.buildPath(
-                Environment.getOemDirectory(), "etc", "sysconfig"), true);
+                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
         readPermissions(Environment.buildPath(
-                Environment.getOemDirectory(), "etc", "permissions"), true);
+                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
+        // Only allow OEM to customize features
+        readPermissions(Environment.buildPath(
+                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
+        readPermissions(Environment.buildPath(
+                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
     }
 
-    void readPermissions(File libraryDir, boolean onlyFeatures) {
+    void readPermissions(File libraryDir, int permissionFlag) {
         // Read permissions from given directory.
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
-            if (!onlyFeatures) {
+            if (permissionFlag == ALLOW_ALL) {
                 Slog.w(TAG, "No directory " + libraryDir + ", skipping");
             }
             return;
@@ -203,16 +216,16 @@
                 continue;
             }
 
-            readPermissionsFromXml(f, onlyFeatures);
+            readPermissionsFromXml(f, permissionFlag);
         }
 
         // Read platform permissions last so it will take precedence
         if (platformFile != null) {
-            readPermissionsFromXml(platformFile, onlyFeatures);
+            readPermissionsFromXml(platformFile, permissionFlag);
         }
     }
 
-    private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
+    private void readPermissionsFromXml(File permFile, int permissionFlag) {
         FileReader permReader = null;
         try {
             permReader = new FileReader(permFile);
@@ -242,6 +255,11 @@
                         + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
             }
 
+            boolean allowAll = permissionFlag == ALLOW_ALL;
+            boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
+            boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
+            boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
+            boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
             while (true) {
                 XmlUtils.nextElement(parser);
                 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -249,7 +267,7 @@
                 }
 
                 String name = parser.getName();
-                if ("group".equals(name) && !onlyFeatures) {
+                if ("group".equals(name) && allowAll) {
                     String gidStr = parser.getAttributeValue(null, "gid");
                     if (gidStr != null) {
                         int gid = android.os.Process.getGidForName(gidStr);
@@ -261,7 +279,7 @@
 
                     XmlUtils.skipCurrentTag(parser);
                     continue;
-                } else if ("permission".equals(name) && !onlyFeatures) {
+                } else if ("permission".equals(name) && allowPermissions) {
                     String perm = parser.getAttributeValue(null, "name");
                     if (perm == null) {
                         Slog.w(TAG, "<permission> without name in " + permFile + " at "
@@ -272,7 +290,7 @@
                     perm = perm.intern();
                     readPermission(parser, perm);
 
-                } else if ("assign-permission".equals(name) && !onlyFeatures) {
+                } else if ("assign-permission".equals(name) && allowPermissions) {
                     String perm = parser.getAttributeValue(null, "name");
                     if (perm == null) {
                         Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
@@ -304,7 +322,7 @@
                     perms.add(perm);
                     XmlUtils.skipCurrentTag(parser);
 
-                } else if ("library".equals(name) && !onlyFeatures) {
+                } else if ("library".equals(name) && allowLibs) {
                     String lname = parser.getAttributeValue(null, "name");
                     String lfile = parser.getAttributeValue(null, "file");
                     if (lname == null) {
@@ -320,7 +338,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("feature".equals(name)) {
+                } else if ("feature".equals(name) && allowFeatures) {
                     String fname = parser.getAttributeValue(null, "name");
                     boolean allowed;
                     if (!lowRam) {
@@ -341,7 +359,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("unavailable-feature".equals(name)) {
+                } else if ("unavailable-feature".equals(name) && allowFeatures) {
                     String fname = parser.getAttributeValue(null, "name");
                     if (fname == null) {
                         Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
@@ -352,7 +370,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) {
+                } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
@@ -363,7 +381,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
+                } else if ("allow-in-power-save".equals(name) && allowAll) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
@@ -374,7 +392,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
+                } else if ("fixed-ime-app".equals(name) && allowAll) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at "
@@ -385,7 +403,7 @@
                     XmlUtils.skipCurrentTag(parser);
                     continue;
 
-                } else if ("app-link".equals(name)) {
+                } else if ("app-link".equals(name) && allowAppConfigs) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<app-link> without package in " + permFile + " at "
@@ -394,7 +412,7 @@
                         mLinkedApps.add(pkgname);
                     }
                     XmlUtils.skipCurrentTag(parser);
-                } else if ("system-user-whitelisted-app".equals(name)) {
+                } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile
@@ -403,7 +421,7 @@
                         mSystemUserWhitelistedApps.add(pkgname);
                     }
                     XmlUtils.skipCurrentTag(parser);
-                } else if ("system-user-blacklisted-app".equals(name)) {
+                } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
                     String pkgname = parser.getAttributeValue(null, "package");
                     if (pkgname == null) {
                         Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1d555c6..de0a23a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -53,6 +53,7 @@
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
 
@@ -323,6 +324,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
 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.ActivityStackSupervisor.ActivityContainer.FORCE_NEW_TASK_FLAGS;
 import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -510,6 +512,8 @@
     /** Run all ActivityStacks through this */
     ActivityStackSupervisor mStackSupervisor;
 
+    ActivityStarter mActivityStarter;
+
     /** Task stack change listeners. */
     private RemoteCallbackList<ITaskStackListener> mTaskStackListeners =
             new RemoteCallbackList<ITaskStackListener>();
@@ -1440,6 +1444,7 @@
     static final int IDLE_UIDS_MSG = 60;
     static final int SYSTEM_USER_UNLOCK_MSG = 61;
     static final int LOG_STACK_STATE = 62;
+    static final int VR_MODE_CHANGE_MSG = 63;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1786,7 +1791,7 @@
             } break;
             case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    mStackSupervisor.doPendingActivityLaunchesLocked(true);
+                    mActivityStarter.doPendingActivityLaunchesLocked(true);
                 }
             } break;
             case KILL_APPLICATION_MSG: {
@@ -2154,6 +2159,10 @@
                     mStackSupervisor.logStackState();
                 }
             } break;
+            case VR_MODE_CHANGE_MSG: {
+                VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+                vrService.setVrMode(msg.arg1 != 0);
+            } break;
             }
         }
     };
@@ -2290,6 +2299,7 @@
     public void setWindowManager(WindowManagerService wm) {
         mWindowManager = wm;
         mStackSupervisor.setWindowManager(wm);
+        mActivityStarter.setWindowManager(wm);
     }
 
     public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
@@ -2477,6 +2487,7 @@
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mRecentTasks = new RecentTasks(this);
         mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks);
+        mActivityStarter = new ActivityStarter(this, mStackSupervisor);
         mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks);
 
         mProcessCpuThread = new Thread("CpuTracker") {
@@ -2776,6 +2787,7 @@
             mWindowManager.setFocusedApp(r.appToken, true);
         }
         applyUpdateLockStateLocked(r);
+        applyUpdateVrModeLocked(r);
         if (mFocusedActivity.userId != mLastFocusedUserId) {
             mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
             mHandler.obtainMessage(
@@ -2872,6 +2884,11 @@
                 mHandler.obtainMessage(IMMERSIVE_MODE_LOCK_MSG, (nextState) ? 1 : 0, 0, r));
     }
 
+    final void applyUpdateVrModeLocked(ActivityRecord r) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(VR_MODE_CHANGE_MSG, (r.isVrActivity) ? 1 : 0, 0));
+    }
+
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
         msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -3601,7 +3618,7 @@
                     aInfo.applicationInfo.uid, true);
             if (app == null || app.instrumentationClass == null) {
                 intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
-                mStackSupervisor.startHomeActivity(intent, aInfo, reason);
+                mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
             }
         }
 
@@ -3679,7 +3696,7 @@
                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     intent.setComponent(new ComponentName(
                             ri.activityInfo.packageName, ri.activityInfo.name));
-                    mStackSupervisor.startActivityLocked(null, intent, null /*ephemeralIntent*/,
+                    mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
                             null, ri.activityInfo, null /*rInfo*/, null, null, null, null, 0, 0, 0,
                             null, 0, 0, 0, null, false, false, null, null, null);
                 }
@@ -4006,6 +4023,25 @@
                 UserHandle.getCallingUserId());
     }
 
+    final int startActivity(Intent intent, ActivityStackSupervisor.ActivityContainer container) {
+        enforceNotIsolatedCaller("ActivityContainer.startActivity");
+        final int userId = mUserController.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), mStackSupervisor.mCurrentUser, false,
+                ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
+
+        // TODO: Switch to user app stacks here.
+        String mimeType = intent.getType();
+        final Uri data = intent.getData();
+        if (mimeType == null && data != null && "content".equals(data.getScheme())) {
+            mimeType = getProviderMimeType(data, userId);
+        }
+        container.checkEmbeddedAllowedInner(userId, intent, mimeType);
+
+        intent.addFlags(FORCE_NEW_TASK_FLAGS);
+        return mActivityStarter.startActivityMayWait(null, -1, null, intent, mimeType, null, null, null,
+                null, 0, 0, null, null, null, null, false, userId, container, null);
+    }
+
     @Override
     public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
             Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
@@ -4014,7 +4050,7 @@
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
-        return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
+        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                 profilerInfo, null, null, bOptions, false, userId, null, null);
     }
@@ -4077,7 +4113,7 @@
 
         // TODO: Switch to user app stacks here.
         try {
-            int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent,
+            int ret = mActivityStarter.startActivityMayWait(null, targetUid, targetPackage, intent,
                     resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null,
                     null, null, bOptions, ignoreTargetSecurity, userId, null, null);
             return ret;
@@ -4106,7 +4142,7 @@
                 userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
         WaitResult res = new WaitResult();
         // TODO: Switch to user app stacks here.
-        mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
+        mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                 null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null,
                 bOptions, false, userId, null, null);
         return res;
@@ -4120,7 +4156,7 @@
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
         // TODO: Switch to user app stacks here.
-        int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
+        int ret = mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                 null, null, config, bOptions, false, userId, null, null);
         return ret;
@@ -4178,7 +4214,7 @@
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
                 ALLOW_FULL_ONLY, "startVoiceActivity", null);
         // TODO: Switch to user app stacks here.
-        return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
+        return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
                 resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null,
                 null, bOptions, false, userId, null, null);
     }
@@ -4289,7 +4325,7 @@
             }
 
             final long origId = Binder.clearCallingIdentity();
-            int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
+            int res = mActivityStarter.startActivityLocked(r.app.thread, intent,
                     null /*ephemeralIntent*/, r.resolvedType, aInfo, null /*rInfo*/, null,
                     null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1,
                     r.launchedFromUid, r.launchedFromPackage, -1, r.launchedFromUid, 0, options,
@@ -4374,7 +4410,7 @@
                 userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
 
         // TODO: Switch to user app stacks here.
-        int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent,
+        int ret = mActivityStarter.startActivityMayWait(null, uid, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                 null, null, null, bOptions, false, userId, container, inTask);
         return ret;
@@ -4388,7 +4424,7 @@
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, ALLOW_FULL_ONLY, "startActivity", null);
         // TODO: Switch to user app stacks here.
-        int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
+        int ret = mActivityStarter.startActivities(caller, -1, callingPackage, intents,
                 resolvedTypes, resultTo, bOptions, userId);
         return ret;
     }
@@ -4400,7 +4436,7 @@
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
         // TODO: Switch to user app stacks here.
-        int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
+        int ret = mActivityStarter.startActivities(null, uid, callingPackage, intents, resolvedTypes,
                 resultTo, bOptions, userId);
         return ret;
     }
@@ -8768,6 +8804,20 @@
                             continue;
                         }
                     }
+                    if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS) != 0) {
+                        if (tr.stack != null && tr.stack.isDockedStack()) {
+                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                                    "Skipping, docked stack task: " + tr);
+                            continue;
+                        }
+                    }
+                    if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
+                        if (tr.stack != null && tr.stack.isPinnedStack()) {
+                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                                    "Skipping, pinned stack task: " + tr);
+                            continue;
+                        }
+                    }
                     if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
                         // Don't include auto remove tasks that are finished or finishing.
                         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
@@ -11712,6 +11762,26 @@
         }
     }
 
+    @Override
+    public void setVrMode(IBinder token, boolean enabled) {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+            throw new UnsupportedOperationException("VR mode not supported on this device!");
+        }
+
+        synchronized(this) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                throw new IllegalArgumentException();
+            }
+            r.isVrActivity = enabled;
+
+            // Update associated state if this activity is currently focused
+            if (r == mFocusedActivity) {
+                applyUpdateVrModeLocked(r);
+            }
+        }
+    }
+
     public boolean isTopActivityImmersive() {
         enforceNotIsolatedCaller("startActivity");
         synchronized (this) {
@@ -20823,7 +20893,7 @@
                     throw new IllegalArgumentException("Bad app thread " + appThread);
                 }
             }
-            return mStackSupervisor.startActivityMayWait(appThread, -1, callingPackage, intent,
+            return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
                     resolvedType, null, null, null, null, 0, 0, null, null,
                     null, bOptions, false, callingUser, null, tr);
         }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 3da0f8d..4d9120b 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -181,6 +181,7 @@
     boolean forceNewConfig; // force re-create with new config next time
     int launchCount;        // count of launches since last state
     long lastLaunchTime;    // time of last lauch of this activity
+    boolean isVrActivity;   // is the activity running in VR mode?
     ArrayList<ActivityContainer> mChildContainers = new ArrayList<ActivityContainer>();
 
     String stringName;      // for caching of toString().
@@ -317,6 +318,7 @@
                 pw.print(" forceNewConfig="); pw.println(forceNewConfig);
         pw.print(prefix); pw.print("mActivityType=");
                 pw.println(activityTypeToString(mActivityType));
+        pw.print(prefix); pw.print("vrMode="); pw.println(isVrActivity);
         if (displayStartTime != 0 || startTime != 0) {
             pw.print(prefix); pw.print("displayStartTime=");
                     if (displayStartTime == 0) pw.print("0");
@@ -650,6 +652,7 @@
             }
 
             immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0;
+            isVrActivity = (aInfo.flags & ActivityInfo.FLAG_ENABLE_VR_MODE) != 0;
         } else {
             realActivity = null;
             taskAffinity = null;
@@ -661,6 +664,7 @@
             noDisplay = false;
             mActivityType = APPLICATION_ACTIVITY_TYPE;
             immersive = false;
+            isVrActivity = false;
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ada71f6..1492e23 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -28,6 +28,7 @@
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 
+import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
 import static com.android.server.am.ActivityStackSupervisor.MOVING;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 
@@ -74,7 +75,6 @@
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
 import android.view.Display;
 
@@ -519,6 +519,14 @@
         return mStackId == HOME_STACK_ID;
     }
 
+    final boolean isDockedStack() {
+        return mStackId == DOCKED_STACK_ID;
+    }
+
+    final boolean isPinnedStack() {
+        return mStackId == PINNED_STACK_ID;
+    }
+
     final boolean isOnHomeDisplay() {
         return isAttached() &&
                 mActivityContainer.mActivityDisplay.mDisplayId == Display.DEFAULT_DISPLAY;
@@ -569,10 +577,10 @@
     }
 
     /**
-     * Returns the top activity in any existing task matching the given
-     * Intent.  Returns null if no such task is found.
+     * Returns the top activity in any existing task matching the given Intent in the input result.
+     * Returns null if no such task is found.
      */
-    ActivityRecord findTaskLocked(ActivityRecord target) {
+    void findTaskLocked(ActivityRecord target, FindTaskResult result) {
         Intent intent = target.intent;
         ActivityInfo info = target.info;
         ComponentName cls = intent.getComponent();
@@ -627,10 +635,15 @@
                     + taskIntent.getComponent().flattenToShortString()
                     + "/aff=" + r.task.rootAffinity + " to new cls="
                     + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
-            if (!isDocument && !taskIsDocument && task.rootAffinity != null) {
+            if (!isDocument && !taskIsDocument
+                    && result.r == null && task.canMatchRootAffinity()) {
                 if (task.rootAffinity.equals(target.taskAffinity)) {
-                    if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity!");
-                    return r;
+                    if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity candidate!");
+                    // It is possible for multiple tasks to have the same root affinity especially
+                    // if they are in separate stacks. We save off this candidate, but keep looking
+                    // to see if there is a better candidate.
+                    result.r = r;
+                    result.matchedByRootAffinity = true;
                 }
             } else if (taskIntent != null && taskIntent.getComponent() != null &&
                     taskIntent.getComponent().compareTo(cls) == 0 &&
@@ -639,7 +652,9 @@
                 //dump();
                 if (DEBUG_TASKS) Slog.d(TAG_TASKS,
                         "For Intent " + intent + " bringing to top: " + r.intent);
-                return r;
+                result.r = r;
+                result.matchedByRootAffinity = false;
+                break;
             } else if (affinityIntent != null && affinityIntent.getComponent() != null &&
                     affinityIntent.getComponent().compareTo(cls) == 0 &&
                     Objects.equals(documentData, taskDocumentData)) {
@@ -647,11 +662,11 @@
                 //dump();
                 if (DEBUG_TASKS) Slog.d(TAG_TASKS,
                         "For Intent " + intent + " bringing to top: " + r.intent);
-                return r;
+                result.r = r;
+                result.matchedByRootAffinity = false;
+                break;
             } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
         }
-
-        return null;
     }
 
     /**
@@ -3347,7 +3362,7 @@
                 try {
                     ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
                             destIntent.getComponent(), 0, srec.userId);
-                    int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
+                    int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, destIntent,
                             null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null,
                             parent.appToken, null, 0, -1, parent.launchedFromUid,
                             parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 483293f..32671acf 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -31,51 +31,37 @@
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
 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.ANIMATE;
 import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -107,9 +93,6 @@
 import android.app.IActivityContainerCallback;
 import android.app.IActivityManager;
 import android.app.IActivityManager.WaitResult;
-import android.app.IApplicationThread;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.StatusBarManager;
@@ -118,7 +101,6 @@
 import android.content.Context;
 import android.content.IIntentSender;
 import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -133,10 +115,7 @@
 import android.hardware.display.VirtualDisplay;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
-import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
@@ -168,8 +147,6 @@
 import android.view.InputEvent;
 import android.view.Surface;
 
-import com.android.internal.app.HeavyWeightSwitcherActivity;
-import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.statusbar.IStatusBarService;
@@ -189,21 +166,17 @@
 
 public final class ActivityStackSupervisor implements DisplayListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
-    private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
-    private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_IDLE = TAG + POSTFIX_IDLE;
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
     private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
     private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
-    private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
     private static final String TAG_STACK = TAG + POSTFIX_STACK;
     private static final String TAG_STATES = TAG + POSTFIX_STATES;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
-    private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+    static final String TAG_TASKS = TAG + POSTFIX_TASKS;
     private static final String TAG_VISIBLE_BEHIND = TAG + POSTFIX_VISIBLE_BEHIND;
-    private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
 
     /** How long we wait until giving up on the last activity telling us it is idle. */
     static final int IDLE_TIMEOUT = 10 * 1000;
@@ -235,7 +208,7 @@
 
     // Used to indicate if an object (e.g. stack) that we are trying to get
     // should be created if it doesn't exist already.
-    private static final boolean CREATE_IF_NEEDED = true;
+    static final boolean CREATE_IF_NEEDED = true;
 
     // Used to indicate that windows of activities should be preserved during the resize.
     static final boolean PRESERVE_WINDOWS = true;
@@ -308,14 +281,14 @@
     private int mCurTaskId = 0;
 
     /** The current user */
-    private int mCurrentUser;
+    int mCurrentUser;
 
     /** The stack containing the launcher app. Assumed to always be attached to
      * Display.DEFAULT_DISPLAY. */
-    private ActivityStack mHomeStack;
+    ActivityStack mHomeStack;
 
     /** The stack currently receiving input or launching the next activity. */
-    private ActivityStack mFocusedStack;
+    ActivityStack mFocusedStack;
 
     /** If this is the same as mFocusedStack then the activity on the top of the focused stack has
      * been resumed. If stacks are changing position this will hold the old stack until the new
@@ -397,8 +370,6 @@
      */
     private LockTaskNotify mLockTaskNotify;
 
-    final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
-
     /** Used to keep resumeTopActivityLocked() from being entered recursively */
     boolean inResumeTopActivity;
 
@@ -418,6 +389,12 @@
 
     private final ActivityMetricsLogger mActivityMetricsLogger;
 
+    static class FindTaskResult {
+        ActivityRecord r;
+        boolean matchedByRootAffinity;
+    }
+    private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
+
     /**
      * Description of a request to start a new activity, which has been held
      * due to app switches being disabled.
@@ -1011,270 +988,6 @@
         return resolveActivity(intent, rInfo, startFlags, profilerInfo);
     }
 
-    void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
-        moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason);
-        startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/,
-                null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/,
-                null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/,
-                0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/,
-                0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/,
-                false /*ignoreTargetSecurity*/, false /*componentSpecified*/,  null /*outActivity*/,
-                null /*container*/, null /*inTask*/);
-        if (inResumeTopActivity) {
-            // If we are in resume section already, home activity will be initialized, but not
-            // resumed (to avoid recursive resume) and will stay that way until something pokes it
-            // again. We need to schedule another resume.
-            scheduleResumeTopActivities();
-        }
-    }
-
-    final int startActivityMayWait(IApplicationThread caller, int callingUid,
-            String callingPackage, Intent intent, String resolvedType,
-            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-            IBinder resultTo, String resultWho, int requestCode, int startFlags,
-            ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
-            Bundle bOptions, boolean ignoreTargetSecurity, int userId,
-            IActivityContainer iContainer, TaskRecord inTask) {
-        // Refuse possible leaked file descriptors
-        if (intent != null && intent.hasFileDescriptors()) {
-            throw new IllegalArgumentException("File descriptors passed in Intent");
-        }
-        boolean componentSpecified = intent.getComponent() != null;
-
-        // Save a copy in case ephemeral needs it
-        final Intent ephemeralIntent = new Intent(intent);
-        // Don't modify the client's object!
-        intent = new Intent(intent);
-
-        ResolveInfo rInfo = resolveIntent(intent, resolvedType, userId);
-        // Collect information about the target of the Intent.
-        ActivityInfo aInfo = resolveActivity(intent, rInfo, startFlags, profilerInfo);
-
-        ActivityOptions options = ActivityOptions.fromBundle(bOptions);
-        ActivityContainer container = (ActivityContainer)iContainer;
-        synchronized (mService) {
-            if (container != null && container.mParentActivity != null &&
-                    container.mParentActivity.state != RESUMED) {
-                // Cannot start a child activity if the parent is not resumed.
-                return ActivityManager.START_CANCELED;
-            }
-            final int realCallingPid = Binder.getCallingPid();
-            final int realCallingUid = Binder.getCallingUid();
-            int callingPid;
-            if (callingUid >= 0) {
-                callingPid = -1;
-            } else if (caller == null) {
-                callingPid = realCallingPid;
-                callingUid = realCallingUid;
-            } else {
-                callingPid = callingUid = -1;
-            }
-
-            final ActivityStack stack;
-            if (container == null || container.mStack.isOnHomeDisplay()) {
-                stack = mFocusedStack;
-            } else {
-                stack = container.mStack;
-            }
-            stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;
-            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                    "Starting activity when config will change = " + stack.mConfigWillChange);
-
-            final long origId = Binder.clearCallingIdentity();
-
-            if (aInfo != null &&
-                    (aInfo.applicationInfo.privateFlags
-                            &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
-                // This may be a heavy-weight process!  Check to see if we already
-                // have another, different heavy-weight process running.
-                if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
-                    if (mService.mHeavyWeightProcess != null &&
-                            (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid ||
-                            !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) {
-                        int appCallingUid = callingUid;
-                        if (caller != null) {
-                            ProcessRecord callerApp = mService.getRecordForAppLocked(caller);
-                            if (callerApp != null) {
-                                appCallingUid = callerApp.info.uid;
-                            } else {
-                                Slog.w(TAG, "Unable to find app for caller " + caller
-                                      + " (pid=" + callingPid + ") when starting: "
-                                      + intent.toString());
-                                ActivityOptions.abort(options);
-                                return ActivityManager.START_PERMISSION_DENIED;
-                            }
-                        }
-
-                        IIntentSender target = mService.getIntentSenderLocked(
-                                ActivityManager.INTENT_SENDER_ACTIVITY, "android",
-                                appCallingUid, userId, null, null, 0, new Intent[] { intent },
-                                new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
-                                | PendingIntent.FLAG_ONE_SHOT, null);
-
-                        Intent newIntent = new Intent();
-                        if (requestCode >= 0) {
-                            // Caller is requesting a result.
-                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
-                        }
-                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,
-                                new IntentSender(target));
-                        if (mService.mHeavyWeightProcess.activities.size() > 0) {
-                            ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0);
-                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,
-                                    hist.packageName);
-                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,
-                                    hist.task.taskId);
-                        }
-                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
-                                aInfo.packageName);
-                        newIntent.setFlags(intent.getFlags());
-                        newIntent.setClassName("android",
-                                HeavyWeightSwitcherActivity.class.getName());
-                        intent = newIntent;
-                        resolvedType = null;
-                        caller = null;
-                        callingUid = Binder.getCallingUid();
-                        callingPid = Binder.getCallingPid();
-                        componentSpecified = true;
-                        rInfo = resolveIntent(intent, null /*resolvedType*/, userId);
-                        aInfo = rInfo != null ? rInfo.activityInfo : null;
-                        if (aInfo != null) {
-                            aInfo = mService.getActivityInfoForUser(aInfo, userId);
-                        }
-                    }
-                }
-            }
-
-            int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
-                    aInfo, rInfo, voiceSession, voiceInteractor,
-                    resultTo, resultWho, requestCode, callingPid,
-                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
-                    options, ignoreTargetSecurity, componentSpecified, null, container, inTask);
-
-            Binder.restoreCallingIdentity(origId);
-
-            if (stack.mConfigWillChange) {
-                // If the caller also wants to switch to a new configuration,
-                // do so now.  This allows a clean switch, as we are waiting
-                // for the current activity to pause (so we will not destroy
-                // it), and have not yet started the next activity.
-                mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
-                        "updateConfiguration()");
-                stack.mConfigWillChange = false;
-                if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
-                        "Updating to new configuration after starting activity.");
-                mService.updateConfigurationLocked(config, null, false);
-            }
-
-            if (outResult != null) {
-                outResult.result = res;
-                if (res == ActivityManager.START_SUCCESS) {
-                    mWaitingActivityLaunched.add(outResult);
-                    do {
-                        try {
-                            mService.wait();
-                        } catch (InterruptedException e) {
-                        }
-                    } while (!outResult.timeout && outResult.who == null);
-                } else if (res == ActivityManager.START_TASK_TO_FRONT) {
-                    ActivityRecord r = stack.topRunningActivityLocked();
-                    if (r.nowVisible && r.state == RESUMED) {
-                        outResult.timeout = false;
-                        outResult.who = new ComponentName(r.info.packageName, r.info.name);
-                        outResult.totalTime = 0;
-                        outResult.thisTime = 0;
-                    } else {
-                        outResult.thisTime = SystemClock.uptimeMillis();
-                        mWaitingActivityVisible.add(outResult);
-                        do {
-                            try {
-                                mService.wait();
-                            } catch (InterruptedException e) {
-                            }
-                        } while (!outResult.timeout && outResult.who == null);
-                    }
-                }
-            }
-
-            return res;
-        }
-    }
-
-    final int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
-            Bundle bOptions, int userId) {
-        if (intents == null) {
-            throw new NullPointerException("intents is null");
-        }
-        if (resolvedTypes == null) {
-            throw new NullPointerException("resolvedTypes is null");
-        }
-        if (intents.length != resolvedTypes.length) {
-            throw new IllegalArgumentException("intents are length different than resolvedTypes");
-        }
-
-
-        int callingPid;
-        if (callingUid >= 0) {
-            callingPid = -1;
-        } else if (caller == null) {
-            callingPid = Binder.getCallingPid();
-            callingUid = Binder.getCallingUid();
-        } else {
-            callingPid = callingUid = -1;
-        }
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mService) {
-                ActivityRecord[] outActivity = new ActivityRecord[1];
-                for (int i=0; i<intents.length; i++) {
-                    Intent intent = intents[i];
-                    if (intent == null) {
-                        continue;
-                    }
-
-                    // Refuse possible leaked file descriptors
-                    if (intent != null && intent.hasFileDescriptors()) {
-                        throw new IllegalArgumentException("File descriptors passed in Intent");
-                    }
-
-                    boolean componentSpecified = intent.getComponent() != null;
-
-                    // Don't modify the client's object!
-                    intent = new Intent(intent);
-
-                    // Collect information about the target of the Intent.
-                    ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], 0, null, userId);
-                    // TODO: New, check if this is correct
-                    aInfo = mService.getActivityInfoForUser(aInfo, userId);
-
-                    if (aInfo != null &&
-                            (aInfo.applicationInfo.privateFlags
-                                    & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)  != 0) {
-                        throw new IllegalArgumentException(
-                                "FLAG_CANT_SAVE_STATE not supported here");
-                    }
-
-                    ActivityOptions options = ActivityOptions.fromBundle(
-                            i == intents.length - 1 ? bOptions : null);
-                    int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/,
-                            resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1,
-                            callingPid, callingUid, callingPackage, callingPid, callingUid, 0,
-                            options, false, componentSpecified, outActivity, null, null);
-                    if (res < 0) {
-                        return res;
-                    }
-
-                    resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-
-        return ActivityManager.START_SUCCESS;
-    }
-
     final boolean realStartActivityLocked(ActivityRecord r,
             ProcessRecord app, boolean andResume, boolean checkConfig)
             throws RemoteException {
@@ -1494,348 +1207,7 @@
                 "activity", r.intent.getComponent(), false, false, true);
     }
 
-    final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
-            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
-            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
-            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
-            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
-            ActivityRecord[] outActivity, ActivityContainer container, TaskRecord inTask) {
-        int err = ActivityManager.START_SUCCESS;
-
-        ProcessRecord callerApp = null;
-        if (caller != null) {
-            callerApp = mService.getRecordForAppLocked(caller);
-            if (callerApp != null) {
-                callingPid = callerApp.pid;
-                callingUid = callerApp.info.uid;
-            } else {
-                Slog.w(TAG, "Unable to find app for caller " + caller
-                      + " (pid=" + callingPid + ") when starting: "
-                      + intent.toString());
-                err = ActivityManager.START_PERMISSION_DENIED;
-            }
-        }
-
-        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
-
-        if (err == ActivityManager.START_SUCCESS) {
-            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
-                    + "} from uid " + callingUid
-                    + " on display " + (container == null ? (mFocusedStack == null ?
-                            Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :
-                            (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
-                                    container.mActivityDisplay.mDisplayId)));
-        }
-
-        ActivityRecord sourceRecord = null;
-        ActivityRecord resultRecord = null;
-        if (resultTo != null) {
-            sourceRecord = isInAnyStackLocked(resultTo);
-            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                    "Will send result to " + resultTo + " " + sourceRecord);
-            if (sourceRecord != null) {
-                if (requestCode >= 0 && !sourceRecord.finishing) {
-                    resultRecord = sourceRecord;
-                }
-            }
-        }
-
-        final int launchFlags = intent.getFlags();
-
-        if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
-            // Transfer the result target from the source activity to the new
-            // one being started, including any failures.
-            if (requestCode >= 0) {
-                ActivityOptions.abort(options);
-                return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
-            }
-            resultRecord = sourceRecord.resultTo;
-            if (resultRecord != null && !resultRecord.isInStackLocked()) {
-                resultRecord = null;
-            }
-            resultWho = sourceRecord.resultWho;
-            requestCode = sourceRecord.requestCode;
-            sourceRecord.resultTo = null;
-            if (resultRecord != null) {
-                resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
-            }
-            if (sourceRecord.launchedFromUid == callingUid) {
-                // The new activity is being launched from the same uid as the previous
-                // activity in the flow, and asking to forward its result back to the
-                // previous.  In this case the activity is serving as a trampoline between
-                // the two, so we also want to update its launchedFromPackage to be the
-                // same as the previous activity.  Note that this is safe, since we know
-                // these two packages come from the same uid; the caller could just as
-                // well have supplied that same package name itself.  This specifially
-                // deals with the case of an intent picker/chooser being launched in the app
-                // flow to redirect to an activity picked by the user, where we want the final
-                // activity to consider it to have been launched by the previous app activity.
-                callingPackage = sourceRecord.launchedFromPackage;
-            }
-        }
-
-        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
-            // We couldn't find a class that can handle the given Intent.
-            // That's the end of that!
-            err = ActivityManager.START_INTENT_NOT_RESOLVED;
-        }
-
-        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
-            // We couldn't find the specific class specified in the Intent.
-            // Also the end of the line.
-            err = ActivityManager.START_CLASS_NOT_FOUND;
-        }
-
-        if (err == ActivityManager.START_SUCCESS && sourceRecord != null
-                && sourceRecord.task.voiceSession != null) {
-            // If this activity is being launched as part of a voice session, we need
-            // to ensure that it is safe to do so.  If the upcoming activity will also
-            // be part of the voice session, we can only launch it if it has explicitly
-            // said it supports the VOICE category, or it is a part of the calling app.
-            if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0
-                    && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
-                try {
-                    intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!AppGlobals.getPackageManager().activitySupportsIntent(
-                            intent.getComponent(), intent, resolvedType)) {
-                        Slog.w(TAG,
-                                "Activity being started in current voice task does not support voice: "
-                                + intent);
-                        err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failure checking voice capabilities", e);
-                    err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
-                }
-            }
-        }
-
-        if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
-            // If the caller is starting a new voice session, just make sure the target
-            // is actually allowing it to run this way.
-            try {
-                if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
-                        intent, resolvedType)) {
-                    Slog.w(TAG,
-                            "Activity being started in new voice task does not support: "
-                            + intent);
-                    err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
-                }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failure checking voice capabilities", e);
-                err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
-            }
-        }
-
-        final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
-
-        if (err != ActivityManager.START_SUCCESS) {
-            if (resultRecord != null) {
-                resultStack.sendActivityResultLocked(-1,
-                    resultRecord, resultWho, requestCode,
-                    Activity.RESULT_CANCELED, null);
-            }
-            ActivityOptions.abort(options);
-            return err;
-        }
-
-        boolean abort = !checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode,
-                callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
-                resultRecord, resultStack);
-        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
-                callingPid, resolvedType, aInfo.applicationInfo);
-
-        if (mService.mController != null) {
-            try {
-                // The Intent we give to the watcher has the extra data
-                // stripped off, since it can contain private information.
-                Intent watchIntent = intent.cloneFilter();
-                abort |= !mService.mController.activityStarting(watchIntent,
-                        aInfo.applicationInfo.packageName);
-            } catch (RemoteException e) {
-                mService.mController = null;
-            }
-        }
-
-        UserInfo user = getUserInfo(userId);
-        KeyguardManager km = (KeyguardManager) mService.mContext
-                .getSystemService(Context.KEYGUARD_SERVICE);
-        if (user.isManagedProfile()
-                && LockPatternUtils.isSeparateWorkChallengeEnabled()
-                && km.isDeviceLocked(userId)) {
-            IIntentSender target = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
-                    new String[]{ resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-            final int flags = intent.getFlags();
-            final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id);
-            if (newIntent != null) {
-                intent = newIntent;
-                intent.setFlags(flags
-                        | Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
-                intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
-
-                resolvedType = null;
-                callingUid = realCallingUid;
-                callingPid = realCallingPid;
-
-                UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId);
-                rInfo = resolveIntent(intent, resolvedType, parent.id);
-                aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
-            }
-        }
-
-        if (abort) {
-            if (resultRecord != null) {
-                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
-                        Activity.RESULT_CANCELED, null);
-            }
-            // We pretend to the caller that it was really started, but
-            // they will just get a cancel result.
-            ActivityOptions.abort(options);
-            return ActivityManager.START_SUCCESS;
-        }
-
-        // If permissions need a review before any of the app components can run, we
-        // launch the review activity and pass a pending intent to start the activity
-        // we are to launching now after the review is completed.
-        if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
-            if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
-                    aInfo.packageName, userId)) {
-                IIntentSender target = mService.getIntentSenderLocked(
-                        ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                        callingUid, userId, null, null, 0, new Intent[]{intent},
-                        new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
-                                | PendingIntent.FLAG_ONE_SHOT, null);
-
-                final int flags = intent.getFlags();
-                Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
-                newIntent.setFlags(flags
-                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
-                newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
-                if (resultRecord != null) {
-                    newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true);
-                }
-                intent = newIntent;
-
-                resolvedType = null;
-                callingUid = realCallingUid;
-                callingPid = realCallingPid;
-
-                rInfo = resolveIntent(intent, resolvedType, userId);
-                aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
-
-                if (DEBUG_PERMISSIONS_REVIEW) {
-                    Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
-                            true, false) + "} from uid " + callingUid + " on display "
-                            + (container == null ? (mFocusedStack == null ?
-                            Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :
-                            (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
-                                    container.mActivityDisplay.mDisplayId)));
-                }
-            }
-        }
-
-        // If we have an ephemeral app, abort the process of launching the resolved intent.
-        // Instead, launch the ephemeral installer. Once the installer is finished, it
-        // starts either the intent we resolved here [on install error] or the ephemeral
-        // app [on install success].
-        if (rInfo != null && rInfo.ephemeralResolveInfo != null) {
-            // Create a pending intent to start the intent resolved here.
-            final IIntentSender failureTarget = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
-                    new String[]{ resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                            | PendingIntent.FLAG_IMMUTABLE, null);
-
-            // Create a pending intent to start the ephemeral application; force it to be
-            // directed to the ephemeral package.
-            ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName());
-            final IIntentSender ephemeralTarget = mService.getIntentSenderLocked(
-                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent },
-                    new String[]{ resolvedType },
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
-                    | PendingIntent.FLAG_IMMUTABLE, null);
-
-            int flags = intent.getFlags();
-            intent = new Intent();
-            intent.setFlags(flags
-                    | Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME,
-                    rInfo.ephemeralResolveInfo.getPackageName());
-            intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget));
-            intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget));
-
-            resolvedType = null;
-            callingUid = realCallingUid;
-            callingPid = realCallingPid;
-
-            rInfo = rInfo.ephemeralInstaller;
-            aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
-        }
-
-        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
-                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
-                requestCode, componentSpecified, voiceSession != null, this, container, options);
-        if (outActivity != null) {
-            outActivity[0] = r;
-        }
-
-        if (r.appTimeTracker == null && sourceRecord != null) {
-            // If the caller didn't specify an explicit time tracker, we want to continue
-            // tracking under any it has.
-            r.appTimeTracker = sourceRecord.appTimeTracker;
-        }
-
-        final ActivityStack stack = mFocusedStack;
-        if (voiceSession == null && (stack.mResumedActivity == null
-                || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
-            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
-                    realCallingPid, realCallingUid, "Activity start")) {
-                PendingActivityLaunch pal =  new PendingActivityLaunch(r,
-                        sourceRecord, startFlags, stack, callerApp);
-                mPendingActivityLaunches.add(pal);
-                ActivityOptions.abort(options);
-                return ActivityManager.START_SWITCHES_CANCELED;
-            }
-        }
-
-        if (mService.mDidAppSwitch) {
-            // This is the second allowed switch since we stopped switches,
-            // so now just generally allow switches.  Use case: user presses
-            // home (switches disabled, switch to home, mDidAppSwitch now true);
-            // user taps a home icon (coming from home so allowed, we hit here
-            // and now allow anyone to switch again).
-            mService.mAppSwitchesAllowedTime = 0;
-        } else {
-            mService.mDidAppSwitch = true;
-        }
-
-        doPendingActivityLaunchesLocked(false);
-
-        err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
-                startFlags, true, options, inTask);
-
-        if (err < 0) {
-            // If someone asked to have the keyguard dismissed on the next
-            // activity start, but we are not actually doing an activity
-            // switch...  just dismiss the keyguard now, because we
-            // probably want to see whatever is behind it.
-            notifyActivityDrawnForKeyguard();
-        }
-        return err;
-    }
-
-    private boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
+    boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
             String resultWho, int requestCode, int callingPid, int callingUid,
             String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
             ActivityRecord resultRecord, ActivityStack resultStack) {
@@ -1895,7 +1267,7 @@
         return true;
     }
 
-    private UserInfo getUserInfo(int userId) {
+    UserInfo getUserInfo(int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             return UserManager.get(mService.mContext).getUserInfo(userId);
@@ -1973,104 +1345,6 @@
         return ACTIVITY_RESTRICTION_NONE;
     }
 
-    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
-            int launchFlags) {
-        final TaskRecord task = r.task;
-        if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
-            return mHomeStack;
-        }
-
-        ActivityStack stack = getLaunchToSideStack(r, launchFlags, task);
-        if (stack != null) {
-            return stack;
-        }
-
-        if (task != null && task.stack != null) {
-            stack = task.stack;
-            if (stack.isOnHomeDisplay()) {
-                if (mFocusedStack != stack) {
-                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Setting " + "focused stack to r=" + r
-                                    + " task=" + task);
-                } else {
-                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Focused stack already=" + mFocusedStack);
-                }
-            }
-            return stack;
-        }
-
-        final ActivityContainer container = r.mInitialActivityContainer;
-        if (container != null) {
-            // The first time put it on the desired stack, after this put on task stack.
-            r.mInitialActivityContainer = null;
-            return container.mStack;
-        }
-
-        // The fullscreen stack can contain any task regardless of if the task is resizeable
-        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-        // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
-        // we can also put it in the focused stack.
-        final int focusedStackId = mFocusedStack.mStackId;
-        final boolean canUseFocusedStack =
-                focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
-                || focusedStackId == DOCKED_STACK_ID
-                || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable);
-        if (canUseFocusedStack
-                && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
-            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                    "computeStackFocus: Have a focused stack=" + mFocusedStack);
-            return mFocusedStack;
-        }
-
-        // We first try to put the task in the first dynamic stack.
-        final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
-        for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            stack = homeDisplayStacks.get(stackNdx);
-            if (!StackId.isStaticStack(stack.mStackId)) {
-                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                        "computeStackFocus: Setting focused stack=" + stack);
-                return stack;
-            }
-        }
-
-        // If there is no suitable dynamic stack then we figure out which static stack to use.
-        final int stackId = task != null ? task.getLaunchStackId() :
-                    bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
-                                     FULLSCREEN_WORKSPACE_STACK_ID;
-        stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
-        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
-                + r + " stackId=" + stack.mStackId);
-        return stack;
-    }
-
-    private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) {
-        if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) {
-            return null;
-        }
-        // The parent activity doesn't want to launch the activity on top of itself, but
-        // instead tries to put it onto other side in side-by-side mode.
-        final ActivityStack parentStack = task != null ? task.stack
-                : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack
-                : mFocusedStack;
-        if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) {
-            // If parent was in docked stack, the natural place to launch another activity
-            // will be fullscreen, so it can appear alongside the docked window.
-            return getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        } else {
-            // If the parent is not in the docked stack, we check if there is docked window
-            // and if yes, we will launch into that stack. If not, we just put the new
-            // activity into parent's stack, because we can't find a better place.
-            final ActivityStack stack = getStack(DOCKED_STACK_ID);
-            if (stack != null && !stack.isStackVisibleLocked()) {
-                // There is a docked stack, but it isn't visible, so we can't launch into that.
-                return null;
-            } else {
-                return stack;
-            }
-        }
-    }
-
     boolean setFocusedStack(ActivityRecord r, String reason) {
         if (r == null) {
             // Not sure what you are trying to do, but it is not going to work...
@@ -2085,735 +1359,14 @@
         return true;
     }
 
-    final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
-            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
-            boolean doResume, ActivityOptions options, TaskRecord inTask) {
-        final Intent intent = r.intent;
-        final int callingUid = r.launchedFromUid;
-
-        boolean overrideBounds = false;
+    Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
         Rect newBounds = null;
         if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) {
             if (canUseActivityOptionsLaunchBounds(options)) {
-                overrideBounds = true;
                 newBounds = options.getLaunchBounds();
             }
         }
-
-        // In some flows in to this function, we retrieve the task record and hold on to it
-        // without a lock before calling back in to here...  so the task at this point may
-        // not actually be in recents.  Check for that, and if it isn't in recents just
-        // consider it invalid.
-        if (inTask != null && !inTask.inRecents) {
-            Slog.w(TAG, "Starting activity in task not in recents: " + inTask);
-            inTask = null;
-        }
-
-        final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
-        final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
-        final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
-
-        int launchFlags = intent.getFlags();
-        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
-                (launchSingleInstance || launchSingleTask)) {
-            // We have a conflict between the Intent and the Activity manifest, manifest wins.
-            Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
-                    "\"singleInstance\" or \"singleTask\"");
-            launchFlags &=
-                    ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
-        } else {
-            switch (r.info.documentLaunchMode) {
-                case ActivityInfo.DOCUMENT_LAUNCH_NONE:
-                    break;
-                case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
-                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-                    break;
-                case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
-                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-                    break;
-                case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
-                    launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK;
-                    break;
-            }
-        }
-
-        final boolean launchTaskBehind = r.mLaunchTaskBehind
-                && !launchSingleTask && !launchSingleInstance
-                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
-
-        if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
-                && r.resultTo.task.stack != null) {
-            // For whatever reason this activity is being launched into a new
-            // task...  yet the caller has requested a result back.  Well, that
-            // is pretty messed up, so instead immediately send back a cancel
-            // and let the new task continue launched as normal without a
-            // dependency on its originator.
-            Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
-            r.resultTo.task.stack.sendActivityResultLocked(-1,
-                    r.resultTo, r.resultWho, r.requestCode,
-                    Activity.RESULT_CANCELED, null);
-            r.resultTo = null;
-        }
-
-        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
-            launchFlags |= FLAG_ACTIVITY_NEW_TASK;
-        }
-
-        // If we are actually going to launch in to a new task, there are some cases where
-        // we further want to do multiple task.
-        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
-            if (launchTaskBehind
-                    || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
-                launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK;
-            }
-        }
-
-        // We'll invoke onUserLeaving before onPause only if the launching
-        // activity did not explicitly state that this is an automated launch.
-        mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
-        if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
-                "startActivity() => mUserLeaving=" + mUserLeaving);
-
-        // If the caller has asked not to resume at this point, we make note
-        // of this in the record so that we can skip it when trying to find
-        // the top running activity.
-        if (!doResume || !okToShowLocked(r)) {
-            r.delayedResume = true;
-            doResume = false;
-        }
-
-        ActivityRecord notTop =
-                (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;
-
-        // If the onlyIfNeeded flag is set, then we can do this if the activity
-        // being launched is the same as the one making the call...  or, as
-        // a special case, if we do not know the caller then we count the
-        // current top activity as the caller.
-        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
-            ActivityRecord checkedCaller = sourceRecord;
-            if (checkedCaller == null) {
-                checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop);
-            }
-            if (!checkedCaller.realActivity.equals(r.realActivity)) {
-                // Caller is not the same as launcher, so always needed.
-                startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED;
-            }
-        }
-
-        boolean addingToTask = false;
-        TaskRecord reuseTask = null;
-
-        // If the caller is not coming from another activity, but has given us an
-        // explicit task into which they would like us to launch the new activity,
-        // then let's see about doing that.
-        if (sourceRecord == null && inTask != null && inTask.stack != null) {
-            final Intent baseIntent = inTask.getBaseIntent();
-            final ActivityRecord root = inTask.getRootActivity();
-            if (baseIntent == null) {
-                ActivityOptions.abort(options);
-                throw new IllegalArgumentException("Launching into task without base intent: "
-                        + inTask);
-            }
-
-            // If this task is empty, then we are adding the first activity -- it
-            // determines the root, and must be launching as a NEW_TASK.
-            if (launchSingleInstance || launchSingleTask) {
-                if (!baseIntent.getComponent().equals(r.intent.getComponent())) {
-                    ActivityOptions.abort(options);
-                    throw new IllegalArgumentException("Trying to launch singleInstance/Task "
-                            + r + " into different task " + inTask);
-                }
-                if (root != null) {
-                    ActivityOptions.abort(options);
-                    throw new IllegalArgumentException("Caller with inTask " + inTask
-                            + " has root " + root + " but target is singleInstance/Task");
-                }
-            }
-
-            // If task is empty, then adopt the interesting intent launch flags in to the
-            // activity being started.
-            if (root == null) {
-                final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK
-                        | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
-                        | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
-                launchFlags = (launchFlags&~flagsOfInterest)
-                        | (baseIntent.getFlags()&flagsOfInterest);
-                intent.setFlags(launchFlags);
-                inTask.setIntent(r);
-                addingToTask = true;
-
-            // If the task is not empty and the caller is asking to start it as the root
-            // of a new task, then we don't actually want to start this on the task.  We
-            // will bring the task to the front, and possibly give it a new intent.
-            } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
-                addingToTask = false;
-
-            } else {
-                addingToTask = true;
-            }
-
-            reuseTask = inTask;
-        } else {
-            inTask = null;
-            // Launch ResolverActivity in the source task, so that it stays in the task
-            // bounds when in freeform workspace.
-            // Also put noDisplay activities in the source task. These by itself can
-            // be placed in any task/stack, however it could launch other activities
-            // like ResolverActivity, and we want those to stay in the original task.
-            if ((r.isResolverActivity() || r.noDisplay) && sourceRecord != null
-                    && sourceRecord.isFreeform())  {
-                addingToTask = true;
-            }
-        }
-
-        if (inTask == null) {
-            if (sourceRecord == null) {
-                // This activity is not being started from another...  in this
-                // case we -always- start a new task.
-                if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
-                    Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
-                            "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
-                    launchFlags |= FLAG_ACTIVITY_NEW_TASK;
-                }
-            } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
-                // The original activity who is starting us is running as a single
-                // instance...  this new activity it is starting must go on its
-                // own task.
-                launchFlags |= FLAG_ACTIVITY_NEW_TASK;
-            } else if (launchSingleInstance || launchSingleTask) {
-                // The activity being started is a single instance...  it always
-                // gets launched into its own task.
-                launchFlags |= FLAG_ACTIVITY_NEW_TASK;
-            }
-        }
-
-        ActivityInfo newTaskInfo = null;
-        Intent newTaskIntent = null;
-        final ActivityStack sourceStack;
-        if (sourceRecord != null) {
-            if (sourceRecord.finishing) {
-                // If the source is finishing, we can't further count it as our source.  This
-                // is because the task it is associated with may now be empty and on its way out,
-                // so we don't want to blindly throw it in to that task.  Instead we will take
-                // the NEW_TASK flow and try to find a task for it. But save the task information
-                // so it can be used when creating the new task.
-                if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) {
-                    Slog.w(TAG, "startActivity called from finishing " + sourceRecord
-                            + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
-                    launchFlags |= FLAG_ACTIVITY_NEW_TASK;
-                    newTaskInfo = sourceRecord.info;
-                    newTaskIntent = sourceRecord.task.intent;
-                }
-                sourceRecord = null;
-                sourceStack = null;
-            } else {
-                sourceStack = sourceRecord.task.stack;
-            }
-        } else {
-            sourceStack = null;
-        }
-
-        boolean movedHome = false;
-        ActivityStack targetStack;
-
-        intent.setFlags(launchFlags);
-        final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;
-
-        ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent,
-                launchSingleInstance, launchSingleTask, launchFlags);
-        if (intentActivity != null) {
-            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused
-            // but still needs to be a lock task mode violation since the task gets
-            // cleared out and the device would otherwise leave the locked task.
-            if (isLockTaskModeViolation(intentActivity.task,
-                    (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                    == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
-                showLockTaskToast();
-                Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
-                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
-            }
-            if (r.task == null) {
-                r.task = intentActivity.task;
-            }
-            if (intentActivity.task.intent == null) {
-                // This task was started because of movement of the activity based on affinity...
-                // Now that we are actually launching it, we can assign the base intent.
-                intentActivity.task.setIntent(r);
-            }
-
-            targetStack = intentActivity.task.stack;
-            targetStack.mLastPausedActivity = null;
-            // If the target task is not in the front, then we need
-            // to bring it to the front...  except...  well, with
-            // SINGLE_TASK_LAUNCH it's not entirely clear.  We'd like
-            // to have the same behavior as if a new instance was
-            // being started, which means not bringing it to the front
-            // if the caller is not itself in the front.
-            final ActivityStack focusStack = getFocusedStack();
-            ActivityRecord curTop = (focusStack == null)
-                    ? null : focusStack.topRunningNonDelayedActivityLocked(notTop);
-            boolean movedToFront = false;
-            if (curTop != null && (curTop.task != intentActivity.task ||
-                    curTop.task != focusStack.topTask())) {
-                r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-                if (sourceRecord == null || (sourceStack.topActivity() != null &&
-                        sourceStack.topActivity().task == sourceRecord.task)) {
-                    // We really do want to push this one into the user's face, right now.
-                    if (launchTaskBehind && sourceRecord != null) {
-                        intentActivity.setTaskToAffiliateWith(sourceRecord.task);
-                    }
-                    movedHome = true;
-                    final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task);
-                    if (sideStack == null || sideStack == targetStack) {
-                        // We only want to move to the front, if we aren't going to launch on a
-                        // different stack. If we launch on a different stack, we will put the
-                        // task on top there.
-                        targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
-                        options, r.appTimeTracker, "bringingFoundTaskToFront");
-                        movedToFront = true;
-                    }
-                    if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
-                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
-                        // Caller wants to appear on home activity.
-                        intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
-                    }
-                    options = null;
-                }
-            }
-            if (!movedToFront && doResume) {
-                if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack
-                        + " from " + intentActivity);
-                targetStack.moveToFront("intentActivityFound");
-            }
-
-            // If the caller has requested that the target task be
-            // reset, then do so.
-            if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
-                intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
-            }
-            if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
-                // We don't need to start a new activity, and
-                // the client said not to do anything if that
-                // is the case, so this is it!  And for paranoia, make
-                // sure we have correctly resumed the top activity.
-                if (doResume) {
-                    resumeTopActivitiesLocked(targetStack, null, options);
-
-                    // Make sure to notify Keyguard as well if we are not running an app
-                    // transition later.
-                    if (!movedToFront) {
-                        notifyActivityDrawnForKeyguard();
-                    }
-                } else {
-                    ActivityOptions.abort(options);
-                }
-                updateUserStackLocked(r.userId, targetStack);
-                return ActivityManager.START_RETURN_INTENT_TO_CALLER;
-            }
-            if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                    == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
-                // The caller has requested to completely replace any
-                // existing task with its new activity.  Well that should
-                // not be too hard...
-                reuseTask = intentActivity.task;
-                reuseTask.performClearTaskLocked();
-                reuseTask.setIntent(r);
-            } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                    || launchSingleInstance || launchSingleTask) {
-                // In this situation we want to remove all activities
-                // from the task up to the one being started.  In most
-                // cases this means we are resetting the task to its
-                // initial state.
-                ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags);
-                if (top != null) {
-                    if (top.frontOfTask) {
-                        // Activity aliases may mean we use different
-                        // intents for the top activity, so make sure
-                        // the task now has the identity of the new
-                        // intent.
-                        top.task.setIntent(r);
-                    }
-                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
-                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
-                } else {
-                    // A special case: we need to start the activity because it is not
-                    // currently running, and the caller has asked to clear the current
-                    // task to have this activity at the top.
-                    addingToTask = true;
-                    // Now pretend like this activity is being started by the top of its
-                    // task, so it is put in the right place.
-                    sourceRecord = intentActivity;
-                    TaskRecord task = sourceRecord.task;
-                    if (task != null && task.stack == null) {
-                        // Target stack got cleared when we all activities were removed
-                        // above. Go ahead and reset it.
-                        targetStack = computeStackFocus(
-                                sourceRecord, false /* newTask */, null /* bounds */, launchFlags);
-                        targetStack.addTask(task,
-                                !launchTaskBehind /* toTop */, "startActivityUnchecked");
-                    }
-
-                }
-            } else if (r.realActivity.equals(intentActivity.task.realActivity)) {
-                // In this case the top activity on the task is the
-                // same as the one being launched, so we take that
-                // as a request to bring the task to the foreground.
-                // If the top activity in the task is the root
-                // activity, deliver this new intent to it if it
-                // desires.
-                if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop)
-                        && intentActivity.realActivity.equals(r.realActivity)) {
-                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,
-                            intentActivity.task);
-                    if (intentActivity.frontOfTask) {
-                        intentActivity.task.setIntent(r);
-                    }
-                    intentActivity.deliverNewIntentLocked(callingUid, r.intent,
-                            r.launchedFromPackage);
-                } else if (!r.intent.filterEquals(intentActivity.task.intent)) {
-                    // In this case we are launching the root activity
-                    // of the task, but with a different intent.  We
-                    // should start a new instance on top.
-                    addingToTask = true;
-                    sourceRecord = intentActivity;
-                }
-            } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
-                // In this case an activity is being launched in to an
-                // existing task, without resetting that task.  This
-                // is typically the situation of launching an activity
-                // from a notification or shortcut.  We want to place
-                // the new activity on top of the current task.
-                addingToTask = true;
-                sourceRecord = intentActivity;
-            } else if (!intentActivity.task.rootWasReset) {
-                // In this case we are launching in to an existing task
-                // that has not yet been started from its front door.
-                // The current task has been brought to the front.
-                // Ideally, we'd probably like to place this new task
-                // at the bottom of its stack, but that's a little hard
-                // to do with the current organization of the code so
-                // for now we'll just drop it.
-                intentActivity.task.setIntent(r);
-            }
-            if (!addingToTask && reuseTask == null) {
-                // We didn't do anything...  but it was needed (a.k.a., client
-                // don't use that intent!)  And for paranoia, make
-                // sure we have correctly resumed the top activity.
-                if (doResume) {
-                    targetStack.resumeTopActivityLocked(null, options);
-                    if (!movedToFront) {
-                        // Make sure to notify Keyguard as well if we are not running an app
-                        // transition later.
-                        notifyActivityDrawnForKeyguard();
-                    }
-                } else {
-                    ActivityOptions.abort(options);
-                }
-                updateUserStackLocked(r.userId, targetStack);
-                return ActivityManager.START_TASK_TO_FRONT;
-            }
-        }
-
-        //String uri = r.intent.toURI();
-        //Intent intent2 = new Intent(uri);
-        //Slog.i(TAG, "Given intent: " + r.intent);
-        //Slog.i(TAG, "URI is: " + uri);
-        //Slog.i(TAG, "To intent: " + intent2);
-
-        if (r.packageName != null) {
-            // If the activity being launched is the same as the one currently
-            // at the top, then we need to check if it should only be launched
-            // once.
-            ActivityStack topStack = mFocusedStack;
-            ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
-            final boolean dontStart = top != null && r.resultTo == null
-                    && top.realActivity.equals(r.realActivity) && top.userId == r.userId
-                    && top.app != null && top.app.thread != null
-                    && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
-                            || launchSingleTop || launchSingleTask);
-            if (dontStart) {
-                ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
-                // For paranoia, make sure we have correctly resumed the top activity.
-                topStack.mLastPausedActivity = null;
-                if (doResume) {
-                    resumeTopActivitiesLocked();
-                }
-                ActivityOptions.abort(options);
-                if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
-                    // We don't need to start a new activity, and the client said not to do
-                    // anything if that is the case, so this is it!
-                    return ActivityManager.START_RETURN_INTENT_TO_CALLER;
-                }
-                top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
-                return ActivityManager.START_DELIVERED_TO_TOP;
-            }
-        } else {
-            if (r.resultTo != null && r.resultTo.task.stack != null) {
-                r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,
-                        r.requestCode, Activity.RESULT_CANCELED, null);
-            }
-            ActivityOptions.abort(options);
-            return ActivityManager.START_CLASS_NOT_FOUND;
-        }
-
-        boolean newTask = false;
-        boolean keepCurTransition = false;
-
-        TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
-                sourceRecord.task : null;
-
-        // Should this be considered a new task?
-        if (r.resultTo == null && inTask == null && !addingToTask
-                && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
-            newTask = true;
-            targetStack = computeStackFocus(r, newTask, newBounds, launchFlags);
-            if (doResume) {
-                targetStack.moveToFront("startingNewTask");
-            }
-
-            if (reuseTask == null) {
-                r.setTask(targetStack.createTaskRecord(getNextTaskId(),
-                        newTaskInfo != null ? newTaskInfo : r.info,
-                        newTaskIntent != null ? newTaskIntent : intent,
-                        voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
-                        taskToAffiliate);
-                if (overrideBounds) {
-                    r.task.updateOverrideConfiguration(newBounds);
-                }
-                if (DEBUG_TASKS) Slog.v(TAG_TASKS,
-                        "Starting new activity " + r + " in new task " + r.task);
-            } else {
-                r.setTask(reuseTask, taskToAffiliate);
-            }
-            if (isLockTaskModeViolation(r.task)) {
-                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
-                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
-            }
-            if (!movedHome) {
-                if ((launchFlags &
-                        (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
-                        == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
-                    // Caller wants to appear on home activity, so before starting
-                    // their own activity we will bring home to the front.
-                    r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
-                }
-            }
-        } else if (sourceRecord != null) {
-            final TaskRecord sourceTask = sourceRecord.task;
-            if (isLockTaskModeViolation(sourceTask)) {
-                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
-                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
-            }
-            targetStack = null;
-            if (sourceTask.stack.topTask() != sourceTask) {
-                // We only want to allow changing stack if the target task is not the top one,
-                // otherwise we would move the launching task to the other side, rather than show
-                // two side by side.
-                targetStack = getLaunchToSideStack(r, launchFlags, r.task);
-            }
-            if (targetStack == null) {
-                targetStack = sourceTask.stack;
-            } else if (targetStack != sourceTask.stack) {
-                moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, ON_TOP,
-                        FORCE_FOCUS, "launchToSide", !ANIMATE);
-            }
-            if (doResume) {
-                targetStack.moveToFront("sourceStackToFront");
-            }
-            final TaskRecord topTask = targetStack.topTask();
-            if (topTask != sourceTask) {
-                targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options,
-                        r.appTimeTracker, "sourceTaskToFront");
-            }
-            if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
-                // In this case, we are adding the activity to an existing
-                // task, but the caller has asked to clear that task if the
-                // activity is already running.
-                ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags);
-                keepCurTransition = true;
-                if (top != null) {
-                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
-                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
-                    // For paranoia, make sure we have correctly
-                    // resumed the top activity.
-                    targetStack.mLastPausedActivity = null;
-                    if (doResume) {
-                        targetStack.resumeTopActivityLocked(null);
-                    }
-                    ActivityOptions.abort(options);
-                    return ActivityManager.START_DELIVERED_TO_TOP;
-                }
-            } else if (!addingToTask &&
-                    (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
-                // In this case, we are launching an activity in our own task
-                // that may already be running somewhere in the history, and
-                // we want to shuffle it to the front of the stack if so.
-                final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r);
-                if (top != null) {
-                    final TaskRecord task = top.task;
-                    task.moveActivityToFrontLocked(top);
-                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task);
-                    top.updateOptionsLocked(options);
-                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
-                    targetStack.mLastPausedActivity = null;
-                    if (doResume) {
-                        targetStack.resumeTopActivityLocked(null);
-                    }
-                    return ActivityManager.START_DELIVERED_TO_TOP;
-                }
-            }
-            // An existing activity is starting this new activity, so we want
-            // to keep the new one in the same task as the one that is starting
-            // it.
-            r.setTask(sourceTask, null);
-            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
-                    + " in existing task " + r.task + " from source " + sourceRecord);
-
-        } else if (inTask != null) {
-            // The caller is asking that the new activity be started in an explicit
-            // task it has provided to us.
-            if (isLockTaskModeViolation(inTask)) {
-                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
-                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
-            }
-            if (overrideBounds) {
-                inTask.updateOverrideConfiguration(newBounds);
-                int stackId = inTask.getLaunchStackId();
-                if (stackId != inTask.stack.mStackId) {
-                    moveTaskToStackUncheckedLocked(
-                            inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
-                }
-            }
-            targetStack = inTask.stack;
-            targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker,
-                    "inTaskToFront");
-
-            // Check whether we should actually launch the new activity in to the task,
-            // or just reuse the current activity on top.
-            ActivityRecord top = inTask.getTopActivity();
-            if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
-                if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
-                        || launchSingleTop || launchSingleTask) {
-                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
-                    if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
-                        // We don't need to start a new activity, and
-                        // the client said not to do anything if that
-                        // is the case, so this is it!
-                        return ActivityManager.START_RETURN_INTENT_TO_CALLER;
-                    }
-                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
-                    return ActivityManager.START_DELIVERED_TO_TOP;
-                }
-            }
-
-            if (!addingToTask) {
-                // We don't actually want to have this activity added to the task, so just
-                // stop here but still tell the caller that we consumed the intent.
-                ActivityOptions.abort(options);
-                return ActivityManager.START_TASK_TO_FRONT;
-            }
-
-            r.setTask(inTask, null);
-            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
-                    + " in explicit task " + r.task);
-
-        } else {
-            // This not being started from an existing activity, and not part
-            // of a new task...  just put it in the top task, though these days
-            // this case should never happen.
-            targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags);
-            if (doResume) {
-                targetStack.moveToFront("addingToTopTask");
-            }
-            ActivityRecord prev = targetStack.topActivity();
-            r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
-                            r.info, intent, null, null, true), null);
-            mWindowManager.moveTaskToTop(r.task.taskId);
-            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
-                    + " in new guessed " + r.task);
-        }
-
-        mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                intent, r.getUriPermissionsLocked(), r.userId);
-
-        if (sourceRecord != null && sourceRecord.isRecentsActivity()) {
-            r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
-        }
-        if (newTask) {
-            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
-        }
-        ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
-        targetStack.mLastPausedActivity = null;
-        targetStack.startActivityLocked(r, newTask, keepCurTransition, options);
-        if (doResume) {
-            if (!launchTaskBehind) {
-                mService.setFocusedActivityLocked(r, "startedActivity");
-            }
-            resumeTopActivitiesLocked(targetStack, r, options);
-        } else {
-            targetStack.addRecentActivityLocked(r);
-        }
-        updateUserStackLocked(r.userId, targetStack);
-
-        if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) {
-            showNonResizeableDockToast(r.task.taskId);
-        }
-
-        return ActivityManager.START_SUCCESS;
-    }
-
-    /**
-     * Decide whether the new activity should be inserted into an existing task. Returns null if not
-     * or an ActivityRecord with the task into which the new activity should be added.
-     */
-    private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, Intent intent,
-            boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) {
-        // We may want to try to place the new activity in to an existing task.  We always
-        // do this if the target activity is singleTask or singleInstance; we will also do
-        // this if NEW_TASK has been requested, and there is not an additional qualifier telling
-        // us to still place it in a new task: multi task, always doc mode, or being asked to
-        // launch this as a new task behind the current one.
-        boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
-                (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
-                || launchSingleInstance || launchSingleTask;
-        // If bring to front is requested, and no result is requested and we have not
-        // been given an explicit task to launch in to, and
-        // we can find a task that was started with this same
-        // component, then instead of launching bring that one to the front.
-        putIntoExistingTask &= inTask == null && r.resultTo == null;
-        ActivityRecord intentActivity = null;
-        if (putIntoExistingTask) {
-            // See if there is a task to bring to the front.  If this is
-            // a SINGLE_INSTANCE activity, there can be one and only one
-            // instance of it in the history, and it is always in its own
-            // unique task, so we do a special search.
-            intentActivity = launchSingleInstance ?
-                    findActivityLocked(intent, r.info) : findTaskLocked(r);
-        }
-        return intentActivity;
-    }
-
-    final void doPendingActivityLaunchesLocked(boolean doResume) {
-        while (!mPendingActivityLaunches.isEmpty()) {
-            PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
-
-            try {
-                startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                        doResume && mPendingActivityLaunches.isEmpty(), null, null);
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
-                pal.sendErrorResult(e.getMessage());
-            }
-        }
-    }
-
-    void removePendingActivityLaunchesLocked(ActivityStack stack) {
-        for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
-            PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            if (pal.stack == stack) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
+        return newBounds;
     }
 
     void setLaunchSource(int uid) {
@@ -3641,7 +2194,7 @@
         }
 
         if (bounds != null) {
-            resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true);
+            resizeStackLocked(stackId, bounds, !PRESERVE_WINDOWS, true);
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
@@ -3670,6 +2223,8 @@
     }
 
     ActivityRecord findTaskLocked(ActivityRecord r) {
+        mTmpFindTaskResult.r = null;
+        mTmpFindTaskResult.matchedByRootAffinity = false;
         if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
             final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -3684,14 +2239,18 @@
                             "Skipping stack: (new task not allowed) " + stack);
                     continue;
                 }
-                final ActivityRecord ar = stack.findTaskLocked(r);
-                if (ar != null) {
-                    return ar;
+                stack.findTaskLocked(r, mTmpFindTaskResult);
+                // It is possible to have task in multiple stacks with the same root affinity.
+                // If the match we found was based on root affinity we keep on looking to see if
+                // there is a better match in another stack. We eventually return the match based
+                // on root affinity if we don't find a better match.
+                if (mTmpFindTaskResult.r != null && !mTmpFindTaskResult.matchedByRootAffinity) {
+                    return mTmpFindTaskResult.r;
                 }
             }
         }
-        if (DEBUG_TASKS) Slog.d(TAG_TASKS, "No task found");
-        return null;
+        if (DEBUG_TASKS && mTmpFindTaskResult.r == null) Slog.d(TAG_TASKS, "No task found");
+        return mTmpFindTaskResult.r;
     }
 
     ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) {
@@ -4539,7 +3098,7 @@
         }
     }
 
-    private void showNonResizeableDockToast(int taskId) {
+    void showNonResizeableDockToast(int taskId) {
         mWindowManager.scheduleShowNonResizeableDockToast(taskId);
     }
 
@@ -4956,7 +3515,7 @@
                 long origId = Binder.clearCallingIdentity();
                 try {
                     mStack.finishAllActivitiesLocked(false);
-                    removePendingActivityLaunchesLocked(mStack);
+                    mService.mActivityStarter.removePendingActivityLaunchesLocked(mStack);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -4975,22 +3534,7 @@
 
         @Override
         public final int startActivity(Intent intent) {
-            mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
-            final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
-                    Binder.getCallingUid(), mCurrentUser, false,
-                    ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
-
-            // TODO: Switch to user app stacks here.
-            String mimeType = intent.getType();
-            final Uri data = intent.getData();
-            if (mimeType == null && data != null && "content".equals(data.getScheme())) {
-                mimeType = mService.getProviderMimeType(data, userId);
-            }
-            checkEmbeddedAllowedInner(userId, intent, mimeType);
-
-            intent.addFlags(FORCE_NEW_TASK_FLAGS);
-            return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null,
-                    0, 0, null, null, null, null, false, userId, this, null);
+            return mService.startActivity(intent, this);
         }
 
         @Override
@@ -5014,7 +3558,7 @@
                     FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this);
         }
 
-        private void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) {
+        void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) {
             ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, userId);
             if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
                 throw new SecurityException(
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
new file mode 100644
index 0000000..b16e160
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -0,0 +1,1545 @@
+package com.android.server.am;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING;
+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.ANIMATE;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
+import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
+import android.app.IActivityContainer;
+import android.app.IActivityManager;
+import android.app.IApplicationThread;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.ProfilerInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.voice.IVoiceInteractionSession;
+import android.util.EventLog;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
+import com.android.server.wm.WindowManagerService;
+
+import java.util.ArrayList;
+
+/**
+ * Controller for interpreting how and then launching activities.
+ *
+ * This class collects all the logic for determining how an intent and flags should be turned into
+ * an activity and associated task and stack.
+ */
+public class ActivityStarter {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStarter" : TAG_AM;
+    private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
+    private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
+    private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+    private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+
+    private final ActivityManagerService mService;
+    private final ActivityStackSupervisor mSupervisor;
+    private WindowManagerService mWindowManager;
+
+    final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
+
+    ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
+        mService = service;
+        mSupervisor = supervisor;
+    }
+
+    final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
+            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
+            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
+            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
+            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
+            TaskRecord inTask) {
+        int err = ActivityManager.START_SUCCESS;
+
+        ProcessRecord callerApp = null;
+        if (caller != null) {
+            callerApp = mService.getRecordForAppLocked(caller);
+            if (callerApp != null) {
+                callingPid = callerApp.pid;
+                callingUid = callerApp.info.uid;
+            } else {
+                Slog.w(TAG, "Unable to find app for caller " + caller
+                        + " (pid=" + callingPid + ") when starting: "
+                        + intent.toString());
+                err = ActivityManager.START_PERMISSION_DENIED;
+            }
+        }
+
+        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+
+        if (err == ActivityManager.START_SUCCESS) {
+            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
+                    + "} from uid " + callingUid
+                    + " on display " + (container == null ? (mSupervisor.mFocusedStack == null ?
+                    Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
+                    (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+                            container.mActivityDisplay.mDisplayId)));
+        }
+
+        ActivityRecord sourceRecord = null;
+        ActivityRecord resultRecord = null;
+        if (resultTo != null) {
+            sourceRecord = mSupervisor.isInAnyStackLocked(resultTo);
+            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+                    "Will send result to " + resultTo + " " + sourceRecord);
+            if (sourceRecord != null) {
+                if (requestCode >= 0 && !sourceRecord.finishing) {
+                    resultRecord = sourceRecord;
+                }
+            }
+        }
+
+        final int launchFlags = intent.getFlags();
+
+        if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
+            // Transfer the result target from the source activity to the new
+            // one being started, including any failures.
+            if (requestCode >= 0) {
+                ActivityOptions.abort(options);
+                return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
+            }
+            resultRecord = sourceRecord.resultTo;
+            if (resultRecord != null && !resultRecord.isInStackLocked()) {
+                resultRecord = null;
+            }
+            resultWho = sourceRecord.resultWho;
+            requestCode = sourceRecord.requestCode;
+            sourceRecord.resultTo = null;
+            if (resultRecord != null) {
+                resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
+            }
+            if (sourceRecord.launchedFromUid == callingUid) {
+                // The new activity is being launched from the same uid as the previous
+                // activity in the flow, and asking to forward its result back to the
+                // previous.  In this case the activity is serving as a trampoline between
+                // the two, so we also want to update its launchedFromPackage to be the
+                // same as the previous activity.  Note that this is safe, since we know
+                // these two packages come from the same uid; the caller could just as
+                // well have supplied that same package name itself.  This specifially
+                // deals with the case of an intent picker/chooser being launched in the app
+                // flow to redirect to an activity picked by the user, where we want the final
+                // activity to consider it to have been launched by the previous app activity.
+                callingPackage = sourceRecord.launchedFromPackage;
+            }
+        }
+
+        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
+            // We couldn't find a class that can handle the given Intent.
+            // That's the end of that!
+            err = ActivityManager.START_INTENT_NOT_RESOLVED;
+        }
+
+        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+            // We couldn't find the specific class specified in the Intent.
+            // Also the end of the line.
+            err = ActivityManager.START_CLASS_NOT_FOUND;
+        }
+
+        if (err == ActivityManager.START_SUCCESS && sourceRecord != null
+                && sourceRecord.task.voiceSession != null) {
+            // If this activity is being launched as part of a voice session, we need
+            // to ensure that it is safe to do so.  If the upcoming activity will also
+            // be part of the voice session, we can only launch it if it has explicitly
+            // said it supports the VOICE category, or it is a part of the calling app.
+            if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0
+                    && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
+                try {
+                    intent.addCategory(Intent.CATEGORY_VOICE);
+                    if (!AppGlobals.getPackageManager().activitySupportsIntent(
+                            intent.getComponent(), intent, resolvedType)) {
+                        Slog.w(TAG,
+                                "Activity being started in current voice task does not support voice: "
+                                        + intent);
+                        err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+                    }
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failure checking voice capabilities", e);
+                    err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+                }
+            }
+        }
+
+        if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
+            // If the caller is starting a new voice session, just make sure the target
+            // is actually allowing it to run this way.
+            try {
+                if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+                        intent, resolvedType)) {
+                    Slog.w(TAG,
+                            "Activity being started in new voice task does not support: "
+                                    + intent);
+                    err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failure checking voice capabilities", e);
+                err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
+            }
+        }
+
+        final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+
+        if (err != ActivityManager.START_SUCCESS) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1,
+                        resultRecord, resultWho, requestCode,
+                        Activity.RESULT_CANCELED, null);
+            }
+            ActivityOptions.abort(options);
+            return err;
+        }
+
+        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
+                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
+                resultRecord, resultStack);
+        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
+                callingPid, resolvedType, aInfo.applicationInfo);
+
+        if (mService.mController != null) {
+            try {
+                // The Intent we give to the watcher has the extra data
+                // stripped off, since it can contain private information.
+                Intent watchIntent = intent.cloneFilter();
+                abort |= !mService.mController.activityStarting(watchIntent,
+                        aInfo.applicationInfo.packageName);
+            } catch (RemoteException e) {
+                mService.mController = null;
+            }
+        }
+
+        UserInfo user = mSupervisor.getUserInfo(userId);
+        KeyguardManager km = (KeyguardManager) mService.mContext
+                .getSystemService(Context.KEYGUARD_SERVICE);
+        if (user.isManagedProfile()
+                && LockPatternUtils.isSeparateWorkChallengeEnabled()
+                && km.isDeviceLocked(userId)) {
+            IIntentSender target = mService.getIntentSenderLocked(
+                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
+                    new String[]{ resolvedType },
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE, null);
+            final int flags = intent.getFlags();
+            final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id);
+            if (newIntent != null) {
+                intent = newIntent;
+                intent.setFlags(flags
+                        | Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
+                intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+                resolvedType = null;
+                callingUid = realCallingUid;
+                callingPid = realCallingPid;
+
+                UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId);
+                rInfo = mSupervisor.resolveIntent(intent, resolvedType, parent.id);
+                aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
+                        null /*profilerInfo*/);
+            }
+        }
+
+        if (abort) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
+                        Activity.RESULT_CANCELED, null);
+            }
+            // We pretend to the caller that it was really started, but
+            // they will just get a cancel result.
+            ActivityOptions.abort(options);
+            return ActivityManager.START_SUCCESS;
+        }
+
+        // If permissions need a review before any of the app components can run, we
+        // launch the review activity and pass a pending intent to start the activity
+        // we are to launching now after the review is completed.
+        if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
+            if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+                    aInfo.packageName, userId)) {
+                IIntentSender target = mService.getIntentSenderLocked(
+                        ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                        callingUid, userId, null, null, 0, new Intent[]{intent},
+                        new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
+                                | PendingIntent.FLAG_ONE_SHOT, null);
+
+                final int flags = intent.getFlags();
+                Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+                newIntent.setFlags(flags
+                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
+                newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+                if (resultRecord != null) {
+                    newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true);
+                }
+                intent = newIntent;
+
+                resolvedType = null;
+                callingUid = realCallingUid;
+                callingPid = realCallingPid;
+
+                rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
+                aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
+                        null /*profilerInfo*/);
+
+                if (DEBUG_PERMISSIONS_REVIEW) {
+                    Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
+                            true, false) + "} from uid " + callingUid + " on display "
+                            + (container == null ? (mSupervisor.mFocusedStack == null ?
+                            Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
+                            (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+                                    container.mActivityDisplay.mDisplayId)));
+                }
+            }
+        }
+
+        // If we have an ephemeral app, abort the process of launching the resolved intent.
+        // Instead, launch the ephemeral installer. Once the installer is finished, it
+        // starts either the intent we resolved here [on install error] or the ephemeral
+        // app [on install success].
+        if (rInfo != null && rInfo.ephemeralResolveInfo != null) {
+            // Create a pending intent to start the intent resolved here.
+            final IIntentSender failureTarget = mService.getIntentSenderLocked(
+                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
+                    new String[]{ resolvedType },
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE, null);
+
+            // Create a pending intent to start the ephemeral application; force it to be
+            // directed to the ephemeral package.
+            ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName());
+            final IIntentSender ephemeralTarget = mService.getIntentSenderLocked(
+                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                    Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent },
+                    new String[]{ resolvedType },
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE, null);
+
+            int flags = intent.getFlags();
+            intent = new Intent();
+            intent.setFlags(flags
+                    | Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME,
+                    rInfo.ephemeralResolveInfo.getPackageName());
+            intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget));
+            intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget));
+
+            resolvedType = null;
+            callingUid = realCallingUid;
+            callingPid = realCallingPid;
+
+            rInfo = rInfo.ephemeralInstaller;
+            aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
+        }
+
+        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
+                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
+                requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
+                options);
+        if (outActivity != null) {
+            outActivity[0] = r;
+        }
+
+        if (r.appTimeTracker == null && sourceRecord != null) {
+            // If the caller didn't specify an explicit time tracker, we want to continue
+            // tracking under any it has.
+            r.appTimeTracker = sourceRecord.appTimeTracker;
+        }
+
+        final ActivityStack stack = mSupervisor.mFocusedStack;
+        if (voiceSession == null && (stack.mResumedActivity == null
+                || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
+            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
+                    realCallingPid, realCallingUid, "Activity start")) {
+                PendingActivityLaunch pal =  new PendingActivityLaunch(r,
+                        sourceRecord, startFlags, stack, callerApp);
+                mPendingActivityLaunches.add(pal);
+                ActivityOptions.abort(options);
+                return ActivityManager.START_SWITCHES_CANCELED;
+            }
+        }
+
+        if (mService.mDidAppSwitch) {
+            // This is the second allowed switch since we stopped switches,
+            // so now just generally allow switches.  Use case: user presses
+            // home (switches disabled, switch to home, mDidAppSwitch now true);
+            // user taps a home icon (coming from home so allowed, we hit here
+            // and now allow anyone to switch again).
+            mService.mAppSwitchesAllowedTime = 0;
+        } else {
+            mService.mDidAppSwitch = true;
+        }
+
+        doPendingActivityLaunchesLocked(false);
+
+        err = startActivityUncheckedLocked(r, sourceRecord, voiceSession,
+                voiceInteractor, startFlags, true, options, inTask);
+
+        if (err < 0) {
+            // If someone asked to have the keyguard dismissed on the next
+            // activity start, but we are not actually doing an activity
+            // switch...  just dismiss the keyguard now, because we
+            // probably want to see whatever is behind it.
+            mSupervisor.notifyActivityDrawnForKeyguard();
+        }
+        return err;
+    }
+
+    void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) {
+        mSupervisor.moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason);
+        startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/,
+                null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/,
+                null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/,
+                0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/,
+                0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/,
+                false /*ignoreTargetSecurity*/, false /*componentSpecified*/,  null /*outActivity*/,
+                null /*container*/, null /*inTask*/);
+        if (mSupervisor.inResumeTopActivity) {
+            // If we are in resume section already, home activity will be initialized, but not
+            // resumed (to avoid recursive resume) and will stay that way until something pokes it
+            // again. We need to schedule another resume.
+            mSupervisor.scheduleResumeTopActivities();
+        }
+    }
+
+    final int startActivityMayWait(IApplicationThread caller, int callingUid,
+            String callingPackage, Intent intent, String resolvedType,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+            IBinder resultTo, String resultWho, int requestCode, int startFlags,
+            ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
+            Bundle bOptions, boolean ignoreTargetSecurity, int userId,
+            IActivityContainer iContainer, TaskRecord inTask) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+        boolean componentSpecified = intent.getComponent() != null;
+
+        // Save a copy in case ephemeral needs it
+        final Intent ephemeralIntent = new Intent(intent);
+        // Don't modify the client's object!
+        intent = new Intent(intent);
+
+        ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
+        // Collect information about the target of the Intent.
+        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
+
+        ActivityOptions options = ActivityOptions.fromBundle(bOptions);
+        ActivityStackSupervisor.ActivityContainer container =
+                (ActivityStackSupervisor.ActivityContainer)iContainer;
+        synchronized (mService) {
+            if (container != null && container.mParentActivity != null &&
+                    container.mParentActivity.state != RESUMED) {
+                // Cannot start a child activity if the parent is not resumed.
+                return ActivityManager.START_CANCELED;
+            }
+            final int realCallingPid = Binder.getCallingPid();
+            final int realCallingUid = Binder.getCallingUid();
+            int callingPid;
+            if (callingUid >= 0) {
+                callingPid = -1;
+            } else if (caller == null) {
+                callingPid = realCallingPid;
+                callingUid = realCallingUid;
+            } else {
+                callingPid = callingUid = -1;
+            }
+
+            final ActivityStack stack;
+            if (container == null || container.mStack.isOnHomeDisplay()) {
+                stack = mSupervisor.mFocusedStack;
+            } else {
+                stack = container.mStack;
+            }
+            stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;
+            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+                    "Starting activity when config will change = " + stack.mConfigWillChange);
+
+            final long origId = Binder.clearCallingIdentity();
+
+            if (aInfo != null &&
+                    (aInfo.applicationInfo.privateFlags
+                            & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
+                // This may be a heavy-weight process!  Check to see if we already
+                // have another, different heavy-weight process running.
+                if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
+                    final ProcessRecord heavy = mService.mHeavyWeightProcess;
+                    if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid
+                            || !heavy.processName.equals(aInfo.processName))) {
+                        int appCallingUid = callingUid;
+                        if (caller != null) {
+                            ProcessRecord callerApp = mService.getRecordForAppLocked(caller);
+                            if (callerApp != null) {
+                                appCallingUid = callerApp.info.uid;
+                            } else {
+                                Slog.w(TAG, "Unable to find app for caller " + caller
+                                        + " (pid=" + callingPid + ") when starting: "
+                                        + intent.toString());
+                                ActivityOptions.abort(options);
+                                return ActivityManager.START_PERMISSION_DENIED;
+                            }
+                        }
+
+                        IIntentSender target = mService.getIntentSenderLocked(
+                                ActivityManager.INTENT_SENDER_ACTIVITY, "android",
+                                appCallingUid, userId, null, null, 0, new Intent[] { intent },
+                                new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
+                                        | PendingIntent.FLAG_ONE_SHOT, null);
+
+                        Intent newIntent = new Intent();
+                        if (requestCode >= 0) {
+                            // Caller is requesting a result.
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
+                        }
+                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,
+                                new IntentSender(target));
+                        if (heavy.activities.size() > 0) {
+                            ActivityRecord hist = heavy.activities.get(0);
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,
+                                    hist.packageName);
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,
+                                    hist.task.taskId);
+                        }
+                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
+                                aInfo.packageName);
+                        newIntent.setFlags(intent.getFlags());
+                        newIntent.setClassName("android",
+                                HeavyWeightSwitcherActivity.class.getName());
+                        intent = newIntent;
+                        resolvedType = null;
+                        caller = null;
+                        callingUid = Binder.getCallingUid();
+                        callingPid = Binder.getCallingPid();
+                        componentSpecified = true;
+                        rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId);
+                        aInfo = rInfo != null ? rInfo.activityInfo : null;
+                        if (aInfo != null) {
+                            aInfo = mService.getActivityInfoForUser(aInfo, userId);
+                        }
+                    }
+                }
+            }
+
+            int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
+                    aInfo, rInfo, voiceSession, voiceInteractor,
+                    resultTo, resultWho, requestCode, callingPid,
+                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
+                    options, ignoreTargetSecurity, componentSpecified, null, container, inTask);
+
+            Binder.restoreCallingIdentity(origId);
+
+            if (stack.mConfigWillChange) {
+                // If the caller also wants to switch to a new configuration,
+                // do so now.  This allows a clean switch, as we are waiting
+                // for the current activity to pause (so we will not destroy
+                // it), and have not yet started the next activity.
+                mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+                        "updateConfiguration()");
+                stack.mConfigWillChange = false;
+                if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+                        "Updating to new configuration after starting activity.");
+                mService.updateConfigurationLocked(config, null, false);
+            }
+
+            if (outResult != null) {
+                outResult.result = res;
+                if (res == ActivityManager.START_SUCCESS) {
+                    mSupervisor.mWaitingActivityLaunched.add(outResult);
+                    do {
+                        try {
+                            mService.wait();
+                        } catch (InterruptedException e) {
+                        }
+                    } while (!outResult.timeout && outResult.who == null);
+                } else if (res == ActivityManager.START_TASK_TO_FRONT) {
+                    ActivityRecord r = stack.topRunningActivityLocked();
+                    if (r.nowVisible && r.state == RESUMED) {
+                        outResult.timeout = false;
+                        outResult.who = new ComponentName(r.info.packageName, r.info.name);
+                        outResult.totalTime = 0;
+                        outResult.thisTime = 0;
+                    } else {
+                        outResult.thisTime = SystemClock.uptimeMillis();
+                        mSupervisor.mWaitingActivityVisible.add(outResult);
+                        do {
+                            try {
+                                mService.wait();
+                            } catch (InterruptedException e) {
+                            }
+                        } while (!outResult.timeout && outResult.who == null);
+                    }
+                }
+            }
+
+            return res;
+        }
+    }
+
+    final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
+            boolean doResume, ActivityOptions options, TaskRecord inTask) {
+        final Intent intent = r.intent;
+        final int callingUid = r.launchedFromUid;
+
+        final Rect newBounds = mSupervisor.getOverrideBounds(r, options, inTask);
+        final boolean overrideBounds = newBounds != null;
+
+        // In some flows in to this function, we retrieve the task record and hold on to it
+        // without a lock before calling back in to here...  so the task at this point may
+        // not actually be in recents.  Check for that, and if it isn't in recents just
+        // consider it invalid.
+        if (inTask != null && !inTask.inRecents) {
+            Slog.w(TAG, "Starting activity in task not in recents: " + inTask);
+            inTask = null;
+        }
+
+        final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
+        final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+        final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
+        int launchFlags = adjustLaunchFlagsToDocumentMode(r, launchSingleInstance, launchSingleTask,
+                intent.getFlags());
+        final boolean launchTaskBehind = r.mLaunchTaskBehind
+                && !launchSingleTask && !launchSingleInstance
+                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
+
+        if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
+                && r.resultTo.task.stack != null) {
+            // For whatever reason this activity is being launched into a new
+            // task...  yet the caller has requested a result back.  Well, that
+            // is pretty messed up, so instead immediately send back a cancel
+            // and let the new task continue launched as normal without a
+            // dependency on its originator.
+            Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
+            r.resultTo.task.stack.sendActivityResultLocked(-1,
+                    r.resultTo, r.resultWho, r.requestCode,
+                    Activity.RESULT_CANCELED, null);
+            r.resultTo = null;
+        }
+
+        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
+            launchFlags |= FLAG_ACTIVITY_NEW_TASK;
+        }
+
+        // If we are actually going to launch in to a new task, there are some cases where
+        // we further want to do multiple task.
+        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+            if (launchTaskBehind
+                    || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
+                launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK;
+            }
+        }
+
+        // We'll invoke onUserLeaving before onPause only if the launching
+        // activity did not explicitly state that this is an automated launch.
+        mSupervisor.mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
+        if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
+                "startActivity() => mUserLeaving=" + mSupervisor.mUserLeaving);
+
+        // If the caller has asked not to resume at this point, we make note
+        // of this in the record so that we can skip it when trying to find
+        // the top running activity.
+        if (!doResume || !mSupervisor.okToShowLocked(r)) {
+            r.delayedResume = true;
+            doResume = false;
+        }
+
+        final ActivityRecord notTop =
+                (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;
+
+        // If the onlyIfNeeded flag is set, then we can do this if the activity
+        // being launched is the same as the one making the call...  or, as
+        // a special case, if we do not know the caller then we count the
+        // current top activity as the caller.
+        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+            ActivityRecord checkedCaller = sourceRecord;
+            if (checkedCaller == null) {
+                checkedCaller = mSupervisor.mFocusedStack.topRunningNonDelayedActivityLocked(
+                        notTop);
+            }
+            if (!checkedCaller.realActivity.equals(r.realActivity)) {
+                // Caller is not the same as launcher, so always needed.
+                startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED;
+            }
+        }
+
+        boolean addingToTask = false;
+        TaskRecord reuseTask = null;
+
+        // If the caller is not coming from another activity, but has given us an
+        // explicit task into which they would like us to launch the new activity,
+        // then let's see about doing that.
+        if (sourceRecord == null && inTask != null && inTask.stack != null) {
+            final Intent baseIntent = inTask.getBaseIntent();
+            final ActivityRecord root = inTask.getRootActivity();
+            if (baseIntent == null) {
+                ActivityOptions.abort(options);
+                throw new IllegalArgumentException("Launching into task without base intent: "
+                        + inTask);
+            }
+
+            // If this task is empty, then we are adding the first activity -- it
+            // determines the root, and must be launching as a NEW_TASK.
+            if (launchSingleInstance || launchSingleTask) {
+                if (!baseIntent.getComponent().equals(r.intent.getComponent())) {
+                    ActivityOptions.abort(options);
+                    throw new IllegalArgumentException("Trying to launch singleInstance/Task "
+                            + r + " into different task " + inTask);
+                }
+                if (root != null) {
+                    ActivityOptions.abort(options);
+                    throw new IllegalArgumentException("Caller with inTask " + inTask
+                            + " has root " + root + " but target is singleInstance/Task");
+                }
+            }
+
+            // If task is empty, then adopt the interesting intent launch flags in to the
+            // activity being started.
+            if (root == null) {
+                final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK
+                        | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                        | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+                launchFlags = (launchFlags&~flagsOfInterest)
+                        | (baseIntent.getFlags()&flagsOfInterest);
+                intent.setFlags(launchFlags);
+                inTask.setIntent(r);
+                addingToTask = true;
+
+                // If the task is not empty and the caller is asking to start it as the root
+                // of a new task, then we don't actually want to start this on the task.  We
+                // will bring the task to the front, and possibly give it a new intent.
+            } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+                addingToTask = false;
+
+            } else {
+                addingToTask = true;
+            }
+
+            reuseTask = inTask;
+        } else {
+            inTask = null;
+            // Launch ResolverActivity in the source task, so that it stays in the task
+            // bounds when in freeform workspace.
+            // Also put noDisplay activities in the source task. These by itself can
+            // be placed in any task/stack, however it could launch other activities
+            // like ResolverActivity, and we want those to stay in the original task.
+            if ((r.isResolverActivity() || r.noDisplay) && sourceRecord != null
+                    && sourceRecord.isFreeform())  {
+                addingToTask = true;
+            }
+        }
+
+        if (inTask == null) {
+            if (sourceRecord == null) {
+                // This activity is not being started from another...  in this
+                // case we -always- start a new task.
+                if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
+                    Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
+                            "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
+                    launchFlags |= FLAG_ACTIVITY_NEW_TASK;
+                }
+            } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                // The original activity who is starting us is running as a single
+                // instance...  this new activity it is starting must go on its
+                // own task.
+                launchFlags |= FLAG_ACTIVITY_NEW_TASK;
+            } else if (launchSingleInstance || launchSingleTask) {
+                // The activity being started is a single instance...  it always
+                // gets launched into its own task.
+                launchFlags |= FLAG_ACTIVITY_NEW_TASK;
+            }
+        }
+
+        ActivityInfo newTaskInfo = null;
+        Intent newTaskIntent = null;
+        final ActivityStack sourceStack;
+        if (sourceRecord != null) {
+            if (sourceRecord.finishing) {
+                // If the source is finishing, we can't further count it as our source.  This
+                // is because the task it is associated with may now be empty and on its way out,
+                // so we don't want to blindly throw it in to that task.  Instead we will take
+                // the NEW_TASK flow and try to find a task for it. But save the task information
+                // so it can be used when creating the new task.
+                if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) {
+                    Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+                            + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
+                    launchFlags |= FLAG_ACTIVITY_NEW_TASK;
+                    newTaskInfo = sourceRecord.info;
+                    newTaskIntent = sourceRecord.task.intent;
+                }
+                sourceRecord = null;
+                sourceStack = null;
+            } else {
+                sourceStack = sourceRecord.task.stack;
+            }
+        } else {
+            sourceStack = null;
+        }
+
+        boolean movedHome = false;
+        ActivityStack targetStack;
+
+        intent.setFlags(launchFlags);
+        final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;
+
+        ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent,
+                launchSingleInstance, launchSingleTask, launchFlags);
+        if (intentActivity != null) {
+            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused
+            // but still needs to be a lock task mode violation since the task gets
+            // cleared out and the device would otherwise leave the locked task.
+            if (mSupervisor.isLockTaskModeViolation(intentActivity.task,
+                    (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
+                mSupervisor.showLockTaskToast();
+                Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
+                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+            if (r.task == null) {
+                r.task = intentActivity.task;
+            }
+            if (intentActivity.task.intent == null) {
+                // This task was started because of movement of the activity based on affinity...
+                // Now that we are actually launching it, we can assign the base intent.
+                intentActivity.task.setIntent(r);
+            }
+
+            targetStack = intentActivity.task.stack;
+            targetStack.mLastPausedActivity = null;
+            // If the target task is not in the front, then we need
+            // to bring it to the front...  except...  well, with
+            // SINGLE_TASK_LAUNCH it's not entirely clear.  We'd like
+            // to have the same behavior as if a new instance was
+            // being started, which means not bringing it to the front
+            // if the caller is not itself in the front.
+            final ActivityStack focusStack = mSupervisor.getFocusedStack();
+            ActivityRecord curTop = (focusStack == null)
+                    ? null : focusStack.topRunningNonDelayedActivityLocked(notTop);
+            boolean movedToFront = false;
+            if (curTop != null && (curTop.task != intentActivity.task ||
+                    curTop.task != focusStack.topTask())) {
+                r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+                if (sourceRecord == null || (sourceStack.topActivity() != null &&
+                        sourceStack.topActivity().task == sourceRecord.task)) {
+                    // We really do want to push this one into the user's face, right now.
+                    if (launchTaskBehind && sourceRecord != null) {
+                        intentActivity.setTaskToAffiliateWith(sourceRecord.task);
+                    }
+                    movedHome = true;
+                    final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task);
+                    if (sideStack == null || sideStack == targetStack) {
+                        // We only want to move to the front, if we aren't going to launch on a
+                        // different stack. If we launch on a different stack, we will put the
+                        // task on top there.
+                        targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
+                                options, r.appTimeTracker, "bringingFoundTaskToFront");
+                        movedToFront = true;
+                    }
+                    if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
+                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
+                        // Caller wants to appear on home activity.
+                        intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+                    }
+                    options = null;
+                }
+            }
+            if (!movedToFront && doResume) {
+                if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack
+                        + " from " + intentActivity);
+                targetStack.moveToFront("intentActivityFound");
+            }
+
+            // If the caller has requested that the target task be
+            // reset, then do so.
+            if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
+            }
+            if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+                // We don't need to start a new activity, and
+                // the client said not to do anything if that
+                // is the case, so this is it!  And for paranoia, make
+                // sure we have correctly resumed the top activity.
+                if (doResume) {
+                    mSupervisor.resumeTopActivitiesLocked(targetStack, null, options);
+
+                    // Make sure to notify Keyguard as well if we are not running an app
+                    // transition later.
+                    if (!movedToFront) {
+                        mSupervisor.notifyActivityDrawnForKeyguard();
+                    }
+                } else {
+                    ActivityOptions.abort(options);
+                }
+                mSupervisor.updateUserStackLocked(r.userId, targetStack);
+                return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+            }
+            if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                    == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
+                // The caller has requested to completely replace any
+                // existing task with its new activity.  Well that should
+                // not be too hard...
+                reuseTask = intentActivity.task;
+                reuseTask.performClearTaskLocked();
+                reuseTask.setIntent(r);
+            } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
+                    || launchSingleInstance || launchSingleTask) {
+                // In this situation we want to remove all activities
+                // from the task up to the one being started.  In most
+                // cases this means we are resetting the task to its
+                // initial state.
+                ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags);
+                if (top != null) {
+                    if (top.frontOfTask) {
+                        // Activity aliases may mean we use different
+                        // intents for the top activity, so make sure
+                        // the task now has the identity of the new
+                        // intent.
+                        top.task.setIntent(r);
+                    }
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
+                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+                } else {
+                    // A special case: we need to start the activity because it is not
+                    // currently running, and the caller has asked to clear the current
+                    // task to have this activity at the top.
+                    addingToTask = true;
+                    // Now pretend like this activity is being started by the top of its
+                    // task, so it is put in the right place.
+                    sourceRecord = intentActivity;
+                    TaskRecord task = sourceRecord.task;
+                    if (task != null && task.stack == null) {
+                        // Target stack got cleared when we all activities were removed
+                        // above. Go ahead and reset it.
+                        targetStack = computeStackFocus(
+                                sourceRecord, false /* newTask */, null /* bounds */, launchFlags);
+                        targetStack.addTask(task,
+                                !launchTaskBehind /* toTop */, "startActivityUnchecked");
+                    }
+
+                }
+            } else if (r.realActivity.equals(intentActivity.task.realActivity)) {
+                // In this case the top activity on the task is the
+                // same as the one being launched, so we take that
+                // as a request to bring the task to the foreground.
+                // If the top activity in the task is the root
+                // activity, deliver this new intent to it if it
+                // desires.
+                if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop)
+                        && intentActivity.realActivity.equals(r.realActivity)) {
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,
+                            intentActivity.task);
+                    if (intentActivity.frontOfTask) {
+                        intentActivity.task.setIntent(r);
+                    }
+                    intentActivity.deliverNewIntentLocked(callingUid, r.intent,
+                            r.launchedFromPackage);
+                } else if (!r.intent.filterEquals(intentActivity.task.intent)) {
+                    // In this case we are launching the root activity
+                    // of the task, but with a different intent.  We
+                    // should start a new instance on top.
+                    addingToTask = true;
+                    sourceRecord = intentActivity;
+                }
+            } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+                // In this case an activity is being launched in to an
+                // existing task, without resetting that task.  This
+                // is typically the situation of launching an activity
+                // from a notification or shortcut.  We want to place
+                // the new activity on top of the current task.
+                addingToTask = true;
+                sourceRecord = intentActivity;
+            } else if (!intentActivity.task.rootWasReset) {
+                // In this case we are launching in to an existing task
+                // that has not yet been started from its front door.
+                // The current task has been brought to the front.
+                // Ideally, we'd probably like to place this new task
+                // at the bottom of its stack, but that's a little hard
+                // to do with the current organization of the code so
+                // for now we'll just drop it.
+                intentActivity.task.setIntent(r);
+            }
+            if (!addingToTask && reuseTask == null) {
+                // We didn't do anything...  but it was needed (a.k.a., client
+                // don't use that intent!)  And for paranoia, make
+                // sure we have correctly resumed the top activity.
+                if (doResume) {
+                    targetStack.resumeTopActivityLocked(null, options);
+                    if (!movedToFront) {
+                        // Make sure to notify Keyguard as well if we are not running an app
+                        // transition later.
+                        mSupervisor.notifyActivityDrawnForKeyguard();
+                    }
+                } else {
+                    ActivityOptions.abort(options);
+                }
+                mSupervisor.updateUserStackLocked(r.userId, targetStack);
+                return ActivityManager.START_TASK_TO_FRONT;
+            }
+        }
+
+        //String uri = r.intent.toURI();
+        //Intent intent2 = new Intent(uri);
+        //Slog.i(TAG, "Given intent: " + r.intent);
+        //Slog.i(TAG, "URI is: " + uri);
+        //Slog.i(TAG, "To intent: " + intent2);
+
+        if (r.packageName != null) {
+            // If the activity being launched is the same as the one currently
+            // at the top, then we need to check if it should only be launched
+            // once.
+            ActivityStack topStack = mSupervisor.mFocusedStack;
+            ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
+            final boolean dontStart = top != null && r.resultTo == null
+                    && top.realActivity.equals(r.realActivity) && top.userId == r.userId
+                    && top.app != null && top.app.thread != null
+                    && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+                    || launchSingleTop || launchSingleTask);
+            if (dontStart) {
+                ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
+                // For paranoia, make sure we have correctly resumed the top activity.
+                topStack.mLastPausedActivity = null;
+                if (doResume) {
+                    mSupervisor.resumeTopActivitiesLocked();
+                }
+                ActivityOptions.abort(options);
+                if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+                    // We don't need to start a new activity, and the client said not to do
+                    // anything if that is the case, so this is it!
+                    return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+                }
+                top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+                return ActivityManager.START_DELIVERED_TO_TOP;
+            }
+        } else {
+            if (r.resultTo != null && r.resultTo.task.stack != null) {
+                r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,
+                        r.requestCode, Activity.RESULT_CANCELED, null);
+            }
+            ActivityOptions.abort(options);
+            return ActivityManager.START_CLASS_NOT_FOUND;
+        }
+
+        boolean newTask = false;
+        boolean keepCurTransition = false;
+
+        final TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
+                sourceRecord.task : null;
+
+        // Should this be considered a new task?
+        if (r.resultTo == null && inTask == null && !addingToTask
+                && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+            newTask = true;
+            targetStack = computeStackFocus(r, newTask, newBounds, launchFlags);
+            if (doResume) {
+                targetStack.moveToFront("startingNewTask");
+            }
+
+            if (reuseTask == null) {
+                r.setTask(targetStack.createTaskRecord(mSupervisor.getNextTaskId(),
+                        newTaskInfo != null ? newTaskInfo : r.info,
+                        newTaskIntent != null ? newTaskIntent : intent,
+                        voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
+                        taskToAffiliate);
+                if (overrideBounds) {
+                    r.task.updateOverrideConfiguration(newBounds);
+                }
+                if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+                        "Starting new activity " + r + " in new task " + r.task);
+            } else {
+                r.setTask(reuseTask, taskToAffiliate);
+            }
+            if (mSupervisor.isLockTaskModeViolation(r.task)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+            if (!movedHome) {
+                if ((launchFlags &
+                        (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
+                        == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
+                    // Caller wants to appear on home activity, so before starting
+                    // their own activity we will bring home to the front.
+                    r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+                }
+            }
+        } else if (sourceRecord != null) {
+            final TaskRecord sourceTask = sourceRecord.task;
+            if (mSupervisor.isLockTaskModeViolation(sourceTask)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+            targetStack = null;
+            if (sourceTask.stack.topTask() != sourceTask) {
+                // We only want to allow changing stack if the target task is not the top one,
+                // otherwise we would move the launching task to the other side, rather than show
+                // two side by side.
+                targetStack = getLaunchToSideStack(r, launchFlags, r.task);
+            }
+            if (targetStack == null) {
+                targetStack = sourceTask.stack;
+            } else if (targetStack != sourceTask.stack) {
+                mSupervisor.moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId,
+                        ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE);
+            }
+            if (doResume) {
+                targetStack.moveToFront("sourceStackToFront");
+            }
+            final TaskRecord topTask = targetStack.topTask();
+            if (topTask != sourceTask) {
+                targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options,
+                        r.appTimeTracker, "sourceTaskToFront");
+            }
+            if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+                // In this case, we are adding the activity to an existing
+                // task, but the caller has asked to clear that task if the
+                // activity is already running.
+                ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags);
+                keepCurTransition = true;
+                if (top != null) {
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
+                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+                    // For paranoia, make sure we have correctly
+                    // resumed the top activity.
+                    targetStack.mLastPausedActivity = null;
+                    if (doResume) {
+                        targetStack.resumeTopActivityLocked(null);
+                    }
+                    ActivityOptions.abort(options);
+                    return ActivityManager.START_DELIVERED_TO_TOP;
+                }
+            } else if (!addingToTask &&
+                    (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
+                // In this case, we are launching an activity in our own task
+                // that may already be running somewhere in the history, and
+                // we want to shuffle it to the front of the stack if so.
+                final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r);
+                if (top != null) {
+                    final TaskRecord task = top.task;
+                    task.moveActivityToFrontLocked(top);
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task);
+                    top.updateOptionsLocked(options);
+                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+                    targetStack.mLastPausedActivity = null;
+                    if (doResume) {
+                        targetStack.resumeTopActivityLocked(null);
+                    }
+                    return ActivityManager.START_DELIVERED_TO_TOP;
+                }
+            }
+            // An existing activity is starting this new activity, so we want
+            // to keep the new one in the same task as the one that is starting
+            // it.
+            r.setTask(sourceTask, null);
+            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+                    + " in existing task " + r.task + " from source " + sourceRecord);
+
+        } else if (inTask != null) {
+            // The caller is asking that the new activity be started in an explicit
+            // task it has provided to us.
+            if (mSupervisor.isLockTaskModeViolation(inTask)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+            if (overrideBounds) {
+                inTask.updateOverrideConfiguration(newBounds);
+                int stackId = inTask.getLaunchStackId();
+                if (stackId != inTask.stack.mStackId) {
+                    mSupervisor.moveTaskToStackUncheckedLocked(
+                            inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
+                }
+            }
+            targetStack = inTask.stack;
+            targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker,
+                    "inTaskToFront");
+
+            // Check whether we should actually launch the new activity in to the task,
+            // or just reuse the current activity on top.
+            ActivityRecord top = inTask.getTopActivity();
+            if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
+                if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+                        || launchSingleTop || launchSingleTask) {
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
+                    if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+                        // We don't need to start a new activity, and
+                        // the client said not to do anything if that
+                        // is the case, so this is it!
+                        return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+                    }
+                    top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+                    return ActivityManager.START_DELIVERED_TO_TOP;
+                }
+            }
+
+            if (!addingToTask) {
+                // We don't actually want to have this activity added to the task, so just
+                // stop here but still tell the caller that we consumed the intent.
+                ActivityOptions.abort(options);
+                return ActivityManager.START_TASK_TO_FRONT;
+            }
+
+            r.setTask(inTask, null);
+            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+                    + " in explicit task " + r.task);
+
+        } else {
+            // This not being started from an existing activity, and not part
+            // of a new task...  just put it in the top task, though these days
+            // this case should never happen.
+            targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags);
+            if (doResume) {
+                targetStack.moveToFront("addingToTopTask");
+            }
+            ActivityRecord prev = targetStack.topActivity();
+            r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(
+                    mSupervisor.getNextTaskId(), r.info, intent, null, null, true), null);
+            mWindowManager.moveTaskToTop(r.task.taskId);
+            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+                    + " in new guessed " + r.task);
+        }
+
+        mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
+                intent, r.getUriPermissionsLocked(), r.userId);
+
+        if (sourceRecord != null && sourceRecord.isRecentsActivity()) {
+            r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
+        }
+        if (newTask) {
+            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
+        }
+        ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
+        targetStack.mLastPausedActivity = null;
+        targetStack.startActivityLocked(r, newTask, keepCurTransition, options);
+        if (doResume) {
+            if (!launchTaskBehind) {
+                mService.setFocusedActivityLocked(r, "startedActivity");
+            }
+            mSupervisor.resumeTopActivitiesLocked(targetStack, r, options);
+        } else {
+            targetStack.addRecentActivityLocked(r);
+        }
+        mSupervisor.updateUserStackLocked(r.userId, targetStack);
+
+        if (!r.task.mResizeable && mSupervisor.isStackDockedInEffect(targetStack.mStackId)) {
+            mSupervisor.showNonResizeableDockToast(r.task.taskId);
+        }
+
+        return ActivityManager.START_SUCCESS;
+    }
+
+    private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance,
+            boolean launchSingleTask, int launchFlags) {
+        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
+                (launchSingleInstance || launchSingleTask)) {
+            // We have a conflict between the Intent and the Activity manifest, manifest wins.
+            Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
+                    "\"singleInstance\" or \"singleTask\"");
+            launchFlags &=
+                    ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
+        } else {
+            switch (r.info.documentLaunchMode) {
+                case ActivityInfo.DOCUMENT_LAUNCH_NONE:
+                    break;
+                case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
+                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+                    break;
+                case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
+                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+                    break;
+                case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
+                    launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK;
+                    break;
+            }
+        }
+        return launchFlags;
+    }
+
+    final int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle bOptions, int userId) {
+        if (intents == null) {
+            throw new NullPointerException("intents is null");
+        }
+        if (resolvedTypes == null) {
+            throw new NullPointerException("resolvedTypes is null");
+        }
+        if (intents.length != resolvedTypes.length) {
+            throw new IllegalArgumentException("intents are length different than resolvedTypes");
+        }
+
+
+        int callingPid;
+        if (callingUid >= 0) {
+            callingPid = -1;
+        } else if (caller == null) {
+            callingPid = Binder.getCallingPid();
+            callingUid = Binder.getCallingUid();
+        } else {
+            callingPid = callingUid = -1;
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                ActivityRecord[] outActivity = new ActivityRecord[1];
+                for (int i=0; i<intents.length; i++) {
+                    Intent intent = intents[i];
+                    if (intent == null) {
+                        continue;
+                    }
+
+                    // Refuse possible leaked file descriptors
+                    if (intent != null && intent.hasFileDescriptors()) {
+                        throw new IllegalArgumentException("File descriptors passed in Intent");
+                    }
+
+                    boolean componentSpecified = intent.getComponent() != null;
+
+                    // Don't modify the client's object!
+                    intent = new Intent(intent);
+
+                    // Collect information about the target of the Intent.
+                    ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0,
+                            null, userId);
+                    // TODO: New, check if this is correct
+                    aInfo = mService.getActivityInfoForUser(aInfo, userId);
+
+                    if (aInfo != null &&
+                            (aInfo.applicationInfo.privateFlags
+                                    & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)  != 0) {
+                        throw new IllegalArgumentException(
+                                "FLAG_CANT_SAVE_STATE not supported here");
+                    }
+
+                    ActivityOptions options = ActivityOptions.fromBundle(
+                            i == intents.length - 1 ? bOptions : null);
+                    int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/,
+                            resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1,
+                            callingPid, callingUid, callingPackage, callingPid, callingUid, 0,
+                            options, false, componentSpecified, outActivity, null, null);
+                    if (res < 0) {
+                        return res;
+                    }
+
+                    resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return ActivityManager.START_SUCCESS;
+    }
+
+    final void doPendingActivityLaunchesLocked(boolean doResume) {
+        while (!mPendingActivityLaunches.isEmpty()) {
+            PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
+
+            try {
+                startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null,
+                        pal.startFlags, doResume && mPendingActivityLaunches.isEmpty(),
+                        null, null);
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
+                pal.sendErrorResult(e.getMessage());
+            }
+        }
+    }
+
+    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
+            int launchFlags) {
+        final TaskRecord task = r.task;
+        if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
+            return mSupervisor.mHomeStack;
+        }
+
+        ActivityStack stack = getLaunchToSideStack(r, launchFlags, task);
+        if (stack != null) {
+            return stack;
+        }
+
+        if (task != null && task.stack != null) {
+            stack = task.stack;
+            if (stack.isOnHomeDisplay()) {
+                if (mSupervisor.mFocusedStack != stack) {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                            "computeStackFocus: Setting " + "focused stack to r=" + r
+                                    + " task=" + task);
+                } else {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                            "computeStackFocus: Focused stack already="
+                                    + mSupervisor.mFocusedStack);
+                }
+            }
+            return stack;
+        }
+
+        final ActivityStackSupervisor.ActivityContainer container = r.mInitialActivityContainer;
+        if (container != null) {
+            // The first time put it on the desired stack, after this put on task stack.
+            r.mInitialActivityContainer = null;
+            return container.mStack;
+        }
+
+        // The fullscreen stack can contain any task regardless of if the task is resizeable
+        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
+        // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
+        // we can also put it in the focused stack.
+        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+        final boolean canUseFocusedStack =
+                focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
+                        || focusedStackId == DOCKED_STACK_ID
+                        || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable);
+        if (canUseFocusedStack && (!newTask
+                || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
+            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                    "computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
+            return mSupervisor.mFocusedStack;
+        }
+
+        // We first try to put the task in the first dynamic stack.
+        final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+        for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            stack = homeDisplayStacks.get(stackNdx);
+            if (!ActivityManager.StackId.isStaticStack(stack.mStackId)) {
+                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                        "computeStackFocus: Setting focused stack=" + stack);
+                return stack;
+            }
+        }
+
+        // If there is no suitable dynamic stack then we figure out which static stack to use.
+        final int stackId = task != null ? task.getLaunchStackId() :
+                bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
+                        FULLSCREEN_WORKSPACE_STACK_ID;
+        stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+                + r + " stackId=" + stack.mStackId);
+        return stack;
+    }
+
+    private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) {
+        if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) {
+            return null;
+        }
+        // The parent activity doesn't want to launch the activity on top of itself, but
+        // instead tries to put it onto other side in side-by-side mode.
+        final ActivityStack parentStack = task != null ? task.stack
+                : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack
+                : mSupervisor.mFocusedStack;
+        if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) {
+            // If parent was in docked stack, the natural place to launch another activity
+            // will be fullscreen, so it can appear alongside the docked window.
+            return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+        } else {
+            // If the parent is not in the docked stack, we check if there is docked window
+            // and if yes, we will launch into that stack. If not, we just put the new
+            // activity into parent's stack, because we can't find a better place.
+            final ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID);
+            if (stack != null && !stack.isStackVisibleLocked()) {
+                // There is a docked stack, but it isn't visible, so we can't launch into that.
+                return null;
+            } else {
+                return stack;
+            }
+        }
+    }
+
+    /**
+     * Decide whether the new activity should be inserted into an existing task. Returns null if not
+     * or an ActivityRecord with the task into which the new activity should be added.
+     */
+    private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask,
+            Intent intent, boolean launchSingleInstance, boolean launchSingleTask,
+            int launchFlags) {
+        // We may want to try to place the new activity in to an existing task.  We always
+        // do this if the target activity is singleTask or singleInstance; we will also do
+        // this if NEW_TASK has been requested, and there is not an additional qualifier telling
+        // us to still place it in a new task: multi task, always doc mode, or being asked to
+        // launch this as a new task behind the current one.
+        boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
+                (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
+                || launchSingleInstance || launchSingleTask;
+        // If bring to front is requested, and no result is requested and we have not
+        // been given an explicit task to launch in to, and
+        // we can find a task that was started with this same
+        // component, then instead of launching bring that one to the front.
+        putIntoExistingTask &= inTask == null && r.resultTo == null;
+        ActivityRecord intentActivity = null;
+        if (putIntoExistingTask) {
+            // See if there is a task to bring to the front.  If this is
+            // a SINGLE_INSTANCE activity, there can be one and only one
+            // instance of it in the history, and it is always in its own
+            // unique task, so we do a special search.
+            intentActivity = launchSingleInstance ? mSupervisor.findActivityLocked(intent, r.info)
+                    : mSupervisor.findTaskLocked(r);
+        }
+        return intentActivity;
+    }
+
+    void setWindowManager(WindowManagerService wm) {
+        mWindowManager = wm;
+    }
+
+    void removePendingActivityLaunchesLocked(ActivityStack stack) {
+        for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
+            PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
+            if (pal.stack == stack) {
+                mPendingActivityLaunches.remove(palNdx);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5ee9eea..4647d77 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1394,6 +1394,12 @@
         return mLastNonFullscreenBounds;
     }
 
+    boolean canMatchRootAffinity() {
+        // We don't allow root affinity matching on the pinned stack as no other task should
+        // be launching in it based on affinity.
+        return rootAffinity != null && (stack == null || stack.mStackId != PINNED_STACK_ID);
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("userId="); pw.print(userId);
                 pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5426b72..43d4e77 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1222,7 +1222,7 @@
     }
 
     private boolean isCurrentUserLocked(int userId) {
-        return mCurrentUserId == userId || mTargetUserId == userId;
+        return userId == getCurrentOrTargetUserIdLocked();
     }
 
     int setTargetUserIdLocked(int targetUserId) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index d577369..b7662da 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -42,6 +42,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -53,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 
@@ -93,6 +95,8 @@
     // List of packages in restored setting across all mUserProfiles, for quick
     // filtering upon package updates.
     private ArraySet<String> mRestoredPackages = new ArraySet<>();
+    // State of current service categories
+    private ArrayMap<String, Boolean> mCategoryEnabled = new ArrayMap<>();
 
 
     // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
@@ -262,6 +266,46 @@
         }
     }
 
+    public void setCategoryState(String category, boolean enabled) {
+        synchronized (mMutex) {
+            final Boolean previous = mCategoryEnabled.put(category, enabled);
+            if (!(previous == null || previous != enabled)) {
+                return;
+            }
+
+            // State changed
+            if (DEBUG) {
+                Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "category " + category);
+            }
+
+            final int[] userIds = mUserProfiles.getCurrentProfileIds();
+            for (int userId : userIds) {
+                final Set<ComponentName> componentNames = queryPackageForServices(null,
+                        userId, category);
+
+                // Disallow services not enabled in Settings
+                final ArraySet<ComponentName> userComponents =
+                        loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+                if (userComponents == null) {
+                    componentNames.clear();
+                } else {
+                    componentNames.retainAll(userComponents);
+                }
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Components for category " + category + ": " + componentNames);
+                }
+                for (ComponentName c : componentNames) {
+                    if (enabled) {
+                        registerServiceLocked(c, userId);
+                    } else {
+                        unregisterServiceLocked(c, userId);
+                    }
+                }
+            }
+
+        }
+    }
 
     private void rebuildRestoredPackages() {
         mRestoredPackages.clear();
@@ -283,9 +327,9 @@
             int userId) {
         final ContentResolver cr = mContext.getContentResolver();
         String settingValue = Settings.Secure.getStringForUser(
-                cr,
-                settingName,
-                userId);
+            cr,
+            settingName,
+            userId);
         if (TextUtils.isEmpty(settingValue))
             return null;
         String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
@@ -314,10 +358,10 @@
                 TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames);
         final ContentResolver cr = mContext.getContentResolver();
         Settings.Secure.putStringForUser(
-                cr,
-                settingName,
-                value,
-                userId);
+            cr,
+            settingName,
+            value,
+            userId);
     }
 
     /**
@@ -333,12 +377,20 @@
     }
 
     protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
+        return queryPackageForServices(packageName, userId, null);
+    }
+
+    protected Set<ComponentName> queryPackageForServices(String packageName, int userId,
+            String category) {
         Set<ComponentName> installed = new ArraySet<>();
         final PackageManager pm = mContext.getPackageManager();
         Intent queryIntent = new Intent(mConfig.serviceInterface);
         if (!TextUtils.isEmpty(packageName)) {
             queryIntent.setPackage(packageName);
         }
+        if (category != null) {
+            queryIntent.addCategory(category);
+        }
         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
                 queryIntent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
@@ -353,9 +405,9 @@
                 ComponentName component = new ComponentName(info.packageName, info.name);
                 if (!mConfig.bindPermission.equals(info.permission)) {
                     Slog.w(TAG, "Skipping " + getCaption() + " service "
-                            + info.packageName + "/" + info.name
-                            + ": it does not require the permission "
-                            + mConfig.bindPermission);
+                        + info.packageName + "/" + info.name
+                        + ": it does not require the permission "
+                        + mConfig.bindPermission);
                     continue;
                 }
                 installed.add(component);
@@ -449,6 +501,16 @@
                 }
 
                 final ArrayList<ComponentName> add = new ArrayList<>(userComponents);
+
+                // Remove components from disabled categories so that those services aren't run.
+                for (Entry<String, Boolean> e : mCategoryEnabled.entrySet()) {
+                    if (!e.getValue()) {
+                        Set<ComponentName> c = queryPackageForServices(null, userIds[i],
+                            e.getKey());
+                        add.removeAll(c);
+                    }
+                }
+
                 toAdd.put(userIds[i], add);
 
                 newEnabled.addAll(userComponents);
@@ -488,93 +550,97 @@
      * Version of registerService that takes the name of a service component to bind to.
      */
     private void registerService(final ComponentName name, final int userid) {
+        synchronized (mMutex) {
+            registerServiceLocked(name, userid);
+        }
+    }
+
+    private void registerServiceLocked(final ComponentName name, final int userid) {
         if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
 
-        synchronized (mMutex) {
-            final String servicesBindingTag = name.toString() + "/" + userid;
-            if (mServicesBinding.contains(servicesBindingTag)) {
-                // stop registering this thing already! we're working on it
-                return;
-            }
-            mServicesBinding.add(servicesBindingTag);
+        final String servicesBindingTag = name.toString() + "/" + userid;
+        if (mServicesBinding.contains(servicesBindingTag)) {
+            // stop registering this thing already! we're working on it
+            return;
+        }
+        mServicesBinding.add(servicesBindingTag);
 
-            final int N = mServices.size();
-            for (int i = N - 1; i >= 0; i--) {
-                final ManagedServiceInfo info = mServices.get(i);
-                if (name.equals(info.component)
-                        && info.userid == userid) {
-                    // cut old connections
-                    if (DEBUG) Slog.v(TAG, "    disconnecting old " + getCaption() + ": "
-                            + info.service);
-                    removeServiceLocked(i);
-                    if (info.connection != null) {
-                        mContext.unbindService(info.connection);
-                    }
+        final int N = mServices.size();
+        for (int i = N - 1; i >= 0; i--) {
+            final ManagedServiceInfo info = mServices.get(i);
+            if (name.equals(info.component)
+                && info.userid == userid) {
+                // cut old connections
+                if (DEBUG) Slog.v(TAG, "    disconnecting old " + getCaption() + ": "
+                    + info.service);
+                removeServiceLocked(i);
+                if (info.connection != null) {
+                    mContext.unbindService(info.connection);
                 }
             }
+        }
 
-            Intent intent = new Intent(mConfig.serviceInterface);
-            intent.setComponent(name);
+        Intent intent = new Intent(mConfig.serviceInterface);
+        intent.setComponent(name);
 
-            intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
+        intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
 
-            final PendingIntent pendingIntent = PendingIntent.getActivity(
-                    mContext, 0, new Intent(mConfig.settingsAction), 0);
-            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(
+            mContext, 0, new Intent(mConfig.settingsAction), 0);
+        intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
 
-            ApplicationInfo appInfo = null;
-            try {
-                appInfo = mContext.getPackageManager().getApplicationInfo(
-                        name.getPackageName(), 0);
-            } catch (NameNotFoundException e) {
-                // Ignore if the package doesn't exist we won't be able to bind to the service.
-            }
-            final int targetSdkVersion =
-                    appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = mContext.getPackageManager().getApplicationInfo(
+                name.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            // Ignore if the package doesn't exist we won't be able to bind to the service.
+        }
+        final int targetSdkVersion =
+            appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
 
-            try {
-                if (DEBUG) Slog.v(TAG, "binding: " + intent);
-                ServiceConnection serviceConnection = new ServiceConnection() {
-                    IInterface mService;
+        try {
+            if (DEBUG) Slog.v(TAG, "binding: " + intent);
+            ServiceConnection serviceConnection = new ServiceConnection() {
+                IInterface mService;
 
-                    @Override
-                    public void onServiceConnected(ComponentName name, IBinder binder) {
-                        boolean added = false;
-                        ManagedServiceInfo info = null;
-                        synchronized (mMutex) {
-                            mServicesBinding.remove(servicesBindingTag);
-                            try {
-                                mService = asInterface(binder);
-                                info = newServiceInfo(mService, name,
-                                        userid, false /*isSystem*/, this, targetSdkVersion);
-                                binder.linkToDeath(info, 0);
-                                added = mServices.add(info);
-                            } catch (RemoteException e) {
-                                // already dead
-                            }
-                        }
-                        if (added) {
-                            onServiceAdded(info);
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder binder) {
+                    boolean added = false;
+                    ManagedServiceInfo info = null;
+                    synchronized (mMutex) {
+                        mServicesBinding.remove(servicesBindingTag);
+                        try {
+                            mService = asInterface(binder);
+                            info = newServiceInfo(mService, name,
+                                userid, false /*isSystem*/, this, targetSdkVersion);
+                            binder.linkToDeath(info, 0);
+                            added = mServices.add(info);
+                        } catch (RemoteException e) {
+                            // already dead
                         }
                     }
-
-                    @Override
-                    public void onServiceDisconnected(ComponentName name) {
-                        Slog.v(TAG, getCaption() + " connection lost: " + name);
+                    if (added) {
+                        onServiceAdded(info);
                     }
-                };
-                if (!mContext.bindServiceAsUser(intent,
-                        serviceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                        new UserHandle(userid))) {
-                    mServicesBinding.remove(servicesBindingTag);
-                    Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
-                    return;
                 }
-            } catch (SecurityException ex) {
-                Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    Slog.v(TAG, getCaption() + " connection lost: " + name);
+                }
+            };
+            if (!mContext.bindServiceAsUser(intent,
+                serviceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                new UserHandle(userid))) {
+                mServicesBinding.remove(servicesBindingTag);
+                Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
                 return;
             }
+        } catch (SecurityException ex) {
+            Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
+            return;
         }
     }
 
@@ -583,20 +649,24 @@
      */
     private void unregisterService(ComponentName name, int userid) {
         synchronized (mMutex) {
-            final int N = mServices.size();
-            for (int i = N - 1; i >= 0; i--) {
-                final ManagedServiceInfo info = mServices.get(i);
-                if (name.equals(info.component)
-                        && info.userid == userid) {
-                    removeServiceLocked(i);
-                    if (info.connection != null) {
-                        try {
-                            mContext.unbindService(info.connection);
-                        } catch (IllegalArgumentException ex) {
-                            // something happened to the service: we think we have a connection
-                            // but it's bogus.
-                            Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
-                        }
+            unregisterServiceLocked(name, userid);
+        }
+    }
+
+    private void unregisterServiceLocked(ComponentName name, int userid) {
+        final int N = mServices.size();
+        for (int i = N - 1; i >= 0; i--) {
+            final ManagedServiceInfo info = mServices.get(i);
+            if (name.equals(info.component)
+                && info.userid == userid) {
+                removeServiceLocked(i);
+                if (info.connection != null) {
+                    try {
+                        mContext.unbindService(info.connection);
+                    } catch (IllegalArgumentException ex) {
+                        // something happened to the service: we think we have a connection
+                        // but it's bogus.
+                        Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0bfbd7f..c6df83a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -124,6 +124,9 @@
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.VrManagerInternal;
+import com.android.server.vr.VrStateListener;
+
 import libcore.io.IoUtils;
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -206,6 +209,8 @@
     AudioManagerInternal mAudioManagerInternal;
     StatusBarManagerInternal mStatusBar;
     Vibrator mVibrator;
+    private VrManagerInternal mVrManagerInternal;
+    private final NotificationVrListener mVrListener = new NotificationVrListener();
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -816,6 +821,14 @@
         }
     }
 
+    private final class NotificationVrListener extends VrStateListener {
+        @Override
+        public void onVrStateChanged(final boolean enabled) {
+            mListeners.setCategoryState(NotificationListenerService.CATEGORY_VR_NOTIFICATIONS,
+                enabled);
+        }
+    }
+
     private SettingsObserver mSettingsObserver;
     private ZenModeHelper mZenModeHelper;
 
@@ -1004,6 +1017,8 @@
             // Grab our optional AudioService
             mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
             mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
+            mVrManagerInternal = getLocalService(VrManagerInternal.class);
+            mVrManagerInternal.registerListener(mVrListener);
             mZenModeHelper.onSystemReady();
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             // This observer will force an update when observe is called, causing us to
@@ -1251,10 +1266,18 @@
         @Override
         public void setAppImportance(String pkg, int uid, int importance) {
             enforceSystemOrSystemUI("Caller not system or systemui");
+            setNotificationsEnabledForPackageImpl(pkg, uid,
+                    importance != NotificationListenerService.Ranking.IMPORTANCE_NONE);
             mRankingHelper.setAppImportance(pkg, uid, importance);
             savePolicyFile();
         }
 
+        @Override
+        public boolean doesAppUseTopics(String pkg, int uid) {
+            enforceSystemOrSystemUI("Caller not system or systemui");
+            return mRankingHelper.doesAppUseTopics(pkg, uid);
+        }
+
         /**
          * System-only API for getting a list of current (i.e. not cleared) notifications.
          *
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index a6c9b0d..9b10ef2 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -37,4 +37,6 @@
     int getTopicImportance(String packageName, int uid, Notification.Topic topic);
 
     void setAppImportance(String packageName, int uid, int importance);
+
+    boolean doesAppUseTopics(String packageName, int uid);
 }
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 32c0ce2..3287f67 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -170,6 +170,7 @@
                         } else {
                             r = getOrCreateRecord(name, uid);
                         }
+                        r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
 
                         // Migrate package level settings to the default topic.
                         // Might be overwritten by parseTopics.
@@ -441,6 +442,18 @@
         updateConfig();
     }
 
+    @Override
+    public boolean doesAppUseTopics(String pkgName, int uid) {
+        final Record r = getOrCreateRecord(pkgName, uid);
+        int numTopics = r.topics.size();
+        if (numTopics == 0
+                || (numTopics == 1 && r.topics.containsKey(Notification.TOPIC_DEFAULT))) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
         if (topic == null) {
             topic = createDefaultTopic();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7c42ae1..901749e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -141,6 +141,7 @@
                 return 1;
             }
             abandonSession = false;
+            pw.println("Success");
             return 0;
         } finally {
             if (abandonSession) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ef2f29b..da1e546 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -65,7 +65,9 @@
 import com.android.server.pm.PackageManagerService.DumpState;
 import com.android.server.pm.PermissionsState.PermissionState;
 
+import java.io.BufferedInputStream;
 import java.io.FileNotFoundException;
+import java.io.InputStream;
 import java.util.Collection;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -2799,9 +2801,9 @@
             }
 
             if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f);
-            FileInputStream str = null;
+            InputStream str = null;
             try {
-                str = new FileInputStream(f);
+                str = new BufferedInputStream(new FileInputStream(f));
                 XmlPullParser parser = Xml.newPullParser();
                 parser.setInput(str, null);
 
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
new file mode 100644
index 0000000..42db364
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.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.server.vr;
+
+/**
+ * VR mode local system service interface.
+ *
+ * @hide Only for use within system server.
+ */
+public abstract class VrManagerInternal {
+
+    /**
+     * Return current VR mode state.
+     *
+     * @return {@code true} if VR mode is enabled.
+     */
+    public abstract boolean isInVrMode();
+
+    /**
+     * Set the current VR mode state.
+     *
+     * @param enabled {@code true} to enable VR mode.
+     */
+    public abstract void setVrMode(boolean enabled);
+
+    /**
+     * Add a listener for VR mode state changes.
+     * <p>
+     * This listener will immediately be called with the current VR mode state.
+     * </p>
+     * @param listener the listener instance to add.
+     */
+    public abstract void registerListener(VrStateListener listener);
+
+    /**
+     * Remove the listener from the current set of listeners.
+     *
+     * @param listener the listener to remove.
+     */
+    public abstract void unregisterListener(VrStateListener listener);
+
+}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
new file mode 100644
index 0000000..9a55e7f
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -0,0 +1,111 @@
+/*
+ * 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.vr;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+
+/**
+ * Service tracking whether VR mode is active, and notifying listening system services of state
+ * changes.
+ *
+ * {@hide}
+ */
+public class VrManagerService extends SystemService {
+
+    public static final boolean DEBUG = false;
+    public static final String TAG = "VrManagerService";
+
+    private final Object mLock = new Object();
+    private boolean mVrModeEnabled = false;
+    private ArraySet<VrStateListener> mListeners = new ArraySet<>();
+
+    private final class LocalService extends VrManagerInternal {
+        @Override
+        public boolean isInVrMode() {
+            return VrManagerService.this.getVrMode();
+        }
+
+        @Override
+        public void setVrMode(boolean enabled) {
+            VrManagerService.this.setVrMode(enabled);
+        }
+
+        @Override
+        public void registerListener(VrStateListener listener) {
+            VrManagerService.this.addListener(listener);
+        }
+
+        @Override
+        public void unregisterListener(VrStateListener listener) {
+            VrManagerService.this.removeListener(listener);
+        }
+    }
+
+    public VrManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishLocalService(VrManagerInternal.class, new LocalService());
+    }
+
+    private void addListener(VrStateListener listener) {
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    private void removeListener(VrStateListener listener) {
+        synchronized (mLock) {
+            mListeners.remove(listener);
+        }
+    }
+
+    private void setVrMode(boolean enabled) {
+        synchronized (mLock) {
+            if (mVrModeEnabled != enabled) {
+                mVrModeEnabled = enabled;
+                if (DEBUG) Slog.d(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
+                onVrModeChangedLocked();
+            }
+        }
+    }
+
+    private boolean getVrMode() {
+        synchronized (mLock) {
+            return mVrModeEnabled;
+        }
+    }
+
+    /**
+     * Notify system services of VR mode change.
+     */
+    private void onVrModeChangedLocked() {
+        for (VrStateListener l : mListeners) {
+            l.onVrStateChanged(mVrModeEnabled);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vr/VrStateListener.java b/services/core/java/com/android/server/vr/VrStateListener.java
new file mode 100644
index 0000000..b8af4b2
--- /dev/null
+++ b/services/core/java/com/android/server/vr/VrStateListener.java
@@ -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.server.vr;
+
+/**
+ * Listener for state changes in VrManagerService,
+ */
+public abstract class VrStateListener {
+
+  /**
+   * Called when the VR mode state changes.
+   *
+   * @param enabled {@code true} if VR mode is enabled.
+   */
+    public abstract void onVrStateChanged(boolean enabled);
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9a24942..c636185 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -74,6 +74,7 @@
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -603,9 +604,15 @@
         mHaveFrame = true;
 
         final Task task = getTask();
-        final boolean nonFullscreenTask = task != null && !task.isFullscreen();
+        final boolean fullscreenTask = task == null || task.isFullscreen();
         final boolean freeformWorkspace = task != null && task.inFreeformWorkspace();
-        if (nonFullscreenTask) {
+
+        if (fullscreenTask || (isChildWindow()
+                && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0)) {
+            // We use the parent frame as the containing frame for fullscreen and child windows
+            mContainingFrame.set(pf);
+            mDisplayFrame.set(df);
+        } else {
             task.getBounds(mContainingFrame);
             final WindowState imeWin = mService.mInputMethodWindow;
             if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this
@@ -623,9 +630,6 @@
                 }
             }
             mDisplayFrame.set(mContainingFrame);
-        } else {
-            mContainingFrame.set(pf);
-            mDisplayFrame.set(df);
         }
 
         final int pw = mContainingFrame.width();
@@ -2276,7 +2280,7 @@
         }
 
         if (nonFullscreenTask) {
-            // Make sure window fits in containing frame since it is in a non-fullscreen stack as
+            // Make sure window fits in containing frame since it is in a non-fullscreen task as
             // required by {@link Gravity#apply} call.
             w = Math.min(w, pw);
             h = Math.min(h, ph);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fb5f21a..3db8376 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -89,14 +89,13 @@
 import com.android.server.tv.TvInputManagerService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.usage.UsageStatsService;
-import com.android.server.usb.UsbService;
+import com.android.server.vr.VrManagerService;
 import com.android.server.wallpaper.WallpaperManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.WindowManagerService;
 
 import dalvik.system.VMRuntime;
 
-import java.io.File;
 import java.util.Locale;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -447,6 +446,7 @@
         ConsumerIrService consumerIr = null;
         MmsServiceBroker mmsService = null;
         EntropyMixer entropyMixer = null;
+        VrManagerService vrManagerService = null;
 
         boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
         boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -532,6 +532,10 @@
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+            traceBeginAndSlog("StartVrManagerService");
+            mSystemServiceManager.startService(VrManagerService.class);
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
             mActivityManagerService.setWindowManager(wm);
 
             inputManager.setWindowManagerCallbacks(wm.getInputMonitor());