<commit_before>/*
 * 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 androidx.car.app.sample.showcase.common.templates;

import static androidx.car.app.CarToast.LENGTH_LONG;

import android.graphics.Color;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarColor;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.InputCallback;
import androidx.car.app.model.MessageTemplate;
import androidx.car.app.model.ParkedOnlyOnClickListener;
import androidx.car.app.model.Template;
import androidx.car.app.model.signin.InputSignInMethod;
import androidx.car.app.model.signin.PinSignInMethod;
import androidx.car.app.model.signin.ProviderSignInMethod;
import androidx.car.app.model.signin.SignInTemplate;
import androidx.car.app.sample.showcase.common.R;
import androidx.car.app.sample.showcase.common.common.Utils;
import androidx.car.app.versioning.CarAppApiLevels;
import androidx.core.graphics.drawable.IconCompat;

/** A screen that demonstrates the sign-in template. */
public class SignInTemplateDemoScreen extends Screen {
    private enum State {
        USERNAME,
        PASSWORD,
        PIN,
        PROVIDER,
        SIGNED_IN,
    }

    private static final String EMAIL_REGEXP = "^(.+)@(.+)$";
    private static final String EXPECTED_PASSWORD = "password";
    private static final int MAX_USERNAME_LENGTH = 5;

    // package private to avoid synthetic accessor
    State mState = State.USERNAME;
    String mErrorMessage = "";
    String mUsername = null;

    private final CharSequence mAdditionalText = Utils.clickable("Please review our terms of "
                    + "service", 18, 16,
            () -> getScreenManager().push(new LongMessageTemplateDemoScreen(getCarContext())));

    private final Action mProviderSignInAction = new Action.Builder()
            .setTitle("Google Sign-In")
            .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
                mState = State.PROVIDER;
                invalidate();
            }))
            .build();

    private final Action mPinSignInAction = new Action.Builder()
            .setTitle("Use PIN")
            .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
                mState = State.PIN;
                invalidate();
            }))
            .build();

    public SignInTemplateDemoScreen(@NonNull CarContext carContext) {
        super(carContext);

        // Handle back pressed events manually, as we use them to navigate between templates within
        // the same screen.
        OnBackPressedCallback callback = new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                mErrorMessage = "";
                if (mState == State.USERNAME || mState == State.SIGNED_IN) {
                    getScreenManager().pop();
                } else {
                    mState = State.USERNAME;
                    invalidate();
                }
            }
        };
        carContext.getOnBackPressedDispatcher().addCallback(this, callback);
    }

    @NonNull
    @Override
    public Template onGetTemplate() {
        if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
            return new MessageTemplate.Builder("Your host doesn't support Sign In template")
                    .setTitle("Incompatible host")
                    .setHeaderAction(Action.BACK)
                    .build();
        }
        switch (mState) {
            case USERNAME:
                return getUsernameSignInTemplate();
            case PASSWORD:
                return getPasswordSignInTemplate();
            case PIN:
                return getPinSignInTemplate();
            case PROVIDER:
                return getProviderSignInTemplate();
            case SIGNED_IN:
                return getSignInCompletedMessageTemplate();
        }
        throw new IllegalStateException("Invalid state: " + mState);
    }

    private Template getUsernameSignInTemplate() {
        InputCallback listener = new InputCallback() {
            @Override
            public void onInputSubmitted(@NonNull String text) {
                // Mocked username validation
                if (!text.matches(EMAIL_REGEXP)) {
                    mErrorMessage = "Invalid user name";
                    mUsername = text;
                } else {
                    mErrorMessage = "";
                    mUsername = text;
                    mState = State.PASSWORD;
                }
                invalidate();
            }

            @Override
            public void onInputTextChanged(@NonNull String text) {
                // This callback demonstrates how to use handle the text changed event.
                // In this case, we check that the user name doesn't exceed a certain length.
                if (mState == State.USERNAME) {
                    String previousErrorMessage = mErrorMessage;
                    if (text.length() > MAX_USERNAME_LENGTH) {
                        mErrorMessage = "User name is too long";
                    } else {
                        mErrorMessage = "";
                    }

                    // If the error message changed, invalidatee the template.
                    if (!mErrorMessage.equals(previousErrorMessage)) {
                        // Make sure to keep the user name so that the template preserves it
                        // after invalidation.
                        mUsername = text;
                        invalidate();
                    }
                }
            }
        };
        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(listener)
                .setHint("Email")
                .setKeyboardType(InputSignInMethod.KEYBOARD_EMAIL);
        if (mErrorMessage != null) {
            builder.setErrorMessage(mErrorMessage);
        }
        if (mUsername != null) {
            builder.setDefaultValue(mUsername);
        }
        InputSignInMethod signInMethod = builder.build();

        return new SignInTemplate.Builder(signInMethod)
                .addAction(mProviderSignInAction)
                .addAction(mPinSignInAction)
                .setTitle("Sign in with username and password")
                .setInstructions("Enter your credentials")
                .setHeaderAction(Action.BACK)
                .setAdditionalText(mAdditionalText)
                .setActionStrip(
                        new ActionStrip.Builder()
                                .addAction(
                                        new Action.Builder()
                                                .setTitle("Settings")
                                                .setOnClickListener(
                                                        () ->
                                                                CarToast.makeText(
                                                                        getCarContext(),
                                                                        "Clicked Settings",
                                                                        LENGTH_LONG)
                                                                        .show())
                                                .build())
                                .build())
                .build();
    }

    private Template getPasswordSignInTemplate() {
        InputCallback callback = new InputCallback() {
            @Override
            public void onInputSubmitted(@NonNull String text) {
                // Mocked password validation
                if (!EXPECTED_PASSWORD.equals(text)) {
                    mErrorMessage = "Invalid password";
                } else {
                    mErrorMessage = "";
                    mState = State.SIGNED_IN;
                }
                invalidate();
            }
        };
        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(callback)
                .setHint("Password")
                .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD);
        if (mErrorMessage != null) {
            builder.setErrorMessage(mErrorMessage);
        }
        InputSignInMethod signInMethod = builder.build();

        return new SignInTemplate.Builder(signInMethod)<commit_msg>Update sign-in demo to provide the option to go to the password screen with a "next" button.<commit_after>/*
 * 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 androidx.car.app.sample.showcase.common.templates;

import static androidx.car.app.CarToast.LENGTH_LONG;

import android.graphics.Color;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.CarToast;
import androidx.car.app.Screen;
import androidx.car.app.model.Action;
import androidx.car.app.model.ActionStrip;
import androidx.car.app.model.CarColor;
import androidx.car.app.model.CarIcon;
import androidx.car.app.model.InputCallback;
import androidx.car.app.model.MessageTemplate;
import androidx.car.app.model.ParkedOnlyOnClickListener;
import androidx.car.app.model.Template;
import androidx.car.app.model.signin.InputSignInMethod;
import androidx.car.app.model.signin.PinSignInMethod;
import androidx.car.app.model.signin.ProviderSignInMethod;
import androidx.car.app.model.signin.SignInTemplate;
import androidx.car.app.sample.showcase.common.R;
import androidx.car.app.sample.showcase.common.common.Utils;
import androidx.car.app.versioning.CarAppApiLevels;
import androidx.core.graphics.drawable.IconCompat;

/** A screen that demonstrates the sign-in template. */
public class SignInTemplateDemoScreen extends Screen {
    private enum State {
        USERNAME,
        PASSWORD,
        PIN,
        PROVIDER,
        SIGNED_IN,
    }

    private static final String EMAIL_REGEXP = "^(.+)@(.+)$";
    private static final String EXPECTED_PASSWORD = "password";
    private static final int MIN_USERNAME_LENGTH = 5;

    // package private to avoid synthetic accessor
    State mState = State.USERNAME;
    String mLastErrorMessage = ""; // last displayed error message
    String mErrorMessage = "";
    String mUsername = null;

    private final CharSequence mAdditionalText = Utils.clickable("Please review our terms of "
                    + "service", 18, 16,
            () -> getScreenManager().push(new LongMessageTemplateDemoScreen(getCarContext())));

    private final Action mProviderSignInAction = new Action.Builder()
            .setTitle("Google Sign-In")
            .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
                mState = State.PROVIDER;
                invalidate();
            }))
            .build();

    private final Action mPinSignInAction = new Action.Builder()
            .setTitle("Use PIN")
            .setOnClickListener(ParkedOnlyOnClickListener.create(() -> {
                mState = State.PIN;
                invalidate();
            }))
            .build();

    public SignInTemplateDemoScreen(@NonNull CarContext carContext) {
        super(carContext);

        // Handle back pressed events manually, as we use them to navigate between templates within
        // the same screen.
        OnBackPressedCallback callback = new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                mErrorMessage = "";
                if (mState == State.USERNAME || mState == State.SIGNED_IN) {
                    getScreenManager().pop();
                } else {
                    mState = State.USERNAME;
                    invalidate();
                }
            }
        };
        carContext.getOnBackPressedDispatcher().addCallback(this, callback);
    }

    @NonNull
    @Override
    public Template onGetTemplate() {
        if (getCarContext().getCarAppApiLevel() < CarAppApiLevels.LEVEL_2) {
            return new MessageTemplate.Builder("Your host doesn't support Sign In template")
                    .setTitle("Incompatible host")
                    .setHeaderAction(Action.BACK)
                    .build();
        }
        switch (mState) {
            case USERNAME:
                return getUsernameSignInTemplate();
            case PASSWORD:
                return getPasswordSignInTemplate();
            case PIN:
                return getPinSignInTemplate();
            case PROVIDER:
                return getProviderSignInTemplate();
            case SIGNED_IN:
                return getSignInCompletedMessageTemplate();
        }
        throw new IllegalStateException("Invalid state: " + mState);
    }

    private Template getUsernameSignInTemplate() {
        InputCallback listener = new InputCallback() {
            @Override
            public void onInputSubmitted(@NonNull String text) {
                if (mState == State.USERNAME) {
                    mUsername = text;
                    submitUsername();
                }
            }

            @Override
            public void onInputTextChanged(@NonNull String text) {
                // This callback demonstrates how to use handle the text changed event.
                // In this case, we check that the user name doesn't exceed a certain length.
                if (mState == State.USERNAME) {
                    mUsername = text;
                    mErrorMessage = validateUsername();

                    // Invalidate the template (and hence possibly update the error message) only
                    // if clearing up the error string, or if the error is changing.
                    if (!mLastErrorMessage.isEmpty()
                            && (mErrorMessage.isEmpty()
                            || !mLastErrorMessage.equals(mErrorMessage))) {
                        invalidate();
                    }
                }
            }
        };

        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(listener)
                .setHint("Email")
                .setKeyboardType(InputSignInMethod.KEYBOARD_EMAIL);
        if (mErrorMessage != null) {
            builder.setErrorMessage(mErrorMessage);
            mLastErrorMessage = mErrorMessage;
        }
        if (mUsername != null) {
            builder.setDefaultValue(mUsername);
        }
        InputSignInMethod signInMethod = builder.build();

        return new SignInTemplate.Builder(signInMethod)
                .addAction(mProviderSignInAction)
                .addAction(mPinSignInAction)
                .setTitle("Sign in with username and password")
                .setInstructions("Enter your credentials")
                .setHeaderAction(Action.BACK)
                .setAdditionalText(mAdditionalText)
                .setActionStrip(
                        new ActionStrip.Builder()
                                .addAction(
                                        new Action.Builder()
                                                .setTitle("Next")
                                                .setOnClickListener(this::submitUsername)
                                                .build())
                                .build())
                .build();
    }

    /**
     * Validates the currently entered user name and returns an error message string if invalid,
     * or an empty string otherwise.
     */
    String validateUsername() {
        if (mUsername == null || mUsername.length() < MIN_USERNAME_LENGTH) {
            return "User name must be at least " + MIN_USERNAME_LENGTH + " characters "
                    + "long";
        } else if (!mUsername.matches(EMAIL_REGEXP)) {
            return "User name must be a valid email address";
        } else {
            return "";
        }
    }

    /**
     * Moves to the password screen if the user name currently entered is valid, or displays
     * an error message otherwise.
     */
    void submitUsername() {
        mErrorMessage = validateUsername();

        boolean isError = !mErrorMessage.isEmpty();
        if (!isError) {
            // If there's no error, go to the password screen.
            mState = State.PASSWORD;
        } else {
            // If there's an error, display a toast.
            CarToast.makeText(getCarContext(), "Please enter a valid user name",
                    LENGTH_LONG).show();
        }

        // Invalidate the template so that we either display an error, or go to the password screen.
        invalidate();
    }

    private Template getPasswordSignInTemplate() {
        InputCallback callback = new InputCallback() {
            @Override
            public void onInputSubmitted(@NonNull String text) {
                // Mocked password validation
                if (!EXPECTED_PASSWORD.equals(text)) {
                    mErrorMessage = "Invalid password";
                } else {
                    mErrorMessage = "";
                    mState = State.SIGNED_IN;
                }
                invalidate();
            }
        };
        InputSignInMethod.Builder builder = new InputSignInMethod.Builder(callback)
                .setHint("Password")
                .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD);
        if (mErrorMessage != null) {
            builder.setErrorMessage(mErrorMessage);
        }
        InputSignInMethod signInMethod = builder.build();

        return new SignInTemplate.Builder(signInMethod)
{'fraction_non_alphanumeric': 0.0823404255319149, 'fraction_numerical': 0.0014893617021276596, 'mean_word_length': 1.7442709093562982, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5942184', 'n_tokens_mistral': 4580, 'n_tokens_neox': 4342, 'n_words': 1151}