diff options
31 files changed, 2338 insertions, 1762 deletions
diff --git a/api/current.txt b/api/current.txt index e4962d822a4f..8ad1f65dbcac 100644 --- a/api/current.txt +++ b/api/current.txt @@ -29311,6 +29311,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -31581,6 +31582,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -31597,6 +31599,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -31613,6 +31618,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; diff --git a/api/system-current.txt b/api/system-current.txt index 586ea06df874..b83990cbc5cd 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -31321,6 +31321,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -33724,6 +33725,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -33740,6 +33742,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -33756,6 +33761,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; diff --git a/api/test-current.txt b/api/test-current.txt index eccd8b006c82..0cb6bc52461d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -29313,6 +29313,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -31583,6 +31584,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -31599,6 +31601,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -31615,6 +31620,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 72f8c779227a..3e6b595da00e 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -31,9 +31,11 @@ import android.view.ActionMode; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; +import android.view.View.OnFocusChangeListener; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; +import android.view.ViewParent; import android.view.Window; import android.widget.SpinnerAdapter; import java.lang.annotation.Retention; @@ -1071,6 +1073,62 @@ public abstract class ActionBar { } /** + * Attempts to move focus to the ActionBar if it does not already contain the focus. + * + * @return {@code true} if focus changes or {@code false} if focus doesn't change. + * @hide + */ + public boolean requestFocus() { + return false; + } + + /** + * Common implementation for requestFocus that takes in the Toolbar and moves focus + * to the contents. This makes the ViewGroups containing the toolbar allow focus while it stays + * in the ActionBar and then prevents it again once it leaves. + * + * @param viewGroup The toolbar ViewGroup + * @return {@code true} if focus changes or {@code false} if focus doesn't change. + * @hide + */ + protected boolean requestFocus(ViewGroup viewGroup) { + if (viewGroup != null && !viewGroup.hasFocus()) { + final ViewGroup toolbar = viewGroup.getTouchscreenBlocksFocus() ? viewGroup : null; + ViewParent parent = viewGroup.getParent(); + ViewGroup container = null; + while (parent != null && parent instanceof ViewGroup) { + final ViewGroup vgParent = (ViewGroup) parent; + if (vgParent.getTouchscreenBlocksFocus()) { + container = vgParent; + break; + } + parent = vgParent.getParent(); + } + if (container != null) { + container.setTouchscreenBlocksFocus(false); + } + if (toolbar != null) { + toolbar.setTouchscreenBlocksFocus(false); + } + viewGroup.requestFocus(); + final View focused = viewGroup.findFocus(); + if (focused != null) { + focused.setOnFocusChangeListener(new FollowOutOfActionBar(viewGroup, + container, toolbar)); + } else { + if (container != null) { + container.setTouchscreenBlocksFocus(true); + } + if (toolbar != null) { + toolbar.setTouchscreenBlocksFocus(true); + } + } + return true; + } + return false; + } + + /** * Listener interface for ActionBar navigation events. * * @deprecated Action bar navigation modes are deprecated and not supported by inline @@ -1388,4 +1446,43 @@ public abstract class ActionBar { encoder.addProperty("gravity", gravity); } } + + /** + * Tracks the focused View until it leaves the ActionBar, then it resets the + * touchscreenBlocksFocus value. + */ + private static class FollowOutOfActionBar implements OnFocusChangeListener, Runnable { + private final ViewGroup mFocusRoot; + private final ViewGroup mContainer; + private final ViewGroup mToolbar; + + public FollowOutOfActionBar(ViewGroup focusRoot, ViewGroup container, ViewGroup toolbar) { + mContainer = container; + mToolbar = toolbar; + mFocusRoot = focusRoot; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + v.setOnFocusChangeListener(null); + final View focused = mFocusRoot.findFocus(); + if (focused != null) { + focused.setOnFocusChangeListener(this); + } else { + mFocusRoot.post(this); + } + } + } + + @Override + public void run() { + if (mContainer != null) { + mContainer.setTouchscreenBlocksFocus(true); + } + if (mToolbar != null) { + mToolbar.setTouchscreenBlocksFocus(true); + } + } + } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4ed2fd7449b1..61fd952d8f73 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -816,6 +816,7 @@ public class Activity extends ContextThemeWrapper SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK; private boolean mHasCurrentPermissionsRequest; + private boolean mEatKeyUpEvent; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -2827,9 +2828,25 @@ public class Activity extends ContextThemeWrapper // Let action bars open menus in response to the menu key prioritized over // the window handling it - if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { return true; + } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { + // Capture the Alt-up and send focus to the ActionBar + final int action = event.getAction(); + if (action == KeyEvent.ACTION_DOWN) { + if (event.hasModifiers(KeyEvent.META_ALT_ON)) { + final ActionBar actionBar = getActionBar(); + if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) { + mEatKeyUpEvent = true; + return true; + } + } + } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) { + mEatKeyUpEvent = false; + return true; + } } Window win = getWindow(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 681ed5b70688..8637dde109d7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1237,6 +1237,18 @@ public class ActivityManager { public static final int RECENT_IGNORE_HOME_STACK_TASKS = 0x0008; /** + * Ignores all tasks that are on the docked stack. + * @hide + */ + public static final int RECENT_INGORE_DOCKED_STACK_TASKS = 0x0010; + + /** + * Ignores all tasks that are on the pinned stack. + * @hide + */ + public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020; + + /** * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 20f34951e363..c6cc452f5bb7 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -31,6 +31,7 @@ import android.view.ViewGroup; import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.accessibility.AccessibilityEvent; import java.util.ArrayList; @@ -500,6 +501,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { @Override protected void onTransitionsComplete() { moveSharedElementsFromOverlay(); + final ViewGroup decorView = getDecor(); + if (decorView != null) { + decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } } private void sharedElementTransitionStarted() { diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index a01d34a386e4..2ca9ab8a0ac2 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -37,6 +37,7 @@ public class Environment { private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE"; private static final String ENV_DOWNLOAD_CACHE = "DOWNLOAD_CACHE"; private static final String ENV_OEM_ROOT = "OEM_ROOT"; + private static final String ENV_ODM_ROOT = "ODM_ROOT"; private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT"; /** {@hide} */ @@ -56,6 +57,7 @@ public class Environment { private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage"); private static final File DIR_DOWNLOAD_CACHE = getDirectory(ENV_DOWNLOAD_CACHE, "/cache"); private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem"); + private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm"); private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); private static UserEnvironment sCurrentUser; @@ -156,6 +158,16 @@ public class Environment { } /** + * Return root directory of the "odm" partition holding ODM customizations, + * if any. If present, the partition is mounted read-only. + * + * @hide + */ + public static File getOdmDirectory() { + return DIR_ODM_ROOT; + } + + /** * Return root directory of the "vendor" partition that holds vendor-provided * software that should persist across simple reflashing of the "system" partition. * @hide diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 1d4d57278a57..6a5d857c4a37 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -421,6 +421,13 @@ public class CallLog { public static final String ADD_FOR_ALL_USERS = "add_for_all_users"; /** + * The date the row is last inserted, updated, or marked as deleted, in milliseconds + * since the epoch. Read only. + * <P>Type: INTEGER (long)</P> + */ + public static final String LAST_MODIFIED = "last_modified"; + + /** * If a successful call is made that is longer than this duration, update the phone number * in the ContactsProvider with the normalized version of the number, based on the user's * current country code. diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 76eaea9998cd..24683cb1d9fd 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -245,6 +245,13 @@ public class VoicemailContract { public static final String DELETED = "deleted"; /** + * The date the row is last inserted, updated, or marked as deleted, in milliseconds + * since the epoch. Read only. + * <P>Type: INTEGER (long)</P> + */ + public static final String LAST_MODIFIED = "last_modified"; + + /** * A convenience method to build voicemail URI specific to a source package by appending * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. */ @@ -449,6 +456,26 @@ public class VoicemailContract { public static final int NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING = 2; /** + * Amount of resource that is used by existing voicemail in the visual voicemail inbox, + * or {@link #QUOTA_UNAVAILABLE}. Unit is not specified. + * <P>Type: INTEGER</P> + */ + public static final String QUOTA_OCCUPIED = "quota_occupied"; + + /** + * Total resource in the visual voicemail inbox that can be used, or + * {@link #QUOTA_UNAVAILABLE}. Unit is not specified. + * <P>Type: INTEGER</P> + */ + public static final String QUOTA_TOTAL = "quota_total"; + + /** + * Value for {@link #QUOTA_OCCUPIED} and {@link #QUOTA_TOTAL} to indicate that no + * information is available. + */ + public static final int QUOTA_UNAVAILABLE = -1; + + /** * A convenience method to build status URI specific to a source package by appending * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. */ @@ -488,6 +515,39 @@ public class VoicemailContract { } /** + * A helper method to set the quota of a voicemail source. Unit is unspecified. + * + * @param context The context from the package calling the method. This will be the source. + * @param accountHandle The handle for the account the source is associated with. + * @param occupied See {@link Status#QUOTA_OCCUPIED} + * @param total See {@link Status#QUOTA_TOTAL} + */ + public static void setQuota(Context context, PhoneAccountHandle accountHandle, int occupied, + int total) { + if (occupied == QUOTA_UNAVAILABLE && total == QUOTA_UNAVAILABLE) { + return; + } + ContentValues values = new ContentValues(); + values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME, + accountHandle.getComponentName().flattenToString()); + values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId()); + if (occupied != QUOTA_UNAVAILABLE) { + values.put(Status.QUOTA_OCCUPIED,occupied); + } + if (total != QUOTA_UNAVAILABLE) { + values.put(Status.QUOTA_TOTAL,total); + } + + ContentResolver contentResolver = context.getContentResolver(); + Uri statusUri = buildSourceUri(context.getPackageName()); + if (isStatusPresent(contentResolver, statusUri)) { + contentResolver.update(statusUri, values, null, null); + } else { + contentResolver.insert(statusUri, values); + } + } + + /** * Determines if a voicemail source exists in the status table. * * @param contentResolver A content resolver constructed from the appropriate context. diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 9d128035a250..4b6e7e4a03d2 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -29,10 +29,14 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.view.ViewParent; import android.view.Window; import android.view.WindowCallbackWrapper; import android.widget.SpinnerAdapter; import android.widget.Toolbar; + import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPresenter; import com.android.internal.widget.DecorToolbar; @@ -499,6 +503,12 @@ public class ToolbarActionBar extends ActionBar { } } + /** @hide */ + @Override + public boolean requestFocus() { + return requestFocus(mDecorToolbar.getViewGroup()); + } + private class ToolbarCallbackWrapper extends WindowCallbackWrapper { public ToolbarCallbackWrapper(Window.Callback wrapped) { super(wrapped); diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 05cfd81766ab..c6bf1b46becf 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -18,6 +18,8 @@ package com.android.internal.app; import android.animation.ValueAnimator; import android.content.res.TypedArray; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; import android.view.ViewParent; import android.widget.Toolbar; @@ -950,6 +952,12 @@ public class WindowDecorActionBar extends ActionBar implements return false; } + /** @hide */ + @Override + public boolean requestFocus() { + return requestFocus(mDecorToolbar.getViewGroup()); + } + /** * @hide */ diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index bdc8f68cf3b8..ad9559ffdf9f 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -91,8 +91,7 @@ public: MergingOpBatch(batchid_t batchId, BakedOpState* op) : BatchBase(batchId, op, true) - , mClipSideFlags(op->computedState.clipSideFlags) - , mClipRect(op->computedState.clipRect) { + , mClipSideFlags(op->computedState.clipSideFlags) { } /* @@ -194,22 +193,17 @@ public: mBounds.unionWith(op->computedState.clippedBounds); mOps.push_back(op); - const int newClipSideFlags = op->computedState.clipSideFlags; - mClipSideFlags |= newClipSideFlags; - - const Rect& opClip = op->computedState.clipRect; - if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left; - if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top; - if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right; - if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom; + // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat + // check, and doesn't extend past a side of the clip that's in use by the merged batch. + // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. + mClipSideFlags |= op->computedState.clipSideFlags; } int getClipSideFlags() const { return mClipSideFlags; } - const Rect& getClipRect() const { return mClipRect; } + const Rect& getClipRect() const { return mBounds; } private: int mClipSideFlags; - Rect mClipRect; }; OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index 288f8af50081..b28e4361bc4a 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -222,6 +222,46 @@ TEST(OpReorderer, simpleBatching) { << "Expect number of ops = 2 * loop count"; } +TEST(OpReorderer, clippedMerging) { + class ClippedMergingTestRenderer : public TestRendererBase { + public: + void onMergedBitmapOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(4u, opList.count); + EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip); + EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right, + opList.clipSideFlags); + } + }; + auto node = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, TestCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(20, 20); + + // left side clipped (to inset left half) + canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 0, 40, nullptr); + + // top side clipped (to inset top half) + canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 0, nullptr); + + // right side clipped (to inset right half) + canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 80, 40, nullptr); + + // bottom not clipped, just abutting (inset bottom half) + canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 70, nullptr); + }); + + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + createSyncedNodeList(node), sLightCenter); + ClippedMergingTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + TEST(OpReorderer, textMerging) { class TextMergingTestRenderer : public TestRendererBase { public: diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java index cb49535ea55d..ac470671a582 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java @@ -66,10 +66,13 @@ class Mapper { database.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[1]; + final ContentValues[] extraValuesList = new ContentValues[1]; valuesList[0] = new ContentValues(); - MtpDatabase.getDeviceDocumentValues(valuesList[0], device); + extraValuesList[0] = new ContentValues(); + MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device); final boolean changed = putDocuments( valuesList, + extraValuesList, COLUMN_PARENT_DOCUMENT_ID + " IS NULL", EMPTY_ARGS, /* heuristic */ false, @@ -88,7 +91,7 @@ class Mapper { * @param roots List of root information. * @return If roots are added or removed from the database. */ - synchronized boolean putRootDocuments( + synchronized boolean putStorageDocuments( String parentDocumentId, Resources resources, MtpRoot[] roots) { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); @@ -109,36 +112,21 @@ class Mapper { throw new Error("Unexpected map mode."); } final ContentValues[] valuesList = new ContentValues[roots.length]; + final ContentValues[] extraValuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { valuesList[i] = new ContentValues(); + extraValuesList[i] = new ContentValues(); MtpDatabase.getStorageDocumentValues( - valuesList[i], resources, parentDocumentId, roots[i]); + valuesList[i], extraValuesList[i], resources, parentDocumentId, roots[i]); } final boolean changed = putDocuments( valuesList, + extraValuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentDocumentId), heuristic, mapColumn); - final ContentValues values = new ContentValues(); - int i = 0; - for (final MtpRoot root : roots) { - // Use the same value for the root ID and the corresponding document ID. - final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID); - // If it fails to insert/update documents, the document ID will be set with -1. - // In this case we don't insert/update root extra information neither. - if (documentId == null) { - continue; - } - values.put(Root.COLUMN_ROOT_ID, documentId); - values.put( - Root.COLUMN_FLAGS, - Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); - values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); - values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); - values.put(Root.COLUMN_MIME_TYPES, ""); - database.replace(TABLE_ROOT_EXTRA, null, values); - } + database.setTransactionSuccessful(); return changed; } finally { @@ -176,6 +164,7 @@ class Mapper { } putDocuments( valuesList, + null, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentId), heuristic, @@ -257,6 +246,7 @@ class Mapper { * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values for documents to be stored in the database. + * @param rootExtraValuesList Values for root extra to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param args Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. @@ -264,6 +254,7 @@ class Mapper { */ private boolean putDocuments( ContentValues[] valuesList, + @Nullable ContentValues[] rootExtraValuesList, String selection, String[] args, boolean heuristic, @@ -272,7 +263,14 @@ class Mapper { boolean added = false; database.beginTransaction(); try { - for (final ContentValues values : valuesList) { + for (int i = 0; i < valuesList.length; i++) { + final ContentValues values = valuesList[i]; + final ContentValues rootExtraValues; + if (rootExtraValuesList != null) { + rootExtraValues = rootExtraValuesList[i]; + } else { + rootExtraValues = null; + } final Cursor candidateCursor = database.query( TABLE_DOCUMENTS, strings(Document.COLUMN_DOCUMENT_ID), @@ -290,25 +288,26 @@ class Mapper { final long rowId; if (candidateCursor.getCount() == 0) { rowId = database.insert(TABLE_DOCUMENTS, null, values); - if (rowId == -1) { - throw new SQLiteException("Failed to put a document into database."); - } added = true; } else if (!heuristic) { candidateCursor.moveToNext(); - final String documentId = candidateCursor.getString(0); - rowId = database.update( + rowId = candidateCursor.getLong(0); + database.update( TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, - strings(documentId)); + strings(rowId)); } else { values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING); - rowId = database.insert(TABLE_DOCUMENTS, null, values); + rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values); } // Document ID is a primary integer key of the table. So the returned row // IDs should be same with the document ID. values.put(Document.COLUMN_DOCUMENT_ID, rowId); + if (rootExtraValues != null) { + rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId); + database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues); + } } finally { candidateCursor.close(); } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 10941eb02a0a..112914e93830 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -23,6 +23,9 @@ import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; @@ -106,17 +109,93 @@ class MtpDatabase { * @return Database cursor. */ Cursor queryRoots(String[] columnNames) { - final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); - builder.setTables(JOIN_ROOTS); - builder.setProjectionMap(COLUMN_MAP_ROOTS); - return builder.query( - mDatabase, - columnNames, - COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?", - strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_STORAGE), + final String selection = + COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?"; + final Cursor deviceCursor = mDatabase.query( + TABLE_DOCUMENTS, + strings(COLUMN_DEVICE_ID), + selection, + strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_DEVICE), + COLUMN_DEVICE_ID, null, null, null); + + try { + final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(JOIN_ROOTS); + builder.setProjectionMap(COLUMN_MAP_ROOTS); + final MatrixCursor result = new MatrixCursor(columnNames); + final ContentValues values = new ContentValues(); + + while (deviceCursor.moveToNext()) { + final int deviceId = deviceCursor.getInt(0); + final Cursor storageCursor = builder.query( + mDatabase, + columnNames, + selection + " AND " + COLUMN_DEVICE_ID + " = ?", + strings(ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + DOCUMENT_TYPE_STORAGE, + deviceId), + null, + null, + null); + try { + values.clear(); + if (storageCursor.getCount() == 1) { + storageCursor.moveToNext(); + DatabaseUtils.cursorRowToContentValues(storageCursor, values); + } else { + final Cursor cursor = builder.query( + mDatabase, + columnNames, + selection + " AND " + COLUMN_DEVICE_ID + " = ?", + strings(ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + DOCUMENT_TYPE_DEVICE, + deviceId), + null, + null, + null); + try { + cursor.moveToNext(); + DatabaseUtils.cursorRowToContentValues(cursor, values); + } finally { + cursor.close(); + } + + long capacityBytes = 0; + long availableBytes = 0; + int capacityIndex = cursor.getColumnIndex(Root.COLUMN_CAPACITY_BYTES); + int availableIndex = cursor.getColumnIndex(Root.COLUMN_AVAILABLE_BYTES); + while (storageCursor.moveToNext()) { + // If requested columnNames does not include COLUMN_XXX_BYTES, we don't + // calculate corresponding values. + if (capacityIndex != -1) { + capacityBytes += cursor.getLong(capacityIndex); + } + if (availableIndex != -1) { + availableBytes += cursor.getLong(availableIndex); + } + } + values.put(Root.COLUMN_CAPACITY_BYTES, capacityBytes); + values.put(Root.COLUMN_AVAILABLE_BYTES, availableBytes); + } + } finally { + storageCursor.close(); + } + + final RowBuilder row = result.newRow(); + for (final String key : values.keySet()) { + row.add(key, values.get(key)); + } + } + + return result; + } finally { + deviceCursor.close(); + } } /** @@ -380,7 +459,10 @@ class MtpDatabase { context.deleteDatabase(DATABASE_NAME); } - static void getDeviceDocumentValues(ContentValues values, MtpDeviceRecord device) { + static void getDeviceDocumentValues( + ContentValues values, + ContentValues extraValues, + MtpDeviceRecord device) { values.clear(); values.put(COLUMN_DEVICE_ID, device.deviceId); values.putNull(COLUMN_STORAGE_ID); @@ -394,11 +476,15 @@ class MtpDatabase { values.putNull(Document.COLUMN_LAST_MODIFIED); values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp); values.put(Document.COLUMN_FLAGS, 0); - long size = 0; - for (final MtpRoot root : device.roots) { - size += root.mMaxCapacity - root.mFreeSpace; - } - values.put(Document.COLUMN_SIZE, size); + values.putNull(Document.COLUMN_SIZE); + + extraValues.clear(); + extraValues.put( + Root.COLUMN_FLAGS, + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.putNull(Root.COLUMN_AVAILABLE_BYTES); + extraValues.putNull(Root.COLUMN_CAPACITY_BYTES); + extraValues.put(Root.COLUMN_MIME_TYPES, ""); } /** @@ -408,7 +494,11 @@ class MtpDatabase { * @param root Root to be converted {@link ContentValues}. */ static void getStorageDocumentValues( - ContentValues values, Resources resources, String parentDocumentId, MtpRoot root) { + ContentValues values, + ContentValues extraValues, + Resources resources, + String parentDocumentId, + MtpRoot root) { values.clear(); values.put(COLUMN_DEVICE_ID, root.mDeviceId); values.put(COLUMN_STORAGE_ID, root.mStorageId); @@ -424,6 +514,13 @@ class MtpDatabase { values.put(Document.COLUMN_FLAGS, 0); values.put(Document.COLUMN_SIZE, (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE)); + + extraValues.put( + Root.COLUMN_FLAGS, + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); + extraValues.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); + extraValues.put(Root.COLUMN_MIME_TYPES, ""); } /** diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java index a23358935987..f252e0f7d337 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java @@ -126,14 +126,14 @@ class MtpDatabaseConstants { Document.COLUMN_LAST_MODIFIED + " INTEGER," + Document.COLUMN_ICON + " INTEGER," + Document.COLUMN_FLAGS + " INTEGER NOT NULL," + - Document.COLUMN_SIZE + " INTEGER NOT NULL);"; + Document.COLUMN_SIZE + " INTEGER);"; static final String QUERY_CREATE_ROOT_EXTRA = "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" + Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," + Root.COLUMN_FLAGS + " INTEGER NOT NULL," + - Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," + - Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," + + Root.COLUMN_AVAILABLE_BYTES + " INTEGER," + + Root.COLUMN_CAPACITY_BYTES + " INTEGER," + Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);"; /** @@ -145,18 +145,26 @@ class MtpDatabaseConstants { COLUMN_MAP_ROOTS = new HashMap<>(); COLUMN_MAP_ROOTS.put(Root.COLUMN_ROOT_ID, TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID); COLUMN_MAP_ROOTS.put(Root.COLUMN_FLAGS, TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS); - COLUMN_MAP_ROOTS.put(Root.COLUMN_ICON, TABLE_DOCUMENTS + "." + Document.COLUMN_ICON); COLUMN_MAP_ROOTS.put( - Root.COLUMN_TITLE, TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME); - COLUMN_MAP_ROOTS.put(Root.COLUMN_SUMMARY, TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY); + Root.COLUMN_ICON, + TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " + Root.COLUMN_ICON); COLUMN_MAP_ROOTS.put( - Root.COLUMN_DOCUMENT_ID, TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID); + Root.COLUMN_TITLE, + TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " + Root.COLUMN_TITLE); + COLUMN_MAP_ROOTS.put( + Root.COLUMN_SUMMARY, + TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " + Root.COLUMN_SUMMARY); + COLUMN_MAP_ROOTS.put( + Root.COLUMN_DOCUMENT_ID, + TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + + " AS " + Root.COLUMN_DOCUMENT_ID); COLUMN_MAP_ROOTS.put( Root.COLUMN_AVAILABLE_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES); COLUMN_MAP_ROOTS.put( Root.COLUMN_CAPACITY_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES); COLUMN_MAP_ROOTS.put( Root.COLUMN_MIME_TYPES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES); + COLUMN_MAP_ROOTS.put(COLUMN_DEVICE_ID, COLUMN_DEVICE_ID); } private static String createJoinFromClosure( diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index e6c2726f531a..619ef54ed90e 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -127,7 +127,7 @@ final class RootScanner { continue; } mDatabase.getMapper().startAddingDocuments(documentId); - if (mDatabase.getMapper().putRootDocuments( + if (mDatabase.getMapper().putStorageDocuments( documentId, mResources, device.roots)) { changed = true; } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java index 7bd9a1790739..f37a55c9568d 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java @@ -41,7 +41,7 @@ public class DocumentLoaderTest extends AndroidTestCase { public void setUp() { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", new TestResources(), new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", new TestResources(), new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index b74517574c2e..1e1ea0a748a8 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -74,20 +74,24 @@ public class MtpDatabaseTest extends AndroidTestCase { return cursor.getString(cursor.getColumnIndex(columnName)); } - public void testPutRootDocuments() throws Exception { - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { - new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""), - new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""), - new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"") + public void testPutSingleStorageDocuments() throws Exception { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "") }); + mDatabase.getMapper().stopAddingDocuments("1"); { final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES); - assertEquals(3, cursor.getCount()); + assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID)); assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID)); assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE)); @@ -101,14 +105,6 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals( MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); - - cursor.moveToNext(); - assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); - assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME)); - cursor.close(); } @@ -123,37 +119,58 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_CAPACITY_BYTES }); + assertEquals(1, cursor.getCount()); + + cursor.moveToNext(); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, + getInt(cursor, Root.COLUMN_FLAGS)); + assertEquals(R.drawable.ic_root_mtp, getInt(cursor, Root.COLUMN_ICON)); + assertEquals("Device Storage", getString(cursor, Root.COLUMN_TITLE)); + assertTrue(isNull(cursor, Root.COLUMN_SUMMARY)); + assertEquals(2, getInt(cursor, Root.COLUMN_DOCUMENT_ID)); + assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); + assertEquals(2000, getInt(cursor, Root.COLUMN_CAPACITY_BYTES)); + + cursor.close(); + } + } + + public void testPutStorageDocuments() throws Exception { + mDatabase.getMapper().startAddingDocuments("deviceDocId"); + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { + new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""), + new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""), + new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"") + }); + + { + final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES); assertEquals(3, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(1, cursor.getInt(5)); - assertEquals(1000, cursor.getInt(6)); - assertEquals(2000, cursor.getInt(7)); + assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID)); + assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID)); + assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE)); + assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE)); + assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertTrue(isNull(cursor, COLUMN_SUMMARY)); + assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED)); + assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON)); + assertEquals(0, getInt(cursor, COLUMN_FLAGS)); + assertEquals(1000, getInt(cursor, COLUMN_SIZE)); + assertEquals( + MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE)); cursor.moveToNext(); - assertEquals(2, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(2, cursor.getInt(5)); - assertEquals(2000, cursor.getInt(6)); - assertEquals(4000, cursor.getInt(7)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(3, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device /@#%&<>Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(3, cursor.getInt(5)); - assertEquals(3000, cursor.getInt(6)); - assertEquals(6000, cursor.getInt(7)); + assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } @@ -245,13 +262,9 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabaseConstants.COLUMN_STORAGE_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - final String[] rootColumns = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_AVAILABLE_BYTES - }; mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""), new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "") }); @@ -270,18 +283,6 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().clearMapping(); { @@ -298,20 +299,8 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""), new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "") }); @@ -334,21 +323,6 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(3, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); { @@ -364,18 +338,6 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } - - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } } public void testRestoreIdForChildDocuments() throws Exception { @@ -461,26 +423,33 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_ROOT_ID, Root.COLUMN_AVAILABLE_BYTES }; - mDatabase.getMapper().startAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().startAddingDocuments("deviceDocIdB"); - mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] { - new MtpRoot(0, 100, "Device", "Storage", 0, 0, "") + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0])); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().startAddingDocuments("2"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device A", "Storage", 0, 0, "") }); - mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] { - new MtpRoot(1, 100, "Device", "Storage", 0, 0, "") + mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] { + new MtpRoot(1, 100, "Device B", "Storage", 0, 0, "") }); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertEquals("Device A Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertEquals("Device B Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } @@ -488,36 +457,36 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().startAddingDocuments("deviceDocIdB"); - mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().startAddingDocuments("2"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "") }); - mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] { new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "") }); - mDatabase.getMapper().stopAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().stopAddingDocuments("deviceDocIdB"); + mDatabase.getMapper().stopAddingDocuments("1"); + mDatabase.getMapper().stopAddingDocuments("2"); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -527,10 +496,10 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(5, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(6, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -591,29 +560,34 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES }; - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", false, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), }); mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -622,7 +596,7 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -634,19 +608,15 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabaseConstants.COLUMN_STORAGE_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - final String[] rootColumns = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_AVAILABLE_BYTES - }; mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""), }); @@ -665,33 +635,27 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } } public void testReplaceExistingRoots() { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + // The client code should be able to replace existing rows with new information. // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); // Replace it. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); { final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, @@ -701,7 +665,7 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -709,12 +673,14 @@ public class MtpDatabaseTest extends AndroidTestCase { { final String[] columns = new String[] { Root.COLUMN_ROOT_ID, + Root.COLUMN_TITLE, Root.COLUMN_AVAILABLE_BYTES }; final Cursor cursor = mDatabase.queryRoots(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals("Device Storage B", getString(cursor, Root.COLUMN_TITLE)); assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -723,8 +689,13 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testFailToReplaceExisitingUnmappedRoots() { // The client code should not be able to replace rows before resolving 'unmapped' rows. // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); @@ -732,18 +703,19 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals(1, oldCursor.getCount()); // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); // Because the roots shares the same name, the roots should have new IDs. - final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); + final Cursor newCursor = mDatabase.queryChildDocuments( + strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(2, newCursor.getCount()); oldCursor.moveToNext(); newCursor.moveToNext(); @@ -755,9 +727,9 @@ public class MtpDatabaseTest extends AndroidTestCase { newCursor.close(); } - public void testQueryDocument() { + public void testQueryDocuments() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -765,13 +737,61 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME)); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals("Device Storage A", cursor.getString(0)); + assertEquals("Device Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME)); cursor.close(); } + public void testQueryRoots() { + // Add device document. + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", false, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + // It the device does not have storages, it shows a device root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device", cursor.getString(0)); + cursor.close(); + } + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device", "Storage A", 0, 0, "") + }); + mDatabase.getMapper().stopAddingDocuments("1"); + + // It the device has single storage, it shows a storage root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device Storage A", cursor.getString(0)); + cursor.close(); + } + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), + new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "") + }); + mDatabase.getMapper().stopAddingDocuments("1"); + + // It the device has multiple storages, it shows a device root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device", cursor.getString(0)); + cursor.close(); + } + } + public void testGetParentId() throws FileNotFoundException { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -790,7 +810,7 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testDeleteDocument() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -834,7 +854,7 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testPutNewDocument() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 884b1e2ccb1d..71c48971ca32 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -147,7 +147,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider.openDevice(0); mResolver.waitForNotification(ROOTS_URI, 1); final Cursor cursor = mProvider.queryRoots(null); - assertEquals(1, cursor.getCount()); + assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); @@ -176,7 +176,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryRoots_error() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mMtpManager.addValidDevice( - new MtpDeviceRecord(0, "Device", false /* unopened */, new MtpRoot[0])); + new MtpDeviceRecord(0, "Device A", false /* unopened */, new MtpRoot[0])); mMtpManager.addValidDevice(new MtpDeviceRecord( 1, "Device", @@ -197,7 +197,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mResolver.waitForNotification(ROOTS_URI, 1); final Cursor cursor = mProvider.queryRoots(null); - assertEquals(1, cursor.getCount()); + assertEquals(2, cursor.getCount()); + + cursor.moveToNext(); + assertEquals("1", cursor.getString(0)); + assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); + assertEquals("Device A", cursor.getString(3)); + assertEquals("1", cursor.getString(4)); + assertEquals(0, cursor.getInt(5)); + cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index d4e428e280b6..7338a9cb654b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -116,6 +116,12 @@ class DatabaseHelper extends SQLiteOpenHelper { // cleaned up automatically when the user is deleted. File databaseFile = new File( Environment.getUserSystemDirectory(userHandle), DATABASE_NAME); + // If databaseFile doesn't exist, database can be kept in memory. It's safe because the + // database will be migrated and disposed of immediately after onCreate finishes + if (!databaseFile.exists()) { + Log.i(TAG, "No previous database file exists - running in in-memory mode"); + return null; + } return databaseFile.getPath(); } } @@ -130,8 +136,16 @@ class DatabaseHelper extends SQLiteOpenHelper { return mValidTables.contains(name); } + private boolean isInMemory() { + return getDatabaseName() == null; + } + public void dropDatabase() { close(); + // No need to remove files if db is in memory + if (isInMemory()) { + return; + } File databaseFile = mContext.getDatabasePath(getDatabaseName()); if (databaseFile.exists()) { databaseFile.delete(); @@ -145,6 +159,10 @@ class DatabaseHelper extends SQLiteOpenHelper { public void backupDatabase() { close(); + // No need to backup files if db is in memory + if (isInMemory()) { + return; + } File databaseFile = mContext.getDatabasePath(getDatabaseName()); if (!databaseFile.exists()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 6a9268a803d6..35e53f69a151 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -194,6 +194,8 @@ public class SystemServicesProxy { int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery, ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS | + ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS | + ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES | ActivityManager.RECENT_WITH_EXCLUDED, userId); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index d06012e90819..6f003ab63282 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -352,28 +352,28 @@ public class TaskStack { @Override public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { if (t.isAffiliatedTask()) { - // If this task is affiliated with another parent in the stack, then the historical state of this - // task depends on the state of the parent task + // If this task is affiliated with another parent in the stack, then the + // historical state of this task depends on the state of the parent task Task parentTask = taskIdMap.get(t.affiliationTaskId); if (parentTask != null) { t = parentTask; } } - return !t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); + return !t.isHistorical; } }); mHistoryTaskList.setFilter(new TaskFilter() { @Override public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { if (t.isAffiliatedTask()) { - // If this task is affiliated with another parent in the stack, then the historical state of this - // task depends on the state of the parent task + // If this task is affiliated with another parent in the stack, then the + // historical state of this task depends on the state of the parent task Task parentTask = taskIdMap.get(t.affiliationTaskId); if (parentTask != null) { t = parentTask; } } - return t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); + return t.isHistorical; } }); } diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 86183af06f7c..4dc46ac63643 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -48,6 +48,13 @@ public class SystemConfig { static SystemConfig sInstance; + // permission flag, determines which types of configuration are allowed to be read + private static final int ALLOW_FEATURES = 0x01; + private static final int ALLOW_LIBS = 0x02; + private static final int ALLOW_PERMISSIONS = 0x04; + private static final int ALLOW_APP_CONFIGS = 0x08; + private static final int ALLOW_ALL = ~0; + // Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids; @@ -161,21 +168,27 @@ public class SystemConfig { SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( - Environment.getRootDirectory(), "etc", "sysconfig"), false); + Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( - Environment.getRootDirectory(), "etc", "permissions"), false); - // Only read features from OEM config + Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); + // Allow ODM to customize system configs around libs, features and apps + int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS; + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag); + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); + // Only allow OEM to customize features readPermissions(Environment.buildPath( - Environment.getOemDirectory(), "etc", "sysconfig"), true); + Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES); readPermissions(Environment.buildPath( - Environment.getOemDirectory(), "etc", "permissions"), true); + Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES); } - void readPermissions(File libraryDir, boolean onlyFeatures) { + void readPermissions(File libraryDir, int permissionFlag) { // Read permissions from given directory. if (!libraryDir.exists() || !libraryDir.isDirectory()) { - if (!onlyFeatures) { + if (permissionFlag == ALLOW_ALL) { Slog.w(TAG, "No directory " + libraryDir + ", skipping"); } return; @@ -203,16 +216,16 @@ public class SystemConfig { continue; } - readPermissionsFromXml(f, onlyFeatures); + readPermissionsFromXml(f, permissionFlag); } // Read platform permissions last so it will take precedence if (platformFile != null) { - readPermissionsFromXml(platformFile, onlyFeatures); + readPermissionsFromXml(platformFile, permissionFlag); } } - private void readPermissionsFromXml(File permFile, boolean onlyFeatures) { + private void readPermissionsFromXml(File permFile, int permissionFlag) { FileReader permReader = null; try { permReader = new FileReader(permFile); @@ -242,6 +255,11 @@ public class SystemConfig { + ": found " + parser.getName() + ", expected 'permissions' or 'config'"); } + boolean allowAll = permissionFlag == ALLOW_ALL; + boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0; + boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0; + boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0; + boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -249,7 +267,7 @@ public class SystemConfig { } String name = parser.getName(); - if ("group".equals(name) && !onlyFeatures) { + if ("group".equals(name) && allowAll) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = android.os.Process.getGidForName(gidStr); @@ -261,7 +279,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("permission".equals(name) && !onlyFeatures) { + } else if ("permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); if (perm == null) { Slog.w(TAG, "<permission> without name in " + permFile + " at " @@ -272,7 +290,7 @@ public class SystemConfig { perm = perm.intern(); readPermission(parser, perm); - } else if ("assign-permission".equals(name) && !onlyFeatures) { + } else if ("assign-permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); if (perm == null) { Slog.w(TAG, "<assign-permission> without name in " + permFile + " at " @@ -304,7 +322,7 @@ public class SystemConfig { perms.add(perm); XmlUtils.skipCurrentTag(parser); - } else if ("library".equals(name) && !onlyFeatures) { + } else if ("library".equals(name) && allowLibs) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); if (lname == null) { @@ -320,7 +338,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("feature".equals(name)) { + } else if ("feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); boolean allowed; if (!lowRam) { @@ -341,7 +359,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("unavailable-feature".equals(name)) { + } else if ("unavailable-feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); if (fname == null) { Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at " @@ -352,7 +370,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) { + } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<allow-in-power-save-except-idle> without package in " @@ -363,7 +381,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("allow-in-power-save".equals(name) && !onlyFeatures) { + } else if ("allow-in-power-save".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at " @@ -374,7 +392,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("fixed-ime-app".equals(name) && !onlyFeatures) { + } else if ("fixed-ime-app".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at " @@ -385,7 +403,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("app-link".equals(name)) { + } else if ("app-link".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<app-link> without package in " + permFile + " at " @@ -394,7 +412,7 @@ public class SystemConfig { mLinkedApps.add(pkgname); } XmlUtils.skipCurrentTag(parser); - } else if ("system-user-whitelisted-app".equals(name)) { + } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile @@ -403,7 +421,7 @@ public class SystemConfig { mSystemUserWhitelistedApps.add(pkgname); } XmlUtils.skipCurrentTag(parser); - } else if ("system-user-blacklisted-app".equals(name)) { + } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1d555c6491ab..3e7b963ddd98 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -323,6 +323,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILIT import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.FORCE_NEW_TASK_FLAGS; import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; @@ -510,6 +511,8 @@ public final class ActivityManagerService extends ActivityManagerNative /** Run all ActivityStacks through this */ ActivityStackSupervisor mStackSupervisor; + ActivityStarter mActivityStarter; + /** Task stack change listeners. */ private RemoteCallbackList<ITaskStackListener> mTaskStackListeners = new RemoteCallbackList<ITaskStackListener>(); @@ -1786,7 +1789,7 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case DO_PENDING_ACTIVITY_LAUNCHES_MSG: { synchronized (ActivityManagerService.this) { - mStackSupervisor.doPendingActivityLaunchesLocked(true); + mActivityStarter.doPendingActivityLaunchesLocked(true); } } break; case KILL_APPLICATION_MSG: { @@ -2290,6 +2293,7 @@ public final class ActivityManagerService extends ActivityManagerNative public void setWindowManager(WindowManagerService wm) { mWindowManager = wm; mStackSupervisor.setWindowManager(wm); + mActivityStarter.setWindowManager(wm); } public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) { @@ -2477,6 +2481,7 @@ public final class ActivityManagerService extends ActivityManagerNative mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mRecentTasks = new RecentTasks(this); mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks); + mActivityStarter = new ActivityStarter(this, mStackSupervisor); mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks); mProcessCpuThread = new Thread("CpuTracker") { @@ -3601,7 +3606,7 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.uid, true); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - mStackSupervisor.startHomeActivity(intent, aInfo, reason); + mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); } } @@ -3679,7 +3684,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - mStackSupervisor.startActivityLocked(null, intent, null /*ephemeralIntent*/, + mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/, null, ri.activityInfo, null /*rInfo*/, null, null, null, null, 0, 0, 0, null, 0, 0, 0, null, false, false, null, null, null); } @@ -4006,6 +4011,25 @@ public final class ActivityManagerService extends ActivityManagerNative UserHandle.getCallingUserId()); } + final int startActivity(Intent intent, ActivityStackSupervisor.ActivityContainer container) { + enforceNotIsolatedCaller("ActivityContainer.startActivity"); + final int userId = mUserController.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), mStackSupervisor.mCurrentUser, false, + ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); + + // TODO: Switch to user app stacks here. + String mimeType = intent.getType(); + final Uri data = intent.getData(); + if (mimeType == null && data != null && "content".equals(data.getScheme())) { + mimeType = getProviderMimeType(data, userId); + } + container.checkEmbeddedAllowedInner(userId, intent, mimeType); + + intent.addFlags(FORCE_NEW_TASK_FLAGS); + return mActivityStarter.startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, + null, 0, 0, null, null, null, null, false, userId, container, null); + } + @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, @@ -4014,7 +4038,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. - return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, + return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, bOptions, false, userId, null, null); } @@ -4077,7 +4101,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. try { - int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent, + int ret = mActivityStarter.startActivityMayWait(null, targetUid, targetPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, bOptions, ignoreTargetSecurity, userId, null, null); return ret; @@ -4106,7 +4130,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null); WaitResult res = new WaitResult(); // TODO: Switch to user app stacks here. - mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, + mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null, bOptions, false, userId, null, null); return res; @@ -4120,7 +4144,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, + int ret = mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, config, bOptions, false, userId, null, null); return ret; @@ -4178,7 +4202,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false, ALLOW_FULL_ONLY, "startVoiceActivity", null); // TODO: Switch to user app stacks here. - return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent, + return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent, resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null, null, bOptions, false, userId, null, null); } @@ -4289,7 +4313,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); - int res = mStackSupervisor.startActivityLocked(r.app.thread, intent, + int res = mActivityStarter.startActivityLocked(r.app.thread, intent, null /*ephemeralIntent*/, r.resolvedType, aInfo, null /*rInfo*/, null, null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, -1, r.launchedFromUid, 0, options, @@ -4374,7 +4398,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, + int ret = mActivityStarter.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, bOptions, false, userId, container, inTask); return ret; @@ -4388,7 +4412,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents, + int ret = mActivityStarter.startActivities(caller, -1, callingPackage, intents, resolvedTypes, resultTo, bOptions, userId); return ret; } @@ -4400,7 +4424,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes, + int ret = mActivityStarter.startActivities(null, uid, callingPackage, intents, resolvedTypes, resultTo, bOptions, userId); return ret; } @@ -8768,6 +8792,20 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } } + if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS) != 0) { + if (tr.stack != null && tr.stack.isDockedStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, docked stack task: " + tr); + continue; + } + } + if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) { + if (tr.stack != null && tr.stack.isPinnedStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, pinned stack task: " + tr); + continue; + } + } if (tr.autoRemoveRecents && tr.getTopActivity() == null) { // Don't include auto remove tasks that are finished or finishing. if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, @@ -20823,7 +20861,7 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("Bad app thread " + appThread); } } - return mStackSupervisor.startActivityMayWait(appThread, -1, callingPackage, intent, + return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent, resolvedType, null, null, null, null, 0, 0, null, null, null, bOptions, false, callingUser, null, tr); } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 59acccd33937..1492e23e37e4 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -75,7 +75,6 @@ import android.os.Trace; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.view.Display; @@ -520,6 +519,14 @@ final class ActivityStack { return mStackId == HOME_STACK_ID; } + final boolean isDockedStack() { + return mStackId == DOCKED_STACK_ID; + } + + final boolean isPinnedStack() { + return mStackId == PINNED_STACK_ID; + } + final boolean isOnHomeDisplay() { return isAttached() && mActivityContainer.mActivityDisplay.mDisplayId == Display.DEFAULT_DISPLAY; @@ -3355,7 +3362,7 @@ final class ActivityStack { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( destIntent.getComponent(), 0, srec.userId); - int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent, + int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null, diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 18bf1c36c52e..32671acfcc30 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -31,51 +31,37 @@ import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; -import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ActivityManagerService.ANIMATE; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; @@ -107,9 +93,6 @@ import android.app.IActivityContainer; import android.app.IActivityContainerCallback; import android.app.IActivityManager; import android.app.IActivityManager.WaitResult; -import android.app.IApplicationThread; -import android.app.KeyguardManager; -import android.app.PendingIntent; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.app.StatusBarManager; @@ -118,7 +101,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; -import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -133,10 +115,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.VirtualDisplay; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; -import android.net.Uri; import android.os.Binder; -import android.os.Build; -import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; @@ -168,8 +147,6 @@ import android.view.DisplayInfo; import android.view.InputEvent; import android.view.Surface; -import com.android.internal.app.HeavyWeightSwitcherActivity; -import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IStatusBarService; @@ -189,21 +166,17 @@ import java.util.Set; public final class ActivityStackSupervisor implements DisplayListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; - private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; - private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_IDLE = TAG + POSTFIX_IDLE; private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; - private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; private static final String TAG_STACK = TAG + POSTFIX_STACK; private static final String TAG_STATES = TAG + POSTFIX_STATES; private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; - private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + static final String TAG_TASKS = TAG + POSTFIX_TASKS; private static final String TAG_VISIBLE_BEHIND = TAG + POSTFIX_VISIBLE_BEHIND; - private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; /** How long we wait until giving up on the last activity telling us it is idle. */ static final int IDLE_TIMEOUT = 10 * 1000; @@ -235,7 +208,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Used to indicate if an object (e.g. stack) that we are trying to get // should be created if it doesn't exist already. - private static final boolean CREATE_IF_NEEDED = true; + static final boolean CREATE_IF_NEEDED = true; // Used to indicate that windows of activities should be preserved during the resize. static final boolean PRESERVE_WINDOWS = true; @@ -308,14 +281,14 @@ public final class ActivityStackSupervisor implements DisplayListener { private int mCurTaskId = 0; /** The current user */ - private int mCurrentUser; + int mCurrentUser; /** The stack containing the launcher app. Assumed to always be attached to * Display.DEFAULT_DISPLAY. */ - private ActivityStack mHomeStack; + ActivityStack mHomeStack; /** The stack currently receiving input or launching the next activity. */ - private ActivityStack mFocusedStack; + ActivityStack mFocusedStack; /** If this is the same as mFocusedStack then the activity on the top of the focused stack has * been resumed. If stacks are changing position this will hold the old stack until the new @@ -397,8 +370,6 @@ public final class ActivityStackSupervisor implements DisplayListener { */ private LockTaskNotify mLockTaskNotify; - final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); - /** Used to keep resumeTopActivityLocked() from being entered recursively */ boolean inResumeTopActivity; @@ -1017,270 +988,6 @@ public final class ActivityStackSupervisor implements DisplayListener { return resolveActivity(intent, rInfo, startFlags, profilerInfo); } - void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) { - moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason); - startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/, - null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/, - null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/, - 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/, - 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/, - false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/, - null /*container*/, null /*inTask*/); - if (inResumeTopActivity) { - // If we are in resume section already, home activity will be initialized, but not - // resumed (to avoid recursive resume) and will stay that way until something pokes it - // again. We need to schedule another resume. - scheduleResumeTopActivities(); - } - } - - final int startActivityMayWait(IApplicationThread caller, int callingUid, - String callingPackage, Intent intent, String resolvedType, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - IBinder resultTo, String resultWho, int requestCode, int startFlags, - ProfilerInfo profilerInfo, WaitResult outResult, Configuration config, - Bundle bOptions, boolean ignoreTargetSecurity, int userId, - IActivityContainer iContainer, TaskRecord inTask) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - boolean componentSpecified = intent.getComponent() != null; - - // Save a copy in case ephemeral needs it - final Intent ephemeralIntent = new Intent(intent); - // Don't modify the client's object! - intent = new Intent(intent); - - ResolveInfo rInfo = resolveIntent(intent, resolvedType, userId); - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, rInfo, startFlags, profilerInfo); - - ActivityOptions options = ActivityOptions.fromBundle(bOptions); - ActivityContainer container = (ActivityContainer)iContainer; - synchronized (mService) { - if (container != null && container.mParentActivity != null && - container.mParentActivity.state != RESUMED) { - // Cannot start a child activity if the parent is not resumed. - return ActivityManager.START_CANCELED; - } - final int realCallingPid = Binder.getCallingPid(); - final int realCallingUid = Binder.getCallingUid(); - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = realCallingPid; - callingUid = realCallingUid; - } else { - callingPid = callingUid = -1; - } - - final ActivityStack stack; - if (container == null || container.mStack.isOnHomeDisplay()) { - stack = mFocusedStack; - } else { - stack = container.mStack; - } - stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, - "Starting activity when config will change = " + stack.mConfigWillChange); - - final long origId = Binder.clearCallingIdentity(); - - if (aInfo != null && - (aInfo.applicationInfo.privateFlags - &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { - // This may be a heavy-weight process! Check to see if we already - // have another, different heavy-weight process running. - if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { - if (mService.mHeavyWeightProcess != null && - (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || - !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { - int appCallingUid = callingUid; - if (caller != null) { - ProcessRecord callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - appCallingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - ActivityOptions.abort(options); - return ActivityManager.START_PERMISSION_DENIED; - } - } - - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, "android", - appCallingUid, userId, null, null, 0, new Intent[] { intent }, - new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT, null); - - Intent newIntent = new Intent(); - if (requestCode >= 0) { - // Caller is requesting a result. - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, - new IntentSender(target)); - if (mService.mHeavyWeightProcess.activities.size() > 0) { - ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, - hist.packageName); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, - hist.task.taskId); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, - aInfo.packageName); - newIntent.setFlags(intent.getFlags()); - newIntent.setClassName("android", - HeavyWeightSwitcherActivity.class.getName()); - intent = newIntent; - resolvedType = null; - caller = null; - callingUid = Binder.getCallingUid(); - callingPid = Binder.getCallingPid(); - componentSpecified = true; - rInfo = resolveIntent(intent, null /*resolvedType*/, userId); - aInfo = rInfo != null ? rInfo.activityInfo : null; - if (aInfo != null) { - aInfo = mService.getActivityInfoForUser(aInfo, userId); - } - } - } - } - - int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, - aInfo, rInfo, voiceSession, voiceInteractor, - resultTo, resultWho, requestCode, callingPid, - callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, - options, ignoreTargetSecurity, componentSpecified, null, container, inTask); - - Binder.restoreCallingIdentity(origId); - - if (stack.mConfigWillChange) { - // If the caller also wants to switch to a new configuration, - // do so now. This allows a clean switch, as we are waiting - // for the current activity to pause (so we will not destroy - // it), and have not yet started the next activity. - mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, - "updateConfiguration()"); - stack.mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, - "Updating to new configuration after starting activity."); - mService.updateConfigurationLocked(config, null, false); - } - - if (outResult != null) { - outResult.result = res; - if (res == ActivityManager.START_SUCCESS) { - mWaitingActivityLaunched.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } else if (res == ActivityManager.START_TASK_TO_FRONT) { - ActivityRecord r = stack.topRunningActivityLocked(); - if (r.nowVisible && r.state == RESUMED) { - outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); - outResult.totalTime = 0; - outResult.thisTime = 0; - } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mWaitingActivityVisible.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } - } - } - - return res; - } - } - - final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle bOptions, int userId) { - if (intents == null) { - throw new NullPointerException("intents is null"); - } - if (resolvedTypes == null) { - throw new NullPointerException("resolvedTypes is null"); - } - if (intents.length != resolvedTypes.length) { - throw new IllegalArgumentException("intents are length different than resolvedTypes"); - } - - - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); - } else { - callingPid = callingUid = -1; - } - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mService) { - ActivityRecord[] outActivity = new ActivityRecord[1]; - for (int i=0; i<intents.length; i++) { - Intent intent = intents[i]; - if (intent == null) { - continue; - } - - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], 0, null, userId); - // TODO: New, check if this is correct - aInfo = mService.getActivityInfoForUser(aInfo, userId); - - if (aInfo != null && - (aInfo.applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { - throw new IllegalArgumentException( - "FLAG_CANT_SAVE_STATE not supported here"); - } - - ActivityOptions options = ActivityOptions.fromBundle( - i == intents.length - 1 ? bOptions : null); - int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/, - resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1, - callingPid, callingUid, callingPackage, callingPid, callingUid, 0, - options, false, componentSpecified, outActivity, null, null); - if (res < 0) { - return res; - } - - resultTo = outActivity[0] != null ? outActivity[0].appToken : null; - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - return ActivityManager.START_SUCCESS; - } - final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { @@ -1500,348 +1207,7 @@ public final class ActivityStackSupervisor implements DisplayListener { "activity", r.intent.getComponent(), false, false, true); } - final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, - String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, - String callingPackage, int realCallingPid, int realCallingUid, int startFlags, - ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, - ActivityRecord[] outActivity, ActivityContainer container, TaskRecord inTask) { - int err = ActivityManager.START_SUCCESS; - - ProcessRecord callerApp = null; - if (caller != null) { - callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - callingPid = callerApp.pid; - callingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - err = ActivityManager.START_PERMISSION_DENIED; - } - } - - final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; - - if (err == ActivityManager.START_SUCCESS) { - Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) - + "} from uid " + callingUid - + " on display " + (container == null ? (mFocusedStack == null ? - Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) : - (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : - container.mActivityDisplay.mDisplayId))); - } - - ActivityRecord sourceRecord = null; - ActivityRecord resultRecord = null; - if (resultTo != null) { - sourceRecord = isInAnyStackLocked(resultTo); - if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, - "Will send result to " + resultTo + " " + sourceRecord); - if (sourceRecord != null) { - if (requestCode >= 0 && !sourceRecord.finishing) { - resultRecord = sourceRecord; - } - } - } - - final int launchFlags = intent.getFlags(); - - if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { - // Transfer the result target from the source activity to the new - // one being started, including any failures. - if (requestCode >= 0) { - ActivityOptions.abort(options); - return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; - } - resultRecord = sourceRecord.resultTo; - if (resultRecord != null && !resultRecord.isInStackLocked()) { - resultRecord = null; - } - resultWho = sourceRecord.resultWho; - requestCode = sourceRecord.requestCode; - sourceRecord.resultTo = null; - if (resultRecord != null) { - resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); - } - if (sourceRecord.launchedFromUid == callingUid) { - // The new activity is being launched from the same uid as the previous - // activity in the flow, and asking to forward its result back to the - // previous. In this case the activity is serving as a trampoline between - // the two, so we also want to update its launchedFromPackage to be the - // same as the previous activity. Note that this is safe, since we know - // these two packages come from the same uid; the caller could just as - // well have supplied that same package name itself. This specifially - // deals with the case of an intent picker/chooser being launched in the app - // flow to redirect to an activity picked by the user, where we want the final - // activity to consider it to have been launched by the previous app activity. - callingPackage = sourceRecord.launchedFromPackage; - } - } - - if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { - // We couldn't find a class that can handle the given Intent. - // That's the end of that! - err = ActivityManager.START_INTENT_NOT_RESOLVED; - } - - if (err == ActivityManager.START_SUCCESS && aInfo == null) { - // We couldn't find the specific class specified in the Intent. - // Also the end of the line. - err = ActivityManager.START_CLASS_NOT_FOUND; - } - - if (err == ActivityManager.START_SUCCESS && sourceRecord != null - && sourceRecord.task.voiceSession != null) { - // If this activity is being launched as part of a voice session, we need - // to ensure that it is safe to do so. If the upcoming activity will also - // be part of the voice session, we can only launch it if it has explicitly - // said it supports the VOICE category, or it is a part of the calling app. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 - && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { - try { - intent.addCategory(Intent.CATEGORY_VOICE); - if (!AppGlobals.getPackageManager().activitySupportsIntent( - intent.getComponent(), intent, resolvedType)) { - Slog.w(TAG, - "Activity being started in current voice task does not support voice: " - + intent); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure checking voice capabilities", e); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } - } - - if (err == ActivityManager.START_SUCCESS && voiceSession != null) { - // If the caller is starting a new voice session, just make sure the target - // is actually allowing it to run this way. - try { - if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), - intent, resolvedType)) { - Slog.w(TAG, - "Activity being started in new voice task does not support: " - + intent); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure checking voice capabilities", e); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } - - final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; - - if (err != ActivityManager.START_SUCCESS) { - if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - ActivityOptions.abort(options); - return err; - } - - boolean abort = !checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode, - callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp, - resultRecord, resultStack); - abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, - callingPid, resolvedType, aInfo.applicationInfo); - - if (mService.mController != null) { - try { - // The Intent we give to the watcher has the extra data - // stripped off, since it can contain private information. - Intent watchIntent = intent.cloneFilter(); - abort |= !mService.mController.activityStarting(watchIntent, - aInfo.applicationInfo.packageName); - } catch (RemoteException e) { - mService.mController = null; - } - } - - UserInfo user = getUserInfo(userId); - KeyguardManager km = (KeyguardManager) mService.mContext - .getSystemService(Context.KEYGUARD_SERVICE); - if (user.isManagedProfile() - && LockPatternUtils.isSeparateWorkChallengeEnabled() - && km.isDeviceLocked(userId)) { - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - final int flags = intent.getFlags(); - final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id); - if (newIntent != null) { - intent = newIntent; - intent.setFlags(flags - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); - intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId); - rInfo = resolveIntent(intent, resolvedType, parent.id); - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - } - } - - if (abort) { - if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - ActivityOptions.abort(options); - return ActivityManager.START_SUCCESS; - } - - // If permissions need a review before any of the app components can run, we - // launch the review activity and pass a pending intent to start the activity - // we are to launching now after the review is completed. - if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) { - if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( - aInfo.packageName, userId)) { - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - callingUid, userId, null, null, 0, new Intent[]{intent}, - new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT, null); - - final int flags = intent.getFlags(); - Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); - newIntent.setFlags(flags - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); - newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); - if (resultRecord != null) { - newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); - } - intent = newIntent; - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - rInfo = resolveIntent(intent, resolvedType, userId); - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - - if (DEBUG_PERMISSIONS_REVIEW) { - Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, - true, false) + "} from uid " + callingUid + " on display " - + (container == null ? (mFocusedStack == null ? - Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) : - (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : - container.mActivityDisplay.mDisplayId))); - } - } - } - - // If we have an ephemeral app, abort the process of launching the resolved intent. - // Instead, launch the ephemeral installer. Once the installer is finished, it - // starts either the intent we resolved here [on install error] or the ephemeral - // app [on install success]. - if (rInfo != null && rInfo.ephemeralResolveInfo != null) { - // Create a pending intent to start the intent resolved here. - final IIntentSender failureTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - // Create a pending intent to start the ephemeral application; force it to be - // directed to the ephemeral package. - ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName()); - final IIntentSender ephemeralTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - int flags = intent.getFlags(); - intent = new Intent(); - intent.setFlags(flags - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, - rInfo.ephemeralResolveInfo.getPackageName()); - intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget)); - intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget)); - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - rInfo = rInfo.ephemeralInstaller; - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - } - - ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, - intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, - requestCode, componentSpecified, voiceSession != null, this, container, options); - if (outActivity != null) { - outActivity[0] = r; - } - - if (r.appTimeTracker == null && sourceRecord != null) { - // If the caller didn't specify an explicit time tracker, we want to continue - // tracking under any it has. - r.appTimeTracker = sourceRecord.appTimeTracker; - } - - final ActivityStack stack = mFocusedStack; - if (voiceSession == null && (stack.mResumedActivity == null - || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { - if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, - realCallingPid, realCallingUid, "Activity start")) { - PendingActivityLaunch pal = new PendingActivityLaunch(r, - sourceRecord, startFlags, stack, callerApp); - mPendingActivityLaunches.add(pal); - ActivityOptions.abort(options); - return ActivityManager.START_SWITCHES_CANCELED; - } - } - - if (mService.mDidAppSwitch) { - // This is the second allowed switch since we stopped switches, - // so now just generally allow switches. Use case: user presses - // home (switches disabled, switch to home, mDidAppSwitch now true); - // user taps a home icon (coming from home so allowed, we hit here - // and now allow anyone to switch again). - mService.mAppSwitchesAllowedTime = 0; - } else { - mService.mDidAppSwitch = true; - } - - doPendingActivityLaunchesLocked(false); - - err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, true, options, inTask); - - if (err < 0) { - // If someone asked to have the keyguard dismissed on the next - // activity start, but we are not actually doing an activity - // switch... just dismiss the keyguard now, because we - // probably want to see whatever is behind it. - notifyActivityDrawnForKeyguard(); - } - return err; - } - - private boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, + boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp, ActivityRecord resultRecord, ActivityStack resultStack) { @@ -1901,7 +1267,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return true; } - private UserInfo getUserInfo(int userId) { + UserInfo getUserInfo(int userId) { final long identity = Binder.clearCallingIdentity(); try { return UserManager.get(mService.mContext).getUserInfo(userId); @@ -1979,104 +1345,6 @@ public final class ActivityStackSupervisor implements DisplayListener { return ACTIVITY_RESTRICTION_NONE; } - private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, - int launchFlags) { - final TaskRecord task = r.task; - if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { - return mHomeStack; - } - - ActivityStack stack = getLaunchToSideStack(r, launchFlags, task); - if (stack != null) { - return stack; - } - - if (task != null && task.stack != null) { - stack = task.stack; - if (stack.isOnHomeDisplay()) { - if (mFocusedStack != stack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting " + "focused stack to r=" + r - + " task=" + task); - } else { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Focused stack already=" + mFocusedStack); - } - } - return stack; - } - - final ActivityContainer container = r.mInitialActivityContainer; - if (container != null) { - // The first time put it on the desired stack, after this put on task stack. - r.mInitialActivityContainer = null; - return container.mStack; - } - - // The fullscreen stack can contain any task regardless of if the task is resizeable - // or not. So, we let the task go in the fullscreen task if it is the focus stack. - // If the freeform or docked stack has focus, and the activity to be launched is resizeable, - // we can also put it in the focused stack. - final int focusedStackId = mFocusedStack.mStackId; - final boolean canUseFocusedStack = - focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID - || focusedStackId == DOCKED_STACK_ID - || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable); - if (canUseFocusedStack - && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Have a focused stack=" + mFocusedStack); - return mFocusedStack; - } - - // We first try to put the task in the first dynamic stack. - final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; - for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { - stack = homeDisplayStacks.get(stackNdx); - if (!StackId.isStaticStack(stack.mStackId)) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting focused stack=" + stack); - return stack; - } - } - - // If there is no suitable dynamic stack then we figure out which static stack to use. - final int stackId = task != null ? task.getLaunchStackId() : - bounds != null ? FREEFORM_WORKSPACE_STACK_ID : - FULLSCREEN_WORKSPACE_STACK_ID; - stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP); - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" - + r + " stackId=" + stack.mStackId); - return stack; - } - - private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) { - if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) { - return null; - } - // The parent activity doesn't want to launch the activity on top of itself, but - // instead tries to put it onto other side in side-by-side mode. - final ActivityStack parentStack = task != null ? task.stack - : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack - : mFocusedStack; - if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) { - // If parent was in docked stack, the natural place to launch another activity - // will be fullscreen, so it can appear alongside the docked window. - return getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP); - } else { - // If the parent is not in the docked stack, we check if there is docked window - // and if yes, we will launch into that stack. If not, we just put the new - // activity into parent's stack, because we can't find a better place. - final ActivityStack stack = getStack(DOCKED_STACK_ID); - if (stack != null && !stack.isStackVisibleLocked()) { - // There is a docked stack, but it isn't visible, so we can't launch into that. - return null; - } else { - return stack; - } - } - } - boolean setFocusedStack(ActivityRecord r, String reason) { if (r == null) { // Not sure what you are trying to do, but it is not going to work... @@ -2091,735 +1359,14 @@ public final class ActivityStackSupervisor implements DisplayListener { return true; } - final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, - boolean doResume, ActivityOptions options, TaskRecord inTask) { - final Intent intent = r.intent; - final int callingUid = r.launchedFromUid; - - boolean overrideBounds = false; + Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) { Rect newBounds = null; if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) { if (canUseActivityOptionsLaunchBounds(options)) { - overrideBounds = true; newBounds = options.getLaunchBounds(); } } - - // In some flows in to this function, we retrieve the task record and hold on to it - // without a lock before calling back in to here... so the task at this point may - // not actually be in recents. Check for that, and if it isn't in recents just - // consider it invalid. - if (inTask != null && !inTask.inRecents) { - Slog.w(TAG, "Starting activity in task not in recents: " + inTask); - inTask = null; - } - - final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; - final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE; - final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; - - int launchFlags = intent.getFlags(); - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && - (launchSingleInstance || launchSingleTask)) { - // We have a conflict between the Intent and the Activity manifest, manifest wins. - Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " + - "\"singleInstance\" or \"singleTask\""); - launchFlags &= - ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK); - } else { - switch (r.info.documentLaunchMode) { - case ActivityInfo.DOCUMENT_LAUNCH_NONE: - break; - case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: - launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - break; - case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: - launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - break; - case ActivityInfo.DOCUMENT_LAUNCH_NEVER: - launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK; - break; - } - } - - final boolean launchTaskBehind = r.mLaunchTaskBehind - && !launchSingleTask && !launchSingleInstance - && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; - - if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 - && r.resultTo.task.stack != null) { - // For whatever reason this activity is being launched into a new - // task... yet the caller has requested a result back. Well, that - // is pretty messed up, so instead immediately send back a cancel - // and let the new task continue launched as normal without a - // dependency on its originator. - Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - r.resultTo.task.stack.sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - r.resultTo = null; - } - - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) { - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - - // If we are actually going to launch in to a new task, there are some cases where - // we further want to do multiple task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - if (launchTaskBehind - || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) { - launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK; - } - } - - // We'll invoke onUserLeaving before onPause only if the launching - // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; - if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, - "startActivity() => mUserLeaving=" + mUserLeaving); - - // If the caller has asked not to resume at this point, we make note - // of this in the record so that we can skip it when trying to find - // the top running activity. - if (!doResume || !okToShowLocked(r)) { - r.delayedResume = true; - doResume = false; - } - - ActivityRecord notTop = - (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; - - // If the onlyIfNeeded flag is set, then we can do this if the activity - // being launched is the same as the one making the call... or, as - // a special case, if we do not know the caller then we count the - // current top activity as the caller. - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - ActivityRecord checkedCaller = sourceRecord; - if (checkedCaller == null) { - checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop); - } - if (!checkedCaller.realActivity.equals(r.realActivity)) { - // Caller is not the same as launcher, so always needed. - startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; - } - } - - boolean addingToTask = false; - TaskRecord reuseTask = null; - - // If the caller is not coming from another activity, but has given us an - // explicit task into which they would like us to launch the new activity, - // then let's see about doing that. - if (sourceRecord == null && inTask != null && inTask.stack != null) { - final Intent baseIntent = inTask.getBaseIntent(); - final ActivityRecord root = inTask.getRootActivity(); - if (baseIntent == null) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Launching into task without base intent: " - + inTask); - } - - // If this task is empty, then we are adding the first activity -- it - // determines the root, and must be launching as a NEW_TASK. - if (launchSingleInstance || launchSingleTask) { - if (!baseIntent.getComponent().equals(r.intent.getComponent())) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Trying to launch singleInstance/Task " - + r + " into different task " + inTask); - } - if (root != null) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Caller with inTask " + inTask - + " has root " + root + " but target is singleInstance/Task"); - } - } - - // If task is empty, then adopt the interesting intent launch flags in to the - // activity being started. - if (root == null) { - final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK - | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT - | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; - launchFlags = (launchFlags&~flagsOfInterest) - | (baseIntent.getFlags()&flagsOfInterest); - intent.setFlags(launchFlags); - inTask.setIntent(r); - addingToTask = true; - - // If the task is not empty and the caller is asking to start it as the root - // of a new task, then we don't actually want to start this on the task. We - // will bring the task to the front, and possibly give it a new intent. - } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - addingToTask = false; - - } else { - addingToTask = true; - } - - reuseTask = inTask; - } else { - inTask = null; - // Launch ResolverActivity in the source task, so that it stays in the task - // bounds when in freeform workspace. - // Also put noDisplay activities in the source task. These by itself can - // be placed in any task/stack, however it could launch other activities - // like ResolverActivity, and we want those to stay in the original task. - if ((r.isResolverActivity() || r.noDisplay) && sourceRecord != null - && sourceRecord.isFreeform()) { - addingToTask = true; - } - } - - if (inTask == null) { - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing " + - "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } else if (launchSingleInstance || launchSingleTask) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - } - - ActivityInfo newTaskInfo = null; - Intent newTaskIntent = null; - final ActivityStack sourceStack; - if (sourceRecord != null) { - if (sourceRecord.finishing) { - // If the source is finishing, we can't further count it as our source. This - // is because the task it is associated with may now be empty and on its way out, - // so we don't want to blindly throw it in to that task. Instead we will take - // the NEW_TASK flow and try to find a task for it. But save the task information - // so it can be used when creating the new task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from finishing " + sourceRecord - + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - newTaskInfo = sourceRecord.info; - newTaskIntent = sourceRecord.task.intent; - } - sourceRecord = null; - sourceStack = null; - } else { - sourceStack = sourceRecord.task.stack; - } - } else { - sourceStack = null; - } - - boolean movedHome = false; - ActivityStack targetStack; - - intent.setFlags(launchFlags); - final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0; - - ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent, - launchSingleInstance, launchSingleTask, launchFlags); - if (intentActivity != null) { - // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused - // but still needs to be a lock task mode violation since the task gets - // cleared out and the device would otherwise leave the locked task. - if (isLockTaskModeViolation(intentActivity.task, - (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { - showLockTaskToast(); - Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (r.task == null) { - r.task = intentActivity.task; - } - if (intentActivity.task.intent == null) { - // This task was started because of movement of the activity based on affinity... - // Now that we are actually launching it, we can assign the base intent. - intentActivity.task.setIntent(r); - } - - targetStack = intentActivity.task.stack; - targetStack.mLastPausedActivity = null; - // If the target task is not in the front, then we need - // to bring it to the front... except... well, with - // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like - // to have the same behavior as if a new instance was - // being started, which means not bringing it to the front - // if the caller is not itself in the front. - final ActivityStack focusStack = getFocusedStack(); - ActivityRecord curTop = (focusStack == null) - ? null : focusStack.topRunningNonDelayedActivityLocked(notTop); - boolean movedToFront = false; - if (curTop != null && (curTop.task != intentActivity.task || - curTop.task != focusStack.topTask())) { - r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - if (sourceRecord == null || (sourceStack.topActivity() != null && - sourceStack.topActivity().task == sourceRecord.task)) { - // We really do want to push this one into the user's face, right now. - if (launchTaskBehind && sourceRecord != null) { - intentActivity.setTaskToAffiliateWith(sourceRecord.task); - } - movedHome = true; - final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task); - if (sideStack == null || sideStack == targetStack) { - // We only want to move to the front, if we aren't going to launch on a - // different stack. If we launch on a different stack, we will put the - // task on top there. - targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, - options, r.appTimeTracker, "bringingFoundTaskToFront"); - movedToFront = true; - } - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity. - intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } - options = null; - } - } - if (!movedToFront && doResume) { - if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack - + " from " + intentActivity); - targetStack.moveToFront("intentActivityFound"); - } - - // If the caller has requested that the target task be - // reset, then do so. - if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); - } - if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivitiesLocked(targetStack, null, options); - - // Make sure to notify Keyguard as well if we are not running an app - // transition later. - if (!movedToFront) { - notifyActivityDrawnForKeyguard(); - } - } else { - ActivityOptions.abort(options); - } - updateUserStackLocked(r.userId, targetStack); - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { - // The caller has requested to completely replace any - // existing task with its new activity. Well that should - // not be too hard... - reuseTask = intentActivity.task; - reuseTask.performClearTaskLocked(); - reuseTask.setIntent(r); - } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 - || launchSingleInstance || launchSingleTask) { - // In this situation we want to remove all activities - // from the task up to the one being started. In most - // cases this means we are resetting the task to its - // initial state. - ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags); - if (top != null) { - if (top.frontOfTask) { - // Activity aliases may mean we use different - // intents for the top activity, so make sure - // the task now has the identity of the new - // intent. - top.task.setIntent(r); - } - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - } else { - // A special case: we need to start the activity because it is not - // currently running, and the caller has asked to clear the current - // task to have this activity at the top. - addingToTask = true; - // Now pretend like this activity is being started by the top of its - // task, so it is put in the right place. - sourceRecord = intentActivity; - TaskRecord task = sourceRecord.task; - if (task != null && task.stack == null) { - // Target stack got cleared when we all activities were removed - // above. Go ahead and reset it. - targetStack = computeStackFocus( - sourceRecord, false /* newTask */, null /* bounds */, launchFlags); - targetStack.addTask(task, - !launchTaskBehind /* toTop */, "startActivityUnchecked"); - } - - } - } else if (r.realActivity.equals(intentActivity.task.realActivity)) { - // In this case the top activity on the task is the - // same as the one being launched, so we take that - // as a request to bring the task to the foreground. - // If the top activity in the task is the root - // activity, deliver this new intent to it if it - // desires. - if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop) - && intentActivity.realActivity.equals(r.realActivity)) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, - intentActivity.task); - if (intentActivity.frontOfTask) { - intentActivity.task.setIntent(r); - } - intentActivity.deliverNewIntentLocked(callingUid, r.intent, - r.launchedFromPackage); - } else if (!r.intent.filterEquals(intentActivity.task.intent)) { - // In this case we are launching the root activity - // of the task, but with a different intent. We - // should start a new instance on top. - addingToTask = true; - sourceRecord = intentActivity; - } - } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { - // In this case an activity is being launched in to an - // existing task, without resetting that task. This - // is typically the situation of launching an activity - // from a notification or shortcut. We want to place - // the new activity on top of the current task. - addingToTask = true; - sourceRecord = intentActivity; - } else if (!intentActivity.task.rootWasReset) { - // In this case we are launching in to an existing task - // that has not yet been started from its front door. - // The current task has been brought to the front. - // Ideally, we'd probably like to place this new task - // at the bottom of its stack, but that's a little hard - // to do with the current organization of the code so - // for now we'll just drop it. - intentActivity.task.setIntent(r); - } - if (!addingToTask && reuseTask == null) { - // We didn't do anything... but it was needed (a.k.a., client - // don't use that intent!) And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - targetStack.resumeTopActivityLocked(null, options); - if (!movedToFront) { - // Make sure to notify Keyguard as well if we are not running an app - // transition later. - notifyActivityDrawnForKeyguard(); - } - } else { - ActivityOptions.abort(options); - } - updateUserStackLocked(r.userId, targetStack); - return ActivityManager.START_TASK_TO_FRONT; - } - } - - //String uri = r.intent.toURI(); - //Intent intent2 = new Intent(uri); - //Slog.i(TAG, "Given intent: " + r.intent); - //Slog.i(TAG, "URI is: " + uri); - //Slog.i(TAG, "To intent: " + intent2); - - if (r.packageName != null) { - // If the activity being launched is the same as the one currently - // at the top, then we need to check if it should only be launched - // once. - ActivityStack topStack = mFocusedStack; - ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); - final boolean dontStart = top != null && r.resultTo == null - && top.realActivity.equals(r.realActivity) && top.userId == r.userId - && top.app != null && top.app.thread != null - && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || launchSingleTop || launchSingleTask); - if (dontStart) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - // For paranoia, make sure we have correctly resumed the top activity. - topStack.mLastPausedActivity = null; - if (doResume) { - resumeTopActivitiesLocked(); - } - ActivityOptions.abort(options); - if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and the client said not to do - // anything if that is the case, so this is it! - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } else { - if (r.resultTo != null && r.resultTo.task.stack != null) { - r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, - r.requestCode, Activity.RESULT_CANCELED, null); - } - ActivityOptions.abort(options); - return ActivityManager.START_CLASS_NOT_FOUND; - } - - boolean newTask = false; - boolean keepCurTransition = false; - - TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ? - sourceRecord.task : null; - - // Should this be considered a new task? - if (r.resultTo == null && inTask == null && !addingToTask - && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - newTask = true; - targetStack = computeStackFocus(r, newTask, newBounds, launchFlags); - if (doResume) { - targetStack.moveToFront("startingNewTask"); - } - - if (reuseTask == null) { - r.setTask(targetStack.createTaskRecord(getNextTaskId(), - newTaskInfo != null ? newTaskInfo : r.info, - newTaskIntent != null ? newTaskIntent : intent, - voiceSession, voiceInteractor, !launchTaskBehind /* toTop */), - taskToAffiliate); - if (overrideBounds) { - r.task.updateOverrideConfiguration(newBounds); - } - if (DEBUG_TASKS) Slog.v(TAG_TASKS, - "Starting new activity " + r + " in new task " + r.task); - } else { - r.setTask(reuseTask, taskToAffiliate); - } - if (isLockTaskModeViolation(r.task)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (!movedHome) { - if ((launchFlags & - (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity, so before starting - // their own activity we will bring home to the front. - r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } - } - } else if (sourceRecord != null) { - final TaskRecord sourceTask = sourceRecord.task; - if (isLockTaskModeViolation(sourceTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - targetStack = null; - if (sourceTask.stack.topTask() != sourceTask) { - // We only want to allow changing stack if the target task is not the top one, - // otherwise we would move the launching task to the other side, rather than show - // two side by side. - targetStack = getLaunchToSideStack(r, launchFlags, r.task); - } - if (targetStack == null) { - targetStack = sourceTask.stack; - } else if (targetStack != sourceTask.stack) { - moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, ON_TOP, - FORCE_FOCUS, "launchToSide", !ANIMATE); - } - if (doResume) { - targetStack.moveToFront("sourceStackToFront"); - } - final TaskRecord topTask = targetStack.topTask(); - if (topTask != sourceTask) { - targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, - r.appTimeTracker, "sourceTaskToFront"); - } - if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - // In this case, we are adding the activity to an existing - // task, but the caller has asked to clear that task if the - // activity is already running. - ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); - keepCurTransition = true; - if (top != null) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - // For paranoia, make sure we have correctly - // resumed the top activity. - targetStack.mLastPausedActivity = null; - if (doResume) { - targetStack.resumeTopActivityLocked(null); - } - ActivityOptions.abort(options); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } else if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { - // In this case, we are launching an activity in our own task - // that may already be running somewhere in the history, and - // we want to shuffle it to the front of the stack if so. - final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); - if (top != null) { - final TaskRecord task = top.task; - task.moveActivityToFrontLocked(top); - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); - top.updateOptionsLocked(options); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - targetStack.mLastPausedActivity = null; - if (doResume) { - targetStack.resumeTopActivityLocked(null); - } - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - // An existing activity is starting this new activity, so we want - // to keep the new one in the same task as the one that is starting - // it. - r.setTask(sourceTask, null); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in existing task " + r.task + " from source " + sourceRecord); - - } else if (inTask != null) { - // The caller is asking that the new activity be started in an explicit - // task it has provided to us. - if (isLockTaskModeViolation(inTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (overrideBounds) { - inTask.updateOverrideConfiguration(newBounds); - int stackId = inTask.getLaunchStackId(); - if (stackId != inTask.stack.mStackId) { - moveTaskToStackUncheckedLocked( - inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront"); - } - } - targetStack = inTask.stack; - targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker, - "inTaskToFront"); - - // Check whether we should actually launch the new activity in to the task, - // or just reuse the current activity on top. - ActivityRecord top = inTask.getTopActivity(); - if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) { - if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || launchSingleTop || launchSingleTask) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - - if (!addingToTask) { - // We don't actually want to have this activity added to the task, so just - // stop here but still tell the caller that we consumed the intent. - ActivityOptions.abort(options); - return ActivityManager.START_TASK_TO_FRONT; - } - - r.setTask(inTask, null); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in explicit task " + r.task); - - } else { - // This not being started from an existing activity, and not part - // of a new task... just put it in the top task, though these days - // this case should never happen. - targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags); - if (doResume) { - targetStack.moveToFront("addingToTopTask"); - } - ActivityRecord prev = targetStack.topActivity(); - r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(), - r.info, intent, null, null, true), null); - mWindowManager.moveTaskToTop(r.task.taskId); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in new guessed " + r.task); - } - - mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r.getUriPermissionsLocked(), r.userId); - - if (sourceRecord != null && sourceRecord.isRecentsActivity()) { - r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE); - } - if (newTask) { - EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); - } - ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - targetStack.mLastPausedActivity = null; - targetStack.startActivityLocked(r, newTask, keepCurTransition, options); - if (doResume) { - if (!launchTaskBehind) { - mService.setFocusedActivityLocked(r, "startedActivity"); - } - resumeTopActivitiesLocked(targetStack, r, options); - } else { - targetStack.addRecentActivityLocked(r); - } - updateUserStackLocked(r.userId, targetStack); - - if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) { - showNonResizeableDockToast(r.task.taskId); - } - - return ActivityManager.START_SUCCESS; - } - - /** - * Decide whether the new activity should be inserted into an existing task. Returns null if not - * or an ActivityRecord with the task into which the new activity should be added. - */ - private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, Intent intent, - boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) { - // We may want to try to place the new activity in to an existing task. We always - // do this if the target activity is singleTask or singleInstance; we will also do - // this if NEW_TASK has been requested, and there is not an additional qualifier telling - // us to still place it in a new task: multi task, always doc mode, or being asked to - // launch this as a new task behind the current one. - boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 && - (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || launchSingleInstance || launchSingleTask; - // If bring to front is requested, and no result is requested and we have not - // been given an explicit task to launch in to, and - // we can find a task that was started with this same - // component, then instead of launching bring that one to the front. - putIntoExistingTask &= inTask == null && r.resultTo == null; - ActivityRecord intentActivity = null; - if (putIntoExistingTask) { - // See if there is a task to bring to the front. If this is - // a SINGLE_INSTANCE activity, there can be one and only one - // instance of it in the history, and it is always in its own - // unique task, so we do a special search. - intentActivity = launchSingleInstance ? - findActivityLocked(intent, r.info) : findTaskLocked(r); - } - return intentActivity; - } - - final void doPendingActivityLaunchesLocked(boolean doResume) { - while (!mPendingActivityLaunches.isEmpty()) { - PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); - - try { - startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags, - doResume && mPendingActivityLaunches.isEmpty(), null, null); - } catch (Exception e) { - Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); - pal.sendErrorResult(e.getMessage()); - } - } - } - - void removePendingActivityLaunchesLocked(ActivityStack stack) { - for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) { - PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx); - if (pal.stack == stack) { - mPendingActivityLaunches.remove(palNdx); - } - } + return newBounds; } void setLaunchSource(int uid) { @@ -4551,7 +3098,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - private void showNonResizeableDockToast(int taskId) { + void showNonResizeableDockToast(int taskId) { mWindowManager.scheduleShowNonResizeableDockToast(taskId); } @@ -4968,7 +3515,7 @@ public final class ActivityStackSupervisor implements DisplayListener { long origId = Binder.clearCallingIdentity(); try { mStack.finishAllActivitiesLocked(false); - removePendingActivityLaunchesLocked(mStack); + mService.mActivityStarter.removePendingActivityLaunchesLocked(mStack); } finally { Binder.restoreCallingIdentity(origId); } @@ -4987,22 +3534,7 @@ public final class ActivityStackSupervisor implements DisplayListener { @Override public final int startActivity(Intent intent) { - mService.enforceNotIsolatedCaller("ActivityContainer.startActivity"); - final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), mCurrentUser, false, - ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); - - // TODO: Switch to user app stacks here. - String mimeType = intent.getType(); - final Uri data = intent.getData(); - if (mimeType == null && data != null && "content".equals(data.getScheme())) { - mimeType = mService.getProviderMimeType(data, userId); - } - checkEmbeddedAllowedInner(userId, intent, mimeType); - - intent.addFlags(FORCE_NEW_TASK_FLAGS); - return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, - 0, 0, null, null, null, null, false, userId, this, null); + return mService.startActivity(intent, this); } @Override @@ -5026,7 +3558,7 @@ public final class ActivityStackSupervisor implements DisplayListener { FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this); } - private void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) { + void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) { ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, userId); if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { throw new SecurityException( diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java new file mode 100644 index 000000000000..b16e160d9505 --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -0,0 +1,1545 @@ +package com.android.server.am; + +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityManagerService.ANIMATE; +import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; +import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; +import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED; +import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; +import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.AppGlobals; +import android.app.IActivityContainer; +import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.ProfilerInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.service.voice.IVoiceInteractionSession; +import android.util.EventLog; +import android.util.Slog; +import android.view.Display; + +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.widget.LockPatternUtils; +import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; +import com.android.server.wm.WindowManagerService; + +import java.util.ArrayList; + +/** + * Controller for interpreting how and then launching activities. + * + * This class collects all the logic for determining how an intent and flags should be turned into + * an activity and associated task and stack. + */ +public class ActivityStarter { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStarter" : TAG_AM; + private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; + private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + + private final ActivityManagerService mService; + private final ActivityStackSupervisor mSupervisor; + private WindowManagerService mWindowManager; + + final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); + + ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) { + mService = service; + mSupervisor = supervisor; + } + + final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, + String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, + String callingPackage, int realCallingPid, int realCallingUid, int startFlags, + ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, + ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container, + TaskRecord inTask) { + int err = ActivityManager.START_SUCCESS; + + ProcessRecord callerApp = null; + if (caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = ActivityManager.START_PERMISSION_DENIED; + } + } + + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + + if (err == ActivityManager.START_SUCCESS) { + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + + "} from uid " + callingUid + + " on display " + (container == null ? (mSupervisor.mFocusedStack == null ? + Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) : + (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : + container.mActivityDisplay.mDisplayId))); + } + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + sourceRecord = mSupervisor.isInAnyStackLocked(resultTo); + if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, + "Will send result to " + resultTo + " " + sourceRecord); + if (sourceRecord != null) { + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + + final int launchFlags = intent.getFlags(); + + if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + ActivityOptions.abort(options); + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + if (resultRecord != null && !resultRecord.isInStackLocked()) { + resultRecord = null; + } + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); + } + if (sourceRecord.launchedFromUid == callingUid) { + // The new activity is being launched from the same uid as the previous + // activity in the flow, and asking to forward its result back to the + // previous. In this case the activity is serving as a trampoline between + // the two, so we also want to update its launchedFromPackage to be the + // same as the previous activity. Note that this is safe, since we know + // these two packages come from the same uid; the caller could just as + // well have supplied that same package name itself. This specifially + // deals with the case of an intent picker/chooser being launched in the app + // flow to redirect to an activity picked by the user, where we want the final + // activity to consider it to have been launched by the previous app activity. + callingPackage = sourceRecord.launchedFromPackage; + } + } + + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = ActivityManager.START_INTENT_NOT_RESOLVED; + } + + if (err == ActivityManager.START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = ActivityManager.START_CLASS_NOT_FOUND; + } + + if (err == ActivityManager.START_SUCCESS && sourceRecord != null + && sourceRecord.task.voiceSession != null) { + // If this activity is being launched as part of a voice session, we need + // to ensure that it is safe to do so. If the upcoming activity will also + // be part of the voice session, we can only launch it if it has explicitly + // said it supports the VOICE category, or it is a part of the calling app. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 + && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { + try { + intent.addCategory(Intent.CATEGORY_VOICE); + if (!AppGlobals.getPackageManager().activitySupportsIntent( + intent.getComponent(), intent, resolvedType)) { + Slog.w(TAG, + "Activity being started in current voice task does not support voice: " + + intent); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failure checking voice capabilities", e); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + } + + if (err == ActivityManager.START_SUCCESS && voiceSession != null) { + // If the caller is starting a new voice session, just make sure the target + // is actually allowing it to run this way. + try { + if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), + intent, resolvedType)) { + Slog.w(TAG, + "Activity being started in new voice task does not support: " + + intent); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failure checking voice capabilities", e); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + + final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; + + if (err != ActivityManager.START_SUCCESS) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + return err; + } + + boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, + requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp, + resultRecord, resultStack); + abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, + callingPid, resolvedType, aInfo.applicationInfo); + + if (mService.mController != null) { + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort |= !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + } + + UserInfo user = mSupervisor.getUserInfo(userId); + KeyguardManager km = (KeyguardManager) mService.mContext + .getSystemService(Context.KEYGUARD_SERVICE); + if (user.isManagedProfile() + && LockPatternUtils.isSeparateWorkChallengeEnabled() + && km.isDeviceLocked(userId)) { + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + final int flags = intent.getFlags(); + final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id); + if (newIntent != null) { + intent = newIntent; + intent.setFlags(flags + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); + intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId); + rInfo = mSupervisor.resolveIntent(intent, resolvedType, parent.id); + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, + null /*profilerInfo*/); + } + } + + if (abort) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + + // If permissions need a review before any of the app components can run, we + // launch the review activity and pass a pending intent to start the activity + // we are to launching now after the review is completed. + if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) { + if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( + aInfo.packageName, userId)) { + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + callingUid, userId, null, null, 0, new Intent[]{intent}, + new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + final int flags = intent.getFlags(); + Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); + newIntent.setFlags(flags + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); + newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); + if (resultRecord != null) { + newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); + } + intent = newIntent; + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, + null /*profilerInfo*/); + + if (DEBUG_PERMISSIONS_REVIEW) { + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, + true, false) + "} from uid " + callingUid + " on display " + + (container == null ? (mSupervisor.mFocusedStack == null ? + Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) : + (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : + container.mActivityDisplay.mDisplayId))); + } + } + } + + // If we have an ephemeral app, abort the process of launching the resolved intent. + // Instead, launch the ephemeral installer. Once the installer is finished, it + // starts either the intent we resolved here [on install error] or the ephemeral + // app [on install success]. + if (rInfo != null && rInfo.ephemeralResolveInfo != null) { + // Create a pending intent to start the intent resolved here. + final IIntentSender failureTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + + // Create a pending intent to start the ephemeral application; force it to be + // directed to the ephemeral package. + ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName()); + final IIntentSender ephemeralTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + + int flags = intent.getFlags(); + intent = new Intent(); + intent.setFlags(flags + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, + rInfo.ephemeralResolveInfo.getPackageName()); + intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget)); + intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget)); + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + rInfo = rInfo.ephemeralInstaller; + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); + } + + ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, + intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, + requestCode, componentSpecified, voiceSession != null, mSupervisor, container, + options); + if (outActivity != null) { + outActivity[0] = r; + } + + if (r.appTimeTracker == null && sourceRecord != null) { + // If the caller didn't specify an explicit time tracker, we want to continue + // tracking under any it has. + r.appTimeTracker = sourceRecord.appTimeTracker; + } + + final ActivityStack stack = mSupervisor.mFocusedStack; + if (voiceSession == null && (stack.mResumedActivity == null + || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, + realCallingPid, realCallingUid, "Activity start")) { + PendingActivityLaunch pal = new PendingActivityLaunch(r, + sourceRecord, startFlags, stack, callerApp); + mPendingActivityLaunches.add(pal); + ActivityOptions.abort(options); + return ActivityManager.START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + doPendingActivityLaunchesLocked(false); + + err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, + voiceInteractor, startFlags, true, options, inTask); + + if (err < 0) { + // If someone asked to have the keyguard dismissed on the next + // activity start, but we are not actually doing an activity + // switch... just dismiss the keyguard now, because we + // probably want to see whatever is behind it. + mSupervisor.notifyActivityDrawnForKeyguard(); + } + return err; + } + + void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) { + mSupervisor.moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason); + startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/, + null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/, + null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/, + 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/, + 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/, + false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/, + null /*container*/, null /*inTask*/); + if (mSupervisor.inResumeTopActivity) { + // If we are in resume section already, home activity will be initialized, but not + // resumed (to avoid recursive resume) and will stay that way until something pokes it + // again. We need to schedule another resume. + mSupervisor.scheduleResumeTopActivities(); + } + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + String callingPackage, Intent intent, String resolvedType, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int startFlags, + ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config, + Bundle bOptions, boolean ignoreTargetSecurity, int userId, + IActivityContainer iContainer, TaskRecord inTask) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + boolean componentSpecified = intent.getComponent() != null; + + // Save a copy in case ephemeral needs it + final Intent ephemeralIntent = new Intent(intent); + // Don't modify the client's object! + intent = new Intent(intent); + + ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); + // Collect information about the target of the Intent. + ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo); + + ActivityOptions options = ActivityOptions.fromBundle(bOptions); + ActivityStackSupervisor.ActivityContainer container = + (ActivityStackSupervisor.ActivityContainer)iContainer; + synchronized (mService) { + if (container != null && container.mParentActivity != null && + container.mParentActivity.state != RESUMED) { + // Cannot start a child activity if the parent is not resumed. + return ActivityManager.START_CANCELED; + } + final int realCallingPid = Binder.getCallingPid(); + final int realCallingUid = Binder.getCallingUid(); + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = realCallingPid; + callingUid = realCallingUid; + } else { + callingPid = callingUid = -1; + } + + final ActivityStack stack; + if (container == null || container.mStack.isOnHomeDisplay()) { + stack = mSupervisor.mFocusedStack; + } else { + stack = container.mStack; + } + stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Starting activity when config will change = " + stack.mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (aInfo != null && + (aInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + final ProcessRecord heavy = mService.mHeavyWeightProcess; + if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid + || !heavy.processName.equals(aInfo.processName))) { + int appCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + appCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + ActivityOptions.abort(options); + return ActivityManager.START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, "android", + appCallingUid, userId, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (heavy.activities.size() > 0) { + ActivityRecord hist = heavy.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + if (aInfo != null) { + aInfo = mService.getActivityInfoForUser(aInfo, userId); + } + } + } + } + + int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, + aInfo, rInfo, voiceSession, voiceInteractor, + resultTo, resultWho, requestCode, callingPid, + callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, + options, ignoreTargetSecurity, componentSpecified, null, container, inTask); + + Binder.restoreCallingIdentity(origId); + + if (stack.mConfigWillChange) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + stack.mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null, false); + } + + if (outResult != null) { + outResult.result = res; + if (res == ActivityManager.START_SUCCESS) { + mSupervisor.mWaitingActivityLaunched.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == ActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = stack.topRunningActivityLocked(); + if (r.nowVisible && r.state == RESUMED) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mSupervisor.mWaitingActivityVisible.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, + boolean doResume, ActivityOptions options, TaskRecord inTask) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + final Rect newBounds = mSupervisor.getOverrideBounds(r, options, inTask); + final boolean overrideBounds = newBounds != null; + + // In some flows in to this function, we retrieve the task record and hold on to it + // without a lock before calling back in to here... so the task at this point may + // not actually be in recents. Check for that, and if it isn't in recents just + // consider it invalid. + if (inTask != null && !inTask.inRecents) { + Slog.w(TAG, "Starting activity in task not in recents: " + inTask); + inTask = null; + } + + final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; + final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE; + final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; + int launchFlags = adjustLaunchFlagsToDocumentMode(r, launchSingleInstance, launchSingleTask, + intent.getFlags()); + final boolean launchTaskBehind = r.mLaunchTaskBehind + && !launchSingleTask && !launchSingleInstance + && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; + + if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 + && r.resultTo.task.stack != null) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + r.resultTo.task.stack.sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) { + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + + // If we are actually going to launch in to a new task, there are some cases where + // we further want to do multiple task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + if (launchTaskBehind + || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) { + launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK; + } + } + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mSupervisor.mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, + "startActivity() => mUserLeaving=" + mSupervisor.mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume || !mSupervisor.okToShowLocked(r)) { + r.delayedResume = true; + doResume = false; + } + + final ActivityRecord notTop = + (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = mSupervisor.mFocusedStack.topRunningNonDelayedActivityLocked( + notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; + } + } + + boolean addingToTask = false; + TaskRecord reuseTask = null; + + // If the caller is not coming from another activity, but has given us an + // explicit task into which they would like us to launch the new activity, + // then let's see about doing that. + if (sourceRecord == null && inTask != null && inTask.stack != null) { + final Intent baseIntent = inTask.getBaseIntent(); + final ActivityRecord root = inTask.getRootActivity(); + if (baseIntent == null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Launching into task without base intent: " + + inTask); + } + + // If this task is empty, then we are adding the first activity -- it + // determines the root, and must be launching as a NEW_TASK. + if (launchSingleInstance || launchSingleTask) { + if (!baseIntent.getComponent().equals(r.intent.getComponent())) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Trying to launch singleInstance/Task " + + r + " into different task " + inTask); + } + if (root != null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Caller with inTask " + inTask + + " has root " + root + " but target is singleInstance/Task"); + } + } + + // If task is empty, then adopt the interesting intent launch flags in to the + // activity being started. + if (root == null) { + final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK + | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; + launchFlags = (launchFlags&~flagsOfInterest) + | (baseIntent.getFlags()&flagsOfInterest); + intent.setFlags(launchFlags); + inTask.setIntent(r); + addingToTask = true; + + // If the task is not empty and the caller is asking to start it as the root + // of a new task, then we don't actually want to start this on the task. We + // will bring the task to the front, and possibly give it a new intent. + } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + addingToTask = false; + + } else { + addingToTask = true; + } + + reuseTask = inTask; + } else { + inTask = null; + // Launch ResolverActivity in the source task, so that it stays in the task + // bounds when in freeform workspace. + // Also put noDisplay activities in the source task. These by itself can + // be placed in any task/stack, however it could launch other activities + // like ResolverActivity, and we want those to stay in the original task. + if ((r.isResolverActivity() || r.noDisplay) && sourceRecord != null + && sourceRecord.isFreeform()) { + addingToTask = true; + } + } + + if (inTask == null) { + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing " + + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } else if (launchSingleInstance || launchSingleTask) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + } + + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; + final ActivityStack sourceStack; + if (sourceRecord != null) { + if (sourceRecord.finishing) { + // If the source is finishing, we can't further count it as our source. This + // is because the task it is associated with may now be empty and on its way out, + // so we don't want to blindly throw it in to that task. Instead we will take + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from finishing " + sourceRecord + + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; + } + sourceRecord = null; + sourceStack = null; + } else { + sourceStack = sourceRecord.task.stack; + } + } else { + sourceStack = null; + } + + boolean movedHome = false; + ActivityStack targetStack; + + intent.setFlags(launchFlags); + final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0; + + ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent, + launchSingleInstance, launchSingleTask, launchFlags); + if (intentActivity != null) { + // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused + // but still needs to be a lock task mode violation since the task gets + // cleared out and the device would otherwise leave the locked task. + if (mSupervisor.isLockTaskModeViolation(intentActivity.task, + (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { + mSupervisor.showLockTaskToast(); + Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (r.task == null) { + r.task = intentActivity.task; + } + if (intentActivity.task.intent == null) { + // This task was started because of movement of the activity based on affinity... + // Now that we are actually launching it, we can assign the base intent. + intentActivity.task.setIntent(r); + } + + targetStack = intentActivity.task.stack; + targetStack.mLastPausedActivity = null; + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + final ActivityStack focusStack = mSupervisor.getFocusedStack(); + ActivityRecord curTop = (focusStack == null) + ? null : focusStack.topRunningNonDelayedActivityLocked(notTop); + boolean movedToFront = false; + if (curTop != null && (curTop.task != intentActivity.task || + curTop.task != focusStack.topTask())) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + if (sourceRecord == null || (sourceStack.topActivity() != null && + sourceStack.topActivity().task == sourceRecord.task)) { + // We really do want to push this one into the user's face, right now. + if (launchTaskBehind && sourceRecord != null) { + intentActivity.setTaskToAffiliateWith(sourceRecord.task); + } + movedHome = true; + final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task); + if (sideStack == null || sideStack == targetStack) { + // We only want to move to the front, if we aren't going to launch on a + // different stack. If we launch on a different stack, we will put the + // task on top there. + targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, + options, r.appTimeTracker, "bringingFoundTaskToFront"); + movedToFront = true; + } + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity. + intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + } + options = null; + } + } + if (!movedToFront && doResume) { + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack + + " from " + intentActivity); + targetStack.moveToFront("intentActivityFound"); + } + + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); + } + if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + mSupervisor.resumeTopActivitiesLocked(targetStack, null, options); + + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + if (!movedToFront) { + mSupervisor.notifyActivityDrawnForKeyguard(); + } + } else { + ActivityOptions.abort(options); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = intentActivity.task; + reuseTask.performClearTaskLocked(); + reuseTask.setIntent(r); + } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 + || launchSingleInstance || launchSingleTask) { + // In this situation we want to remove all activities + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r); + } + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + } else { + // A special case: we need to start the activity because it is not + // currently running, and the caller has asked to clear the current + // task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started by the top of its + // task, so it is put in the right place. + sourceRecord = intentActivity; + TaskRecord task = sourceRecord.task; + if (task != null && task.stack == null) { + // Target stack got cleared when we all activities were removed + // above. Go ahead and reset it. + targetStack = computeStackFocus( + sourceRecord, false /* newTask */, null /* bounds */, launchFlags); + targetStack.addTask(task, + !launchTaskBehind /* toTop */, "startActivityUnchecked"); + } + + } + } else if (r.realActivity.equals(intentActivity.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop) + && intentActivity.realActivity.equals(r.realActivity)) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, + intentActivity.task); + if (intentActivity.frontOfTask) { + intentActivity.task.setIntent(r); + } + intentActivity.deliverNewIntentLocked(callingUid, r.intent, + r.launchedFromPackage); + } else if (!r.intent.filterEquals(intentActivity.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = intentActivity; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = intentActivity; + } else if (!intentActivity.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + intentActivity.task.setIntent(r); + } + if (!addingToTask && reuseTask == null) { + // We didn't do anything... but it was needed (a.k.a., client + // don't use that intent!) And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + targetStack.resumeTopActivityLocked(null, options); + if (!movedToFront) { + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + mSupervisor.notifyActivityDrawnForKeyguard(); + } + } else { + ActivityOptions.abort(options); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + return ActivityManager.START_TASK_TO_FRONT; + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityStack topStack = mSupervisor.mFocusedStack; + ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); + final boolean dontStart = top != null && r.resultTo == null + && top.realActivity.equals(r.realActivity) && top.userId == r.userId + && top.app != null && top.app.thread != null + && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || launchSingleTop || launchSingleTask); + if (dontStart) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + // For paranoia, make sure we have correctly resumed the top activity. + topStack.mLastPausedActivity = null; + if (doResume) { + mSupervisor.resumeTopActivitiesLocked(); + } + ActivityOptions.abort(options); + if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and the client said not to do + // anything if that is the case, so this is it! + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else { + if (r.resultTo != null && r.resultTo.task.stack != null) { + r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, + r.requestCode, Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + return ActivityManager.START_CLASS_NOT_FOUND; + } + + boolean newTask = false; + boolean keepCurTransition = false; + + final TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ? + sourceRecord.task : null; + + // Should this be considered a new task? + if (r.resultTo == null && inTask == null && !addingToTask + && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + newTask = true; + targetStack = computeStackFocus(r, newTask, newBounds, launchFlags); + if (doResume) { + targetStack.moveToFront("startingNewTask"); + } + + if (reuseTask == null) { + r.setTask(targetStack.createTaskRecord(mSupervisor.getNextTaskId(), + newTaskInfo != null ? newTaskInfo : r.info, + newTaskIntent != null ? newTaskIntent : intent, + voiceSession, voiceInteractor, !launchTaskBehind /* toTop */), + taskToAffiliate); + if (overrideBounds) { + r.task.updateOverrideConfiguration(newBounds); + } + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "Starting new activity " + r + " in new task " + r.task); + } else { + r.setTask(reuseTask, taskToAffiliate); + } + if (mSupervisor.isLockTaskModeViolation(r.task)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (!movedHome) { + if ((launchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + } + } + } else if (sourceRecord != null) { + final TaskRecord sourceTask = sourceRecord.task; + if (mSupervisor.isLockTaskModeViolation(sourceTask)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + targetStack = null; + if (sourceTask.stack.topTask() != sourceTask) { + // We only want to allow changing stack if the target task is not the top one, + // otherwise we would move the launching task to the other side, rather than show + // two side by side. + targetStack = getLaunchToSideStack(r, launchFlags, r.task); + } + if (targetStack == null) { + targetStack = sourceTask.stack; + } else if (targetStack != sourceTask.stack) { + mSupervisor.moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, + ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE); + } + if (doResume) { + targetStack.moveToFront("sourceStackToFront"); + } + final TaskRecord topTask = targetStack.topTask(); + if (topTask != sourceTask) { + targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, + r.appTimeTracker, "sourceTaskToFront"); + } + if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // task, but the caller has asked to clear that task if the + // activity is already running. + ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); + keepCurTransition = true; + if (top != null) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + // For paranoia, make sure we have correctly + // resumed the top activity. + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + ActivityOptions.abort(options); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); + if (top != null) { + final TaskRecord task = top.task; + task.moveActivityToFrontLocked(top); + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); + top.updateOptionsLocked(options); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.setTask(sourceTask, null); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in existing task " + r.task + " from source " + sourceRecord); + + } else if (inTask != null) { + // The caller is asking that the new activity be started in an explicit + // task it has provided to us. + if (mSupervisor.isLockTaskModeViolation(inTask)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (overrideBounds) { + inTask.updateOverrideConfiguration(newBounds); + int stackId = inTask.getLaunchStackId(); + if (stackId != inTask.stack.mStackId) { + mSupervisor.moveTaskToStackUncheckedLocked( + inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront"); + } + } + targetStack = inTask.stack; + targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker, + "inTaskToFront"); + + // Check whether we should actually launch the new activity in to the task, + // or just reuse the current activity on top. + ActivityRecord top = inTask.getTopActivity(); + if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) { + if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || launchSingleTop || launchSingleTask) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + + if (!addingToTask) { + // We don't actually want to have this activity added to the task, so just + // stop here but still tell the caller that we consumed the intent. + ActivityOptions.abort(options); + return ActivityManager.START_TASK_TO_FRONT; + } + + r.setTask(inTask, null); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in explicit task " + r.task); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags); + if (doResume) { + targetStack.moveToFront("addingToTopTask"); + } + ActivityRecord prev = targetStack.topActivity(); + r.setTask(prev != null ? prev.task : targetStack.createTaskRecord( + mSupervisor.getNextTaskId(), r.info, intent, null, null, true), null); + mWindowManager.moveTaskToTop(r.task.taskId); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in new guessed " + r.task); + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r.getUriPermissionsLocked(), r.userId); + + if (sourceRecord != null && sourceRecord.isRecentsActivity()) { + r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE); + } + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); + } + ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + targetStack.mLastPausedActivity = null; + targetStack.startActivityLocked(r, newTask, keepCurTransition, options); + if (doResume) { + if (!launchTaskBehind) { + mService.setFocusedActivityLocked(r, "startedActivity"); + } + mSupervisor.resumeTopActivitiesLocked(targetStack, r, options); + } else { + targetStack.addRecentActivityLocked(r); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + + if (!r.task.mResizeable && mSupervisor.isStackDockedInEffect(targetStack.mStackId)) { + mSupervisor.showNonResizeableDockToast(r.task.taskId); + } + + return ActivityManager.START_SUCCESS; + } + + private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, + boolean launchSingleTask, int launchFlags) { + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && + (launchSingleInstance || launchSingleTask)) { + // We have a conflict between the Intent and the Activity manifest, manifest wins. + Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " + + "\"singleInstance\" or \"singleTask\""); + launchFlags &= + ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK); + } else { + switch (r.info.documentLaunchMode) { + case ActivityInfo.DOCUMENT_LAUNCH_NONE: + break; + case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: + launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + break; + case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: + launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + break; + case ActivityInfo.DOCUMENT_LAUNCH_NEVER: + launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK; + break; + } + } + return launchFlags; + } + + final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle bOptions, int userId) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + ActivityRecord[] outActivity = new ActivityRecord[1]; + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0, + null, userId); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); + + if (aInfo != null && + (aInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + ActivityOptions options = ActivityOptions.fromBundle( + i == intents.length - 1 ? bOptions : null); + int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/, + resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1, + callingPid, callingUid, callingPackage, callingPid, callingUid, 0, + options, false, componentSpecified, outActivity, null, null); + if (res < 0) { + return res; + } + + resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return ActivityManager.START_SUCCESS; + } + + final void doPendingActivityLaunchesLocked(boolean doResume) { + while (!mPendingActivityLaunches.isEmpty()) { + PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); + + try { + startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, + pal.startFlags, doResume && mPendingActivityLaunches.isEmpty(), + null, null); + } catch (Exception e) { + Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); + pal.sendErrorResult(e.getMessage()); + } + } + } + + private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, + int launchFlags) { + final TaskRecord task = r.task; + if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { + return mSupervisor.mHomeStack; + } + + ActivityStack stack = getLaunchToSideStack(r, launchFlags, task); + if (stack != null) { + return stack; + } + + if (task != null && task.stack != null) { + stack = task.stack; + if (stack.isOnHomeDisplay()) { + if (mSupervisor.mFocusedStack != stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting " + "focused stack to r=" + r + + " task=" + task); + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Focused stack already=" + + mSupervisor.mFocusedStack); + } + } + return stack; + } + + final ActivityStackSupervisor.ActivityContainer container = r.mInitialActivityContainer; + if (container != null) { + // The first time put it on the desired stack, after this put on task stack. + r.mInitialActivityContainer = null; + return container.mStack; + } + + // The fullscreen stack can contain any task regardless of if the task is resizeable + // or not. So, we let the task go in the fullscreen task if it is the focus stack. + // If the freeform or docked stack has focus, and the activity to be launched is resizeable, + // we can also put it in the focused stack. + final int focusedStackId = mSupervisor.mFocusedStack.mStackId; + final boolean canUseFocusedStack = + focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID + || focusedStackId == DOCKED_STACK_ID + || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable); + if (canUseFocusedStack && (!newTask + || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack); + return mSupervisor.mFocusedStack; + } + + // We first try to put the task in the first dynamic stack. + final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks; + for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { + stack = homeDisplayStacks.get(stackNdx); + if (!ActivityManager.StackId.isStaticStack(stack.mStackId)) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting focused stack=" + stack); + return stack; + } + } + + // If there is no suitable dynamic stack then we figure out which static stack to use. + final int stackId = task != null ? task.getLaunchStackId() : + bounds != null ? FREEFORM_WORKSPACE_STACK_ID : + FULLSCREEN_WORKSPACE_STACK_ID; + stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + + r + " stackId=" + stack.mStackId); + return stack; + } + + private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) { + if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) { + return null; + } + // The parent activity doesn't want to launch the activity on top of itself, but + // instead tries to put it onto other side in side-by-side mode. + final ActivityStack parentStack = task != null ? task.stack + : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack + : mSupervisor.mFocusedStack; + if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) { + // If parent was in docked stack, the natural place to launch another activity + // will be fullscreen, so it can appear alongside the docked window. + return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP); + } else { + // If the parent is not in the docked stack, we check if there is docked window + // and if yes, we will launch into that stack. If not, we just put the new + // activity into parent's stack, because we can't find a better place. + final ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID); + if (stack != null && !stack.isStackVisibleLocked()) { + // There is a docked stack, but it isn't visible, so we can't launch into that. + return null; + } else { + return stack; + } + } + } + + /** + * Decide whether the new activity should be inserted into an existing task. Returns null if not + * or an ActivityRecord with the task into which the new activity should be added. + */ + private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, + Intent intent, boolean launchSingleInstance, boolean launchSingleTask, + int launchFlags) { + // We may want to try to place the new activity in to an existing task. We always + // do this if the target activity is singleTask or singleInstance; we will also do + // this if NEW_TASK has been requested, and there is not an additional qualifier telling + // us to still place it in a new task: multi task, always doc mode, or being asked to + // launch this as a new task behind the current one. + boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || launchSingleInstance || launchSingleTask; + // If bring to front is requested, and no result is requested and we have not + // been given an explicit task to launch in to, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + putIntoExistingTask &= inTask == null && r.resultTo == null; + ActivityRecord intentActivity = null; + if (putIntoExistingTask) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + intentActivity = launchSingleInstance ? mSupervisor.findActivityLocked(intent, r.info) + : mSupervisor.findTaskLocked(r); + } + return intentActivity; + } + + void setWindowManager(WindowManagerService wm) { + mWindowManager = wm; + } + + void removePendingActivityLaunchesLocked(ActivityStack stack) { + for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) { + PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx); + if (pal.stack == stack) { + mPendingActivityLaunches.remove(palNdx); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 7c42ae103e8a..901749ed874c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -141,6 +141,7 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } abandonSession = false; + pw.println("Success"); return 0; } finally { if (abandonSession) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index ef2f29b67e20..da1e5468a649 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -65,7 +65,9 @@ import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.PackageManagerService.DumpState; import com.android.server.pm.PermissionsState.PermissionState; +import java.io.BufferedInputStream; import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.Collection; import org.xmlpull.v1.XmlPullParser; @@ -2799,9 +2801,9 @@ final class Settings { } if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f); - FileInputStream str = null; + InputStream str = null; try { - str = new FileInputStream(f); + str = new BufferedInputStream(new FileInputStream(f)); XmlPullParser parser = Xml.newPullParser(); parser.setInput(str, null); |