SliceQuery.java

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

import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_SLICE;

import android.net.Uri;
import android.text.TextUtils;

import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.slice.Slice;
import androidx.slice.SliceItem;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Utilities for finding content within a Slice.
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(19)
public class SliceQuery {

    /**
     */
    public static boolean hasAnyHints(SliceItem item, String... hints) {
        if (hints == null) return false;
        List<String> itemHints = item.getHints();
        for (String hint : hints) {
            if (itemHints.contains(hint)) {
                return true;
            }
        }
        return false;
    }

    /**
     */
    public static boolean hasHints(SliceItem item, String... hints) {
        if (hints == null) return true;
        List<String> itemHints = item.getHints();
        for (String hint : hints) {
            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
                return false;
            }
        }
        return true;
    }

    /**
     */
    public static boolean hasHints(Slice item, String... hints) {
        if (hints == null) return true;
        List<String> itemHints = item.getHints();
        for (String hint : hints) {
            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
                return false;
            }
        }
        return true;
    }

    /**
     */
    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
        SliceItem ret = null;
        while (ret == null && list.size() != 0) {
            SliceItem remove = list.remove(0);
            if (!contains(container, remove)) {
                ret = remove;
            }
        }
        return ret;
    }

    /**
     */
    private static boolean contains(SliceItem container, final SliceItem item) {
        if (container == null || item == null) return false;
        return findFirst(filter(stream(container), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem s) {
                return s == item;
            }
        }), null) != null;
    }

    /**
     */
    public static List<SliceItem> findAll(SliceItem s, String format) {
        return findAll(s, format, (String[]) null, null);
    }

    /**
     */
    public static List<SliceItem> findAll(Slice s, String format, String hints, String nonHints) {
        return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
    }

    /**
     */
    public static List<SliceItem> findAll(SliceItem s, String format, String hints,
            String nonHints) {
        return findAll(s, format, new String[]{ hints }, new String[]{ nonHints });
    }

    /**
     */
    public static List<SliceItem> findAll(Slice s, final String format, final String[] hints,
            final String[] nonHints) {
        return collect(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format)
                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
            }
        }));
    }

    /**
     */
    public static List<SliceItem> findAll(SliceItem s, final String format, final String[] hints,
            final String[] nonHints) {
        return collect(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format)
                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
            }
        }));
    }

    /**
     */
    public static SliceItem find(Slice s, String format, String hints, String nonHints) {
        return find(s, format, new String[]{ hints }, new String[]{ nonHints });
    }

    /**
     */
    public static SliceItem find(Slice s, String format) {
        return find(s, format, (String[]) null, null);
    }

    /**
     */
    public static SliceItem find(SliceItem s, String format) {
        return find(s, format, (String[]) null, null);
    }

    /**
     */
    public static SliceItem find(SliceItem s, String format, String hints, String nonHints) {
        return find(s, format, new String[]{ hints }, new String[]{ nonHints });
    }

    /**
     */
    public static SliceItem find(Slice s, final String format, final String[] hints,
            final String[] nonHints) {
        return findFirst(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format)
                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
            }
        }), null);
    }

    /**
     */
    public static SliceItem findSubtype(Slice s, final String format, final String subtype) {
        return findFirst(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format) && checkSubtype(item, subtype);
            }
        }), null);
    }

    /**
     */
    public static SliceItem findSubtype(SliceItem s, final String format, final String subtype) {
        return findFirst(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format) && checkSubtype(item, subtype);
            }
        }), null);
    }

    /**
     */
    public static SliceItem find(SliceItem s, final String format, final String[] hints,
            final String[] nonHints) {
        return findFirst(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem item) {
                return checkFormat(item, format)
                        && (hasHints(item, hints) && !hasAnyHints(item, nonHints));
            }
        }), null);
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static boolean checkFormat(SliceItem item, String format) {
        return format == null || format.equals(item.getFormat());
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static boolean checkSubtype(SliceItem item, String subtype) {
        return subtype == null || subtype.equals(item.getSubType());
    }

    /**
     */
    public static Iterator<SliceItem> stream(SliceItem slice) {
        ArrayList<SliceItem> items = new ArrayList<>();
        items.add(slice);
        return getSliceItemStream(items);
    }

    /**
     */
    public static Iterator<SliceItem> stream(Slice slice) {
        ArrayList<SliceItem> items = new ArrayList<>();
        if (slice != null) {
            items.addAll(slice.getItems());
        }
        return getSliceItemStream(items);
    }

    /**
     */
    private static Iterator<SliceItem> getSliceItemStream(final ArrayList<SliceItem> items) {
        return new Iterator<SliceItem>() {
            @Override
            public boolean hasNext() {
                return items.size() != 0;
            }

            @Override
            public SliceItem next() {
                SliceItem item = items.remove(0);
                if (FORMAT_SLICE.equals(item.getFormat())
                        || FORMAT_ACTION.equals(item.getFormat())) {
                    items.addAll(item.getSlice().getItems());
                }
                return item;
            }
        };
    }

    /**
     * Finds an item matching provided params that is a direct child of the slice.
     */
    public static SliceItem findTopLevelItem(Slice s, final String format, final String subtype,
            final String[] hints, final String[] nonHints) {
        List<SliceItem> items = s.getItems();
        for (int i = 0; i < items.size(); i++) {
            SliceItem item = items.get(i);
            if (checkFormat(item, format)
                    && checkSubtype(item, subtype)
                    && hasHints(item, hints)
                    && !hasAnyHints(item, nonHints)) {
                return item;
            }
        }
        return null;
    }

    private static <T> List<T> collect(Iterator<T> iter) {
        List<T> list = new ArrayList<>();
        while (iter.hasNext()) list.add(iter.next());
        return list;
    }

    private static <T> Iterator<T> filter(final Iterator<T> input, final Filter<T> f) {
        return new Iterator<T>() {
            T mNext = findNext();

            private T findNext() {
                while (input.hasNext()) {
                    T i = input.next();
                    if (f.filter(i)) {
                        return i;
                    }
                }
                return null;
            }

            @Override
            public boolean hasNext() {
                return mNext != null;
            }

            @Override
            public T next() {
                T ret = mNext;
                mNext = findNext();
                return ret;
            }
        };
    }

    private static <T> T findFirst(Iterator<T> filter, T def) {
        while (filter.hasNext()) {
            T r = filter.next();
            if (r != null) return r;
        }
        return def;
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static SliceItem findItem(Slice s, final Uri uri) {
        return findFirst(filter(stream(s), new Filter<SliceItem>() {
            @Override
            public boolean filter(SliceItem input) {
                if (FORMAT_ACTION.equals(input.getFormat()) || FORMAT_SLICE.equals(
                        input.getFormat())) {
                    return uri.equals(input.getSlice().getUri());
                }
                return false;
            }
        }), null);
    }

    private interface Filter<T> {
        boolean filter(T input);
    }

    private SliceQuery() {
    }
}