diff options
| author | 2024-02-09 15:16:55 +0000 | |
|---|---|---|
| committer | 2024-02-09 15:16:55 +0000 | |
| commit | 7816eba7ad5ab6bfce48950f28142a6a54d568ef (patch) | |
| tree | 3a31634b17c2e84a0ad2378f803d3c7a9e10ce9e | |
| parent | 0776401122ca6e821be62a1f976a7740dd3a6cac (diff) | |
| parent | 6adf3761979cff99ebb38468bd2eada82dfd13c5 (diff) | |
Merge "Add ComponentCaller#checkContentUriPermission API for Activity" into main
8 files changed, 372 insertions, 1 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7dedbd8daf1a..321d35de5249 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5425,6 +5425,7 @@ package android.app { @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller { ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder); + method public int checkContentUriPermission(@NonNull android.net.Uri, int); method @Nullable public String getPackage(); method public int getUid(); } diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index b8bd030872c1..a59f04bf4f3a 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -17,13 +17,16 @@ package android.app; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.os.UserHandle.getCallingUserId; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -296,6 +299,18 @@ public class ActivityClient { } } + /** Checks if the app that launched the activity has access to the URI. */ + public int checkActivityCallerContentUriPermission(IBinder activityToken, IBinder callerToken, + Uri uri, int modeFlags) { + try { + return getActivityClientController().checkActivityCallerContentUriPermission( + activityToken, callerToken, ContentProvider.getUriWithoutUserId(uri), modeFlags, + ContentProvider.getUserIdFromUri(uri, getCallingUserId())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + public void setRequestedOrientation(IBinder token, int requestedOrientation) { try { getActivityClientController().setRequestedOrientation(token, requestedOrientation); diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java index 583408ed8db9..a440dbc48db6 100644 --- a/core/java/android/app/ComponentCaller.java +++ b/core/java/android/app/ComponentCaller.java @@ -18,6 +18,9 @@ package android.app; import android.annotation.FlaggedApi; import android.annotation.Nullable; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.IBinder; import android.os.Process; @@ -118,6 +121,40 @@ public final class ComponentCaller { return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken); } + /** + * Determines whether this component caller had access to a specific content URI at launch time. + * Apps can use this API to validate content URIs coming from other apps. + * + * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only + * {@link Activity} has access to {@link ComponentCaller} instances. + * + * <p>Before using this method, note the following: + * <ul> + * <li>You must have access to the supplied URI, otherwise it will throw a + * {@link SecurityException}. + * <li>This is not a real time check, i.e. the permissions have been computed at launch + * time. + * <li>This method will return the correct result for content URIs passed at launch time, + * specifically the ones from {@link Intent#getData()}, and {@link Intent#getClipData()} in + * the intent of {@code startActivity(intent)}. For others, it will throw an + * {@link IllegalArgumentException}. + * </ul> + * + * @param uri The content uri that is being checked + * @param modeFlags The access modes to check + * @return {@link PackageManager#PERMISSION_GRANTED} if this activity caller is allowed to + * access that uri, or {@link PackageManager#PERMISSION_DENIED} if it is not + * @throws IllegalArgumentException if uri is a non-content URI or it wasn't passed at launch + * @throws SecurityException if you don't have access to uri + * + * @see android.content.Context#checkContentUriPermissionFull(Uri, int, int, int) + */ + @PackageManager.PermissionResult + public int checkContentUriPermission(@NonNull Uri uri, @Intent.AccessUriMode int modeFlags) { + return ActivityClient.getInstance().checkActivityCallerContentUriPermission(mActivityToken, + mCallerToken, uri, modeFlags); + } + @Override public boolean equals(@Nullable Object obj) { if (obj == null || !(obj instanceof ComponentCaller other)) { diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 5b044f616487..05fee72b7e61 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -23,6 +23,7 @@ import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Intent; import android.content.res.Configuration; +import android.net.Uri; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.PersistableBundle; @@ -91,6 +92,9 @@ interface IActivityClientController { int getLaunchedFromUid(in IBinder token); String getLaunchedFromPackage(in IBinder token); + int checkActivityCallerContentUriPermission(in IBinder activityToken, in IBinder callerToken, + in Uri uri, int modeFlags, int userId); + void setRequestedOrientation(in IBinder token, int requestedOrientation); int getRequestedOrientation(in IBinder token); diff --git a/services/core/java/com/android/server/wm/ActivityCallerState.java b/services/core/java/com/android/server/wm/ActivityCallerState.java new file mode 100644 index 000000000000..4416605d9f04 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityCallerState.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.content.ClipData; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Intent; +import android.net.Uri; +import android.os.IBinder; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.util.XmlUtils; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.uri.GrantUri; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.WeakHashMap; + +/** + * Represents the state of activity callers. Used by {@link ActivityRecord}. + * @hide + */ +final class ActivityCallerState { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityCallerState" : TAG_ATM; + + // XML tags for CallerInfo + private static final String TAG_READABLE_CONTENT_URI = "readable_content_uri"; + private static final String TAG_WRITABLE_CONTENT_URI = "writable_content_uri"; + private static final String TAG_INACCESSIBLE_CONTENT_URI = "inaccessible_content_uri"; + private static final String ATTR_SOURCE_USER_ID = "source_user_id"; + private static final String ATTR_URI = "uri"; + private static final String ATTR_PREFIX = "prefix"; + + // Map for storing CallerInfo instances + private final WeakHashMap<IBinder, CallerInfo> mCallerTokenInfoMap = new WeakHashMap<>(); + + final ActivityTaskManagerService mAtmService; + + ActivityCallerState(ActivityTaskManagerService service) { + mAtmService = service; + } + + CallerInfo getCallerInfoOrNull(IBinder callerToken) { + return mCallerTokenInfoMap.getOrDefault(callerToken, null); + } + + void add(IBinder callerToken, CallerInfo callerInfo) { + mCallerTokenInfoMap.put(callerToken, callerInfo); + } + + void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) { + final CallerInfo callerInfo = new CallerInfo(); + mCallerTokenInfoMap.put(callerToken, callerInfo); + + final ArraySet<Uri> contentUris = getContentUrisFromIntent(intent); + for (int i = contentUris.size() - 1; i >= 0; i--) { + final Uri contentUri = contentUris.valueAt(i); + + final boolean hasRead = addContentUriIfUidHasPermission(contentUri, callerUid, + Intent.FLAG_GRANT_READ_URI_PERMISSION, callerInfo.mReadableContentUris); + + final boolean hasWrite = addContentUriIfUidHasPermission(contentUri, callerUid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, callerInfo.mWritableContentUris); + + if (!hasRead && !hasWrite) { + callerInfo.mInaccessibleContentUris.add(convertToGrantUri(contentUri, + /* modeFlags */ 0)); + } + } + } + + boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) { + if (!Intent.isAccessUriMode(modeFlags)) { + throw new IllegalArgumentException("Mode flags are not access URI mode flags: " + + modeFlags); + } + + final CallerInfo callerInfo = mCallerTokenInfoMap.getOrDefault(callerToken, null); + if (callerInfo == null) { + Slog.e(TAG, "Caller not found for checkContentUriPermission of: " + + grantUri.uri.toSafeString()); + return false; + } + + if (callerInfo.mInaccessibleContentUris.contains(grantUri)) { + return false; + } + + final boolean readMet = callerInfo.mReadableContentUris.contains(grantUri); + final boolean writeMet = callerInfo.mWritableContentUris.contains(grantUri); + + if (!readMet && !writeMet) { + throw new IllegalArgumentException("The supplied URI wasn't passed at launch: " + + grantUri.uri.toSafeString()); + } + + final boolean checkRead = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0; + if (checkRead && !readMet) { + return false; + } + + final boolean checkWrite = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0; + if (checkWrite && !writeMet) { + return false; + } + + return true; + } + + private boolean addContentUriIfUidHasPermission(Uri contentUri, int uid, int modeFlags, + ArraySet<GrantUri> grantUris) { + final GrantUri grantUri = convertToGrantUri(contentUri, modeFlags); + if (mAtmService.mUgmInternal.checkUriPermission(grantUri, uid, + modeFlags, /* isFullAccessForContentUri */ true)) { + grantUris.add(grantUri); + return true; + } + return false; + } + + private static GrantUri convertToGrantUri(Uri contentUri, int modeFlags) { + return new GrantUri(ContentProvider.getUserIdFromUri(contentUri, + UserHandle.getCallingUserId()), ContentProvider.getUriWithoutUserId(contentUri), + modeFlags); + } + + private static ArraySet<Uri> getContentUrisFromIntent(Intent intent) { + final ArraySet<Uri> uris = new ArraySet<>(); + if (intent == null) return uris; + + // getData + addUriIfContentUri(intent.getData(), uris); + + final ClipData clipData = intent.getClipData(); + if (clipData == null) return uris; + + for (int i = 0; i < clipData.getItemCount(); i++) { + final ClipData.Item item = clipData.getItemAt(i); + + // getUri + addUriIfContentUri(item.getUri(), uris); + + // getIntent + uris.addAll(getContentUrisFromIntent(item.getIntent())); + } + return uris; + } + + private static void addUriIfContentUri(Uri uri, ArraySet<Uri> uris) { + if (uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + uris.add(uri); + } + } + + public static final class CallerInfo { + final ArraySet<GrantUri> mReadableContentUris = new ArraySet<>(); + final ArraySet<GrantUri> mWritableContentUris = new ArraySet<>(); + final ArraySet<GrantUri> mInaccessibleContentUris = new ArraySet<>(); + + public void saveToXml(TypedXmlSerializer out) + throws IOException, XmlPullParserException { + for (int i = mReadableContentUris.size() - 1; i >= 0; i--) { + saveGrantUriToXml(out, mReadableContentUris.valueAt(i), TAG_READABLE_CONTENT_URI); + } + + for (int i = mWritableContentUris.size() - 1; i >= 0; i--) { + saveGrantUriToXml(out, mWritableContentUris.valueAt(i), TAG_WRITABLE_CONTENT_URI); + } + + for (int i = mInaccessibleContentUris.size() - 1; i >= 0; i--) { + saveGrantUriToXml(out, mInaccessibleContentUris.valueAt(i), + TAG_INACCESSIBLE_CONTENT_URI); + } + } + + public static CallerInfo restoreFromXml(TypedXmlPullParser in) + throws IOException, XmlPullParserException { + CallerInfo callerInfo = new CallerInfo(); + final int outerDepth = in.getDepth(); + int event; + while (((event = in.next()) != END_DOCUMENT) + && (event != END_TAG || in.getDepth() >= outerDepth)) { + if (event == START_TAG) { + final String name = in.getName(); + if (TAG_READABLE_CONTENT_URI.equals(name)) { + callerInfo.mReadableContentUris.add(restoreGrantUriFromXml(in)); + } else if (TAG_WRITABLE_CONTENT_URI.equals(name)) { + callerInfo.mWritableContentUris.add(restoreGrantUriFromXml(in)); + } else if (TAG_INACCESSIBLE_CONTENT_URI.equals(name)) { + callerInfo.mInaccessibleContentUris.add(restoreGrantUriFromXml(in)); + } else { + Slog.w(TAG, "restoreActivity: unexpected name=" + name); + XmlUtils.skipCurrentTag(in); + } + } + } + return callerInfo; + } + + private void saveGrantUriToXml(TypedXmlSerializer out, GrantUri grantUri, String tag) + throws IOException, XmlPullParserException { + out.startTag(null, tag); + out.attributeInt(null, ATTR_SOURCE_USER_ID, grantUri.sourceUserId); + out.attribute(null, ATTR_URI, String.valueOf(grantUri.uri)); + out.attributeBoolean(null, ATTR_PREFIX, grantUri.prefix); + out.endTag(null, tag); + } + + private static GrantUri restoreGrantUriFromXml(TypedXmlPullParser in) + throws IOException, XmlPullParserException { + int sourceUserId = in.getAttributeInt(null, ATTR_SOURCE_USER_ID, 0); + Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI)); + boolean prefix = in.getAttributeBoolean(null, ATTR_PREFIX, false); + return new GrantUri(sourceUserId, uri, + prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0); + } + } +} diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 2e0546eee8e7..173e139e124c 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -30,6 +30,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -80,6 +82,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -103,6 +106,7 @@ import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.KnownPackages; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.uri.GrantUri; import com.android.server.uri.NeededUriGrants; import com.android.server.vr.VrManagerInternal; @@ -715,6 +719,32 @@ class ActivityClientController extends IActivityClientController.Stub { return null; } + /** + * @param uri This uri must NOT contain an embedded userId. + * @param userId The userId in which the uri is to be resolved. + */ + @Override + public int checkActivityCallerContentUriPermission(IBinder activityToken, IBinder callerToken, + Uri uri, int modeFlags, int userId) { + // 1. Check if we have access to the URI - > throw if we don't + GrantUri grantUri = new GrantUri(userId, uri, modeFlags); + if (!mService.mUgmInternal.checkUriPermission(grantUri, Binder.getCallingUid(), modeFlags, + /* isFullAccessForContentUri */ true)) { + throw new SecurityException("You don't have access to the content URI, hence can't" + + " check if the caller has access to it: " + uri); + } + + // 2. Get the permission result for the caller + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null) { + boolean granted = r.checkContentUriPermission(callerToken, grantUri, modeFlags); + return granted ? PERMISSION_GRANTED : PERMISSION_DENIED; + } + } + return PERMISSION_DENIED; + } + /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */ private boolean isInternalCallerGetLaunchedFrom(int uid) { if (UserHandle.getAppId(uid) == SYSTEM_UID) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c2117eaa72c4..09c329be7d09 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -381,6 +381,7 @@ import com.android.server.am.PendingIntentRecord; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.display.color.ColorDisplayService; import com.android.server.pm.UserManagerInternal; +import com.android.server.uri.GrantUri; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; @@ -436,6 +437,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private static final String ATTR_LAUNCHEDFROMFEATURE = "launched_from_feature"; private static final String ATTR_RESOLVEDTYPE = "resolved_type"; private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; + private static final String TAG_INITIAL_CALLER_INFO = "initial_caller_info"; static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; // How many activities have to be scheduled to stop to force a stop pass. @@ -472,6 +474,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f; final ActivityTaskManagerService mAtmService; + final ActivityCallerState mCallerState; @NonNull final ActivityInfo info; // activity info provided by developer in AndroidManifest // Which user is this running for? @@ -2021,6 +2024,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + void computeInitialCallerInfo() { + computeCallerInfo(initialCallerInfoAccessToken, intent, launchedFromUid); + } + + void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) { + mCallerState.computeCallerInfo(callerToken, intent, callerUid); + } + + boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) { + return mCallerState.checkContentUriPermission(callerToken, grantUri, modeFlags); + } + private ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller, int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage, @Nullable String _launchedFromFeature, Intent _intent, String _resolvedType, @@ -2246,6 +2261,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } return appContext; }); + mCallerState = new ActivityCallerState(mAtmService); } /** @@ -10113,6 +10129,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mPersistentState.saveToXml(out); out.endTag(null, TAG_PERSISTABLEBUNDLE); } + + if (android.security.Flags.contentUriPermissionApis()) { + ActivityCallerState.CallerInfo initialCallerInfo = mCallerState.getCallerInfoOrNull( + initialCallerInfoAccessToken); + if (initialCallerInfo != null) { + out.startTag(null, TAG_INITIAL_CALLER_INFO); + initialCallerInfo.saveToXml(out); + out.endTag(null, TAG_INITIAL_CALLER_INFO); + } + } } static ActivityRecord restoreFromXml(TypedXmlPullParser in, @@ -10127,6 +10153,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A int userId = in.getAttributeInt(null, ATTR_USERID, 0); long createTime = in.getAttributeLong(null, ATTR_ID, -1); final int outerDepth = in.getDepth(); + ActivityCallerState.CallerInfo initialCallerInfo = null; TaskDescription taskDescription = new TaskDescription(); taskDescription.restoreFromXml(in); @@ -10146,6 +10173,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A persistentState = PersistableBundle.restoreFromXml(in); if (DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: persistentState=" + persistentState); + } else if (android.security.Flags.contentUriPermissionApis() + && TAG_INITIAL_CALLER_INFO.equals(name)) { + initialCallerInfo = ActivityCallerState.CallerInfo.restoreFromXml(in); } else { Slog.w(TAG, "restoreActivity: unexpected name=" + name); XmlUtils.skipCurrentTag(in); @@ -10164,7 +10194,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent + " resolvedType=" + resolvedType); } - return new ActivityRecord.Builder(service) + final ActivityRecord r = new ActivityRecord.Builder(service) .setLaunchedFromUid(launchedFromUid) .setLaunchedFromPackage(launchedFromPackage) .setLaunchedFromFeature(launchedFromFeature) @@ -10176,6 +10206,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A .setTaskDescription(taskDescription) .setCreateTime(createTime) .build(); + + if (android.security.Flags.contentUriPermissionApis() && initialCallerInfo != null) { + r.mCallerState.add(r.initialCallerInfoAccessToken, initialCallerInfo); + } + return r; } private static boolean isInVrUiMode(Configuration config) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d99000efeeb4..07afa5fc21be 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1586,6 +1586,10 @@ class ActivityStarter { return null; } + if (android.security.Flags.contentUriPermissionApis() && started.isAttached()) { + started.computeInitialCallerInfo(); + } + // Apply setAlwaysOnTop when starting an activity is successful regardless of creating // a new Activity or reusing the existing activity. if (options != null && options.getTaskAlwaysOnTop()) { |