/*
 * Copyright (C) 2018 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.car.drivingstate;

import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;

import android.annotation.IntDef;
import android.car.annotation.AddedInOrBefore;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Car UX Restrictions event.  This contains information on the set of UX restrictions that is in
 * place due to the car's driving state.
 * <p>
 * The restriction information is organized as follows:
 * <ul>
 * <li> When there are no restrictions in place, for example when the car is parked,
 * <ul>
 * <li> {@link #isRequiresDistractionOptimization()} returns false.  Apps can display activities
 * that are not distraction optimized.
 * <li> When {@link #isRequiresDistractionOptimization()} returns false, apps don't have to call
 * {@link #getActiveRestrictions()}, since there is no distraction optimization required.
 * </ul>
 * <li> When the driving state changes, causing the UX restrictions to come in effect,
 * <ul>
 * <li> {@link #isRequiresDistractionOptimization()} returns true.  Apps can only display activities
 * that are distraction optimized.  Distraction optimized activities must follow the base design
 * guidelines to ensure a distraction free driving experience for the user.
 * <li> When {@link #isRequiresDistractionOptimization()} returns true, apps must call
 * {@link #getActiveRestrictions()}, to get the currently active UX restrictions to adhere to.
 * {@link #getActiveRestrictions()} provides additional information on the set of UX
 * restrictions that are in place for the current driving state.
 * <p>
 * The UX restrictions returned by {@link #getActiveRestrictions()}, for the same driving state of
 * the vehicle, could vary depending on the OEM and the market.  For example, when the car is
 * idling, the set of active UX restrictions will depend on the car maker and the safety standards
 * of the market that the vehicle is deployed in.
 * </ul>
 * </ul>
 * <p>
 * Apps that intend to be run when the car is being driven need to
 * <ul>
 * <li> Comply with the general distraction optimization guidelines.
 * <li> Listen and react to the UX restrictions changes as detailed above.  Since the restrictions
 * could vary depending on the market, apps are expected to react to the restriction information
 * and not to the absolute driving state.
 * </ul>
 */
public final class CarUxRestrictions implements Parcelable {

    // Default fallback values for the restriction related parameters if the information is
    // not available from the underlying service.
    private static final int DEFAULT_MAX_LENGTH = 120;
    private static final int DEFAULT_MAX_CUMULATIVE_ITEMS = 21;
    private static final int DEFAULT_MAX_CONTENT_DEPTH = 3;

    /**
     * No specific restrictions in place, but baseline distraction optimization guidelines need to
     * be adhered to when {@link #isRequiresDistractionOptimization()} is true.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_BASELINE = 0;

    // Granular UX Restrictions that are imposed when distraction optimization is required.
    /**
     * No dialpad for the purpose of initiating a phone call.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_DIALPAD = 1;

    /**
     * No filtering a list with alpha-numeric character via the use of a character entry method.
     *
     * For example, do not allow entering a letter to filter the content of a list down to
     * items only containing that letter.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_FILTERING = 0x1 << 1;

    /**
     * General purpose strings length cannot exceed the character limit provided by
     * {@link #getMaxRestrictedStringLength()}
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_LIMIT_STRING_LENGTH = 0x1 << 2;

    /**
     * No text entry for the purpose of searching or other manual text string entry actvities.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_KEYBOARD = 0x1 << 3;

    /**
     * No video - no animated frames > 1fps.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_VIDEO = 0x1 << 4;

    /**
     * Limit the number of items user can browse through in total in a single task.
     *
     * <p>Refer to {@link #getMaxCumulativeContentItems()} and
     * {@link #getMaxContentDepth()} for the upper bounds on content
     * serving.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_LIMIT_CONTENT = 0x1 << 5;

    /**
     * No setup that requires form entry or interaction with external devices.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_SETUP = 0x1 << 6;

    /**
     * No Text Message (SMS, email, conversational, etc.)
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_TEXT_MESSAGE = 0x1 << 7;

    /**
     * No text transcription (live or leave behind) of voice can be shown.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION = 0x1 << 8;

    /**
     * All restrictions are in effect.
     */
    @AddedInOrBefore(majorVersion = 33)
    public static final int UX_RESTRICTIONS_FULLY_RESTRICTED =
            UX_RESTRICTIONS_NO_DIALPAD | UX_RESTRICTIONS_NO_FILTERING
                    | UX_RESTRICTIONS_LIMIT_STRING_LENGTH | UX_RESTRICTIONS_NO_KEYBOARD
                    | UX_RESTRICTIONS_NO_VIDEO | UX_RESTRICTIONS_LIMIT_CONTENT
                    | UX_RESTRICTIONS_NO_SETUP | UX_RESTRICTIONS_NO_TEXT_MESSAGE
                    | UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION;

    @IntDef(flag = true,
            prefix = {"UX_RESTRICTIONS_"},
            value = {UX_RESTRICTIONS_BASELINE,
                    UX_RESTRICTIONS_NO_DIALPAD,
                    UX_RESTRICTIONS_NO_FILTERING,
                    UX_RESTRICTIONS_LIMIT_STRING_LENGTH,
                    UX_RESTRICTIONS_NO_KEYBOARD,
                    UX_RESTRICTIONS_NO_VIDEO,
                    UX_RESTRICTIONS_LIMIT_CONTENT,
                    UX_RESTRICTIONS_NO_SETUP,
                    UX_RESTRICTIONS_NO_TEXT_MESSAGE,
                    UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CarUxRestrictionsInfo {
    }

    private final long mTimeStamp;
    private final boolean mRequiresDistractionOptimization;
    @CarUxRestrictionsInfo
    private final int mActiveRestrictions;
    // Restriction Parameters
    private final int mMaxStringLength;
    private final int mMaxCumulativeContentItems;
    private final int mMaxContentDepth;

    /**
     * Builder class for {@link CarUxRestrictions}
     */
    public static class Builder {
        private final long mTimeStamp;
        private final boolean mRequiresDistractionOptimization;
        @CarUxRestrictionsInfo
        private final int mActiveRestrictions;
        // Restriction Parameters
        private int mMaxStringLength = DEFAULT_MAX_LENGTH;
        private int mMaxCumulativeContentItems = DEFAULT_MAX_CUMULATIVE_ITEMS;
        private int mMaxContentDepth = DEFAULT_MAX_CONTENT_DEPTH;

        public Builder(boolean reqOpt, @CarUxRestrictionsInfo int restrictions, long time) {
            mRequiresDistractionOptimization = reqOpt;
            mActiveRestrictions = restrictions;
            mTimeStamp = time;
        }

        /**
         * Set the maximum length of general purpose strings that can be displayed when
         * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_STRING_LENGTH} is imposed.
         */
        @AddedInOrBefore(majorVersion = 33)
        public Builder setMaxStringLength(int length) {
            mMaxStringLength = length;
            return this;
        }

        /**
         *  Set the maximum number of cumulative content items that can be displayed when
         * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
         */
        @AddedInOrBefore(majorVersion = 33)
        public Builder setMaxCumulativeContentItems(int number) {
            mMaxCumulativeContentItems = number;
            return this;
        }

        /**
         * Set the maximum number of levels that the user can navigate to when
         * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
         */
        @AddedInOrBefore(majorVersion = 33)
        public Builder setMaxContentDepth(int depth) {
            mMaxContentDepth = depth;
            return this;
        }

        /**
         * Build and return the {@link CarUxRestrictions} object
         */
        @AddedInOrBefore(majorVersion = 33)
        public CarUxRestrictions build() {
            return new CarUxRestrictions(this);
        }

    }

    /**
     * Time at which this UX restriction event was deduced based on the car's driving state.
     *
     * @return Elapsed time in nanoseconds since system boot.
     *
     * @hide
     */
    @AddedInOrBefore(majorVersion = 33)
    public long getTimeStamp() {
        return mTimeStamp;
    }

    /**
     * Conveys if the foreground activity needs to be distraction optimized.
     * Activities that can handle distraction optimization need to be tagged as a distraction
     * optimized in the app's manifest.
     * <p>
     * If the app has a foreground activity that has not been distraction optimized, the app has
     * to switch to another activity that is distraction optimized.  Failing that, the system will
     * stop the foreground activity.
     *
     * @return true if distraction optimization is required, false if not
     */
    @AddedInOrBefore(majorVersion = 33)
    public boolean isRequiresDistractionOptimization() {
        return mRequiresDistractionOptimization;
    }

    /**
     * A combination of the Car UX Restrictions that is active for the current state of driving.
     *
     * @return A combination of the above {@code @CarUxRestrictionsInfo}
     */
    @CarUxRestrictionsInfo
    @AddedInOrBefore(majorVersion = 33)
    public int getActiveRestrictions() {
        return mActiveRestrictions;
    }

    /**
     * Get the maximum length of general purpose strings that can be displayed when
     * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_STRING_LENGTH} is imposed.
     *
     * @return the maximum length of string that can be displayed
     */
    @AddedInOrBefore(majorVersion = 33)
    public int getMaxRestrictedStringLength() {
        return mMaxStringLength;
    }

    /**
     * Get the maximum allowable number of content items that can be displayed to a user during
     * traversal through any one path in a single task, when
     * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
     * <p>
     * For example, if a task involving only one view, this represents the maximum allowable number
     * of content items in this single view.
     * <p>
     * However, if the task involves selection of a content item in an originating view that then
     * surfaces a secondary view to the user, then this value represents the maximum allowable
     * number of content items between the originating and secondary views combined.
     * <p>
     * Specifically, if the maximum allowable value was 60 and a task involved browsing a list of
     * countries and then viewing the top songs within a country, it would be acceptable to do
     * either of the following:
     * <ul>
     * <li> list 10 countries, and then display the top 50 songs after country selection, or
     * <li> list 20 countries, and then display the top 40 songs after country selection.
     * </ul>
     * <p>
     * Please refer to this and {@link #getMaxContentDepth()} to know the upper bounds on the
     * content display when the restriction is in place.
     *
     * @return maximum number of cumulative items that can be displayed
     */
    @AddedInOrBefore(majorVersion = 33)
    public int getMaxCumulativeContentItems() {
        return mMaxCumulativeContentItems;
    }

    /**
     * Get the maximum allowable number of content depth levels or view traversals through any one
     * path in a single task.  This is applicable when
     * {@link CarUxRestrictions#UX_RESTRICTIONS_LIMIT_CONTENT} is imposed.
     * <p>
     * For example, if a task involves only selecting an item from a single list on one view,
     * the task's content depth would be considered 1.
     * <p>
     * However, if the task involves selection of a content item in an originating view that then
     * surfaces a secondary view to the user, the task's content depth would be considered 2.
     * <p>
     * Specifically, if a task involved browsing a list of countries, selecting a genre within the
     * country, and then viewing the top songs within a country, the task's content depth would be
     * considered 3.
     * <p>
     * Please refer to this and {@link #getMaxCumulativeContentItems()} to know the upper bounds on
     * the content display when the restriction is in place.
     *
     * @return maximum number of cumulative items that can be displayed
     */
    @AddedInOrBefore(majorVersion = 33)
    public int getMaxContentDepth() {
        return mMaxContentDepth;
    }

    @Override
    @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
    @AddedInOrBefore(majorVersion = 33)
    public int describeContents() {
        return 0;
    }

    @Override
    @AddedInOrBefore(majorVersion = 33)
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mActiveRestrictions);
        dest.writeLong(mTimeStamp);
        dest.writeInt(mRequiresDistractionOptimization ? 1 : 0);
        dest.writeInt(mMaxStringLength);
        dest.writeInt(mMaxCumulativeContentItems);
        dest.writeInt(mMaxContentDepth);
    }

    @AddedInOrBefore(majorVersion = 33)
    public static final Parcelable.Creator<CarUxRestrictions> CREATOR =
            new Parcelable.Creator<CarUxRestrictions>() {
        @Override
        public CarUxRestrictions createFromParcel(Parcel in) {
            return new CarUxRestrictions(in);
        }

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

    public CarUxRestrictions(CarUxRestrictions uxRestrictions) {
        mTimeStamp = uxRestrictions.getTimeStamp();
        mRequiresDistractionOptimization = uxRestrictions.isRequiresDistractionOptimization();
        mActiveRestrictions = uxRestrictions.getActiveRestrictions();
        mMaxStringLength = uxRestrictions.mMaxStringLength;
        mMaxCumulativeContentItems = uxRestrictions.mMaxCumulativeContentItems;
        mMaxContentDepth = uxRestrictions.mMaxContentDepth;
    }

    private CarUxRestrictions(Builder builder) {
        mTimeStamp = builder.mTimeStamp;
        mActiveRestrictions = builder.mActiveRestrictions;
        mRequiresDistractionOptimization = builder.mRequiresDistractionOptimization;
        mMaxStringLength = builder.mMaxStringLength;
        mMaxCumulativeContentItems = builder.mMaxCumulativeContentItems;
        mMaxContentDepth = builder.mMaxContentDepth;
    }

    private CarUxRestrictions(Parcel in) {
        mActiveRestrictions = in.readInt();
        mTimeStamp = in.readLong();
        mRequiresDistractionOptimization = in.readInt() != 0;
        mMaxStringLength = in.readInt();
        mMaxCumulativeContentItems = in.readInt();
        mMaxContentDepth = in.readInt();
    }

    @Override
    public String toString() {
        return "DO: " + mRequiresDistractionOptimization + " UxR: " + mActiveRestrictions
                + " time: " + mTimeStamp;
    }

    /**
     * Compares if the restrictions are the same.  Doesn't compare the timestamps.
     *
     * @param other the other CarUxRestrictions object
     * @return true if the restrictions are same, false otherwise
     */
    @AddedInOrBefore(majorVersion = 33)
    public boolean isSameRestrictions(CarUxRestrictions other) {
        if (other == null) {
            return false;
        }
        if (other == this) {
            return true;
        }
        return other.mRequiresDistractionOptimization == mRequiresDistractionOptimization
                && other.mActiveRestrictions == mActiveRestrictions;
    }
}
