public final class

TilesTimelineCache

extends java.lang.Object

 java.lang.Object

↳androidx.wear.tiles.timeline.TilesTimelineCache

Gradle dependencies

compile group: 'androidx.wear.tiles', name: 'tiles-renderer', version: '1.4.0'

  • groupId: androidx.wear.tiles
  • artifactId: tiles-renderer
  • version: 1.4.0

Artifact androidx.wear.tiles:tiles-renderer:1.4.0 it located at Google repository (https://maven.google.com/)

Overview

Timeline cache for Wear Tiles. This will take in a full timeline, and return the appropriate entry for the given time from findTimelineEntryForTime.

Summary

Constructors
publicTilesTimelineCache(TimelineBuilders.Timeline tile)

Methods
public TimelineBuilders.TimelineEntryfindClosestTimelineEntry(long timeMillis)

A (very) inexact version of TilesTimelineCache.findTimelineEntryForTime(long) which finds the closest timeline entry to the current time, regardless of validity.

public longfindCurrentTimelineEntryExpiry(TimelineBuilders.TimelineEntry entry, long fromTimeMillis)

Finds when the timeline entry entry should be considered "expired".

public TimelineBuilders.TimelineEntryfindTimelineEntryForTime(long timeMillis)

Finds the entry which should be active at the given time.

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

Constructors

public TilesTimelineCache(TimelineBuilders.Timeline tile)

Methods

public TimelineBuilders.TimelineEntry findTimelineEntryForTime(long timeMillis)

Finds the entry which should be active at the given time. This will return the entry which has the _shortest_ validity period at the current time, if validity periods overlap. Note that an entry which has no validity period set will be considered a "default" and will be used if no other entries are suitable.

Parameters:

timeMillis: The time to base the search on, in milliseconds.

Returns:

The timeline entry which should be active at the given time. Returns null if none are valid.

public TimelineBuilders.TimelineEntry findClosestTimelineEntry(long timeMillis)

A (very) inexact version of TilesTimelineCache.findTimelineEntryForTime(long) which finds the closest timeline entry to the current time, regardless of validity. This should only used as a fallback if findTimelineEntryForTime fails, so it can attempt to at least show something.

By this point, we're technically in an error state, so just show _something_. Note that calling this if findTimelineEntryForTime returns a valid entry is invalid, and may lead to incorrect results.

Parameters:

timeMillis: The time to search from, in milliseconds.

Returns:

The timeline entry with validity period closest to timeMillis.

public long findCurrentTimelineEntryExpiry(TimelineBuilders.TimelineEntry entry, long fromTimeMillis)

Finds when the timeline entry entry should be considered "expired". This is either when it is no longer valid (i.e. end_millis), or when another entry should be presented instead.

Parameters:

entry: The entry to find the expiry time of.
fromTimeMillis: The time to start searching from. The returned time will never be lower than the value passed here.

Returns:

The time in millis that entry should be considered to be expired. This value will be MAX_VALUE if entry does not expire.

Source

/*
 * Copyright 2021 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.wear.tiles.timeline;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.wear.tiles.builders.TimelineBuilders;
import androidx.wear.tiles.proto.TimelineProto.TimeInterval;
import androidx.wear.tiles.proto.TimelineProto.Timeline;
import androidx.wear.tiles.proto.TimelineProto.TimelineEntry;

/**
 * Timeline cache for Wear Tiles. This will take in a full timeline, and return the appropriate
 * entry for the given time from {@code findTimelineEntryForTime}.
 */
public final class TilesTimelineCache {
    private final Timeline mTimeline;

    public TilesTimelineCache(@NonNull TimelineBuilders.Timeline tile) {
        this.mTimeline = tile.toProto();
    }

    /**
     * Finds the entry which should be active at the given time. This will return the entry which
     * has the _shortest_ validity period at the current time, if validity periods overlap. Note
     * that an entry which has no validity period set will be considered a "default" and will be
     * used if no other entries are suitable.
     *
     * @param timeMillis The time to base the search on, in milliseconds.
     * @return The timeline entry which should be active at the given time. Returns {@code null} if
     *     none are valid.
     */
    @MainThread
    @Nullable
    public TimelineBuilders.TimelineEntry findTimelineEntryForTime(long timeMillis) {
        TimelineEntry currentEntry = null;
        long currentEntryLength = Long.MAX_VALUE;

        // Iterate through, finding the _shortest_ valid timeline entry.
        for (TimelineEntry entry : mTimeline.getTimelineEntriesList()) {
            if (!entry.hasValidity()) {
                // Only override a default if there's no more specific entry found.
                if (currentEntryLength == Long.MAX_VALUE) {
                    // Let's treat an entry with no validity as being a "default", as long as we
                    // haven't found
                    // any other valid entries
                    currentEntry = entry;
                }
            } else {
                TimeInterval validity = entry.getValidity();

                long validityLength = validity.getEndMillis() - validity.getStartMillis();

                if (validityLength > currentEntryLength) {
                    continue;
                }

                if (validity.getStartMillis() <= timeMillis
                        && timeMillis < validity.getEndMillis()) {
                    currentEntry = entry;
                    currentEntryLength = validityLength;
                }
            }
        }

        if (currentEntry == null) {
            return null;
        }

        return TimelineBuilders.TimelineEntry.fromProto(currentEntry);
    }

    /**
     * A (very) inexact version of {@link TilesTimelineCache#findTimelineEntryForTime(long)} which
     * finds the closest timeline entry to the current time, regardless of validity. This should
     * only used as a fallback if {@code findTimelineEntryForTime} fails, so it can attempt to at
     * least show something.
     *
     * <p>By this point, we're technically in an error state, so just show _something_. Note that
     * calling this if {@code findTimelineEntryForTime} returns a valid entry is invalid, and may
     * lead to incorrect results.
     *
     * @param timeMillis The time to search from, in milliseconds.
     * @return The timeline entry with validity period closest to {@code timeMillis}.
     */
    @MainThread
    @Nullable
    public TimelineBuilders.TimelineEntry findClosestTimelineEntry(long timeMillis) {
        long currentEntryError = Long.MAX_VALUE;
        TimelineEntry currentEntry = null;

        for (TimelineEntry entry : mTimeline.getTimelineEntriesList()) {
            if (!entry.hasValidity()) {
                // It's a default. This shouldn't happen if we've been called. Skip it.
                continue;
            }

            TimeInterval validity = entry.getValidity();

            if (!isTimeIntervalValid(validity)) {
                continue;
            }

            // It's valid in this time interval. Shouldn't happen. Skip anyway.
            if (validity.getStartMillis() <= timeMillis && timeMillis < validity.getEndMillis()) {
                continue;
            }

            long error;

            // It's in the future.
            if (validity.getStartMillis() > timeMillis) {
                error = validity.getStartMillis() - timeMillis;
            } else {
                // It's in the past.
                error = timeMillis - validity.getEndMillis();
            }

            if (error < currentEntryError) {
                currentEntry = entry;
                currentEntryError = error;
            }
        }

        if (currentEntry == null) {
            return null;
        }

        return TimelineBuilders.TimelineEntry.fromProto(currentEntry);
    }

    /**
     * Finds when the timeline entry {@code entry} should be considered "expired". This is either
     * when it is no longer valid (i.e. end_millis), or when another entry should be presented
     * instead.
     *
     * @param entry The entry to find the expiry time of.
     * @param fromTimeMillis The time to start searching from. The returned time will never be lower
     *     than the value passed here.
     * @return The time in millis that {@code entry} should be considered to be expired. This value
     *     will be {@link Long#MAX_VALUE} if {@code entry} does not expire.
     */
    public long findCurrentTimelineEntryExpiry(
            @NonNull TimelineBuilders.TimelineEntry entry, long fromTimeMillis) {
        long currentSmallestExpiry = Long.MAX_VALUE;
        long entryValidityLength = Long.MAX_VALUE;

        TimelineEntry protoEntry = entry.toProto();

        if (protoEntry.hasValidity() && protoEntry.getValidity().getEndMillis() > fromTimeMillis) {
            currentSmallestExpiry = protoEntry.getValidity().getEndMillis();
            entryValidityLength =
                    protoEntry.getValidity().getEndMillis()
                            - protoEntry.getValidity().getStartMillis();
        }

        // Search for the starting edge of an overlapping period (i.e. one with startTime between
        // entry.startTime and entry.endTime), with a validity period shorter than the one currently
        // being considered.
        for (TimelineEntry nextEntry : mTimeline.getTimelineEntriesList()) {
            // The entry can't invalidate itself
            if (nextEntry.equals(entry.toProto())) {
                continue;
            }

            // Discard if nextEntry doesn't have a validity period. In this case, it's a default (so
            // would potentially be used at entry.end_millis anyway).
            if (!nextEntry.hasValidity()) {
                continue;
            }

            TimeInterval nextEntryValidity = nextEntry.getValidity();

            // Discard if the validity period is flat out invalid.
            if (!isTimeIntervalValid(nextEntryValidity)) {
                continue;
            }

            // Discard if the start time of nextEntry doesn't fall in the current period (it can't
            // interrupt this entry, so this entry's expiry should be used).
            if (protoEntry.hasValidity()) {
                if (nextEntryValidity.getStartMillis() > protoEntry.getValidity().getEndMillis()
                        || nextEntryValidity.getStartMillis()
                                < protoEntry.getValidity().getStartMillis()) {
                    continue;
                }
            }

            // Discard if its start time is greater than the current smallest one we've found. In
            // that
            // case, the entry that gave us currentSmallestExpiry would be shown next.
            if (nextEntryValidity.getStartMillis() > currentSmallestExpiry) {
                continue;
            }

            // Discard if it's less than "fromTime". This prevents accidentally returning valid
            // times in
            // the past.
            if (nextEntryValidity.getStartMillis() < fromTimeMillis) {
                continue;
            }

            // Finally, consider whether the length of the validity period is shorter than the
            // current
            // one. If this doesn't hold, the current entry would be shown instead (the timeline
            // entry
            // with the shortest validity period is always shown if overlapping).
            //
            // We don't need to deal with the case of shortest validity between this entry, and an
            // already
            // chosen candidate time, as if we've got here, the start time of nextEntry is lower
            // than
            // the entry that is driving currentSmallestExpiry, so nextEntry would be shown
            // regardless.
            long nextEntryValidityLength =
                    nextEntryValidity.getEndMillis() - nextEntryValidity.getStartMillis();

            if (nextEntryValidityLength < entryValidityLength) {
                // It's valid!
                currentSmallestExpiry = nextEntryValidity.getStartMillis();
            }
        }

        return currentSmallestExpiry;
    }

    private static boolean isTimeIntervalValid(TimeInterval timeInterval) {
        // Zero-width (and "negative width") validity periods are not valid, and should never be
        // considered.
        return timeInterval.getEndMillis() > timeInterval.getStartMillis();
    }
}