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

Commit cdc587d3 authored by Reema Bajwa's avatar Reema Bajwa Committed by Android (Google) Code Review
Browse files

Merge "Add provider facing GET APIs for Credential Manager."

parents 2287fbb4 9238ef21
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