InlinePresentationBuilder.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;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.slice.Slice;
import android.app.slice.SliceSpec;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.service.autofill.AutofillService;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.Preconditions;
import java.util.Collections;
/**
* Helper class to create {@link Slice} for rendering into inline suggestions.
*
* <p>This builder is used by {@link AutofillService} and providers to create slices representing
* their inline suggestions UI.</p>
*
* TODO(b/147116534): Add documentation about UI templating.
*/
@SuppressLint("TopLevelBuilder")
@RequiresApi(api = Build.VERSION_CODES.Q) //TODO(b/147116534): Update to R.
public class InlinePresentationBuilder {
private static final String INLINE_PRESENTATION_SPEC_TYPE = "InlinePresentation";
private static final int INLINE_PRESENTATION_SPEC_VERSION = 1;
private static final String INLINE_PRESENTATION_SLICE_URI = "inline.slice";
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_ACTION = "inline_action";
@Nullable private Icon mStartIcon;
@Nullable private Icon mEndIcon;
@Nullable private String mTitle;
@Nullable private String mSubtitle;
@Nullable private PendingIntent mAction;
private boolean mDestroyed;
/**
* Initializes an {@link InlinePresentationBuilder} with title text.
*
* @param title String displayed as title of slice.
*/
public InlinePresentationBuilder(@NonNull CharSequence title) {
Preconditions.checkNotNull(title, "Title must not be null");
mTitle = title.toString();
}
/**
* Initializes a an {@link InlinePresentationBuilder}.
*/
public InlinePresentationBuilder() {
}
/**
* Sets the subtitle of {@link Slice}.
*
* @param subtitle String displayed as subtitle of slice.
*/
public @NonNull InlinePresentationBuilder setSubtitle(@NonNull CharSequence subtitle) {
throwIfDestroyed();
Preconditions.checkNotNull(subtitle, "Subtitle should not be null");
mSubtitle = subtitle.toString();
return this;
}
/**
* Sets the start icon of {@link Slice}.
*
* @param startIcon {@link Icon} resource displayed at start of slice.
*/
public @NonNull InlinePresentationBuilder setStartIcon(@NonNull Icon startIcon) {
throwIfDestroyed();
Preconditions.checkNotNull(startIcon, "StartIcon should not be null");
mStartIcon = startIcon;
return this;
}
/**
* Sets the end icon of {@link Slice}.
*
* @param endIcon {@link Icon} resource displayed at end of slice.
*/
public @NonNull InlinePresentationBuilder setEndIcon(@NonNull Icon endIcon) {
throwIfDestroyed();
Preconditions.checkNotNull(endIcon, "EndIcon should not be null");
mEndIcon = endIcon;
return this;
}
/**
* Sets the action of {@link Slice}.
*
* @param action invoked when the slice is tapped.
*/
public @NonNull InlinePresentationBuilder setAction(@NonNull PendingIntent action) {
throwIfDestroyed();
Preconditions.checkNotNull(action, "Action should not be null");
mAction = action;
return this;
}
/**
* Creates a new {@link Slice} instance.
*
* <p>You should not interact with this builder once this method is called.
*
* @throws IllegalStateException if none of the title, subtitle, start icon and end icon is
* set, or if the subtitle is set without the title.
*
* @return The built slice.
*/
public @NonNull Slice build() {
throwIfDestroyed();
mDestroyed = true;
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.");
}
Slice.Builder builder = new Slice.Builder(Uri.parse(INLINE_PRESENTATION_SLICE_URI),
new SliceSpec(INLINE_PRESENTATION_SPEC_TYPE, INLINE_PRESENTATION_SPEC_VERSION));
if (mStartIcon != null) {
builder.addIcon(mStartIcon, null, Collections.singletonList(HINT_INLINE_START_ICON));
}
if (mTitle != null) {
builder.addText(mTitle, null, Collections.singletonList(HINT_INLINE_TITLE));
}
if (mSubtitle != null) {
builder.addText(mSubtitle, null, Collections.singletonList(HINT_INLINE_SUBTITLE));
}
if (mEndIcon != null) {
builder.addIcon(mEndIcon, null, Collections.singletonList(HINT_INLINE_END_ICON));
}
if (mAction != null) {
builder.addAction(mAction, new Slice.Builder(builder).addHints(
Collections.singletonList(HINT_INLINE_ACTION)).build(), null);
}
return builder.build();
}
private void throwIfDestroyed() {
if (mDestroyed) {
throw new IllegalStateException("Already called #build()");
}
}
}