public final class

InlineSuggestionUi

extends java.lang.Object

 java.lang.Object

↳androidx.autofill.inline.v1.InlineSuggestionUi

Gradle dependencies

compile group: 'androidx.autofill', name: 'autofill', version: '1.2.0-beta01'

  • groupId: androidx.autofill
  • artifactId: autofill
  • version: 1.2.0-beta01

Artifact androidx.autofill:autofill:1.2.0-beta01 it located at Google repository (https://maven.google.com/)

Overview

The entry point for building the content or style for the V1 inline suggestion UI.

The V1 UI composes of four widgets, put in order in a horizontal linear layout: start icon, title, subtitle, and end icon. Some of the widgets are optional, or conditionally optional based on existence of other widgets. See InlineSuggestionUi.Content.Builder.build() for the conditions.

A default theme will be applied on the UI. The client can use InlineSuggestionUi.Style to customize the style for individual widgets as well as the overall UI background.

For Autofill provider developer, to build a content Slice that can be used as input to the , you may use the InlineSuggestionUi.Content.Builder. For example:

   public Slice createSlice(
       InlinePresentationSpec imeSpec,
       CharSequence title,
       CharSequence subtitle,
       Icon startIcon,
       Icon endIcon,
       CharSequence contentDescription,
       PendingIntent attribution) {
     // Make sure that the IME spec claims support for v1 UI template.
     Bundle imeStyle = imeSpec.getStyle();
     if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) {
       return null;
     }

     // Build the content for the v1 UI.
     Content.Builder builder =
         InlineSuggestionUi.newContentBuilder(attribution)
           .setContentDescription(contentDescription);
     if(!TextUtils.isEmpty(title)) {
       builder.setTitle(title);
     }
     if (!TextUtils.isEmpty(subtitle)) {
       builder.setSubtitle(subtitle);
     }
     if (startIcon != null) {
       startIcon.setTintBlendMode(BlendMode.DST)
       builder.setStartIcon(startIcon);
     }
     if (endIcon != null) {
       builder.setEndIcon(endIcon);
     }
     return builder.build().getSlice();
   }
 

For IME developer, to build a styles that can be used as input to the , you may use the . For example:

   public Bundle createBundle(Bundle uiExtras) {
     // We have styles builder, because it's possible that the IME can support multiple UI
     // templates in the future.
     StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();

     // Assuming we only want to support v1 UI template. If the provided uiExtras doesn't contain
     // v1, then return null.
     if (!UiVersions.getVersions(uiExtras).contains(UiVersions.INLINE_UI_VERSION_1)) {
       return null;
     }

     // Create the style for v1 template.
     Style style = InlineSuggestionUi.newStyleBuilder()
         .setSingleIconChipStyle(
             new ViewStyle.Builder()
                 .setBackgroundColor(Color.TRANSPARENT)
                 .setPadding(0, 0, 0, 0)
                 .setLayoutMargin(0, 0, 0, 0)
                 .build())
         .setSingleIconChipIconStyle(
             new ImageViewStyle.Builder()
                 .setMaxWidth(actionIconSize)
                 .setMaxHeight(actionIconSize)
                 .setScaleType(ScaleType.FIT_CENTER)
                 .setLayoutMargin(0, 0, pinnedActionMarginEnd, 0)
                 .setTintList(actionIconColor)
                 .build())
         .setChipStyle(
             new ViewStyle.Builder()
                 .setBackground(
                     Icon.createWithResource(this, R.drawable.chip_background))
                 .setPadding(toPixel(13), 0, toPixel(13), 0)
                 .build())
         .setStartIconStyle(
             new ImageViewStyle.Builder()
                 .setLayoutMargin(0, 0, 0, 0)
                 .setTintList(chipIconColor)
                 .build())
         .setTitleStyle(
             new TextViewStyle.Builder()
                 .setLayoutMargin(toPixel(4), 0, toPixel(4), 0)
                 .setTextColor(Color.parseColor("#FF202124"))
                 .setTextSize(16)
                 .build())
         .setSubtitleStyle(
             new TextViewStyle.Builder()
                 .setLayoutMargin(0, 0, toPixel(4), 0)
                 .setTextColor(Color.parseColor("#99202124")) // 60% opacity
                 .setTextSize(14)
                 .build())
         .setEndIconStyle(
             new ImageViewStyle.Builder()
                 .setLayoutMargin(0, 0, 0, 0)
                 .setTintList(chipIconColor)
                 .build())
         .build();

     // Add v1 UI style to the supported styles and return.
     stylesBuilder.addStyle(style);
     Bundle stylesBundle = stylesBuilder.build();
     return stylesBundle;
   }
 

Alternatively, if the IME wants to use the default style, then:

   public Bundle createBundle(Bundle uiExtras) {
     if (!UiVersions.getVersions(uiExtras).contains(UiVersions.INLINE_UI_VERSION_1)) {
       return null;
     }
     StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
     stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
     return stylesBuilder.build();
   }
 

Summary

Methods
public static InlineSuggestionUi.StylefromBundle(Bundle styleBundle)

public static InlineSuggestionUi.ContentfromSlice(Slice contentSlice)

public static PendingIntentgetAttributionIntent(InlineSuggestionUi.Content content)

public static InlineSuggestionUi.Content.BuildernewContentBuilder(PendingIntent attributionIntent)

Returns a builder to build the content for V1 inline suggestion UI.

public static InlineSuggestionUi.Style.BuildernewStyleBuilder()

Returns a builder to build the style for V1 inline suggestion UI.

public static Viewrender(Context context, InlineSuggestionUi.Content content, InlineSuggestionUi.Style style)

Renders the V1 inline suggestion view with the provided content and style.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static InlineSuggestionUi.Content.Builder newContentBuilder(PendingIntent attributionIntent)

Returns a builder to build the content for V1 inline suggestion UI.

Important Note: The is responsible for keeping track of the PendingIntent attribution intents it has used and cleaning them up properly with PendingIntent, or reusing them for the next set of suggestions. Intents are safe to cleanup on receiving a new call.

Parameters:

attributionIntent: invoked when the UI is long-pressed.

See also: Renderer.getAttributionIntent(Slice)

public static InlineSuggestionUi.Style.Builder newStyleBuilder()

Returns a builder to build the style for V1 inline suggestion UI.

public static InlineSuggestionUi.Content fromSlice(Slice contentSlice)

Parameters:

contentSlice: the content slice for V1

Returns:

the V1 content created from the slice, or null if the slice is invalid

public static InlineSuggestionUi.Style fromBundle(Bundle styleBundle)

Parameters:

styleBundle: the style bundle for V1

Returns:

the V1 style created from the bundle, or null if the bundle is invalid

public static View render(Context context, InlineSuggestionUi.Content content, InlineSuggestionUi.Style style)

Renders the V1 inline suggestion view with the provided content and style.

public static PendingIntent getAttributionIntent(InlineSuggestionUi.Content content)

See also: Renderer.getAttributionIntent(Slice), InlineSuggestionUi.Content.getAttributionIntent()

Source

/*
 * 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.v1;

import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_TEXT;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;

import android.app.PendingIntent;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.autofill.R;
import androidx.autofill.inline.UiVersions;
import androidx.autofill.inline.common.BundledStyle;
import androidx.autofill.inline.common.ImageViewStyle;
import androidx.autofill.inline.common.SlicedContent;
import androidx.autofill.inline.common.TextViewStyle;
import androidx.autofill.inline.common.ViewStyle;

import java.util.Collections;
import java.util.List;

/**
 * The entry point for building the content or style for the V1 inline suggestion UI.
 *
 * <p>The V1 UI composes of four widgets, put in order in a horizontal linear layout: start icon,
 * title, subtitle, and end icon. Some of the widgets are optional, or conditionally optional
 * based on existence of other widgets. See {@link Content.Builder#build()} for the conditions.
 *
 * <p>A default theme will be applied on the UI. The client can use {@link Style} to customize
 * the style for individual widgets as well as the overall UI background.
 *
 * <p>For Autofill provider developer, to build a content {@link Slice} that can be used as input to
 * the {@link android.service.autofill.InlinePresentation}, you may use the
 * {@link InlineSuggestionUi.Content.Builder}. For example:
 *
 * <pre class="prettyprint">
 *   public Slice createSlice(
 *       InlinePresentationSpec imeSpec,
 *       CharSequence title,
 *       CharSequence subtitle,
 *       Icon startIcon,
 *       Icon endIcon,
 *       CharSequence contentDescription,
 *       PendingIntent attribution) {
 *     // Make sure that the IME spec claims support for v1 UI template.
 *     Bundle imeStyle = imeSpec.getStyle();
 *     if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1)) {
 *       return null;
 *     }
 *
 *     // Build the content for the v1 UI.
 *     Content.Builder builder =
 *         InlineSuggestionUi.newContentBuilder(attribution)
 *           .setContentDescription(contentDescription);
 *     if(!TextUtils.isEmpty(title)) {
 *       builder.setTitle(title);
 *     }
 *     if (!TextUtils.isEmpty(subtitle)) {
 *       builder.setSubtitle(subtitle);
 *     }
 *     if (startIcon != null) {
 *       startIcon.setTintBlendMode(BlendMode.DST)
 *       builder.setStartIcon(startIcon);
 *     }
 *     if (endIcon != null) {
 *       builder.setEndIcon(endIcon);
 *     }
 *     return builder.build().getSlice();
 *   }
 * </pre>
 *
 * <p>For IME developer, to build a styles {@link Bundle} that can be used as input to the
 * {@link android.widget.inline.InlinePresentationSpec}, you may use the
 * {@link UiVersions.StylesBuilder}. For example:
 *
 * <pre class="prettyprint">
 *   public Bundle createBundle(Bundle uiExtras) {
 *     // We have styles builder, because it's possible that the IME can support multiple UI
 *     // templates in the future.
 *     StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
 *
 *     // Assuming we only want to support v1 UI template. If the provided uiExtras doesn't contain
 *     // v1, then return null.
 *     if (!UiVersions.getVersions(uiExtras).contains(UiVersions.INLINE_UI_VERSION_1)) {
 *       return null;
 *     }
 *
 *     // Create the style for v1 template.
 *     Style style = InlineSuggestionUi.newStyleBuilder()
 *         .setSingleIconChipStyle(
 *             new ViewStyle.Builder()
 *                 .setBackgroundColor(Color.TRANSPARENT)
 *                 .setPadding(0, 0, 0, 0)
 *                 .setLayoutMargin(0, 0, 0, 0)
 *                 .build())
 *         .setSingleIconChipIconStyle(
 *             new ImageViewStyle.Builder()
 *                 .setMaxWidth(actionIconSize)
 *                 .setMaxHeight(actionIconSize)
 *                 .setScaleType(ScaleType.FIT_CENTER)
 *                 .setLayoutMargin(0, 0, pinnedActionMarginEnd, 0)
 *                 .setTintList(actionIconColor)
 *                 .build())
 *         .setChipStyle(
 *             new ViewStyle.Builder()
 *                 .setBackground(
 *                     Icon.createWithResource(this, R.drawable.chip_background))
 *                 .setPadding(toPixel(13), 0, toPixel(13), 0)
 *                 .build())
 *         .setStartIconStyle(
 *             new ImageViewStyle.Builder()
 *                 .setLayoutMargin(0, 0, 0, 0)
 *                 .setTintList(chipIconColor)
 *                 .build())
 *         .setTitleStyle(
 *             new TextViewStyle.Builder()
 *                 .setLayoutMargin(toPixel(4), 0, toPixel(4), 0)
 *                 .setTextColor(Color.parseColor("#FF202124"))
 *                 .setTextSize(16)
 *                 .build())
 *         .setSubtitleStyle(
 *             new TextViewStyle.Builder()
 *                 .setLayoutMargin(0, 0, toPixel(4), 0)
 *                 .setTextColor(Color.parseColor("#99202124")) // 60% opacity
 *                 .setTextSize(14)
 *                 .build())
 *         .setEndIconStyle(
 *             new ImageViewStyle.Builder()
 *                 .setLayoutMargin(0, 0, 0, 0)
 *                 .setTintList(chipIconColor)
 *                 .build())
 *         .build();
 *
 *     // Add v1 UI style to the supported styles and return.
 *     stylesBuilder.addStyle(style);
 *     Bundle stylesBundle = stylesBuilder.build();
 *     return stylesBundle;
 *   }
 * </pre>
 *
 * <p>Alternatively, if the IME wants to use the default style, then:
 *
 * <pre class="prettyprint">
 *   public Bundle createBundle(Bundle uiExtras) {
 *     if (!UiVersions.getVersions(uiExtras).contains(UiVersions.INLINE_UI_VERSION_1)) {
 *       return null;
 *     }
 *     StylesBuilder stylesBuilder = UiVersions.newStylesBuilder();
 *     stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
 *     return stylesBuilder.build();
 *   }
 * </pre>
 */
@RequiresApi(api = Build.VERSION_CODES.R)
public final class InlineSuggestionUi {
    private static final String TAG = "InlineSuggestionUi";

    /**
     * Returns a builder to build the content for V1 inline suggestion UI.
     *
     * <p><b>Important Note:</b> The
     * {@link android.service.autofill.AutofillService AutofillService} is responsible for keeping
     * track of the {@link PendingIntent} attribution intents it has used and cleaning them up
     * properly with {@link PendingIntent#cancel()}, or reusing them for the next set of
     * suggestions. Intents are safe to cleanup on receiving a new
     * {@link android.service.autofill.AutofillService#onFillRequest} call.
     * </p>
     *
     * @param attributionIntent invoked when the UI is long-pressed.
     * @see androidx.autofill.inline.Renderer#getAttributionIntent(Slice)
     */
    @NonNull
    public static Content.Builder newContentBuilder(@NonNull PendingIntent attributionIntent) {
        return new Content.Builder(attributionIntent);
    }

    /**
     * Returns a builder to build the style for V1 inline suggestion UI.
     */
    @NonNull
    public static Style.Builder newStyleBuilder() {
        return new Style.Builder();
    }

    /**
     * @param contentSlice the content slice for V1
     * @return the V1 content created from the slice, or null if the slice is invalid
     * @hide
     */
    @RestrictTo(LIBRARY)
    @Nullable
    public static Content fromSlice(@NonNull Slice contentSlice) {
        Content content = new Content(contentSlice);
        if (!content.isValid()) {
            Log.w(TAG, "Invalid content for " + UiVersions.INLINE_UI_VERSION_1);
            return null;
        }
        return content;
    }

    /**
     * @param styleBundle the style bundle for V1
     * @return the V1 style created from the bundle, or null if the bundle is invalid
     * @hide
     */
    @RestrictTo(LIBRARY)
    @Nullable
    public static Style fromBundle(@NonNull Bundle styleBundle) {
        Style style = new Style(styleBundle);
        if (!style.isValid()) {
            Log.w(TAG, "Invalid style for " + UiVersions.INLINE_UI_VERSION_1);
            return null;
        }
        return style;
    }

    /**
     * Renders the V1 inline suggestion view with the provided content and style.
     *
     * @hide
     */
    @RestrictTo(LIBRARY)
    @NonNull
    public static View render(@NonNull Context context, @NonNull Content content,
            @NonNull Style style) {
        context = getDefaultContextThemeWrapper(context);
        final LayoutInflater inflater = LayoutInflater.from(context);
        final ViewGroup suggestionView =
                (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);

        final ImageView startIconView =
                suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
        final TextView titleView =
                suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
        final TextView subtitleView =
                suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
        final ImageView endIconView =
                suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);

        final CharSequence title = content.getTitle();
        if (title != null) {
            titleView.setText(title);
            titleView.setVisibility(View.VISIBLE);
        }
        final CharSequence subtitle = content.getSubtitle();
        if (subtitle != null) {
            subtitleView.setText(subtitle);
            subtitleView.setVisibility(View.VISIBLE);
        }
        final Icon startIcon = content.getStartIcon();
        if (startIcon != null) {
            startIconView.setImageIcon(startIcon);
            startIconView.setVisibility(View.VISIBLE);
        }
        final Icon endIcon = content.getEndIcon();
        if (endIcon != null) {
            endIconView.setImageIcon(endIcon);
            endIconView.setVisibility(View.VISIBLE);
        }
        final CharSequence contentDescription = content.getContentDescription();
        if (!TextUtils.isEmpty(contentDescription)) {
            suggestionView.setContentDescription(contentDescription);
        }

        if (style.isValid()) {
            if (content.isSingleIconOnly()) {
                style.applyStyle(suggestionView, startIconView);
            } else {
                style.applyStyle(suggestionView, startIconView, titleView,
                        subtitleView, endIconView);
            }
        }
        return suggestionView;
    }

    /**
     * @hide
     * @see androidx.autofill.inline.Renderer#getAttributionIntent(Slice)
     * @see Content#getAttributionIntent()
     */
    @RestrictTo(LIBRARY)
    @Nullable
    public static PendingIntent getAttributionIntent(@NonNull Content content) {
        return content.getAttributionIntent();
    }

    private static Context getDefaultContextThemeWrapper(@NonNull Context context) {
        Resources.Theme theme = context.getResources().newTheme();
        theme.applyStyle(R.style.Theme_AutofillInlineSuggestion, true);
        return new ContextThemeWrapper(context, theme);
    }

    private InlineSuggestionUi() {
    }

    /**
     * Style for the V1 inline suggestion UI.
     */
    public static final class Style extends BundledStyle implements UiVersions.Style {
        private static final String KEY_STYLE_V1 = "style_v1";
        private static final String KEY_CHIP_STYLE = "chip_style";
        private static final String KEY_TITLE_STYLE = "title_style";
        private static final String KEY_SUBTITLE_STYLE = "subtitle_style";
        private static final String KEY_START_ICON_STYLE = "start_icon_style";
        private static final String KEY_END_ICON_STYLE = "end_icon_style";
        private static final String KEY_SINGLE_ICON_CHIP_STYLE = "single_icon_chip_style";
        private static final String KEY_SINGLE_ICON_CHIP_ICON_STYLE = "single_icon_chip_icon_style";
        private static final String KEY_LAYOUT_DIRECTION = "layout_direction";

        /**
         * Use {@link InlineSuggestionUi#fromBundle(Bundle)} or {@link Builder#build()} to
         * instantiate the class.
         */
        Style(@NonNull Bundle bundle) {
            super(bundle);
        }

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

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public void applyStyle(@NonNull View singleIconChipView,
                @NonNull ImageView singleIconView) {
            if (!isValid()) {
                return;
            }

            // layout direction
            singleIconChipView.setLayoutDirection(getLayoutDirection());

            // single icon
            if (singleIconView.getVisibility() != View.GONE) {
                ImageViewStyle singleIconViewStyle = getSingleIconChipIconStyle();
                if (singleIconViewStyle == null) {
                    singleIconViewStyle = getStartIconStyle();
                }
                if (singleIconViewStyle != null) {
                    singleIconViewStyle.applyStyleOnImageViewIfValid(singleIconView);
                }
            }
            // entire chip
            ViewStyle chipViewStyle = getSingleIconChipStyle();
            if (chipViewStyle == null) {
                chipViewStyle = getChipStyle();
            }
            if (chipViewStyle != null) {
                chipViewStyle.applyStyleOnViewIfValid(singleIconChipView);
            }
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public void applyStyle(@NonNull View chipView, @NonNull ImageView startIconView,
                @NonNull TextView titleView, @NonNull TextView subtitleView,
                @NonNull ImageView endIconView) {
            if (!isValid()) {
                return;
            }

            // layout direction
            chipView.setLayoutDirection(getLayoutDirection());

            // start icon
            if (startIconView.getVisibility() != View.GONE) {
                ImageViewStyle startIconViewStyle = getStartIconStyle();
                if (startIconViewStyle != null) {
                    startIconViewStyle.applyStyleOnImageViewIfValid(startIconView);
                }
            }
            // title
            if (titleView.getVisibility() != View.GONE) {
                TextViewStyle titleStyle = getTitleStyle();
                if (titleStyle != null) {
                    titleStyle.applyStyleOnTextViewIfValid(titleView);
                }
            }
            // subtitle
            if (subtitleView.getVisibility() != View.GONE) {
                TextViewStyle subtitleStyle = getSubtitleStyle();
                if (subtitleStyle != null) {
                    subtitleStyle.applyStyleOnTextViewIfValid(subtitleView);
                }
            }
            // end icon
            if (endIconView.getVisibility() != View.GONE) {
                ImageViewStyle endIconViewStyle = getEndIconStyle();
                if (endIconViewStyle != null) {
                    endIconViewStyle.applyStyleOnImageViewIfValid(endIconView);
                }
            }
            // entire chip
            ViewStyle chipViewStyle = getChipStyle();
            if (chipViewStyle != null) {
                chipViewStyle.applyStyleOnViewIfValid(chipView);
            }
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @NonNull
        @Override
        public String getVersion() {
            return UiVersions.INLINE_UI_VERSION_1;
        }

        /**
         * @see Builder#setLayoutDirection(int)
         */
        public int getLayoutDirection() {
            int layoutDirection = mBundle.getInt(KEY_LAYOUT_DIRECTION, View.LAYOUT_DIRECTION_LTR);
            if (layoutDirection != View.LAYOUT_DIRECTION_LTR
                    && layoutDirection != View.LAYOUT_DIRECTION_RTL) {
                layoutDirection = View.LAYOUT_DIRECTION_LTR;
            }
            return layoutDirection;
        }

        /**
         * @see Builder#setChipStyle(ViewStyle)
         */
        @Nullable
        public ViewStyle getChipStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_CHIP_STYLE);
            return styleBundle == null ? null : new ViewStyle(styleBundle);
        }

        /**
         * @see Builder#setTitleStyle(TextViewStyle)
         */
        @Nullable
        public TextViewStyle getTitleStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_TITLE_STYLE);
            return styleBundle == null ? null : new TextViewStyle(styleBundle);
        }

        /**
         * @see Builder#setSubtitleStyle(TextViewStyle)
         */
        @Nullable
        public TextViewStyle getSubtitleStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_SUBTITLE_STYLE);
            return styleBundle == null ? null : new TextViewStyle(styleBundle);
        }

        /**
         * @see Builder#setStartIconStyle(ImageViewStyle)
         */
        @Nullable
        public ImageViewStyle getStartIconStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_START_ICON_STYLE);
            return styleBundle == null ? null : new ImageViewStyle(styleBundle);
        }

        /**
         * @see Builder#setEndIconStyle(ImageViewStyle)
         */
        @Nullable
        public ImageViewStyle getEndIconStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_END_ICON_STYLE);
            return styleBundle == null ? null : new ImageViewStyle(styleBundle);
        }

        /**
         * @see Builder#setSingleIconChipStyle(ViewStyle)
         */
        @Nullable
        public ViewStyle getSingleIconChipStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_SINGLE_ICON_CHIP_STYLE);
            return styleBundle == null ? null : new ViewStyle(styleBundle);
        }

        /**
         * @see Builder#setSingleIconChipIconStyle(ImageViewStyle)
         */
        @Nullable
        public ImageViewStyle getSingleIconChipIconStyle() {
            Bundle styleBundle = mBundle.getBundle(KEY_SINGLE_ICON_CHIP_ICON_STYLE);
            return styleBundle == null ? null : new ImageViewStyle(styleBundle);
        }

        /**
         * Builder for the {@link Style}.
         */
        public static final class Builder extends BundledStyle.Builder<Style> {

            /**
             * Use {@link InlineSuggestionUi#newStyleBuilder()} to instantiate this class.
             */
            Builder() {
                super(KEY_STYLE_V1);
            }

            /**
             * Sets the layout direction for the UI.
             *
             * <p>Note that the process that renders the UI needs to have
             * {@code android:supportsRtl="true"} for this to take effect.
             *
             * @param layoutDirection the layout direction to set. Should be one of:
             *                        {@link View#LAYOUT_DIRECTION_LTR},
             *                        {@link View#LAYOUT_DIRECTION_RTL}.
             *
             * @see View#setLayoutDirection(int)
             */
            @NonNull
            public Builder setLayoutDirection(int layoutDirection) {
                mBundle.putInt(KEY_LAYOUT_DIRECTION, layoutDirection);
                return this;
            }

            /**
             * Sets the chip style.
             *
             * <p>See {@link #setSingleIconChipStyle(ViewStyle)} for more information about setting
             * a special chip style for the case where the entire chip is a single icon.
             */
            @NonNull
            public Builder setChipStyle(@NonNull ViewStyle chipStyle) {
                chipStyle.assertIsValid();
                mBundle.putBundle(KEY_CHIP_STYLE, chipStyle.getBundle());
                return this;
            }

            /**
             * Sets the title style.
             */
            @NonNull
            public Builder setTitleStyle(@NonNull TextViewStyle titleStyle) {
                titleStyle.assertIsValid();
                mBundle.putBundle(KEY_TITLE_STYLE, titleStyle.getBundle());
                return this;
            }

            /**
             * Sets the subtitle style.
             */
            @NonNull
            public Builder setSubtitleStyle(@NonNull TextViewStyle subtitleStyle) {
                subtitleStyle.assertIsValid();
                mBundle.putBundle(KEY_SUBTITLE_STYLE, subtitleStyle.getBundle());
                return this;
            }

            /**
             * Sets the start icon style.
             *
             * <p>See {@link #setSingleIconChipIconStyle(ImageViewStyle)} for more information
             * about setting a special icon style for the case where the entire chip is a single
             * icon.
             */
            @NonNull
            public Builder setStartIconStyle(@NonNull ImageViewStyle startIconStyle) {
                startIconStyle.assertIsValid();
                mBundle.putBundle(KEY_START_ICON_STYLE, startIconStyle.getBundle());
                return this;
            }

            /**
             * Sets the end icon style.
             */
            @NonNull
            public Builder setEndIconStyle(@NonNull ImageViewStyle endIconStyle) {
                endIconStyle.assertIsValid();
                mBundle.putBundle(KEY_END_ICON_STYLE, endIconStyle.getBundle());
                return this;
            }

            /**
             * Sets the chip style for the case where there is a single icon and no text. If not
             * provided, will fallback to use the chip style provided by {@link #setChipStyle
             * (ViewStyle)}.
             */
            @NonNull
            public Builder setSingleIconChipStyle(@NonNull ViewStyle chipStyle) {
                chipStyle.assertIsValid();
                mBundle.putBundle(KEY_SINGLE_ICON_CHIP_STYLE, chipStyle.getBundle());
                return this;
            }

            /**
             * Sets the icon style for the case where there is a single icon and no text in the
             * chip. If not provided, will fallback to use the icon style provided by
             * {@link #setStartIconStyle(ImageViewStyle)}
             */
            @NonNull
            public Builder setSingleIconChipIconStyle(@NonNull ImageViewStyle iconStyle) {
                iconStyle.assertIsValid();
                mBundle.putBundle(KEY_SINGLE_ICON_CHIP_ICON_STYLE, iconStyle.getBundle());
                return this;
            }

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

    /**
     * Content for the V1 inline suggestion UI.
     */
    public static final class Content extends SlicedContent {
        static final String HINT_INLINE_TITLE = "inline_title";
        static final String HINT_INLINE_SUBTITLE = "inline_subtitle";
        static final String HINT_INLINE_START_ICON = "inline_start_icon";
        static final String HINT_INLINE_END_ICON = "inline_end_icon";
        static final String HINT_INLINE_ATTRIBUTION_INTENT = "inline_attribution";
        static final String HINT_INLINE_CONTENT_DESCRIPTION = "inline_content_description";

        @Nullable
        private Icon mStartIcon;
        @Nullable
        private Icon mEndIcon;
        @Nullable
        private CharSequence mTitle;
        @Nullable
        private CharSequence mSubtitle;
        @Nullable
        private PendingIntent mAttributionIntent;
        @Nullable
        private CharSequence mContentDescription;

        /**
         * Use {@link InlineSuggestionUi#fromSlice(Slice)} or {@link Builder#build()} to
         * instantiate this class.
         */
        Content(@NonNull Slice slice) {
            super(slice);
            for (SliceItem sliceItem : slice.getItems()) {
                final String itemType = itemType(sliceItem);
                if (itemType == null) {
                    continue;
                }
                switch (itemType) {
                    case HINT_INLINE_TITLE:
                        mTitle = sliceItem.getText().toString();
                        break;
                    case HINT_INLINE_SUBTITLE:
                        mSubtitle = sliceItem.getText().toString();
                        break;
                    case HINT_INLINE_START_ICON:
                        mStartIcon = sliceItem.getIcon();
                        break;
                    case HINT_INLINE_END_ICON:
                        mEndIcon = sliceItem.getIcon();
                        break;
                    case HINT_INLINE_ATTRIBUTION_INTENT:
                        mAttributionIntent = sliceItem.getAction();
                        break;
                    case HINT_INLINE_CONTENT_DESCRIPTION:
                        mContentDescription = sliceItem.getText();
                        break;
                    default:
                        break;
                }
            }
        }

        boolean isSingleIconOnly() {
            return mStartIcon != null && mTitle == null && mSubtitle == null && mEndIcon == null;
        }

        /**
         * @see Builder#setTitle(CharSequence)
         */
        @Nullable
        public CharSequence getTitle() {
            return mTitle;
        }

        /**
         * @see Builder#setSubtitle(CharSequence)
         */
        @Nullable
        public CharSequence getSubtitle() {
            return mSubtitle;
        }

        /**
         * @see Builder#setStartIcon(Icon)
         */
        @Nullable
        public Icon getStartIcon() {
            return mStartIcon;
        }

        /**
         * @see Builder#setEndIcon(Icon)
         */
        @Nullable
        public Icon getEndIcon() {
            return mEndIcon;
        }

        /**
         * @see Builder#setContentDescription(CharSequence)
         */
        @Nullable
        public CharSequence getContentDescription() {
            return mContentDescription;
        }

        /**
         * @see InlineSuggestionUi#newContentBuilder(PendingIntent)
         */
        @Nullable
        @Override
        public PendingIntent getAttributionIntent() {
            return mAttributionIntent;
        }

        /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        @Override
        public boolean isValid() {
            return UiVersions.INLINE_UI_VERSION_1.equals(SlicedContent.getVersion(mSlice));
        }

        @Nullable
        private static String itemType(SliceItem sliceItem) {
            switch (sliceItem.getFormat()) {
                case FORMAT_IMAGE:
                    if (sliceItem.getIcon() == null) {
                        return null;
                    }
                    if (sliceItem.getHints().contains(HINT_INLINE_START_ICON)) {
                        return HINT_INLINE_START_ICON;
                    } else if (sliceItem.getHints().contains(HINT_INLINE_END_ICON)) {
                        return HINT_INLINE_END_ICON;
                    }
                    break;
                case FORMAT_TEXT:
                    if (TextUtils.isEmpty(sliceItem.getText())) {
                        return null;
                    }
                    if (sliceItem.getHints().contains(HINT_INLINE_TITLE)) {
                        return HINT_INLINE_TITLE;
                    } else if (sliceItem.getHints().contains(HINT_INLINE_SUBTITLE)) {
                        return HINT_INLINE_SUBTITLE;
                    } else if (sliceItem.getHints().contains(HINT_INLINE_CONTENT_DESCRIPTION)) {
                        return HINT_INLINE_CONTENT_DESCRIPTION;
                    }
                    break;
                case FORMAT_ACTION:
                    if (sliceItem.getAction() != null && sliceItem.getHints().contains(
                            HINT_INLINE_ATTRIBUTION_INTENT)) {
                        return HINT_INLINE_ATTRIBUTION_INTENT;
                    }
                    break;
                default:
                    return null;
            }
            return null;
        }

        /**
         * Builder for the {@link Content}.
         */
        public static final class Builder extends SlicedContent.Builder<Content> {
            @NonNull
            private final PendingIntent mAttributionIntent;
            @Nullable
            private Icon mStartIcon;
            @Nullable
            private Icon mEndIcon;
            @Nullable
            private CharSequence mTitle;
            @Nullable
            private CharSequence mSubtitle;
            @Nullable
            private CharSequence mContentDescription;
            @Nullable
            private List<String> mHints;

            /**
             * Use {@link InlineSuggestionUi#newContentBuilder(PendingIntent)} to instantiate
             * this class.
             *
             * @param attributionIntent invoked when the UI is long-pressed.
             * @see androidx.autofill.inline.Renderer#getAttributionIntent(Slice)
             */
            Builder(@NonNull PendingIntent attributionIntent) {
                super(UiVersions.INLINE_UI_VERSION_1);
                mAttributionIntent = attributionIntent;
            }

            /**
             * Sets the title of the suggestion UI.
             *
             * @param title displayed as title of slice.
             */
            @NonNull
            public Builder setTitle(@NonNull CharSequence title) {
                mTitle = title;
                return this;
            }

            /**
             * Sets the subtitle of the suggestion UI.
             *
             * @param subtitle displayed as subtitle of slice.
             */
            @NonNull
            public Builder setSubtitle(@NonNull CharSequence subtitle) {
                mSubtitle = subtitle;
                return this;
            }

            /**
             * Sets the start icon of the suggestion UI.
             *
             * <p>Note that the {@link ImageViewStyle} style may specify the tint list to be
             * applied on the icon. If you don't want that, you may disable it by calling {@code
             * Icon#setTintBlendMode(BlendMode.DST)}.
             *
             * @param startIcon {@link Icon} resource displayed at start of slice.
             */
            @NonNull
            public Builder setStartIcon(@NonNull Icon startIcon) {
                mStartIcon = startIcon;
                return this;
            }

            /**
             * Sets the end icon of the suggestion UI.
             *
             * <p>Note that the {@link ImageViewStyle} style may specify the tint list to be
             * applied on the icon. If you don't want that, you may disable it by calling {@code
             * Icon#setTintBlendMode(BlendMode.DST)}.
             *
             * @param endIcon {@link Icon} resource displayed at end of slice.
             */
            @NonNull
            public Builder setEndIcon(@NonNull Icon endIcon) {
                mEndIcon = endIcon;
                return this;
            }

            /**
             * Sets the content description for the suggestion view.
             *
             * @param contentDescription the content description.
             * @see View#setContentDescription(CharSequence)
             */
            @NonNull
            public Builder setContentDescription(@NonNull CharSequence contentDescription) {
                mContentDescription = contentDescription;
                return this;
            }

            /**
             * Sets hints to indicate the kind of data in the suggestion.
             *
             * @param hints defined in {@link androidx.autofill.inline.SuggestionHintConstants}
             */
            @NonNull
            public Builder setHints(@NonNull List<String> hints) {
                mHints = hints;
                return this;
            }

            @NonNull
            @Override
            public Content build() {
                if (mTitle == null && mStartIcon == null && mEndIcon == null && mSubtitle == null) {
                    throw new IllegalStateException(
                            "Title, subtitle, start icon, end icon are all null. "
                                    + "Please set value for at least one of them");
                }
                if (mTitle == null && mSubtitle != null) {
                    throw new IllegalStateException(
                            "Cannot set the subtitle without setting the title.");
                }
                if (mAttributionIntent == null) {
                    throw new IllegalStateException("Attribution intent cannot be null.");
                }
                if (mStartIcon != null) {
                    mSliceBuilder.addIcon(mStartIcon, null,
                            Collections.singletonList(HINT_INLINE_START_ICON));
                }
                if (mTitle != null) {
                    mSliceBuilder.addText(mTitle, null,
                            Collections.singletonList(HINT_INLINE_TITLE));
                }
                if (mSubtitle != null) {
                    mSliceBuilder.addText(mSubtitle, null,
                            Collections.singletonList(HINT_INLINE_SUBTITLE));
                }
                if (mEndIcon != null) {
                    mSliceBuilder.addIcon(mEndIcon, null,
                            Collections.singletonList(HINT_INLINE_END_ICON));
                }
                if (mAttributionIntent != null) {
                    mSliceBuilder.addAction(mAttributionIntent, new Slice.Builder(
                                    mSliceBuilder).addHints(
                            Collections.singletonList(HINT_INLINE_ATTRIBUTION_INTENT)).build(),
                            null);
                }
                if (mContentDescription != null) {
                    mSliceBuilder.addText(mContentDescription, null,
                            Collections.singletonList(HINT_INLINE_CONTENT_DESCRIPTION));
                }
                if (mHints != null) {
                    mSliceBuilder.addHints(mHints);
                }
                return new Content(mSliceBuilder.build());
            }
        }
    }
}