Merge "Make public pointer icon API with custom icons."
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3539ff..3962abd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4399,10 +4399,14 @@
             // onConfigurationChanged
             int diff = activity.mCurrentConfig.diff(config);
             if (diff != 0) {
-                // If this activity doesn't handle any of the config changes
-                // then don't bother calling onConfigurationChanged as we're
-                // going to destroy it.
-                if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
+                // If this activity doesn't handle any of the config changes then don't bother
+                // calling onConfigurationChanged as we're going to destroy it.
+                // Except in the case where the configuration changed on the activity manager side,
+                // but wasn't big enough to cause a resource change so the activity wasn't destroyed.
+                // In this case we still want to change the configuration of the activity but not
+                // report it to the app.
+                if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
+                        || !reportToActivity) {
                     shouldChangeConfig = true;
                 }
             }
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 5d9d929..568b267 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -72,10 +72,20 @@
                 return result;
             }
 
-            // TODO: Convert this a proper locale-fallback system
+            // If there's a variant, fall back to language+variant only, if available
+            final String variant = locale.getVariant();
+            if (!variant.isEmpty()) {
+                final Locale languageAndVariantOnlyLocale =
+                        new Locale(locale.getLanguage(), "", variant);
+                result = sMap.get(languageAndVariantOnlyLocale);
+                if (result != null) {
+                    sMap.put(locale, result);
+                    return result;
+                }
+            }
 
             // Fall back to language-only, if available
-            Locale languageOnlyLocale = new Locale(locale.getLanguage());
+            final Locale languageOnlyLocale = new Locale(locale.getLanguage());
             result = sMap.get(languageOnlyLocale);
             if (result != null) {
                 sMap.put(locale, result);
@@ -83,9 +93,9 @@
             }
 
             // Fall back to script-only, if available
-            String script = locale.getScript();
+            final String script = locale.getScript();
             if (!script.equals("")) {
-                Locale scriptOnlyLocale = new Locale.Builder()
+                final Locale scriptOnlyLocale = new Locale.Builder()
                         .setLanguage("und")
                         .setScript(script)
                         .build();
@@ -142,6 +152,11 @@
         {"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
         {"en-VI", "en-US"}, // English (Virgin Islands)
 
+        // For German, we're assuming the 1996 (and later) orthography by default.
+        {"de", "de-1996"},
+        // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+        {"de-LI-1901", "de-CH-1901"},
+
         // Norwegian is very probably Norwegian Bokmål.
         {"no", "nb"},
 
@@ -166,7 +181,18 @@
         sMap.put(null, null);
 
         // TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
-        String[] availableLanguages = {"en-US", "es", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
+        String[] availableLanguages = {
+            "de-1901", "de-1996", "de-CH-1901",
+            "en-US",
+            "es",
+            "eu",
+            "hu",
+            "hy",
+            "nb",
+            "nn",
+            "pt",
+            "und-Ethi"
+        };
         for (int i = 0; i < availableLanguages.length; i++) {
             String languageTag = availableLanguages[i];
             Hyphenator h = loadHyphenator(languageTag);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d6bc27c..2e884cc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1171,6 +1171,16 @@
          */
         public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
 
+        /**
+         * Flag to indicate that this window is not expected to be replaced across
+         * configuration change triggered activity relaunches. In general the WindowManager
+         * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
+         * until the replacement is ready to show in order to prevent visual glitch. However
+         * some windows, such as PopupWindows expect to be cleared across configuration change,
+         * and thus should hint to the WindowManager that it should not wait for a replacement.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
 
         /**
          * Control flags that are private to the platform.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7b9de79..f4c343a 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
 import com.android.internal.R;
 
 import android.annotation.NonNull;
@@ -1311,6 +1313,8 @@
             p.width = mLastWidth = mWidth;
         }
 
+        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
         // Used for debugging.
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 6c223c3..e38d82f 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -64,6 +64,11 @@
     private static final boolean DEBUG = false;
 
     /**
+     * The key to identify when the lock pattern enabled flag is being acccessed for legacy reasons.
+     */
+    public static final String LEGACY_LOCK_PATTERN_ENABLED = "legacy_lock_pattern_enabled";
+
+    /**
      * The number of incorrect attempts before which we fall back on an alternative
      * method of verifying the user, and resetting their lock pattern.
      */
@@ -1014,6 +1019,19 @@
         return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
     }
 
+    @Deprecated
+    public boolean isLegacyLockPatternEnabled(int userId) {
+        // Note: this value should default to {@code true} to avoid any reset that might result.
+        // We must use a special key to read this value, since it will by default return the value
+        // based on the new logic.
+        return getBoolean(LEGACY_LOCK_PATTERN_ENABLED, true, userId);
+    }
+
+    @Deprecated
+    public void setLegacyLockPatternEnabled(int userId) {
+        setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, true, userId);
+    }
+
     private boolean isLockPatternEnabled(int mode, int userId) {
         return mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
                 && savedPatternExists(userId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a94741..de7f6ed 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -369,6 +369,22 @@
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="wifi_scan_available" />
 
+    <protected-broadcast android:name="action.cne.started" />
+    <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+    <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
+    <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
+    <protected-broadcast android:name="android.location.HIGH_POWER_REQUEST_CHANGE" />
+    <protected-broadcast android:name="android.location.HIGH_POWER_REQUEST_CHANGE" />
+    <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
+    <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+    <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
+    <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
+    <protected-broadcast android:name="com.android.server.action.UPDATE_TWILIGHT_STATE" />
+    <protected-broadcast android:name="com.android.server.device_idle.STEP_IDLE_STATE" />
+    <protected-broadcast android:name="com.android.server.device_idle.STEP_LIGHT_IDLE_STATE" />
+    <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
+    <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index b99c806..44ff079 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -170,8 +170,27 @@
 
             setupCopyJob(srcs, stack, transferMode);
 
+            final String opDesc = transferMode == TRANSFER_MODE_COPY ? "copy" : "move";
+            DocumentInfo srcInfo;
+            DocumentInfo dstInfo;
             for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
-                copy(srcs.get(i), stack.peek(), transferMode);
+                srcInfo = srcs.get(i);
+                dstInfo = stack.peek();
+
+                // Guard unsupported recursive operation.
+                if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
+                    if (DEBUG) Log.d(TAG,
+                            "Skipping recursive " + opDesc + " of directory " + dstInfo.derivedUri);
+                    mFailedFiles.add(srcInfo);
+                    continue;
+                }
+
+                if (DEBUG) Log.d(TAG,
+                        "Performing " + opDesc + " of " + srcInfo.displayName
+                        + " (" + srcInfo.derivedUri + ")" + " to " + dstInfo.displayName
+                        + " (" + dstInfo.derivedUri + ")");
+
+                copy(srcInfo, dstInfo, transferMode);
             }
         } catch (Exception e) {
             // Catch-all to prevent any copy errors from wedging the app.
@@ -447,22 +466,6 @@
      */
     private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
             throws RemoteException {
-
-        String opDesc = mode == TRANSFER_MODE_COPY ? "copy" : "move";
-
-        // Guard unsupported recursive operation.
-        if (dstDirInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstDirInfo)) {
-            if (DEBUG) Log.d(TAG,
-                    "Skipping recursive " + opDesc + " of directory " + dstDirInfo.derivedUri);
-            mFailedFiles.add(srcInfo);
-            return;
-        }
-
-        if (DEBUG) Log.d(TAG,
-                "Performing " + opDesc + " of " + srcInfo.displayName
-                + " (" + srcInfo.derivedUri + ")" + " to " + dstDirInfo.displayName
-                + " (" + dstDirInfo.derivedUri + ")");
-
         // When copying within the same provider, try to use optimized copying and moving.
         // If not supported, then fallback to byte-by-byte copy/move.
         if (srcInfo.authority.equals(dstDirInfo.authority)) {
@@ -490,6 +493,8 @@
             }
         }
 
+        // Create the target document (either a file or a directory), then copy recursively the
+        // contents (bytes or children).
         final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirInfo.derivedUri,
                 srcInfo.mimeType, srcInfo.displayName);
         if (dstUri == null) {
@@ -498,10 +503,30 @@
             return;
         }
 
-        if (srcInfo.isDirectory()) {
-            copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
+        DocumentInfo dstInfo = null;
+        try {
+            final DocumentInfo dstDocInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
+        } catch (FileNotFoundException e) {
+            mFailedFiles.add(srcInfo);
+            return;
+        }
+
+        if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+            copyDirectoryHelper(srcInfo, dstInfo, mode);
         } else {
-            copyFileHelper(srcInfo.derivedUri, dstUri, mode);
+            copyFileHelper(srcInfo, dstInfo, mode);
+        }
+
+        if (mode == TRANSFER_MODE_MOVE) {
+            try {
+                DocumentsContract.deleteDocument(mSrcClient, srcInfo.derivedUri);
+            } catch (RemoteException e) {
+                // RemoteExceptions usually signal that the connection is dead, so there's no
+                // point attempting to continue. Propagate the exception up so the copy job is
+                // cancelled.
+                Log.w(TAG, "Failed to clean up after move: " + srcInfo.derivedUri, e);
+                throw e;
+            }
         }
     }
 
@@ -520,48 +545,29 @@
      * Handles recursion into a directory and copying its contents. Note that in linux terms, this
      * does the equivalent of "cp src/* dst", not "cp -r src dst".
      *
-     * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's
+     * @param srcDirInfo Info of the directory to copy from. The routine will copy the directory's
      *            contents, not the directory itself.
-     * @param dstDirUri URI of the directory to copy to. Must be created beforehand.
+     * @param dstDirInfo Info of the directory to copy to. Must be created beforehand.
      * @throws RemoteException
      */
-    private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri, int mode)
+    private void copyDirectoryHelper(DocumentInfo srcDirInfo, DocumentInfo dstDirInfo, int mode)
             throws RemoteException {
         // Recurse into directories. Copy children into the new subdirectory.
         final String queryColumns[] = new String[] {
                 Document.COLUMN_DISPLAY_NAME,
                 Document.COLUMN_DOCUMENT_ID,
                 Document.COLUMN_MIME_TYPE,
-                Document.COLUMN_SIZE
+                Document.COLUMN_SIZE,
+                Document.COLUMN_FLAGS
         };
-        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(),
-                DocumentsContract.getDocumentId(srcDirUri));
         Cursor cursor = null;
         try {
             // Iterate over srcs in the directory; copy to the destination directory.
-            cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+            DocumentInfo srcInfo;
+            cursor = mSrcClient.query(srcDirInfo.derivedUri, queryColumns, null, null, null);
             while (cursor.moveToNext()) {
-                final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-                final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
-                        childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME));
-                final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
-                        getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
-                if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
-                    copyDirectoryHelper(childUri, dstUri, mode);
-                } else {
-                    copyFileHelper(childUri, dstUri, mode);
-                }
-            }
-            if (mode == TRANSFER_MODE_MOVE) {
-                try {
-                    DocumentsContract.deleteDocument(mSrcClient, srcDirUri);
-                } catch (RemoteException e) {
-                    // RemoteExceptions usually signal that the connection is dead, so there's no
-                    // point attempting to continue. Propagate the exception up so the copy job is
-                    // cancelled.
-                    Log.w(TAG, "Failed to clean up after move: " + srcDirUri, e);
-                    throw e;
-                }
+                srcInfo = DocumentInfo.fromCursor(cursor, srcDirInfo.authority);
+                copy(srcInfo, dstDirInfo, mode);
             }
         } finally {
             IoUtils.closeQuietly(cursor);
@@ -571,11 +577,11 @@
     /**
      * Handles copying a single file.
      *
-     * @param srcUri URI of the file to copy from.
-     * @param dstUri URI of the *file* to copy to. Must be created beforehand.
+     * @param srcUriInfo Info of the file to copy from.
+     * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand.
      * @throws RemoteException
      */
-    private void copyFileHelper(Uri srcUri, Uri dstUri, int mode)
+    private void copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, int mode)
             throws RemoteException {
         // Copy an individual file.
         CancellationSignal canceller = new CancellationSignal();
@@ -586,8 +592,8 @@
 
         IOException copyError = null;
         try {
-            srcFile = mSrcClient.openFile(srcUri, "r", canceller);
-            dstFile = mDstClient.openFile(dstUri, "w", canceller);
+            srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller);
+            dstFile = mDstClient.openFile(dstInfo.derivedUri, "w", canceller);
             src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
             dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
 
@@ -601,18 +607,7 @@
             srcFile.checkError();
         } catch (IOException e) {
             copyError = e;
-
-            try {
-                DocumentInfo info = DocumentInfo.fromUri(getContentResolver(), srcUri);
-                mFailedFiles.add(info);
-            } catch (FileNotFoundException ignore) {
-                // Generate a dummy DocumentInfo so an error still gets reflected in the UI for this
-                // file.
-                DocumentInfo info = new DocumentInfo();
-                info.derivedUri = srcUri;
-                info.displayName = "Unknown [" + srcUri + "]";
-                mFailedFiles.add(info);
-            }
+            mFailedFiles.add(srcInfo);
 
             if (dstFile != null) {
                 try {
@@ -627,26 +622,17 @@
             IoUtils.closeQuietly(dst);
         }
 
-
         if (copyError != null || mIsCancelled) {
             // Clean up half-copied files.
             canceller.cancel();
             try {
-                DocumentsContract.deleteDocument(mDstClient, dstUri);
+                DocumentsContract.deleteDocument(mDstClient, dstInfo.derivedUri);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed to clean up after copy error: " + dstUri, e);
+                Log.w(TAG, "Failed to clean up after copy error: " + dstInfo.derivedUri, e);
                 // RemoteExceptions usually signal that the connection is dead, so there's no point
                 // attempting to continue. Propagate the exception up so the copy job is cancelled.
                 throw e;
             }
-        } else if (mode == TRANSFER_MODE_MOVE) {
-            // Clean up src files after a successful move.
-            try {
-                DocumentsContract.deleteDocument(mSrcClient, srcUri);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to clean up after move: " + srcUri, e);
-                throw e;
-            }
         }
     }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 6fa0df2..1d72647 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -255,7 +255,7 @@
                 return;
             }
             if (mNumLoaded == 0) {
-                mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
+                mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
             }
             try {
                 mDatabase.getMapper().putChildDocuments(
@@ -266,7 +266,7 @@
                 mNumLoaded = 0;
             }
             if (getState() != STATE_LOADING) {
-                mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
+                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
             }
         }
 
@@ -275,7 +275,7 @@
             mError = message;
             mNumLoaded = 0;
             if (lastState == STATE_LOADING) {
-                mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
+                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
             }
         }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 0d9d60c..15ebe99 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -18,6 +18,7 @@
 
 import static com.android.mtp.MtpDatabaseConstants.*;
 
+import android.annotation.Nullable;
 import android.content.ContentValues;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -36,7 +37,6 @@
 
 import static com.android.mtp.MtpDatabase.strings;
 
-
 /**
  * Mapping operations for MtpDatabase.
  * Also see the comments of {@link MtpDatabase}.
@@ -45,8 +45,9 @@
     private final MtpDatabase mDatabase;
 
     /**
-     * Mapping mode for roots/documents where we start adding child documents.
+     * Mapping mode for a parent. The key is document ID of parent, or null for root documents.
      * Methods operate the state needs to be synchronized.
+     * TODO: Replace this with unboxing int map.
      */
     private final Map<String, Integer> mMappingMode = new HashMap<>();
 
@@ -55,32 +56,6 @@
     }
 
     /**
-     * Invokes {@link #startAddingDocuments} for root documents.
-     * @param deviceId Device ID.
-     */
-    synchronized void startAddingRootDocuments(int deviceId) {
-        final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
-        Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
-        mMappingMode.put(
-                mappingStateKey,
-                startAddingDocuments(
-                        SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
-    }
-
-    /**
-     * Invokes {@link #startAddingDocuments} for child of specific documents.
-     * @param parentDocumentId Document ID for parent document.
-     */
-    @VisibleForTesting
-    synchronized void startAddingChildDocuments(String parentDocumentId) {
-        final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
-        Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
-        mMappingMode.put(
-                mappingStateKey,
-                startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
-    }
-
-    /**
      * Puts root information to database.
      * @param deviceId Device ID
      * @param resources Resources required to localize root name.
@@ -93,9 +68,8 @@
         try {
             final boolean heuristic;
             final String mapColumn;
-            final String key = getRootDocumentsMappingStateKey(deviceId);
-            Preconditions.checkState(mMappingMode.containsKey(key));
-            switch (mMappingMode.get(key)) {
+            Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null));
+            switch (mMappingMode.get(/* no parent for root */ null)) {
                 case MAP_BY_MTP_IDENTIFIER:
                     heuristic = false;
                     mapColumn = COLUMN_STORAGE_ID;
@@ -117,8 +91,8 @@
             }
             final boolean changed = putDocuments(
                     valuesList,
-                    SELECTION_ROOT_DOCUMENTS,
-                    Integer.toString(deviceId),
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
+                    new String[0],
                     heuristic,
                     mapColumn);
             final ContentValues values = new ContentValues();
@@ -156,9 +130,8 @@
     synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
         final boolean heuristic;
         final String mapColumn;
-        final String key = getChildDocumentsMappingStateKey(parentId);
-        Preconditions.checkState(mMappingMode.containsKey(key));
-        switch (mMappingMode.get(key)) {
+        Preconditions.checkState(mMappingMode.containsKey(parentId));
+        switch (mMappingMode.get(parentId)) {
             case MAP_BY_MTP_IDENTIFIER:
                 heuristic = false;
                 mapColumn = COLUMN_OBJECT_HANDLE;
@@ -177,59 +150,11 @@
                     valuesList[i], deviceId, parentId, documents[i]);
         }
         putDocuments(
-                valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
-    }
-
-    /**
-     * Stops adding root documents.
-     * @param deviceId Device ID.
-     * @return True if new rows are added/removed.
-     */
-    synchronized boolean stopAddingRootDocuments(int deviceId) {
-        final String key = getRootDocumentsMappingStateKey(deviceId);
-        Preconditions.checkState(mMappingMode.containsKey(key));
-        switch (mMappingMode.get(key)) {
-            case MAP_BY_MTP_IDENTIFIER:
-                mMappingMode.remove(key);
-                return stopAddingDocuments(
-                        SELECTION_ROOT_DOCUMENTS,
-                        Integer.toString(deviceId),
-                        COLUMN_STORAGE_ID);
-            case MAP_BY_NAME:
-                mMappingMode.remove(key);
-                return stopAddingDocuments(
-                        SELECTION_ROOT_DOCUMENTS,
-                        Integer.toString(deviceId),
-                        Document.COLUMN_DISPLAY_NAME);
-            default:
-                throw new Error("Unexpected mapping state.");
-        }
-    }
-
-    /**
-     * Stops adding documents under the parent.
-     * @param parentId Document ID of the parent.
-     */
-    synchronized void stopAddingChildDocuments(String parentId) {
-        final String key = getChildDocumentsMappingStateKey(parentId);
-        Preconditions.checkState(mMappingMode.containsKey(key));
-        switch (mMappingMode.get(key)) {
-            case MAP_BY_MTP_IDENTIFIER:
-                stopAddingDocuments(
-                        SELECTION_CHILD_DOCUMENTS,
-                        parentId,
-                        COLUMN_OBJECT_HANDLE);
-                break;
-            case MAP_BY_NAME:
-                stopAddingDocuments(
-                        SELECTION_CHILD_DOCUMENTS,
-                        parentId,
-                        Document.COLUMN_DISPLAY_NAME);
-                break;
-            default:
-                throw new Error("Unexpected mapping state.");
-        }
-        mMappingMode.remove(key);
+                valuesList,
+                COLUMN_PARENT_DOCUMENT_ID + "=?",
+                strings(parentId),
+                heuristic,
+                mapColumn);
     }
 
     @VisibleForTesting
@@ -257,31 +182,42 @@
      * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
      * a corresponding existing row. Otherwise it does heuristic.
      *
-     * @param selection Query matches valid documents.
-     * @param arg Argument for selection.
-     * @return Mapping mode.
+     * @param parentDocumentId Parent document ID or NULL for root documents.
      */
-    private int startAddingDocuments(String selection, String arg) {
+    void startAddingDocuments(@Nullable String parentDocumentId) {
+        Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
+        final String selection;
+        final String[] args;
+        if (parentDocumentId != null) {
+            selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+            args = strings(parentDocumentId);
+        } else {
+            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = new String[0];
+        }
+
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
             // Delete all pending rows.
             mDatabase.deleteDocumentsAndRootsRecursively(
-                    selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+                    selection + " AND " + COLUMN_ROW_STATE + "=?",
+                    DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING)));
 
             // Set all documents as invalidated.
             final ContentValues values = new ContentValues();
             values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
-            database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+            database.update(TABLE_DOCUMENTS, values, selection, args);
 
             // If we have rows that does not have MTP identifier, do heuristic mapping by name.
             final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
                     database,
                     TABLE_DOCUMENTS,
                     selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
-                    new String[] { arg }) > 0;
+                    args) > 0;
             database.setTransactionSuccessful();
-            return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+            mMappingMode.put(
+                    parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER);
         } finally {
             database.endTransaction();
         }
@@ -292,19 +228,19 @@
      * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
      * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
      * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
-     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+     * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid'
      * 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 selection SQL where closure to select rows that shares the same parent.
-     * @param arg Argument for selection SQL.
+     * @param args Argument for selection SQL.
      * @param heuristic Whether the mapping mode is heuristic.
      * @return Whether the method adds new rows.
      */
     private boolean putDocuments(
             ContentValues[] valuesList,
             String selection,
-            String arg,
+            String[] args,
             boolean heuristic,
             String mappingKey) {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
@@ -318,7 +254,9 @@
                         selection + " AND " +
                         COLUMN_ROW_STATE + "=? AND " +
                         mappingKey + "=?",
-                        strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+                        DatabaseUtils.appendSelectionArgs(
+                                args,
+                                strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))),
                         null,
                         null,
                         null,
@@ -362,12 +300,32 @@
      * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
      * If the database does not find corresponding 'invalidated' document, it just removes
      * 'invalidated' document from the database.
-     * @param selection Query to select rows for resolving.
-     * @param arg Argument for selection SQL.
-     * @param groupKey Column name used to find corresponding rows.
+     * @param parentId Parent document ID or null for root documents.
      * @return Whether the methods adds or removed visible rows.
      */
-    private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+    boolean stopAddingDocuments(@Nullable String parentId) {
+        Preconditions.checkState(mMappingMode.containsKey(parentId));
+        final String selection;
+        final String[] args;
+        if (parentId != null) {
+            selection = COLUMN_PARENT_DOCUMENT_ID + "=?";
+            args = strings(parentId);
+        } else {
+            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = new String[0];
+        }
+        final String groupKey;
+        switch (mMappingMode.get(parentId)) {
+            case MAP_BY_MTP_IDENTIFIER:
+                groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID;
+                break;
+            case MAP_BY_NAME:
+                groupKey = Document.COLUMN_DISPLAY_NAME;
+                break;
+            default:
+                throw new Error("Unexpected mapping state.");
+        }
+        mMappingMode.remove(parentId);
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
@@ -390,7 +348,7 @@
                             "group_concat(" + pendingIdQuery + ")"
                     },
                     selection,
-                    strings(arg),
+                    args,
                     groupKey,
                     "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
                     null);
@@ -439,7 +397,7 @@
             // Delete all invalidated rows that cannot be mapped.
             if (mDatabase.deleteDocumentsAndRootsRecursively(
                     COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_INVALIDATED, arg))) {
+                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
                 changed = true;
             }
 
@@ -452,7 +410,7 @@
                     TABLE_DOCUMENTS,
                     values,
                     COLUMN_ROW_STATE + " = ? AND " + selection,
-                    strings(ROW_STATE_PENDING, arg)) != 0) {
+                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
                 changed = true;
             }
             database.setTransactionSuccessful();
@@ -494,20 +452,4 @@
         return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
                 " THEN " + a + " ELSE NULL END";
     }
-
-    /**
-     * @param deviceId Device ID.
-     * @return Key for {@link #mMappingMode}.
-     */
-    private static String getRootDocumentsMappingStateKey(int deviceId) {
-        return "RootDocuments/" + deviceId;
-    }
-
-    /**
-     * @param parentDocumentId Document ID for the parent document.
-     * @return Key for {@link #mMappingMode}.
-     */
-    private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
-        return "ChildDocuments/" + parentDocumentId;
-    }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 0e91fdd..eac9b98 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -53,13 +53,13 @@
  * by comparing the directory structure and object name.
  *
  * To start putting documents into the database, the client needs to call
- * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
- * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
+ * {@link Mapper#startAddingDocuments(String)} with the parent document ID. Also it
+ * needs to call {@link Mapper#stopAddingDocuments(String)} after putting all child
  * documents to the database. (All explanations are same for root documents)
  *
- * database.getMapper().startAddingChildDocuments();
+ * database.getMapper().startAddingDocuments();
  * database.getMapper().putChildDocuments();
- * database.getMapper().stopAddingChildDocuments();
+ * database.getMapper().stopAddingDocuments();
  *
  * To update the existing documents, the client code can repeat to call the three methods again.
  * The newly added rows update corresponding existing rows that have same MTP identifier like
@@ -307,32 +307,6 @@
         }
     }
 
-    /**
-     * Returns the set of device ID stored in the database.
-     */
-    int[] getDeviceIds() {
-        final Cursor cursor = mDatabase.query(
-                true,
-                TABLE_DOCUMENTS,
-                strings(COLUMN_DEVICE_ID),
-                null,
-                null,
-                null,
-                null,
-                null,
-                null);
-        try {
-            final int[] ids = new int[cursor.getCount()];
-            for (int i = 0; i < ids.length; i++) {
-                cursor.moveToNext();
-                ids[i] = cursor.getInt(0);
-            }
-            return ids;
-        } finally {
-            cursor.close();
-        }
-    }
-
     private boolean deleteDocumentsAndRoots(String selection, String[] args) {
         mDatabase.beginTransaction();
         try {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 72bd6ee..43dc8f5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -103,9 +103,6 @@
 
     static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
     static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
-    static final String SELECTION_ROOT_DOCUMENTS =
-            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
-    static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
 
     static final String QUERY_CREATE_DOCUMENTS =
             "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index d5f00e6..9338c1b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -293,19 +293,11 @@
     }
 
     /**
-     * Reopens MTP devices based on database state.
+     * Clears MTP identifier in the database.
      */
     private void resume() {
         synchronized (mDeviceListLock) {
             mDatabase.getMapper().clearMapping();
-            final int[] ids = mDatabase.getDeviceIds();
-            for (final int id : ids) {
-                try {
-                    openDevice(id);
-                } catch (IOException exception) {
-                    mDatabase.removeDeviceRows(id);
-                }
-            }
         }
     }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index df2ab01..52a751b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -110,19 +110,12 @@
                     return;
                 }
                 boolean changed = false;
+                mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
                 for (int deviceId : deviceIds) {
                     try {
                         final MtpRoot[] roots = mManager.getRoots(deviceId);
-                        mDatabase.getMapper().startAddingRootDocuments(deviceId);
-                        try {
-                            if (mDatabase.getMapper().putRootDocuments(
-                                    deviceId, mResources, roots)) {
-                                changed = true;
-                            }
-                        } finally {
-                            if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
-                                changed = true;
-                            }
+                        if (mDatabase.getMapper().putRootDocuments(deviceId, mResources, roots)) {
+                            changed = true;
                         }
                     } catch (IOException | SQLiteException exception) {
                         // The error may happen on the device. We would like to continue getting
@@ -130,6 +123,9 @@
                         Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
                     }
                 }
+                if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) {
+                    changed = true;
+                }
                 if (changed) {
                     notifyChange();
                 }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index f0b4343..22d954d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -40,11 +40,11 @@
     @Override
     public void setUp() {
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, new TestResources(), new MtpRoot[] {
                 new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
         mManager = new BlockableTestMtpManager(getContext());
         mResolver = new TestContentResolver();
         mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 8166de1..18623e6 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -61,7 +61,7 @@
     }
 
     public void testPutRootDocuments() throws Exception {
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
@@ -155,7 +155,7 @@
     }
 
     public void testPutChildDocuments() throws Exception {
-        mDatabase.getMapper().startAddingChildDocuments("parentId");
+        mDatabase.getMapper().startAddingDocuments("parentId");
         mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -236,7 +236,7 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
@@ -296,7 +296,7 @@
             cursor.close();
         }
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
@@ -335,7 +335,7 @@
             cursor.close();
         }
 
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -370,7 +370,7 @@
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        mDatabase.getMapper().startAddingChildDocuments("parentId");
+        mDatabase.getMapper().startAddingDocuments("parentId");
         mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -400,7 +400,7 @@
             cursor.close();
         }
 
-        mDatabase.getMapper().startAddingChildDocuments("parentId");
+        mDatabase.getMapper().startAddingDocuments("parentId");
         mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
@@ -418,7 +418,7 @@
             cursor.close();
         }
 
-        mDatabase.getMapper().stopAddingChildDocuments("parentId");
+        mDatabase.getMapper().stopAddingDocuments("parentId");
 
         {
             final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -437,7 +437,10 @@
         }
     }
 
-    public void testRestoreIdForDifferentDevices() throws Exception {
+    /**
+     * TODO: Enable this test after introducing device documents.
+     */
+    public void disabled_testRestoreIdForDifferentDevices() throws Exception {
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -447,8 +450,7 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        mDatabase.getMapper().startAddingRootDocuments(0);
-        mDatabase.getMapper().startAddingRootDocuments(1);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
         });
@@ -484,16 +486,14 @@
 
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
-        mDatabase.getMapper().startAddingRootDocuments(1);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
         mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
-        mDatabase.getMapper().stopAddingRootDocuments(1);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -528,8 +528,8 @@
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
 
-        mDatabase.getMapper().startAddingChildDocuments("parentId1");
-        mDatabase.getMapper().startAddingChildDocuments("parentId2");
+        mDatabase.getMapper().startAddingDocuments("parentId1");
+        mDatabase.getMapper().startAddingDocuments("parentId2");
         mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
@@ -538,15 +538,15 @@
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingChildDocuments("parentId1");
-        mDatabase.getMapper().startAddingChildDocuments("parentId2");
+        mDatabase.getMapper().startAddingDocuments("parentId1");
+        mDatabase.getMapper().startAddingDocuments("parentId2");
         mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
         mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        mDatabase.getMapper().stopAddingChildDocuments("parentId1");
+        mDatabase.getMapper().stopAddingDocuments("parentId1");
 
         {
             final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
@@ -577,23 +577,23 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -625,18 +625,18 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -667,17 +667,17 @@
     public void testReplaceExistingRoots() {
         // The client code should be able to replace existing rows with new information.
         // Add one.
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
         // Replace it.
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
         {
             final String[] columns = new String[] {
                     DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -709,7 +709,7 @@
     public void testFailToReplaceExisitingUnmappedRoots() {
         // The client code should not be able to replace rows before resolving 'unmapped' rows.
         // Add one.
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
@@ -718,7 +718,7 @@
         assertEquals(1, oldCursor.getCount());
 
         // Add one.
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
         });
@@ -726,7 +726,7 @@
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         // Because the roots shares the same name, the roots should have new IDs.
         final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
@@ -742,11 +742,11 @@
     }
 
     public void testQueryDocument() {
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
         assertEquals(1, cursor.getCount());
@@ -756,48 +756,48 @@
     }
 
     public void testGetParentId() throws FileNotFoundException {
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
-        mDatabase.getMapper().startAddingChildDocuments("1");
+        mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().putChildDocuments(
                 0,
                 "1",
                 new MtpObjectInfo[] {
                         createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 });
-        mDatabase.getMapper().stopAddingChildDocuments("1");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         assertEquals("1", mDatabase.getParentId("2"));
     }
 
     public void testDeleteDocument() {
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
-        mDatabase.getMapper().startAddingChildDocuments("1");
+        mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().putChildDocuments(
                 0,
                 "1",
                 new MtpObjectInfo[] {
                         createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
                 });
-        mDatabase.getMapper().stopAddingChildDocuments("1");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
-        mDatabase.getMapper().startAddingChildDocuments("2");
+        mDatabase.getMapper().startAddingDocuments("2");
         mDatabase.getMapper().putChildDocuments(
                 0,
                 "2",
                 new MtpObjectInfo[] {
                         createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 });
-        mDatabase.getMapper().stopAddingChildDocuments("2");
+        mDatabase.getMapper().stopAddingDocuments("2");
 
         mDatabase.deleteDocument("2");
 
@@ -819,11 +819,11 @@
     }
 
     public void testPutNewDocument() {
-        mDatabase.getMapper().startAddingRootDocuments(0);
+        mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingRootDocuments(0);
+        mDatabase.getMapper().stopAddingDocuments(null);
 
         assertEquals(
                 "2",
@@ -841,12 +841,12 @@
 
         // The new document should not be mapped with existing invalidated document.
         mDatabase.getMapper().clearMapping();
-        mDatabase.getMapper().startAddingChildDocuments("1");
+        mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.putNewDocument(
                 0,
                 "1",
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
-        mDatabase.getMapper().stopAddingChildDocuments("1");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         {
             final Cursor cursor =
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index dc6f79e..597d51e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -408,34 +408,6 @@
                         MtpDocumentsProvider.AUTHORITY, "1")));
     }
 
-    @MediumTest
-    public void testPauseAndResume() throws Exception {
-        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
-        mMtpManager.addValidDevice(0);
-        mProvider.openDevice(0);
-        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")});
-
-        {
-            final Cursor cursor = mProvider.queryRoots(
-                    strings(DocumentsContract.Root.COLUMN_ROOT_ID));
-            cursor.moveToNext();
-            assertEquals(1, cursor.getInt(0));
-        }
-
-        mProvider.shutdown();
-        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
-
-        {
-            // We can still fetch roots after relaunching the provider.
-            final Cursor cursor = mProvider.queryRoots(
-                    strings(DocumentsContract.Root.COLUMN_ROOT_ID));
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals(1, cursor.getInt(0));
-            assertEquals(1, mMtpManager.getOpenedDeviceIds().length);
-        }
-    }
-
     private void setupProvider(int flag) {
         mDatabase = new MtpDatabase(getContext(), flag);
         mProvider = new MtpDocumentsProvider();
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 033a4b8..1a21f5c 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -356,6 +356,10 @@
             }
         }
 
+        if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
+            key = Settings.Secure.LOCK_PATTERN_ENABLED;
+        }
+
         return mStorage.readKeyValue(key, defaultValue, userId);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e8100f0..6c00e73 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -29,12 +29,12 @@
 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_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 android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static com.android.server.am.ActivityManagerDebugConfig.*;
 import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -76,7 +76,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
@@ -107,7 +106,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
-import android.os.storage.StorageManager;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -123,7 +121,6 @@
 import android.view.DisplayInfo;
 import android.view.InputEvent;
 import android.view.Surface;
-import android.widget.Toast;
 
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
@@ -1549,7 +1546,7 @@
             // 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 & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+            if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
@@ -1596,63 +1593,9 @@
             return err;
         }
 
-        boolean abort = false;
-
-        final int startAnyPerm = mService.checkPermission(
-                START_ANY_ACTIVITY, callingPid, callingUid);
-
-        if (startAnyPerm != PERMISSION_GRANTED) {
-            final int componentRestriction = getComponentRestrictionForCallingPackage(
-                    aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
-            final int actionRestriction = getActionRestrictionForCallingPackage(
-                    intent.getAction(), callingPackage, callingPid, callingUid);
-
-            if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
-                    || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
-                if (resultRecord != null) {
-                    resultStack.sendActivityResultLocked(-1,
-                            resultRecord, resultWho, requestCode,
-                            Activity.RESULT_CANCELED, null);
-                }
-                String msg;
-                if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
-                    msg = "Permission Denial: starting " + intent.toString()
-                            + " from " + callerApp + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")" + " with revoked permission "
-                            + ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
-                } else if (!aInfo.exported) {
-                    msg = "Permission Denial: starting " + intent.toString()
-                            + " from " + callerApp + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " not exported from uid " + aInfo.applicationInfo.uid;
-                } else {
-                    msg = "Permission Denial: starting " + intent.toString()
-                            + " from " + callerApp + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires " + aInfo.permission;
-                }
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-
-            if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
-                String message = "Appop Denial: starting " + intent.toString()
-                        + " from " + callerApp + " (pid=" + callingPid
-                        + ", uid=" + callingUid + ")"
-                        + " requires " + AppOpsManager.permissionToOp(
-                                ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
-                Slog.w(TAG, message);
-                abort = true;
-            } else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
-                String message = "Appop Denial: starting " + intent.toString()
-                        + " from " + callerApp + " (pid=" + callingPid
-                        + ", uid=" + callingUid + ")"
-                        + " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
-                Slog.w(TAG, message);
-                abort = true;
-            }
-        }
-
+        boolean abort = !checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode,
+                callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
+                resultRecord, resultStack);
         abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                 callingPid, resolvedType, aInfo.applicationInfo);
 
@@ -1682,7 +1625,7 @@
                             | PendingIntent.FLAG_IMMUTABLE, null);
             int flags = intent.getFlags();
             intent = km.createConfirmDeviceCredentialIntent(null, null);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+            intent.addFlags(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));
@@ -1801,6 +1744,66 @@
         return err;
     }
 
+    private boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
+            String resultWho, int requestCode, int callingPid, int callingUid,
+            String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
+            ActivityRecord resultRecord, ActivityStack resultStack) {
+        final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
+                callingUid);
+        if (startAnyPerm ==  PERMISSION_GRANTED) {
+            return true;
+        }
+        final int componentRestriction = getComponentRestrictionForCallingPackage(
+                aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
+        final int actionRestriction = getActionRestrictionForCallingPackage(
+                intent.getAction(), callingPackage, callingPid, callingUid);
+        if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
+                || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1,
+                        resultRecord, resultWho, requestCode,
+                        Activity.RESULT_CANCELED, null);
+            }
+            final String msg;
+            if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
+                msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")" + " with revoked permission "
+                        + ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
+            } else if (!aInfo.exported) {
+                msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")"
+                        + " not exported from uid " + aInfo.applicationInfo.uid;
+            } else {
+                msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")"
+                        + " requires " + aInfo.permission;
+            }
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
+            final String message = "Appop Denial: starting " + intent.toString()
+                    + " from " + callerApp + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ")"
+                    + " requires " + AppOpsManager.permissionToOp(
+                            ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
+            Slog.w(TAG, message);
+            return false;
+        } else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
+            final String message = "Appop Denial: starting " + intent.toString()
+                    + " from " + callerApp + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ")"
+                    + " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
+            Slog.w(TAG, message);
+            return false;
+        }
+        return true;
+    }
+
     private UserInfo getUserInfo(int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -1879,7 +1882,7 @@
         return ACTIVITY_RESTRICTION_NONE;
     }
 
-    ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
+    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
         final TaskRecord task = r.task;
 
         if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
@@ -1994,7 +1997,7 @@
             Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
                     "\"singleInstance\" or \"singleTask\"");
             launchFlags &=
-                    ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+                    ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
         } else {
             switch (r.info.documentLaunchMode) {
                 case ActivityInfo.DOCUMENT_LAUNCH_NONE:
@@ -2006,7 +2009,7 @@
                     launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
                     break;
                 case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
-                    launchFlags &= ~Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+                    launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK;
                     break;
             }
         }
@@ -2015,7 +2018,7 @@
                 && !launchSingleTask && !launchSingleInstance
                 && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
-        if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 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
@@ -2030,15 +2033,15 @@
         }
 
         if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
-            launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+            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 & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
             if (launchTaskBehind
                     || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
-                launchFlags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+                launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK;
             }
         }
 
@@ -2107,8 +2110,8 @@
             // If task is empty, then adopt the interesting intent launch flags in to the
             // activity being started.
             if (root == null) {
-                final int flagsOfInterest = Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                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);
@@ -2119,7 +2122,7 @@
             // 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 & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
                 addingToTask = false;
 
             } else {
@@ -2143,20 +2146,20 @@
             if (sourceRecord == null) {
                 // This activity is not being started from another...  in this
                 // case we -always- start a new task.
-                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
+                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 |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                    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 |= Intent.FLAG_ACTIVITY_NEW_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 |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                launchFlags |= FLAG_ACTIVITY_NEW_TASK;
             }
         }
 
@@ -2170,10 +2173,10 @@
                 // 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 & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+                if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) {
                     Slog.w(TAG, "startActivity called from finishing " + sourceRecord
                             + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
-                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                    launchFlags |= FLAG_ACTIVITY_NEW_TASK;
                     newTaskInfo = sourceRecord.info;
                     newTaskIntent = sourceRecord.task.intent;
                 }
@@ -2192,216 +2195,192 @@
         intent.setFlags(launchFlags);
         final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;
 
-        // 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.
-        if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
-                (launchFlags & Intent.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.
-            if (inTask == null && r.resultTo == null) {
-                // 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.
-                ActivityRecord intentActivity = !launchSingleInstance ?
-                        findTaskLocked(r) : findActivityLocked(intent, r.info);
-                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;
+        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);
                     }
-                    if (r.task == null) {
-                        r.task = intentActivity.task;
+                    movedHome = true;
+                    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);
                     }
-                    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;
-                            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 */);
-                                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;
-                    }
+                    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 */);
+                        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();
@@ -2460,7 +2439,7 @@
 
         // Should this be considered a new task?
         if (r.resultTo == null && inTask == null && !addingToTask
-                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+                && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
             newTask = true;
             targetStack = computeStackFocus(r, newTask, newBounds);
             if (doResume) {
@@ -2646,6 +2625,37 @@
         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);
@@ -4708,8 +4718,8 @@
     }
 
     class ActivityContainer extends android.app.IActivityContainer.Stub {
-        final static int FORCE_NEW_TASK_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK |
-                Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
+        final static int FORCE_NEW_TASK_FLAGS = FLAG_ACTIVITY_NEW_TASK |
+                FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
         final int mStackId;
         IActivityContainerCallback mCallback = null;
         final ActivityStack mStack;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3a8a988..e0f85c5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -436,12 +436,6 @@
     final String[] mSeparateProcesses;
     final boolean mIsUpgrade;
 
-    // This is where all application persistent data goes.
-    final File mAppDataDir;
-
-    // This is where all application persistent data goes for secondary users.
-    final File mUserAppDataDir;
-
     /** The location for ASEC container files on internal storage. */
     final String mAsecInternalPath;
 
@@ -953,7 +947,7 @@
 
     // Recordkeeping of restore-after-install operations that are currently in flight
     // between the Package Manager and the Backup Manager
-    class PostInstallData {
+    static class PostInstallData {
         public InstallArgs args;
         public PackageInstalledInfo res;
 
@@ -1070,7 +1064,7 @@
                         }
                         long timeInMillis;
                         try {
-                            timeInMillis = Long.parseLong(timeInMillisString.toString());
+                            timeInMillis = Long.parseLong(timeInMillisString);
                         } catch (NumberFormatException e) {
                             throw new IOException("Failed to parse " + timeInMillisString
                                                   + " as a long.", e);
@@ -1987,12 +1981,10 @@
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
 
             File dataDir = Environment.getDataDirectory();
-            mAppDataDir = new File(dataDir, "data");
             mAppInstallDir = new File(dataDir, "app");
             mAppLib32InstallDir = new File(dataDir, "app-lib");
             mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
             mAsecInternalPath = new File(dataDir, "app-asec").getPath();
-            mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
             sUserManager = new UserManagerService(context, this, mPackages);
@@ -3368,14 +3360,6 @@
         }
     }
 
-    private void checkValidCaller(int uid, int userId) {
-        if (UserHandle.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0)
-            return;
-
-        throw new SecurityException("Caller uid=" + uid
-                + " is not privileged to communicate with user=" + userId);
-    }
-
     @Override
     public int checkPermission(String permName, String pkgName, int userId) {
         if (!sUserManager.exists(userId)) {
@@ -4102,10 +4086,12 @@
         synchronized (mPackages) {
             if (mProtectedBroadcasts.contains(actionName)) {
                 return true;
-            } else if (actionName != null
-                    && actionName.startsWith("android.net.netmon.lingerExpired")) {
-                // TODO: remove this terrible hack
-                return true;
+            } else if (actionName != null) {
+                // TODO: remove these terrible hacks
+                if (actionName.startsWith("android.net.netmon.lingerExpired")
+                        || actionName.startsWith("com.android.server.sip.SipWakeupTimer")) {
+                    return true;
+                }
             }
         }
         return false;
@@ -10497,7 +10483,7 @@
             ArrayList<IntentFilter> result = new ArrayList<>();
             for (int n=0; n<count; n++) {
                 PackageParser.Activity activity = pkg.activities.get(n);
-                if (activity.intents != null || activity.intents.size() > 0) {
+                if (activity.intents != null && activity.intents.size() > 0) {
                     result.addAll(activity.intents);
                 }
             }
@@ -10916,7 +10902,7 @@
         }
     }
 
-    class MoveInfo {
+    static class MoveInfo {
         final int moveId;
         final String fromUuid;
         final String toUuid;
@@ -12222,7 +12208,7 @@
         }
     }
 
-    class PackageInstalledInfo {
+    static class PackageInstalledInfo {
         String name;
         int uid;
         // The set of users that originally had this package installed.
@@ -13163,10 +13149,6 @@
         }
     }
 
-    private static boolean isMultiArch(PackageSetting ps) {
-        return (ps.pkgFlags & ApplicationInfo.FLAG_MULTIARCH) != 0;
-    }
-
     private static boolean isMultiArch(ApplicationInfo info) {
         return (info.flags & ApplicationInfo.FLAG_MULTIARCH) != 0;
     }
@@ -13179,10 +13161,6 @@
         return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
-    private static boolean isExternal(ApplicationInfo info) {
-        return (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
-    }
-
     private static boolean isEphemeral(PackageParser.Package pkg) {
         return pkg.applicationInfo.isEphemeralApp();
     }
@@ -13866,7 +13844,7 @@
         return ret;
     }
 
-    private final class ClearStorageConnection implements ServiceConnection {
+    private final static class ClearStorageConnection implements ServiceConnection {
         IMediaContainerService mContainerService;
 
         @Override
@@ -15116,7 +15094,9 @@
                 // First, verify that this is a valid class name.
                 PackageParser.Package pkg = pkgSetting.pkg;
                 if (pkg == null || !pkg.hasComponentClassName(className)) {
-                    if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
+                    if (pkg != null &&
+                            pkg.applicationInfo.targetSdkVersion >=
+                                    Build.VERSION_CODES.JELLY_BEAN) {
                         throw new IllegalArgumentException("Component class " + className
                                 + " does not exist in " + packageName);
                     } else {
@@ -17326,7 +17306,7 @@
         }
     }
 
-    private final class OnPermissionChangeListeners extends Handler {
+    private final static class OnPermissionChangeListeners extends Handler {
         private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
 
         private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index fd5c704..b49641f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -122,21 +123,6 @@
     // True if the windows associated with this token should be cropped to their stack bounds.
     boolean mCropWindowsToStack;
 
-    // This application will have its window replaced due to relaunch. This allows window manager
-    // to differentiate between simple removal of a window and replacement. In the latter case it
-    // will preserve the old window until the new one is drawn.
-    boolean mWillReplaceWindow;
-    // If true, the replaced window was already requested to be removed.
-    boolean mReplacingRemoveRequested;
-    // Whether the replacement of the window should trigger app transition animation.
-    boolean mAnimateReplacingWindow;
-    // If not null, the window that will be used to replace the old one. This is being set when
-    // the window is added and unset when this window reports its first draw.
-    WindowState mReplacingWindow;
-    // Whether the new window has replaced the old one, so the old one can be removed without
-    // blinking.
-    boolean mHasReplacedWindow;
-
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
         super(_service, _token.asBinder(),
@@ -392,6 +378,62 @@
         }
     }
 
+    void setReplacingWindows(boolean animate) {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
+                + " with replacing windows.");
+
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState w = allAppWindows.get(i);
+            w.setReplacing(animate);
+        }
+        if (animate) {
+            // Set-up dummy animation so we can start treating windows associated with this
+            // token like they are in transition before the new app window is ready for us to
+            // run the real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+                    "setReplacingWindow() Setting dummy animation on: " + this);
+            mAppAnimator.setDummyAnimation();
+        }
+    }
+
+    void addWindow(WindowState w) {
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow && candidate.mReplacingWindow == null &&
+                    candidate.getWindowTag().equals(w.getWindowTag().toString())) {
+                candidate.mReplacingWindow = w;
+            }
+        }
+        allAppWindows.add(w);
+    }
+
+    boolean waitingForReplacement() {
+        for (int i = allAppWindows.size() -1; i >= 0; i--) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void clearTimedoutReplaceesLocked() {
+        for (int i = allAppWindows.size() - 1; i >= 0;
+             // removeWindowLocked at bottom of loop may remove multiple entries from
+             // allAppWindows if the window to be removed has child windows. It also may
+             // not remove any windows from allAppWindows at all if win is exiting and
+             // currently animating away. This ensures that winNdx is monotonically decreasing
+             // and never beyond allAppWindows bounds.
+             i = Math.min(i - 1, allAppWindows.size() - 1)) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow == false) {
+                continue;
+            }
+            candidate.mWillReplaceWindow = false;
+            service.removeWindowLocked(candidate);
+        }
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3db9ae0..2e424d0 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -506,7 +506,7 @@
                 inFreeformSpace = stack != null && stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
             }
 
-            replacing = replacing || (w.mAppToken != null && w.mAppToken.mWillReplaceWindow);
+            replacing = replacing || w.mWillReplaceWindow;
 
             // If the app is executing an animation because the keyguard is going away,
             // keep the wallpaper during the animation so it doesn't flicker out.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a6523a4..6a5183f 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -918,10 +918,6 @@
     }
 
     void requestRemovalOfReplacedWindows(WindowState win) {
-        final AppWindowToken token = win.mAppToken;
-        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == win) {
-            token.mHasReplacedWindow = true;
-        }
         mRemoveReplacedWindows = true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4190bd77..456c416 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -269,6 +269,9 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
+    /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
+    static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
+
     /** Amount of time to allow a last ANR message to exist before freeing the memory. */
     static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
     /**
@@ -1350,10 +1353,7 @@
         final AppWindowToken appToken = win.mAppToken;
         if (appToken != null) {
             if (addToToken) {
-                appToken.allAppWindows.add(win);
-            }
-            if (appToken.mWillReplaceWindow) {
-                appToken.mReplacingWindow = win;
+                appToken.addWindow(win);
             }
         }
     }
@@ -2088,14 +2088,15 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null) {
             return;
         }
         atoken.allDrawn = false;
         WindowState replacedWindow = null;
         for (int i = atoken.windows.size() - 1; i >= 0 && replacedWindow == null; i--) {
             WindowState candidate = atoken.windows.get(i);
-            if (candidate.mExiting) {
+            if (candidate.mExiting && candidate.mWillReplaceWindow
+                    && candidate.mAnimateReplacingWindow) {
                 replacedWindow = candidate;
             }
         }
@@ -2195,7 +2196,7 @@
                 + " app-animation="
                 + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
                 + " mWillReplaceWindow="
-                + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
+                + win.mWillReplaceWindow
                 + " inPendingTransaction="
                 + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
                 + " mDisplayFrozen=" + mDisplayFrozen);
@@ -2207,13 +2208,13 @@
         // animation wouldn't be seen.
         if (win.mHasSurface && okToDisplay()) {
             final AppWindowToken appToken = win.mAppToken;
-            if (appToken != null && appToken.mWillReplaceWindow) {
+            if (win.mWillReplaceWindow) {
                 // This window is going to be replaced. We need to keep it around until the new one
                 // gets added, then we will get rid of this one.
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Preserving " + win + " until the new one is "
                         + "added");
                 win.mExiting = true;
-                appToken.mReplacingRemoveRequested = true;
+                win.mReplacingRemoveRequested = true;
                 Binder.restoreCallingIdentity(origId);
                 return;
             }
@@ -4052,7 +4053,7 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
-                (visible && wtoken.mWillReplaceWindow)) {
+                (visible && wtoken.waitingForReplacement())) {
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG_WM, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -7514,6 +7515,8 @@
         public static final int TWO_FINGER_SCROLL_START = 45;
         public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
 
+        public static final int WINDOW_REPLACEMENT_TIMEOUT = 47;
+
         /**
          * Used to denote that an integer field in a message will not be used.
          */
@@ -8096,6 +8099,13 @@
                     toast.show();
                 }
                 break;
+                case WINDOW_REPLACEMENT_TIMEOUT: {
+                    final AppWindowToken token = (AppWindowToken) msg.obj;
+                    synchronized (mWindowMap) {
+                        token.clearTimedoutReplaceesLocked();
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
@@ -8618,7 +8628,7 @@
                 final int numTokens = tokens.size();
                 for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                     final AppWindowToken wtoken = tokens.get(tokenNdx);
-                    if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
+                    if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) {
                         continue;
                     }
                     i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8727,8 +8737,8 @@
     private void forceHigherLayerIfNeeded(WindowState w, WindowStateAnimator winAnimator,
             AppWindowToken wtoken) {
         boolean force = false;
-        if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w
-                && wtoken.mAnimateReplacingWindow) {
+
+        if (w.mWillReplaceWindow) {
             // We know that we will be animating a relaunching window in the near future,
             // which will receive a z-order increase. We want the replaced window to
             // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -10244,26 +10254,20 @@
      * @param token Application token for which the activity will be relaunched.
      */
     public void setReplacingWindow(IBinder token, boolean animate) {
+        AppWindowToken appWindowToken = null;
         synchronized (mWindowMap) {
-            AppWindowToken appWindowToken = findAppWindowToken(token);
+            appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null || !appWindowToken.isVisible()) {
                 Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
                 return;
             }
-            if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
-                    + " as replacing window.");
-            appWindowToken.mWillReplaceWindow = true;
-            appWindowToken.mHasReplacedWindow = false;
-            appWindowToken.mAnimateReplacingWindow = animate;
+            appWindowToken.setReplacingWindows(animate);
+        }
 
-            if (animate) {
-                // Set-up dummy animation so we can start treating windows associated with this
-                // token like they are in transition before the new app window is ready for us to
-                // run the real transition animation.
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                        "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
-                appWindowToken.mAppAnimator.setDummyAnimation();
-            }
+        if (appWindowToken != null) {
+            mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+            mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
+                    WINDOW_REPLACEMENT_TIMEOUT_DURATION);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5a589e3..e4a6806 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -75,6 +75,7 @@
 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_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -403,6 +404,18 @@
     // used to start an entering animation earlier.
     public boolean mSurfaceSaved = false;
 
+    // This window will be replaced due to relaunch. This allows window manager
+    // to differentiate between simple removal of a window and replacement. In the latter case it
+    // will preserve the old window until the new one is drawn.
+    boolean mWillReplaceWindow = false;
+    // If true, the replaced window was already requested to be removed.
+    boolean mReplacingRemoveRequested = false;
+    // Whether the replacement of the window should trigger app transition animation.
+    boolean mAnimateReplacingWindow = false;
+    // If not null, the window that will be used to replace the old one. This is being set when
+    // the window is added and unset when this window reports its first draw.
+    WindowState mReplacingWindow = null;
+
     /**
      * Wake lock for drawing.
      * Even though it's slightly more expensive to do so, we will use a separate wake lock
@@ -580,8 +593,7 @@
     @Override
     public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
             Rect osf) {
-        if (mAppToken != null && mAppToken.mWillReplaceWindow
-                && (mExiting || !mAppToken.mReplacingRemoveRequested)) {
+        if (mWillReplaceWindow && (mExiting || !mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
             // want to apply any more changes to it, so it remains in this state until new window
@@ -1343,17 +1355,17 @@
     }
 
     void maybeRemoveReplacedWindow() {
-        AppWindowToken token = mAppToken;
-        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this
-                && token.mHasReplacedWindow) {
-            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
-            token.mWillReplaceWindow = false;
-            token.mAnimateReplacingWindow = false;
-            token.mReplacingRemoveRequested = false;
-            token.mReplacingWindow = null;
-            token.mHasReplacedWindow = false;
-            for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
-                final WindowState win = token.allAppWindows.get(i);
+        if (mAppToken == null) {
+            return;
+        }
+        for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState win = mAppToken.allAppWindows.get(i);
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win);
+            if (win.mWillReplaceWindow && win.mReplacingWindow == this) {
+                win.mWillReplaceWindow = false;
+                win.mAnimateReplacingWindow = false;
+                win.mReplacingRemoveRequested = false;
+                win.mReplacingWindow = null;
                 if (win.mExiting) {
                     mService.removeWindowInnerLocked(win);
                 }
@@ -2161,7 +2173,7 @@
             + " " + getWindowTag();
     }
 
-    private CharSequence getWindowTag() {
+    CharSequence getWindowTag() {
         CharSequence tag = mAttrs.getTitle();
         if (tag == null || tag.length() <= 0) {
             tag = mAttrs.packageName;
@@ -2259,4 +2271,12 @@
     boolean isChildWindow() {
         return mAttachedWindow != null;
     }
+
+    void setReplacing(boolean animate) {
+        if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) == 0) {
+            mWillReplaceWindow = true;
+            mReplacingWindow = null;
+            mAnimateReplacingWindow = animate;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index cd82a5f..d4001cd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1192,7 +1192,7 @@
         // different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
         // We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
         // The window that will replace it will abide them.
-        if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace()
+        if (isAnimating() && (w.mWillReplaceWindow || w.inDockedWorkspace()
                 || w.inFreeformWorkspace())) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 6001321..160c97f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1193,7 +1193,7 @@
                     // if app window is removed, or window relayout to invisible. We don't want to
                     // clear it out for windows that get replaced, because the animation depends on
                     // the flag to remove the replaced window.
-                    if (win.mAppToken == null || !win.mAppToken.mWillReplaceWindow) {
+                    if (!win.mWillReplaceWindow) {
                         win.mExiting = false;
                     }
                     if (win.mWinAnimator.mAnimLayer > layer) {