Merge "Add ComponentCaller#checkContentUriPermission API for Activity" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7dedbd8..321d35d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5425,6 +5425,7 @@
@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 b8bd030..a59f04b 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 @@
}
}
+ /** 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 583408e..a440dbc 100644
--- a/core/java/android/app/ComponentCaller.java
+++ b/core/java/android/app/ComponentCaller.java
@@ -18,6 +18,9 @@
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 @@
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 5b044f6..05fee72 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -23,6 +23,7 @@
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 @@
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 0000000..4416605
--- /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 2e0546e..173e139 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_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.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.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 @@
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 c2117ea..09c329b 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.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 @@
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 @@
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 @@
}
}
+ 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 @@
}
return appContext;
});
+ mCallerState = new ActivityCallerState(mAtmService);
}
/**
@@ -10113,6 +10129,16 @@
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 @@
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 @@
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 @@
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 @@
.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 d99000e..07afa5f 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 @@
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()) {