ViewStyle.java

/*
 * Copyright 2020 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.autofill.inline.common;

import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;

/**
 * Specifies the style for a {@link View} or a {@link android.view.ViewGroup}.
 */
@RequiresApi(api = Build.VERSION_CODES.R)
public class ViewStyle extends BundledStyle {

    private static final String KEY_VIEW_STYLE = "view_style";

    private static final String KEY_BACKGROUND = "background";
    private static final String KEY_BACKGROUND_COLOR = "background_color";
    private static final String KEY_PADDING = "padding";
    private static final String KEY_LAYOUT_MARGIN = "layout_margin";

    /**
     * This is made public so it can be used by the renderer to converted the received bundle to
     * a style. It does not validate the provided bundle. {@link #isValid()} or
     * {@link #assertIsValid()} can be used for validation.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public ViewStyle(@NonNull Bundle bundle) {
        super(bundle);
    }

    /**
     * Applies the specified style on the {@code view}.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public void applyStyleOnViewIfValid(@NonNull View view) {
        if (!isValid()) {
            return;
        }
        if (mBundle.containsKey(KEY_BACKGROUND)) {
            Icon background = mBundle.getParcelable(KEY_BACKGROUND);
            if (background != null) {
                final Drawable drawable = background.loadDrawable(view.getContext());
                if (drawable != null) {
                    view.setBackground(drawable);
                }
            }
        }
        if (mBundle.containsKey(KEY_BACKGROUND_COLOR)) {
            int color = mBundle.getInt(KEY_BACKGROUND_COLOR);
            view.setBackgroundColor(color);
        }
        if (mBundle.containsKey(KEY_PADDING)) {
            int[] padding = mBundle.getIntArray(KEY_PADDING);
            if (padding != null && padding.length == 4) {
                if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
                    view.setPadding(padding[0], padding[1], padding[2], padding[3]);
                } else {
                    view.setPadding(padding[2], padding[1], padding[0], padding[3]);
                }
            }
        }
        if (mBundle.containsKey(KEY_LAYOUT_MARGIN)) {
            int[] layoutMargin = mBundle.getIntArray(KEY_LAYOUT_MARGIN);
            if (layoutMargin != null && layoutMargin.length == 4) {
                ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
                if (layoutParams == null) {
                    layoutParams = new ViewGroup.MarginLayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT);
                } else if (!(layoutParams instanceof ViewGroup.MarginLayoutParams)) {
                    layoutParams = new ViewGroup.MarginLayoutParams(layoutParams);
                }
                ViewGroup.MarginLayoutParams marginLayoutParams =
                        (ViewGroup.MarginLayoutParams) layoutParams;
                if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
                    marginLayoutParams.setMargins(layoutMargin[0],
                            layoutMargin[1], layoutMargin[2], layoutMargin[3]);
                } else {
                    marginLayoutParams.setMargins(layoutMargin[2],
                            layoutMargin[1], layoutMargin[0], layoutMargin[3]);
                }
                view.setLayoutParams(marginLayoutParams);
            }
        }
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @NonNull
    @Override
    protected String getStyleKey() {
        return KEY_VIEW_STYLE;
    }

    /**
     * An abstract builder class for any subclass of {@link ViewStyle}.
     *
     * @param <T> represents the type this builder can build.
     * @param <B> represents the subclass of {@link ViewStyle.BaseBuilder}.
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public abstract static class BaseBuilder<T extends ViewStyle,
            B extends BaseBuilder<T, B>> extends BundledStyle.Builder<T> {

        protected BaseBuilder(@NonNull String style) {
            super(style);
        }

        /**
         * Returns {@code this} with the actual type of the subclass, so the setter methods can
         * be chained.
         */
        @NonNull
        protected abstract B getThis();

        /**
         * Sets the background.
         *
         * @param icon The icon to use as the background
         * @see android.graphics.drawable.Icon#loadDrawable(android.content.Context)
         * @see android.view.View#setBackground(android.graphics.drawable.Drawable)
         */
        @NonNull
        public B setBackground(@NonNull Icon icon) {
            Preconditions.checkNotNull(icon, "background icon should not be null");
            mBundle.putParcelable(KEY_BACKGROUND, icon);
            return getThis();
        }

        /**
         * Sets the background color, it will always override the {@link #setBackground(Icon)},
         * regardless of which set method is call first.
         *
         * @param color the color of the background
         * @see android.view.View#setBackgroundColor(int)
         */
        @NonNull
        public B setBackgroundColor(@ColorInt int color) {
            mBundle.putInt(KEY_BACKGROUND_COLOR, color);
            return getThis();
        }

        /**
         * Sets the padding.
         *
         * <p> Note that the method takes start/end rather than left/right, respecting the layout
         * direction.
         *
         * @param start   the start padding in pixels
         * @param top    the top padding in pixels
         * @param end  the end padding in pixels
         * @param bottom the bottom padding in pixels
         * @see android.view.View#setPadding(int, int, int, int)
         */
        @NonNull
        public B setPadding(int start, int top, int end, int bottom) {
            mBundle.putIntArray(KEY_PADDING, new int[]{start, top, end, bottom});
            return getThis();
        }

        /**
         * Sets the layout margin through the view's layout param.
         *
         * <p> Note that the method takes start/end rather than left/right, respecting the layout
         * direction.
         *
         * @param start   the start margin size
         * @param top    the top margin size
         * @param end  the end margin size
         * @param bottom the bottom margin size
         * @see android.view.ViewGroup.MarginLayoutParams#setMargins(int, int, int, int)
         * @see android.view.View#setLayoutParams(android.view.ViewGroup.LayoutParams)
         */
        @NonNull
        public B setLayoutMargin(int start, int top, int end,
                int bottom) {
            mBundle.putIntArray(KEY_LAYOUT_MARGIN, new int[]{start, top, end, bottom});
            return getThis();
        }
    }

    /**
     * Builder for the {@link ViewStyle}.
     */
    public static final class Builder extends BaseBuilder<ViewStyle, Builder> {

        public Builder() {
            super(KEY_VIEW_STYLE);
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @NonNull
        @Override
        protected Builder getThis() {
            return this;
        }

        @Override
        @NonNull
        public ViewStyle build() {
            return new ViewStyle(mBundle);
        }
    }
}