Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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(); } core/java/android/app/ActivityClient.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading core/java/android/app/ComponentCaller.java +37 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)) { Loading core/java/android/app/IActivityClientController.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading services/core/java/com/android/server/wm/ActivityCallerState.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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); } } } Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -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(); }
core/java/android/app/ActivityClient.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading
core/java/android/app/ComponentCaller.java +37 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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)) { Loading
core/java/android/app/IActivityClientController.aidl +4 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading
services/core/java/com/android/server/wm/ActivityCallerState.java 0 → 100644 +245 −0 Original line number Diff line number Diff line /* * 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); } } }