Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54e7036e authored by MingWei's avatar MingWei
Browse files

Add AppFunctionUriGrant API

Flag: android.permission.flags.app_function_access_api_enabled
Test: atest AppFunctionGrantUri
Bug: 418982101
Change-Id: Ie9931a491d35f00cf427dd6651d8e7d56637ae54
parent c92c6491
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -8973,6 +8973,15 @@ package android.app.appfunctions {
    field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
  }
  @FlaggedApi("android.permission.flags.app_function_access_api_enabled") public final class AppFunctionUriGrant implements android.os.Parcelable {
    ctor public AppFunctionUriGrant(@NonNull android.net.Uri, int);
    method public int describeContents();
    method public int getModeFlags();
    method @NonNull public android.net.Uri getUri();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.AppFunctionUriGrant> CREATOR;
  }
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public android.os.Bundle getExtras();
@@ -8993,9 +9002,11 @@ package android.app.appfunctions {
  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
    ctor @FlaggedApi("android.permission.flags.app_function_access_api_enabled") public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle, @NonNull java.util.List<android.app.appfunctions.AppFunctionUriGrant>);
    method public int describeContents();
    method @NonNull public android.os.Bundle getExtras();
    method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
    method @FlaggedApi("android.permission.flags.app_function_access_api_enabled") @NonNull public java.util.List<android.app.appfunctions.AppFunctionUriGrant> getUriGrants();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
    field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
+181 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app.appfunctions;

import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.permission.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;

/**
 * Represents a {@link android.net.Uri} for which temporary access permission is to be granted to
 * the caller of an AppFunction execution.
 *
 * <p>This class encapsulates a {@link android.net.Uri} along with the specific access mode flags
 * (e.g., {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}) that define the type of
 * temporary access to be granted for that URI. However, {@link
 * android.content.Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} is not allowed as only the
 * temporary access can be granted.
 *
 * <p>When an AppFunction implementation returns an {@link ExecuteAppFunctionResponse} containing
 * a {@link Uri}, the {@link Uri} itself must be placed in either
 * {@link ExecuteAppFunctionResponse#getResultDocument()} ()} or
 * {@link ExecuteAppFunctionResponse#getExtras()}. Concurrently, a corresponding
 * {@link AppFunctionUriGrant} detailing the intended permissions must be added to
 * {@link ExecuteAppFunctionResponse#getUriGrants()}. This ensures the App Function's caller
 * receives the necessary access rights to the returned {@link Uri}.
 *
 * <p>To succeed, the content provider owning the Uri must have set the {@link
 * android.R.styleable#AndroidManifestProvider_grantUriPermissions grantUriPermissions} attribute in
 * its manifest or included the {@link android.R.styleable#AndroidManifestGrantUriPermission
 * &lt;grant-uri-permissions&gt;} tag.
 *
 * @see ExecuteAppFunctionResponse#ExecuteAppFunctionResponse(GenericDocument, Bundle, List)
 * @see android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION
 * @see android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION
 * @see android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
 */
@FlaggedApi(Flags.FLAG_APP_FUNCTION_ACCESS_API_ENABLED)
public final class AppFunctionUriGrant implements Parcelable {
    private static final int ALLOWED_MODE_FLAG_MASK = FLAG_GRANT_READ_URI_PERMISSION
            | FLAG_GRANT_WRITE_URI_PERMISSION
            | FLAG_GRANT_PREFIX_URI_PERMISSION;

    @NonNull
    public static final Creator<AppFunctionUriGrant> CREATOR =
            new Creator<AppFunctionUriGrant>() {
                @Override
                public AppFunctionUriGrant createFromParcel(Parcel parcel) {
                    final Uri uri = Objects.requireNonNull(Uri.CREATOR.createFromParcel(parcel));
                    final int modeFlags = parcel.readInt();
                    return new AppFunctionUriGrant(uri, modeFlags);
                }

                @Override
                public AppFunctionUriGrant[] newArray(int size) {
                    return new AppFunctionUriGrant[size];
                }
            };

    /** The {@link android.net.Uri} to be granted. */
    @NonNull private final Uri mUri;

    /** The access mode flags. */
    @GrantUriMode private final int mModeFlags;

    /** @hide */
    @IntDef(
            flag = true,
            prefix = {"FLAG_GRANT_"},
            value = {
                FLAG_GRANT_READ_URI_PERMISSION,
                FLAG_GRANT_WRITE_URI_PERMISSION,
                FLAG_GRANT_PREFIX_URI_PERMISSION
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface GrantUriMode {}

    /**
     * Create a new {@link AppFunctionUriGrant}
     *
     * @param uri The {@link Uri} to be granted.
     * @param modeFlags The access mode flags.
     *     This value must include at least one of
     *     {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} or
     *     {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
     *     It may optionally also include
     *     {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
     */
    public AppFunctionUriGrant(@NonNull Uri uri, @GrantUriMode int modeFlags) {
        mUri = Objects.requireNonNull(uri);
        if ((modeFlags & ~ALLOWED_MODE_FLAG_MASK) != 0) {
            throw new IllegalArgumentException(
                    "Contains invalid flags: Allowed flags are FLAG_GRANT_READ_URI_PERMISSION, "
                            + "FLAG_GRANT_WRITE_URI_PERMISSION and "
                            + "FLAG_GRANT_PREFIX_URI_PERMISSION");
        }
        if (!Intent.isAccessUriMode(modeFlags)) {
            throw new IllegalArgumentException(
                    "Must set either FLAG_GRANT_READ_URI_PERMISSION or "
                            + "FLAG_GRANT_WRITE_URI_PERMISSION to specify the access mode");
        }
        mModeFlags = modeFlags;
    }

    /** Return the {@link Uri} to be granted. */
    @NonNull
    public Uri getUri() {
        return mUri;
    }

    /** Return the access mode flags. */
    @GrantUriMode
    public int getModeFlags() {
        return mModeFlags;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AppFunctionUriGrant that = (AppFunctionUriGrant) o;
        return mModeFlags == that.mModeFlags && mUri.equals(that.mUri);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mUri, mModeFlags);
    }

    @NonNull
    @Override
    public String toString() {
        return "AppFunctionGrantUri("
                + "uri="
                + mUri.toString()
                + ","
                + "modeFlags"
                + mModeFlags
                + ")";
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        Uri.writeToParcel(dest, mUri);
        dest.writeInt(mModeFlags);
    }

    @Override
    public int describeContents() {
        return 0;
    }
}
+48 −1
Original line number Diff line number Diff line
@@ -24,7 +24,10 @@ import android.app.appsearch.GenericDocument;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.permission.flags.Flags;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
@@ -50,8 +53,16 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
                    Bundle extras =
                            Objects.requireNonNull(
                                    parcel.readBundle(Bundle.class.getClassLoader()));
                    if (Flags.appFunctionAccessApiEnabled()) {
                        List<AppFunctionUriGrant> uriGrants =
                                Objects.requireNonNull(
                                        parcel.createTypedArrayList(AppFunctionUriGrant.CREATOR));
                        return new ExecuteAppFunctionResponse(
                                resultWrapper.getValue(), extras, uriGrants);
                    } else {
                        return new ExecuteAppFunctionResponse(resultWrapper.getValue(), extras);
                    }
                }

                @Override
                public ExecuteAppFunctionResponse[] newArray(int size) {
@@ -88,6 +99,12 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
    /** Returns the additional metadata data relevant to this function execution response. */
    @NonNull private final Bundle mExtras;

    /**
     * The list of {@link AppFunctionUriGrant} to which the caller of this
     * app function execution should have temporary access granted.
     */
    @NonNull private final List<AppFunctionUriGrant> mUriGrants;

    /**
     * @param resultDocument The return value of the executed function.
     */
@@ -103,6 +120,23 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
            @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
        mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument));
        mExtras = Objects.requireNonNull(extras);
        mUriGrants = Collections.emptyList();
    }

    /**
     * @param resultDocument The return value of the executed function.
     * @param extras The additional metadata for this function execution response.
     * @param uriGrants The list of {@link AppFunctionUriGrant} to which
     *     the caller of this app function execution should have temporary access granted.
     */
    @FlaggedApi(Flags.FLAG_APP_FUNCTION_ACCESS_API_ENABLED)
    public ExecuteAppFunctionResponse(
            @NonNull GenericDocument resultDocument,
            @NonNull Bundle extras,
            @NonNull List<AppFunctionUriGrant> uriGrants) {
        mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument));
        mExtras = Objects.requireNonNull(extras);
        mUriGrants = Objects.requireNonNull(uriGrants);
    }

    /**
@@ -135,6 +169,16 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
        return mExtras;
    }

    /**
     * The list of {@link AppFunctionUriGrant} to which the caller of this
     * app function execution should have temporary access granted.
     */
    @FlaggedApi(Flags.FLAG_APP_FUNCTION_ACCESS_API_ENABLED)
    @NonNull
    public List<AppFunctionUriGrant> getUriGrants() {
        return mUriGrants;
    }

    /**
     * Returns the size of the response in bytes.
     *
@@ -153,5 +197,8 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        mResultDocumentWrapper.writeToParcel(dest, flags);
        dest.writeBundle(mExtras);
        if (Flags.appFunctionAccessApiEnabled()) {
            dest.writeTypedList(mUriGrants, flags);
        }
    }
}