/*
 * Copyright (C) 2021 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.content;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.permission.flags.Flags;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.annotations.Immutable;

import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;

/**
 * This class represents a source to which access to permission protected data should be
 * attributed. Attribution sources can be chained to represent cases where the protected
 * data would flow through several applications. For example, app A may ask app B for
 * contacts and in turn app B may ask app C for contacts. In this case, the attribution
 * chain would be A -> B -> C and the data flow would be C -> B -> A. There are two
 * main benefits of using the attribution source mechanism: avoid doing explicit permission
 * checks on behalf of the calling app if you are accessing private data on their behalf
 * to send back; avoid double data access blaming which happens as you check the calling
 * app's permissions and when you access the data behind these permissions (for runtime
 * permissions). Also if not explicitly blaming the caller the data access would be
 * counted towards your app vs to the previous app where yours was just a proxy.
 * <p>
 * Every {@link Context} has an attribution source and you can get it via {@link
 * Context#getAttributionSource()} representing itself, which is a chain of one. You
 * can attribute work to another app, or more precisely to a chain of apps, through
 * which the data you would be accessing would flow, via {@link Context#createContext(
 * ContextParams)} plus specifying an attribution source for the next app to receive
 * the protected data you are accessing via {@link AttributionSource.Builder#setNext(
 * AttributionSource)}. Creating this attribution chain ensures that the datasource would
 * check whether every app in the attribution chain has permission to access the data
 * before releasing it. The datasource will also record appropriately that this data was
 * accessed by the apps in the sequence if the data is behind a sensitive permission
 * (e.g. dangerous). Again, this is useful if you are accessing the data on behalf of another
 * app, for example a speech recognizer using the mic so it can provide recognition to
 * a calling app.
 * <p>
 * You can create an attribution chain of you and any other app without any verification
 * as this is something already available via the {@link android.app.AppOpsManager} APIs.
 * This is supported to handle cases where you don't have access to the caller's attribution
 * source and you can directly use the {@link AttributionSource.Builder} APIs. However,
 * if the data flows through more than two apps (more than you access the data for the
 * caller) you need to have a handle to the {@link AttributionSource} for the calling app's
 * context in order to create an attribution context. This means you either need to have an
 * API for the other app to send you its attribution source or use a platform API that pipes
 * the callers attribution source.
 * <p>
 * You cannot forge an attribution chain without the participation of every app in the
 * attribution chain (aside of the special case mentioned above). To create an attribution
 * source that is trusted you need to create an attribution context that points to an
 * attribution source that was explicitly created by the app that it refers to, recursively.
 * <p>
 * Since creating an attribution context leads to all permissions for apps in the attribution
 * chain being checked, you need to expect getting a security exception when accessing
 * permission protected APIs since some app in the chain may not have the permission.
 */
@Immutable
public final class AttributionSource implements Parcelable {
    private static final String TAG = "AttributionSource";
    private static final String DESCRIPTOR = "android.content.AttributionSource";

    private static final Binder sDefaultToken = new Binder(DESCRIPTOR);

    private final @NonNull AttributionSourceState mAttributionSourceState;

    private @Nullable AttributionSource mNextCached;
    private @Nullable Set<String> mRenouncedPermissionsCached;

    /** @hide */
    @TestApi
    public AttributionSource(int uid, @Nullable String packageName,
            @Nullable String attributionTag) {
        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
    }

    /** @hide */
    public AttributionSource(int uid, @Nullable String packageName,
            @Nullable String attributionTag, int virtualDeviceId) {
        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken, null,
                virtualDeviceId, null);
    }

    /** @hide */
    public AttributionSource(int uid, int pid, @Nullable String packageName,
            @Nullable String attributionTag) {
        this(uid, pid, packageName, attributionTag, sDefaultToken);
    }

    /** @hide */
    @TestApi
    public AttributionSource(int uid, @Nullable String packageName,
            @Nullable String attributionTag, @NonNull IBinder token) {
        this(uid, Process.INVALID_PID, packageName, attributionTag, token,
                /*renouncedPermissions*/ null, Context.DEVICE_ID_DEFAULT, /*next*/ null);
    }

    /** @hide */
    public AttributionSource(int uid, int pid, @Nullable String packageName,
            @Nullable String attributionTag, @NonNull IBinder token) {
        this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
                Context.DEVICE_ID_DEFAULT, /*next*/ null);
    }

    /** @hide */
    @TestApi
    public AttributionSource(int uid, @Nullable String packageName,
            @Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
            @Nullable AttributionSource next) {
        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
                (renouncedPermissions != null)
                ? renouncedPermissions.toArray(new String[0]) : null, Context.DEVICE_ID_DEFAULT,
                /*next*/ next);
    }

    /** @hide */
    public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
        this(current.getUid(), current.getPid(), current.getPackageName(),
                current.getAttributionTag(), current.getToken(),
                current.mAttributionSourceState.renouncedPermissions, current.getDeviceId(), next);
    }

    /** @hide */
    public AttributionSource(int uid, int pid, @Nullable String packageName,
            @Nullable String attributionTag, @Nullable String[] renouncedPermissions, int deviceId,
            @Nullable AttributionSource next) {
        this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, deviceId,
                next);
    }

    /** @hide */
    @TestApi
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public AttributionSource(int uid, int pid, @Nullable String packageName,
            @Nullable String attributionTag, @NonNull IBinder token,
            @Nullable String[] renouncedPermissions,
            int deviceId, @Nullable AttributionSource next) {
        mAttributionSourceState = new AttributionSourceState();
        mAttributionSourceState.uid = uid;
        mAttributionSourceState.pid = pid;
        mAttributionSourceState.token = token;
        mAttributionSourceState.packageName = packageName;
        mAttributionSourceState.attributionTag = attributionTag;
        mAttributionSourceState.renouncedPermissions = renouncedPermissions;
        mAttributionSourceState.deviceId = deviceId;
        mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
                {next.mAttributionSourceState} : new AttributionSourceState[0];
    }

    AttributionSource(@NonNull Parcel in) {
        this(AttributionSourceState.CREATOR.createFromParcel(in));

        if (!Binder.isDirectlyHandlingTransaction()) {
            throw new SecurityException("AttributionSource should be unparceled during a binder "
                    + "transaction for proper verification.");
        }

        // Since we just unpacked this object as part of it transiting a Binder
        // call, this is the perfect time to enforce that its UID and PID can be trusted
        enforceCallingUid();

        // If this object is being constructed as part of a oneway Binder call, getCallingPid will
        // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
        // INVALID_PID (-1).
        final int callingPid = Binder.getCallingPid();
        if (callingPid == 0) {
            mAttributionSourceState.pid = Process.INVALID_PID;
        }

        enforceCallingPid();
    }

    /** @hide */
    public AttributionSource(@NonNull AttributionSourceState attributionSourceState) {
        mAttributionSourceState = attributionSourceState;
    }

    /** @hide */
    public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), next);
    }

    /** @hide */
    public AttributionSource withPackageName(@Nullable String packageName) {
        return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
               getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
    }

    /** @hide */
    public AttributionSource withToken(@NonNull IBinder token) {
        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
    }

    /** @hide */
    public AttributionSource withDefaultToken() {
        return withToken(sDefaultToken);
    }

    /** @hide */
    public AttributionSource withPid(int pid) {
        return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
                getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
    }

    /** @hide */
    public AttributionSource withDeviceId(int deviceId) {
        return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
                getToken(), mAttributionSourceState.renouncedPermissions, deviceId, getNext());
    }

    /** @hide */
    public @NonNull AttributionSourceState asState() {
        return mAttributionSourceState;
    }

    /** @hide */
    public @NonNull ScopedParcelState asScopedParcelState() {
        return new ScopedParcelState(this);
    }

    /**
     * Returns a generic {@link AttributionSource} that represents the entire
     * calling process.
     *
     * <p>Callers are <em>strongly</em> encouraged to use a more specific
     * attribution source whenever possible, such as from
     * {@link Context#getAttributionSource()}, since that enables developers to
     * have more detailed and scoped control over attribution within
     * sub-components of their app.
     *
     * @see Context#createAttributionContext(String)
     * @see Context#getAttributionTag()
     * @return a generic {@link AttributionSource} representing the entire
     *         calling process
     * @throws IllegalStateException when no accurate {@link AttributionSource}
     *         can be determined
     */
    public static @NonNull AttributionSource myAttributionSource() {

        final AttributionSource globalSource = ActivityThread.currentAttributionSource();
        if (globalSource != null) {
            if (Flags.enforceDefaultDeviceIdInMyAttributionSource()
                    && globalSource.getDeviceId() != Context.DEVICE_ID_DEFAULT) {
                Log.w(TAG,
                        "Avoid using myAttributionSource() to fetch an attributionSource with a "
                                + "non-default device Id");
                return globalSource.withDeviceId(Context.DEVICE_ID_DEFAULT);
            }
            return globalSource;
        }

        int uid = Process.myUid();
        if (uid == Process.ROOT_UID) {
            uid = Process.SYSTEM_UID;
        }
        try {
            return new AttributionSource.Builder(uid)
                .setPid(Process.myPid())
                .setDeviceId(Context.DEVICE_ID_DEFAULT)
                .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                .build();
        } catch (Exception ignored) {
        }

        throw new IllegalStateException("Failed to resolve AttributionSource");
    }

    /**
     * This is a scoped object that exposes the content of an attribution source
     * as a parcel. This is useful when passing one to native and avoid custom
     * conversion logic from Java to native state that needs to be kept in sync
     * as attribution source evolves. This way we use the same logic for passing
     * to native as the ones for passing in an IPC - in both cases this is the
     * same auto generated code.
     *
     * @hide
     */
    public static class ScopedParcelState implements AutoCloseable {
        private final Parcel mParcel;

        public @NonNull Parcel getParcel() {
            return mParcel;
        }

        public ScopedParcelState(AttributionSource attributionSource) {
            mParcel = Parcel.obtain();
            attributionSource.writeToParcel(mParcel, 0);
            mParcel.setDataPosition(0);
        }

        public void close() {
            mParcel.recycle();
        }
    }

    /**
     * If you are handling an IPC and you don't trust the caller you need to validate
     * whether the attribution source is one for the calling app to prevent the caller
     * to pass you a source from another app without including themselves in the
     * attribution chain.
     *
     * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
     */
    public void enforceCallingUid() {
        if (!checkCallingUid()) {
            throw new SecurityException("Calling uid: " + Binder.getCallingUid()
                    + " doesn't match source uid: " + mAttributionSourceState.uid);
        }
        // No need to check package as app ops manager does it already.
    }

    /**
     * If you are handling an IPC and you don't trust the caller you need to validate
     * whether the attribution source is one for the calling app to prevent the caller
     * to pass you a source from another app without including themselves in the
     * attribution chain.
     *
     * @return if the attribution source cannot be trusted to be from the caller.
     */
    public boolean checkCallingUid() {
        final int callingUid = Binder.getCallingUid();
        if (callingUid != Process.ROOT_UID
                && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
                && callingUid != mAttributionSourceState.uid) {
            return false;
        }
        // No need to check package as app ops manager does it already.
        return true;
    }

    /**
     * Validate that the pid being claimed for the calling app is not spoofed.
     *
     * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
     * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
     *
     * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
     * @hide
     */
    @TestApi
    public void enforceCallingPid() {
        if (!checkCallingPid()) {
            if (Binder.getCallingPid() == 0) {
                throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
            } else {
                throw new SecurityException("Calling pid: " + Binder.getCallingPid()
                        + " doesn't match source pid: " + mAttributionSourceState.pid);
            }
        }
    }

    /**
     * Validate that the pid being claimed for the calling app is not spoofed
     *
     * @return if the attribution source cannot be trusted to be from the caller.
     */
    private boolean checkCallingPid() {
        final int callingPid = Binder.getCallingPid();
        if (mAttributionSourceState.pid != Process.INVALID_PID
                && callingPid != mAttributionSourceState.pid) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        if (Build.IS_DEBUGGABLE) {
            return "AttributionSource { " +
                    "uid = " + mAttributionSourceState.uid + ", " +
                    "packageName = " + mAttributionSourceState.packageName + ", " +
                    "attributionTag = " + mAttributionSourceState.attributionTag + ", " +
                    "token = " + mAttributionSourceState.token + ", " +
                    "deviceId = " + mAttributionSourceState.deviceId + ", " +
                    "next = " + (mAttributionSourceState.next != null
                    && mAttributionSourceState.next.length > 0
                    ? new AttributionSource(mAttributionSourceState.next[0]).toString() : null) +
                    " }";
        }
        return super.toString();
    }

    /**
     * @return The next UID that would receive the permission protected data.
     *
     * @hide
     */
    public int getNextUid() {
        if (mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            return mAttributionSourceState.next[0].uid;
        }
        return Process.INVALID_UID;
    }

    /**
     * @return The next package that would receive the permission protected data.
     *
     * @hide
     */
    public @Nullable String getNextPackageName() {
        if (mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            return mAttributionSourceState.next[0].packageName;
        }
        return null;
    }

    /**
     * @return The next package's attribution tag that would receive
     * the permission protected data.
     *
     * @hide
     */
    public @Nullable String getNextAttributionTag() {
        if (mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            return mAttributionSourceState.next[0].attributionTag;
        }
        return null;
    }

    /**
     * @return The next package's token that would receive
     * the permission protected data.
     *
     * @hide
     */
    public @Nullable IBinder getNextToken() {
        if (mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            return mAttributionSourceState.next[0].token;
        }
        return null;
    }

    /**
     * @return The next package's device Id from its context.
     * This device ID is used for permissions checking during attribution source validation.
     *
     * @hide
     */
    public int getNextDeviceId() {
        if (mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            return mAttributionSourceState.next[0].deviceId;
        }
        return Context.DEVICE_ID_DEFAULT;
    }

    /**
     * Checks whether this attribution source can be trusted. That is whether
     * the app it refers to created it and provided to the attribution chain.
     *
     * @param context Context handle.
     * @return Whether this is a trusted source.
     */
    public boolean isTrusted(@NonNull Context context) {
        return mAttributionSourceState.token != null
                && context.getSystemService(PermissionManager.class)
                        .isRegisteredAttributionSource(this);
    }

    /**
     * Permissions that should be considered revoked regardless if granted.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
    @NonNull
    public Set<String> getRenouncedPermissions() {
        if (mRenouncedPermissionsCached == null) {
            if (mAttributionSourceState.renouncedPermissions != null) {
                mRenouncedPermissionsCached = new ArraySet<>(
                        mAttributionSourceState.renouncedPermissions);
            } else {
                mRenouncedPermissionsCached = Collections.emptySet();
            }
        }
        return mRenouncedPermissionsCached;
    }

    /**
     * The UID that is accessing the permission protected data.
     */
    public int getUid() {
        return mAttributionSourceState.uid;
    }

    /**
     * The PID that is accessing the permission protected data.
     */
    public int getPid() {
        return mAttributionSourceState.pid;
    }

    /**
     * The package that is accessing the permission protected data.
     */
    public @Nullable String getPackageName() {
        return mAttributionSourceState.packageName;
    }

    /**
     * The attribution tag of the app accessing the permission protected data.
     */
    public @Nullable String getAttributionTag() {
        return mAttributionSourceState.attributionTag;
    }

    /**
     * Gets the device ID for this attribution source. Attribution source can set the device ID
     * using {@link Builder#setDeviceId(int)}, the default device ID is
     * {@link Context#DEVICE_ID_DEFAULT}.
     * <p>
     * This device ID is used for permissions checking during attribution source validation.
     */
    @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
    public int getDeviceId() {
        return mAttributionSourceState.deviceId;
    }

    /**
     * Unique token for that source.
     *
     * @hide
     */
    public @NonNull IBinder getToken() {
        return mAttributionSourceState.token;
    }

    /**
     * The next app to receive the permission protected data.
     */
    public @Nullable AttributionSource getNext() {
        if (mNextCached == null && mAttributionSourceState.next != null
                && mAttributionSourceState.next.length > 0) {
            mNextCached = new AttributionSource(mAttributionSourceState.next[0]);
        }
        return mNextCached;
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AttributionSource that = (AttributionSource) o;
        return equalsExceptToken(that) && Objects.equals(
                mAttributionSourceState.token, that.mAttributionSourceState.token);
    }

    /**
     * We store trusted attribution sources without their token (the token is the key to the map)
     * to avoid having a strong reference to the token. This means, when checking the equality of a
     * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to
     * compare everything except the token.
     *
     * @hide
     */
    public boolean equalsExceptToken(@Nullable AttributionSource o) {
        if (o == null) return false;
        return mAttributionSourceState.uid == o.mAttributionSourceState.uid
                && Objects.equals(mAttributionSourceState.packageName,
                o.mAttributionSourceState.packageName)
                && Objects.equals(mAttributionSourceState.attributionTag,
                o.mAttributionSourceState.attributionTag)
                && Arrays.equals(mAttributionSourceState.renouncedPermissions,
                o.mAttributionSourceState.renouncedPermissions)
                && Objects.equals(getNext(), o.getNext());
    }

    @Override
    public int hashCode() {
        return Objects.hash(mAttributionSourceState.uid, mAttributionSourceState.packageName,
                mAttributionSourceState.attributionTag, mAttributionSourceState.token,
                Arrays.hashCode(mAttributionSourceState.renouncedPermissions), getNext());
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        mAttributionSourceState.writeToParcel(dest, flags);
    }

    @Override
    public int describeContents() { return 0; }

    public static final @NonNull Parcelable.Creator<AttributionSource> CREATOR
            = new Parcelable.Creator<AttributionSource>() {
        @Override
        public AttributionSource[] newArray(int size) {
            return new AttributionSource[size];
        }

        @Override
        public AttributionSource createFromParcel(@NonNull Parcel in) {
            return new AttributionSource(in);
        }
    };

    /**
     * A builder for {@link AttributionSource}
     */
    public static final class Builder {
        private boolean mHasBeenUsed;
        private @NonNull final AttributionSourceState mAttributionSourceState =
                new AttributionSourceState();

        /**
         * Creates a new Builder.
         *
         * @param uid
         *   The UID that is accessing the permission protected data.
         */
        public Builder(int uid) {
            mAttributionSourceState.uid = uid;
            mAttributionSourceState.pid = Process.INVALID_PID;
            mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
            mAttributionSourceState.token = sDefaultToken;
        }

        /**
         * Creates a builder that is ready to build a new {@link AttributionSource} where
         * all fields (primitive, immutable data, pointers) are copied from the given
         * {@link AttributionSource}. Builder methods can still be used to mutate fields further.
         * @param current The source to copy fields from.
         */
        public Builder(@NonNull AttributionSource current) {
            if (current == null) {
                throw new IllegalArgumentException("current AttributionSource can not be null");
            }
            mAttributionSourceState.uid = current.getUid();
            mAttributionSourceState.pid = current.getPid();
            mAttributionSourceState.packageName = current.getPackageName();
            mAttributionSourceState.attributionTag = current.getAttributionTag();
            mAttributionSourceState.renouncedPermissions =
                current.mAttributionSourceState.renouncedPermissions;
            mAttributionSourceState.deviceId = current.getDeviceId();
            mAttributionSourceState.next = current.mAttributionSourceState.next;
            mAttributionSourceState.token = current.getToken();
        }

        /**
         * The PID of the process that is accessing the permission protected data.
         *
         * If not called, pid will default to {@link Process@INVALID_PID} (-1). This indicates that
         * the PID data is missing. Supplying a PID is not required, but recommended when
         * accessible.
         */
        public @NonNull Builder setPid(int value) {
            checkNotUsed();
            mAttributionSourceState.pid = value;
            return this;
        }

        /**
         * The package that is accessing the permission protected data.
         */
        public @NonNull Builder setPackageName(@Nullable String value) {
            checkNotUsed();
            mAttributionSourceState.packageName = value;
            return this;
        }

        /**
         * The attribution tag of the app accessing the permission protected data.
         */
        public @NonNull Builder setAttributionTag(@Nullable String value) {
            checkNotUsed();
            mAttributionSourceState.attributionTag = value;
            return this;
        }

        /**
         * Sets permissions which have been voluntarily "renounced" by the
         * calling app.
         * <p>
         * Interactions performed through services obtained from the created
         * Context will ideally be treated as if these "renounced" permissions
         * have not actually been granted to the app, regardless of their actual
         * grant status.
         * <p>
         * This is designed for use by separate logical components within an app
         * which have no intention of interacting with data or services that are
         * protected by the renounced permissions.
         * <p>
         * Note that only {@link PermissionInfo#PROTECTION_DANGEROUS}
         * permissions are supported by this mechanism. Additionally, this
         * mechanism only applies to calls made through services obtained via
         * {@link Context#getSystemService}; it has no effect on static or raw
         * Binder calls.
         *
         * @param renouncedPermissions The set of permissions to treat as
         *            renounced, which is as if not granted.
         * @return This builder.
         * @hide
         */
        @SystemApi
        @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
        public @NonNull Builder setRenouncedPermissions(
                @Nullable Set<String> renouncedPermissions) {
            checkNotUsed();
            mAttributionSourceState.renouncedPermissions = (renouncedPermissions != null)
                    ? renouncedPermissions.toArray(new String[0]) : null;
            return this;
        }

        /**
         * Set the device ID for this attribution source, permission check would happen
         * against this device ID.
         *
         * @return the builder
         */
        @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
        public @NonNull Builder setDeviceId(int deviceId) {
            checkNotUsed();
            mAttributionSourceState.deviceId = deviceId;
            return this;
        }

        /**
         * The next app to receive the permission protected data.
         */
        public @NonNull Builder setNext(@Nullable AttributionSource value) {
            checkNotUsed();
            mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
                    {value.mAttributionSourceState} : mAttributionSourceState.next;
            return this;
        }

        /**
         * The next app to receive the permission protected data.
         */
        @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
        public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
            checkNotUsed();
            if (value == null) {
                throw new IllegalArgumentException("Null AttributionSource not permitted.");
            }
            mAttributionSourceState.next =
                    new AttributionSourceState[]{value.mAttributionSourceState};
            return this;
        }

        /** Builds the instance. This builder should not be touched after calling this! */
        public @NonNull AttributionSource build() {
            checkNotUsed();
            mHasBeenUsed = true;

            if (mAttributionSourceState.next == null) {
                // The NDK aidl backend doesn't support null parcelable arrays.
                mAttributionSourceState.next = new AttributionSourceState[0];
            }
            return new AttributionSource(mAttributionSourceState);
        }

        private void checkNotUsed() {
            if (mHasBeenUsed) {
                throw new IllegalStateException(
                        "This Builder should not be reused. Use a new Builder instance instead");
            }
        }
    }
}
