public class

SliceUtils

extends java.lang.Object

 java.lang.Object

↳androidx.slice.SliceUtils

Gradle dependencies

compile group: 'androidx.slice', name: 'slice-view', version: '1.1.0-alpha02'

  • groupId: androidx.slice
  • artifactId: slice-view
  • version: 1.1.0-alpha02

Artifact androidx.slice:slice-view:1.1.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.slice:slice-view com.android.support:slices-view

Overview

Utilities for dealing with slices.

Summary

Methods
public static IconCompatconvert(Context context, IconCompat icon, SliceUtils.SerializeOptions options)

public static intparseImageMode(SliceItem iconItem)

public static SliceparseSlice(Context context, java.io.InputStream input, java.lang.String encoding, SliceUtils.SliceActionListener listener)

Parse a slice that has been previously serialized.

public static voidserializeSlice(Slice s, Context context, java.io.OutputStream output, SliceUtils.SerializeOptions options)

Serialize a slice to an OutputStream.

public static SlicestripSlice(Slice s, int mode, boolean isScrollable)

Attempt to remove any extra content from the slice.

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

Methods

public static Slice stripSlice(Slice s, int mode, boolean isScrollable)

Attempt to remove any extra content from the slice.

This is meant to be used alongside SliceUtils.serializeSlice(Slice, Context, OutputStream, SliceUtils.SerializeOptions) to reduce the size of a slice by only including data for the slice to be displayed in a specific mode.

Parameters:

s: The slice to strip.
mode: The mode that will be used with SliceView.setMode(int) to display the slice.
isScrollable: The value that will be used with SliceView.setScrollable(boolean) to display the slice.

Returns:

returns A new smaller slice if stripping can be done, or the original slice if no content can be removed.

public static void serializeSlice(Slice s, Context context, java.io.OutputStream output, SliceUtils.SerializeOptions options)

Serialize a slice to an OutputStream.

The serialized slice can later be read into slice form again with SliceUtils.parseSlice(Context, InputStream, String, SliceUtils.SliceActionListener). Some slice types cannot be serialized, their handling is controlled by SliceUtils.SerializeOptions.

Parameters:

s: The slice to serialize.
context: Context used to load any resources in the slice.
output: The output of the serialization.
options: Options defining how to handle non-serializable items.

public static IconCompat convert(Context context, IconCompat icon, SliceUtils.SerializeOptions options)

public static Slice parseSlice(Context context, java.io.InputStream input, java.lang.String encoding, SliceUtils.SliceActionListener listener)

Parse a slice that has been previously serialized.

Parses a slice that was serialized with SliceUtils.serializeSlice(Slice, Context, OutputStream, SliceUtils.SerializeOptions).

Note: Slices returned by this cannot be passed to SliceConvert.unwrap(Slice).

Parameters:

input: The input stream to read from.
encoding: The encoding to read as.
listener: Listener used to handle actions when reconstructing the slice.

public static int parseImageMode(SliceItem iconItem)

Source

/*
 * Copyright 2017 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.slice;

import static android.app.slice.Slice.HINT_ACTIONS;
import static android.app.slice.Slice.HINT_KEYWORDS;
import static android.app.slice.Slice.HINT_LAST_UPDATED;
import static android.app.slice.Slice.HINT_TTL;
import static android.app.slice.Slice.SUBTYPE_COLOR;
import static android.app.slice.Slice.SUBTYPE_LAYOUT_DIRECTION;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
import static android.app.slice.SliceItem.FORMAT_INT;
import static android.app.slice.SliceItem.FORMAT_LONG;
import static android.app.slice.SliceItem.FORMAT_REMOTE_INPUT;

import static androidx.slice.core.SliceHints.SUBTYPE_MILLIS;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.net.Uri;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.core.SliceAction;
import androidx.slice.core.SliceActionImpl;
import androidx.slice.core.SliceHints;
import androidx.slice.core.SliceQuery;
import androidx.slice.widget.ListContent;
import androidx.slice.widget.SliceView;
import androidx.slice.widget.SliceView.SliceMode;
import androidx.versionedparcelable.ParcelUtils;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.nio.charset.Charset;
import java.util.List;

/**
 * Utilities for dealing with slices.
 */
@RequiresApi(19)
public class SliceUtils {

    private SliceUtils() {
    }

    /**
     * Attempt to remove any extra content from the slice.
     * <p>
     * This is meant to be used alongside {@link #serializeSlice} to reduce
     * the size of a slice by only including data for the slice to be displayed
     * in a specific mode.
     *
     * @param s            The slice to strip.
     * @param mode         The mode that will be used with {@link SliceView#setMode} to
     *                     display the slice.
     * @param isScrollable The value that will be used with {@link SliceView#setScrollable} to
     *                     display the slice.
     * @return returns A new smaller slice if stripping can be done, or the
     * original slice if no content can be removed.
     */
    @NonNull
    public static Slice stripSlice(@NonNull Slice s, @SliceMode int mode, boolean isScrollable) {
        ListContent listContent = new ListContent(s);
        if (listContent.isValid()) {
            Slice.Builder builder = copyMetadata(s);
            switch (mode) {
                case SliceView.MODE_SHORTCUT:
                    // TODO -- passing context in here will ensure we always have shortcut / can
                    // fall back to appInfo
                    SliceAction shortcutAction = listContent.getShortcut(null);
                    if (shortcutAction != null) {
                        return ((SliceActionImpl) shortcutAction).buildSlice(builder);
                    } else {
                        return s;
                    }
                case SliceView.MODE_SMALL:
                    builder.addItem(listContent.getHeader().getSliceItem());
                    List<SliceAction> actions = listContent.getSliceActions();
                    if (actions != null && actions.size() > 0) {
                        Slice.Builder sb = new Slice.Builder(builder);
                        for (SliceAction action : actions) {
                            Slice.Builder b = new Slice.Builder(sb).addHints(HINT_ACTIONS);
                            sb.addSubSlice(((SliceActionImpl) action).buildSlice(b));
                        }
                        builder.addSubSlice(sb.addHints(HINT_ACTIONS).build());
                    }
                    return builder.build();
                case SliceView.MODE_LARGE:
                    // TODO: Implement stripping for large mode
                default:
                    return s;
            }
        }
        return s;
    }

    /**
     * @return A slice builder that contains slice metadata from top-level items.
     */
    private static Slice.Builder copyMetadata(@NonNull Slice s) {
        Slice.Builder slice = new Slice.Builder(s.getUri());

        // Adds TTL item into new slice builder.
        SliceItem ttlItem = SliceQuery.findTopLevelItem(s, FORMAT_LONG, HINT_TTL, null, null);
        if (ttlItem != null) {
            slice.addLong(ttlItem.getLong(), SUBTYPE_MILLIS, HINT_TTL);
        }

        // Adds last updated item into new slice builder.
        SliceItem updatedItem = SliceQuery.findTopLevelItem(
                s, FORMAT_LONG, HINT_LAST_UPDATED, null, null);
        if (updatedItem != null) {
            slice.addLong(updatedItem.getLong(), SUBTYPE_MILLIS, HINT_LAST_UPDATED);
        }

        // Adds color item into new slice builder.
        SliceItem colorItem = SliceQuery.findTopLevelItem(s, FORMAT_INT, SUBTYPE_COLOR, null,
                null);
        if (colorItem != null) {
            slice.addInt(colorItem.getInt(), SUBTYPE_COLOR);
        }

        // Adds layout direction item into new slice builder.
        SliceItem layoutDirItem = SliceQuery.findTopLevelItem(
                s, FORMAT_INT, SUBTYPE_LAYOUT_DIRECTION, null, null);
        if (layoutDirItem != null) {
            slice.addInt(layoutDirItem.getInt(), SUBTYPE_LAYOUT_DIRECTION);
        }

        // Adds key words item into new slice builder.
        List<String> keyWords = SliceMetadata.from(null, s).getSliceKeywords();
        if (keyWords != null && keyWords.size() > 0) {
            Slice.Builder sb = new Slice.Builder(slice);
            for (String keyword : keyWords) {
                sb.addText(keyword, null);
            }
            slice.addSubSlice(sb.addHints(HINT_KEYWORDS).build());
        }

        // Adds top-level hints into new slice builder.
        List<String> hints = s.getHints();
        if (hints.size() > 0) {
            slice.addHints(hints);
        }
        return slice;
    }

    /**
     * Serialize a slice to an OutputStream.
     * <p>
     * The serialized slice can later be read into slice form again with {@link #parseSlice}.
     * Some slice types cannot be serialized, their handling is controlled by
     * {@link SerializeOptions}.
     *
     * @param s       The slice to serialize.
     * @param context Context used to load any resources in the slice.
     * @param output  The output of the serialization.
     * @param options Options defining how to handle non-serializable items.
     * @throws IllegalArgumentException if the slice cannot be serialized using the given options.
     */
    public static void serializeSlice(@NonNull Slice s, @NonNull final Context context,
            @NonNull OutputStream output,
            @NonNull final SerializeOptions options) throws IllegalArgumentException {
        synchronized (SliceItemHolder.sSerializeLock) {
            SliceItemHolder.sHandler = new SliceItemHolder.HolderHandler() {
                @Override
                public void handle(SliceItemHolder holder, String format) {
                    handleOptions(context, holder, format, options);
                }
            };
            ParcelUtils.toOutputStream(s, output);
            SliceItemHolder.sHandler = null;
        }
    }

    static void handleOptions(Context context, SliceItemHolder holder, String format,
            SerializeOptions options) {
        switch (format) {
            case FORMAT_IMAGE:
                switch (options.getImageMode()) {
                    case SerializeOptions.MODE_THROW:
                        throw new IllegalArgumentException("Cannot serialize icon");
                    case SerializeOptions.MODE_REMOVE:
                        // Remove the icon.
                        holder.mVersionedParcelable = null;
                        break;
                    case SerializeOptions.MODE_CONVERT:
                        holder.mVersionedParcelable = convert(context,
                                (IconCompat) holder.mVersionedParcelable, options);
                        break;
                }
                break;
            case FORMAT_REMOTE_INPUT:
                if (options.getActionMode() == SerializeOptions.MODE_THROW) {
                    throw new IllegalArgumentException("Cannot serialize action");
                } else {
                    holder.mParcelable = null;
                }
                break;
            case FORMAT_ACTION:
                switch (options.getActionMode()) {
                    case SerializeOptions.MODE_THROW:
                        throw new IllegalArgumentException("Cannot serialize action");
                    case SerializeOptions.MODE_REMOVE:
                        holder.mVersionedParcelable = null;
                        break;
                    case SerializeOptions.MODE_CONVERT:
                        holder.mParcelable = null;
                        break;
                }
                break;
        }
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static IconCompat convert(Context context, IconCompat icon, SerializeOptions options) {
        switch (icon.getType()) {
            case Icon.TYPE_RESOURCE:
                return icon;
            default:
                byte[] data = SliceXml.convertToBytes(icon, context, options);
                return IconCompat.createWithData(data, 0, data.length);
        }
    }

    /**
     * Parse a slice that has been previously serialized.
     * <p>
     * Parses a slice that was serialized with {@link #serializeSlice}.
     * <p>
     * Note: Slices returned by this cannot be passed to {@link SliceConvert#unwrap(Slice)}.
     *
     * @param input    The input stream to read from.
     * @param encoding The encoding to read as.
     * @param listener Listener used to handle actions when reconstructing the slice.
     * @throws SliceParseException if the InputStream cannot be parsed.
     */
    public static @NonNull Slice parseSlice(@NonNull final Context context,
            @NonNull InputStream input, @NonNull String encoding,
            @NonNull final SliceActionListener listener) throws IOException, SliceParseException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(input);
        String parcelName = Slice.class.getName();

        bufferedInputStream.mark(parcelName.length() + 4);
        boolean usesParcel = doesStreamStartWith(parcelName, bufferedInputStream);
        bufferedInputStream.reset();
        if (usesParcel) {
            Slice slice;
            final SliceItem.ActionHandler handler = new SliceItem.ActionHandler() {
                @Override
                public void onAction(SliceItem item, Context context, Intent intent) {
                    listener.onSliceAction(item.getSlice().getUri(), context, intent);
                }
            };
            synchronized (SliceItemHolder.sSerializeLock) {
                SliceItemHolder.sHandler = new SliceItemHolder.HolderHandler() {
                    @Override
                    public void handle(SliceItemHolder holder, String format) {
                        setActionsAndUpdateIcons(holder, handler, context, format);
                    }
                };
                slice = ParcelUtils.fromInputStream(bufferedInputStream);
                slice.mHints = ArrayUtils.appendElement(String.class, slice.mHints,
                        SliceHints.HINT_CACHED);
                SliceItemHolder.sHandler = null;
            }
            return slice;
        }
        Slice s = SliceXml.parseSlice(context, bufferedInputStream, encoding, listener);
        s.mHints = ArrayUtils.appendElement(String.class, s.mHints, SliceHints.HINT_CACHED);
        return s;
    }

    static void setActionsAndUpdateIcons(SliceItemHolder holder,
            SliceItem.ActionHandler listener,
            Context context, String format) {
        switch (format) {
            case FORMAT_IMAGE:
                if (holder.mVersionedParcelable instanceof IconCompat) {
                    ((IconCompat) holder.mVersionedParcelable).checkResource(context);
                }
                break;
            case FORMAT_ACTION:
                holder.mCallback = listener;
                break;
        }
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static int parseImageMode(@NonNull SliceItem iconItem) {
        return SliceActionImpl.parseImageMode(iconItem);
    }

    private static boolean doesStreamStartWith(String parcelName, BufferedInputStream inputStream) {
        byte[] data = parcelName.getBytes(Charset.forName("UTF-16"));
        byte[] buf = new byte[data.length];
        try {
            // Read out the int size of the string.
            if (inputStream.read(buf, 0, 4) < 0) {
                return false;
            }
            if (inputStream.read(buf, 0, buf.length) < 0) {
                return false;
            }
            return parcelName.equals(new String(buf, "UTF-16"));
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * Holds options for how to handle SliceItems that cannot be serialized.
     */
    public static class SerializeOptions {
        /**
         * Constant indicating that the an {@link IllegalArgumentException} should be thrown
         * when this format is encountered.
         */
        public static final int MODE_THROW = 0;
        /**
         * Constant indicating that the SliceItem should be removed when this format is encountered.
         */
        public static final int MODE_REMOVE = 1;
        /**
         * Constant indicating that the SliceItem should be serialized as much as possible.
         * <p>
         * For images this means they will be attempted to be serialized. For actions, the
         * action will be removed but the content of the action will be serialized. The action
         * may be triggered later on a de-serialized slice by binding the slice again and activating
         * a pending-intent at the same location as the serialized action.
         */
        public static final int MODE_CONVERT = 2;

        @IntDef({MODE_THROW, MODE_REMOVE, MODE_CONVERT})
        @Retention(SOURCE)
        @interface FormatMode {
        }

        private int mActionMode = MODE_THROW;
        private int mImageMode = MODE_THROW;
        private int mMaxWidth = 1000;
        private int mMaxHeight = 1000;

        private Bitmap.CompressFormat mFormat = Bitmap.CompressFormat.PNG;
        private int mQuality = 100;

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public void checkThrow(String format) {
            switch (format) {
                case FORMAT_ACTION:
                case FORMAT_REMOTE_INPUT:
                    if (mActionMode != MODE_THROW) return;
                    break;
                case FORMAT_IMAGE:
                    if (mImageMode != MODE_THROW) return;
                    break;
                default:
                    return;
            }
            throw new IllegalArgumentException(format + " cannot be serialized");
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public @FormatMode int getActionMode() {
            return mActionMode;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public @FormatMode int getImageMode() {
            return mImageMode;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public int getMaxWidth() {
            return mMaxWidth;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public int getMaxHeight() {
            return mMaxHeight;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public Bitmap.CompressFormat getFormat() {
            return mFormat;
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public int getQuality() {
            return mQuality;
        }

        /**
         * Sets how {@link android.app.slice.SliceItem#FORMAT_ACTION} items should be handled.
         *
         * The default mode is {@link #MODE_THROW}.
         *
         * @param mode The desired mode.
         */
        public SerializeOptions setActionMode(@FormatMode int mode) {
            mActionMode = mode;
            return this;
        }

        /**
         * Sets how {@link android.app.slice.SliceItem#FORMAT_IMAGE} items should be handled.
         *
         * The default mode is {@link #MODE_THROW}.
         *
         * @param mode The desired mode.
         */
        public SerializeOptions setImageMode(@FormatMode int mode) {
            mImageMode = mode;
            return this;
        }

        /**
         * Set the maximum width of an image to use when serializing.
         * <p>
         * Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}.
         * Any images larger than the maximum size will be scaled down to fit within that size.
         * The default value is 1000.
         */
        public SerializeOptions setMaxImageWidth(int width) {
            mMaxWidth = width;
            return this;
        }

        /**
         * Set the maximum height of an image to use when serializing.
         * <p>
         * Will only be used if the {@link #setImageMode(int)} is set to {@link #MODE_CONVERT}.
         * Any images larger than the maximum size will be scaled down to fit within that size.
         * The default value is 1000.
         */
        public SerializeOptions setMaxImageHeight(int height) {
            mMaxHeight = height;
            return this;
        }

        /**
         * Sets the options to use when converting icons to be serialized. Only used if
         * the image mode is set to {@link #MODE_CONVERT}.
         *
         * @param format  The format to encode images with, default is
         *                {@link android.graphics.Bitmap.CompressFormat#PNG}.
         * @param quality The quality to use when encoding images.
         */
        public SerializeOptions setImageConversionFormat(Bitmap.CompressFormat format,
                int quality) {
            mFormat = format;
            mQuality = quality;
            return this;
        }
    }

    /**
     * A listener used to receive events on slices parsed with
     * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}.
     */
    public interface SliceActionListener {
        /**
         * Called when an action is triggered on a slice parsed with
         * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}.
         *
         * @param actionUri The uri of the action selected.
         * @param context   The context passed to {@link SliceItem#fireAction(Context, Intent)}
         * @param intent    The intent passed to {@link SliceItem#fireAction(Context, Intent)}
         */
        void onSliceAction(Uri actionUri, Context context, Intent intent);
    }

    /**
     * Exception thrown during
     * {@link #parseSlice(Context, InputStream, String, SliceActionListener)}.
     */
    public static class SliceParseException extends Exception {
        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public SliceParseException(String s, Throwable e) {
            super(s, e);
        }

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public SliceParseException(String s) {
            super(s);
        }
    }
}