Add ComponentCaller#checkContentUriPermission API for Activity

The new API checks if the app that launched the activity, i.e. activity
caller, had access to the content URI at launch time. It doesn't perform
a real time check to ensure no loss of grant information in the case of
caller's termination, hence only works for content URIs passed at
launch to mimic the lifetime of grant flags. Finally, for security
reasons, the method requires the caller of the API to the same access to
the content URI, otherwise it throws.

The code for this check is located in a new class ActivityCallerState.

Bug: 293467489
Test: atest CtsAndroidAppTestCases:android.app.cts.ComponentCallerTest
Change-Id: Ide1ea3470e8cc48f4d59e431ef19681050273af2
diff --git a/core/api/current.txt b/core/api/current.txt
index bfa486b..48887df6 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()) {