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

Commit 9238ef21 authored by Reema Bajwa's avatar Reema Bajwa
Browse files

Add provider facing GET APIs for Credential Manager.

Credential Manager is a set of APIs that allows developers to get and
create credentials. These credentials are sourced from providers that
are registered with the android framework.

Test: Built and Deployed on device locally.
Bug: 247545196
CTS-Coverage-Bug: 247549381
Change-Id: If5b6569810be30ec6f7997e9161fe9b85d286cfc
parent d88385c1
Loading
Loading
Loading
Loading
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.service.credentials;

import android.app.PendingIntent;
import android.app.slice.Slice;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import java.util.Objects;

/**
 * An action defined by the provider that intents into the provider's app for specific
 * user actions.
 *
 * @hide
 */
public final class Action implements Parcelable {
    /** Info to be displayed with this action on the UI. */
    private final @NonNull Slice mInfo;
    /**
     * The pending intent to be invoked when the user selects this action.
     */
    private final @NonNull PendingIntent mPendingIntent;

    /**
     * Constructs an action to be displayed on the UI.
     *
     * @param actionInfo The info to be displayed along with this action.
     * @param pendingIntent The intent to be invoked when the user selects this action.
     * @throws NullPointerException If {@code actionInfo}, or {@code pendingIntent} is null.
     */
    public Action(@NonNull Slice actionInfo, @NonNull PendingIntent pendingIntent) {
        Objects.requireNonNull(actionInfo, "actionInfo must not be null");
        Objects.requireNonNull(pendingIntent, "pendingIntent must not be null");
        mInfo = actionInfo;
        mPendingIntent = pendingIntent;
    }

    private Action(@NonNull Parcel in) {
        mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
                PendingIntent.class);
    }

    public static final @NonNull Creator<Action> CREATOR = new Creator<Action>() {
        @Override
        public Action createFromParcel(@NonNull Parcel in) {
            return new Action(in);
        }

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

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

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

    /**
     * Returns the action info as a {@link Slice} object, to be displayed on the UI.
     */
    public @NonNull Slice getActionInfo() {
        return mInfo;
    }

    /**
     * Returns the {@link PendingIntent} to be invoked when the action is selected.
     */
    public @NonNull PendingIntent getPendingIntent() {
        return mPendingIntent;
    }
}
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.service.credentials;

import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import static java.util.Objects.requireNonNull;

import com.android.internal.util.Preconditions;

/**
 * A Credential object that contains type specific data that is returned from the credential
 * provider to the framework. Framework then converts it to an app facing representation and
 * returns to the calling app.
 *
 * @hide
 */
public final class Credential implements Parcelable {
    /** The type of this credential. */
    private final @NonNull String mType;

    /** The data associated with this credential. */
    private final @NonNull Bundle mData;

    /**
     * Constructs a credential object.
     *
     * @param type The type of the credential.
     * @param data The data of the credential that is passed back to the framework, and eventually
     *             to the calling app.
     * @throws NullPointerException If {@code data} is null.
     * @throws IllegalArgumentException If {@code type} is null or empty.
     */
    public Credential(@NonNull String type, @NonNull Bundle data) {
        Preconditions.checkStringNotEmpty(type, "type must not be null, or empty");
        requireNonNull(data, "data must not be null");
        this.mType = type;
        this.mData = data;
    }

    private Credential(@NonNull Parcel in) {
        mType = in.readString16NoHelper();
        mData = in.readBundle();
    }

    /**
     * Returns the type of the credential.
     */
    public @NonNull String getType() {
        return mType;
    }

    /**
     * Returns the data associated with the credential.
     */
    public @NonNull Bundle getData() {
        return mData;
    }

    public static final @NonNull Creator<Credential> CREATOR = new Creator<Credential>() {
        @Override
        public Credential createFromParcel(@NonNull Parcel in) {
            return new Credential(in);
        }

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

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString8(mType);
        dest.writeBundle(mData);
    }
}
+216 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.service.credentials;

import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.slice.Slice;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import com.android.internal.util.Preconditions;

import java.util.Objects;

/**
 * A credential entry that is displayed on the account selector UI. Each entry corresponds to
 * something that the user can select.
 *
 * @hide
 */
public final class CredentialEntry implements Parcelable {
    /** The type of the credential entry to be shown on the UI. */
    private final @NonNull String mType;

    /** The info to be displayed along with this credential entry on the UI. */
    private final @NonNull Slice mInfo;

    /** The pending intent to be invoked when this credential entry is selected. */
    private final @Nullable PendingIntent mPendingIntent;

    /**
     * The underlying credential to be returned to the app when the user selects
     * this credential entry.
     */
    private final @Nullable Credential mCredential;

    /** A flag denoting whether auto-select is enabled for this entry. */
    private final @NonNull boolean mAutoSelectAllowed;

    private CredentialEntry(@NonNull String type, @NonNull Slice entryInfo,
            @Nullable PendingIntent pendingIntent, @Nullable Credential credential,
            @NonNull boolean autoSeletAllowed) {
        mType = type;
        mInfo = entryInfo;
        mPendingIntent = pendingIntent;
        mCredential = credential;
        mAutoSelectAllowed = autoSeletAllowed;
    }

    private CredentialEntry(@NonNull Parcel in) {
        mType = in.readString();
        mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
                PendingIntent.class);
        mCredential = in.readParcelable(Credential.class.getClassLoader(),
                Credential.class);
        mAutoSelectAllowed = in.readBoolean();
    }

    public static final @NonNull Creator<CredentialEntry> CREATOR =
            new Creator<CredentialEntry>() {
        @Override
        public CredentialEntry createFromParcel(@NonNull Parcel in) {
            return new CredentialEntry(in);
        }

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

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString8(mType);
        mInfo.writeToParcel(dest, flags);
        mPendingIntent.writeToParcel(dest, flags);
        mCredential.writeToParcel(dest, flags);
        dest.writeBoolean(mAutoSelectAllowed);
    }

    /**
     * Returns the specific credential type of the entry.
     */
    public @NonNull String getType() {
        return mType;
    }

    /**
     * Returns the UI info to be displayed for this entry.
     */
    public @NonNull Slice getInfo() {
        return mInfo;
    }

    /**
     * Returns the pending intent to be invoked if the user selects this entry.
     */
    public @Nullable PendingIntent getPendingIntent() {
        return mPendingIntent;
    }

    /**
     * Returns the credential associated with this entry.
     */
    public @Nullable Credential getCredential() {
        return mCredential;
    }

    /**
     * Returns whether this entry can be auto selected if it is the only option for the user.
     */
    public @NonNull boolean isAutoSelectAllowed() {
        return mAutoSelectAllowed;
    }

    /**
     * Builder for {@link CredentialEntry}.
     */
    public static final class Builder {
        private String mType;
        private Slice mInfo;
        private PendingIntent mPendingIntent;
        private Credential mCredential;
        private boolean mAutoSelectAllowed = false;

        /**
         * Builds the instance.
         * @param type The type of credential underlying this credential entry.
         * @param info The info to be displayed with this entry on the UI.
         *
         * @throws IllegalArgumentException If {@code type} is null or empty.
         * @throws NullPointerException If {@code info} is null.
         */
        public Builder(@NonNull String type, @NonNull Slice info) {
            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
                    + "null, or empty");
            mInfo = Objects.requireNonNull(info, "info must not be null");
        }

        /**
         * Sets the pendingIntent to be invoked if the user selects this entry.
         *
         * @throws IllegalStateException If {@code credential} is already set. Must either set the
         * {@code credential}, or the {@code pendingIntent}.
         */
        public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
            Preconditions.checkState(pendingIntent != null && mCredential != null,
                    "credential is already set. Cannot set both the pendingIntent "
                            + "and the credential");
            mPendingIntent = pendingIntent;
            return this;
        }

        /**
         * Sets the credential to be used, if the user selects this entry.
         *
         * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
         * the {@code pendingIntent}, or the {@code credential}.
         */
        public @NonNull Builder setCredential(@Nullable Credential credential) {
            Preconditions.checkState(credential != null && mPendingIntent != null,
                    "pendingIntent is already set. Cannot set both the "
                            + "pendingIntent and the credential");
            mCredential = credential;
            return this;
        }

        /**
         * Sets whether the entry is allowed to be auto selected by the framework.
         * The default value is set to false.
         */
        public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
            mAutoSelectAllowed = autoSelectAllowed;
            return this;
        }

        /**
         * Creates a new {@link CredentialEntry} instance.
         *
         * @throws NullPointerException If {@code info} is null.
         * @throws IllegalArgumentException If {@code type} is null, or empty.
         * @throws IllegalStateException If neither {@code pendingIntent} nor {@code credential}
         * is set, or if both are set.
         */
        public @NonNull CredentialEntry build() {
            Preconditions.checkState(mPendingIntent == null && mCredential == null,
                    "Either pendingIntent or credential must be set");
            Preconditions.checkState(mPendingIntent != null && mCredential != null,
                    "Cannot set both the pendingIntent and credential");
            return new CredentialEntry(mType, mInfo, mPendingIntent,
                    mCredential, mAutoSelectAllowed);
        }
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.service.credentials;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;

import java.util.Objects;

/**
 * Main service to be extended by credential providers, in order to return user credentials
 * to the framework.
 *
 * @hide
 */
public abstract class CredentialProviderService extends Service {
    private static final String TAG = "CredProviderService";
    private Handler mHandler;

    public static final String SERVICE_INTERFACE =
            "android.service.credentials.CredentialProviderService";

    @CallSuper
    @Override
    public void onCreate() {
        super.onCreate();
        mHandler = new Handler(Looper.getMainLooper(), null, true);
    }

    @Override
    public final @NonNull IBinder onBind(@NonNull Intent intent) {
        if (SERVICE_INTERFACE.equals(intent.getAction())) {
            return mInterface.asBinder();
        }
        Log.i(TAG, "Failed to bind with intent: " + intent);
        return null;
    }

    private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
        @Override
        public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
                IGetCredentialsCallback callback) throws RemoteException {
            Objects.requireNonNull(request);
            Objects.requireNonNull(callback);

            mHandler.sendMessage(obtainMessage(
                    CredentialProviderService::onGetCredentials,
                    CredentialProviderService.this, request,
                    CancellationSignal.fromTransport(transport),
                    new GetCredentialsCallback(callback)
            ));
        }
    };

    /**
     * Called by the android system to retrieve user credentials from the connected provider
     * service.
     * @param request The credential request for the provider to handle.
     * @param cancellationSignal Signal for providers to listen to any cancellation requests from
     *                           the android system.
     * @param callback Object used to relay the response of the credentials request.
     */
    public abstract void onGetCredentials(@NonNull GetCredentialsRequest request,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull GetCredentialsCallback callback);
}
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.service.credentials;

import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Content to be displayed on the account selector UI, including credential entries,
 * actions etc.
 *
 * @hide
 */
public final class CredentialsDisplayContent implements Parcelable {
    /** Header to be displayed on the UI. */
    private final @Nullable CharSequence mHeader;

    /** List of credential entries to be displayed on the UI. */
    private final @NonNull List<CredentialEntry> mCredentialEntries;

    /** List of provider actions to be displayed on the UI. */
    private final @NonNull List<Action> mActions;

    private CredentialsDisplayContent(@Nullable CharSequence header,
            @NonNull List<CredentialEntry> credentialEntries,
            @NonNull List<Action> actions) {
        mHeader = header;
        mCredentialEntries = credentialEntries;
        mActions = actions;
    }

    private CredentialsDisplayContent(@NonNull Parcel in) {
        mHeader = in.readCharSequence();
        mCredentialEntries = in.createTypedArrayList(CredentialEntry.CREATOR);
        mActions = in.createTypedArrayList(Action.CREATOR);
    }

    public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
            new Creator<CredentialsDisplayContent>() {
                @Override
                public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
                    return new CredentialsDisplayContent(in);
                }

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

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

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeCharSequence(mHeader);
        dest.writeTypedList(mCredentialEntries);
        dest.writeTypedList(mActions);
    }

    /**
     * Returns the header to be displayed on the UI.
     */
    public @Nullable CharSequence getHeader() {
        return mHeader;
    }

    /**
     * Returns the list of credential entries to be displayed on the UI.
     */
    public @NonNull List<CredentialEntry> getCredentialEntries() {
        return mCredentialEntries;
    }

    /**
     * Returns the list of actions to be displayed on the UI.
     */
    public @NonNull List<Action> getActions() {
        return mActions;
    }

    /**
     * Builds an instance of {@link CredentialsDisplayContent}.
     */
    public static final class Builder {
        private CharSequence mHeader = null;
        private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
        private List<Action> mActions = new ArrayList<>();

        /**
         * Sets the header to be displayed on the UI.
         */
        public @NonNull Builder setHeader(@Nullable CharSequence header) {
            mHeader = header;
            return this;
        }

        /**
         * Adds a {@link CredentialEntry} to the list of entries to be displayed on
         * the UI.
         *
         * @throws NullPointerException If the {@code credentialEntry} is null.
         */
        public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
            mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
            return this;
        }

        /**
         * Adds an {@link Action} to the list of actions to be displayed on
         * the UI.
         *
         * @throws NullPointerException If {@code action} is null.
         */
        public @NonNull Builder addAction(@NonNull Action action) {
            mActions.add(Objects.requireNonNull(action, "action must not be null"));
            return this;
        }

        /**
         * Sets the list of actions to be displayed on the UI.
         *
         * @throws NullPointerException If {@code actions} is null, or any of its elements
         * is null.
         */
        public @NonNull Builder setActions(@NonNull List<Action> actions) {
            mActions = Preconditions.checkCollectionElementsNotNull(actions,
                    "actions");
            return this;
        }

        /**
         * Sets the list of credential entries to be displayed on the
         * account selector UI.
         *
         * @throws NullPointerException If {@code credentialEntries} is null, or any of its
         * elements is null.
         */
        public @NonNull Builder setCredentialEntries(
                @NonNull List<CredentialEntry> credentialEntries) {
            mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
                    credentialEntries,
                    "credentialEntries");
            return this;
        }

        /**
         * Builds a {@link GetCredentialsResponse} instance.
         *
         * @throws NullPointerException If {@code credentialEntries} is null.
         * @throws IllegalStateException if both {@code credentialEntries} and
         * {@code actions} are empty.
         */
        public @NonNull CredentialsDisplayContent build() {
            if (mCredentialEntries != null && mCredentialEntries.isEmpty()
                    && mActions != null && mActions.isEmpty()) {
                throw new IllegalStateException("credentialEntries and actions must not both "
                        + "be empty");
            }
            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
        }
    }
}
Loading