public final class

TilesTimelineCacheInternal

extends java.lang.Object

 java.lang.Object

↳androidx.wear.tiles.timeline.internal.TilesTimelineCacheInternal

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 Tiles. This will take in a full timeline, and return the appropriate entry for the given time from findTimelineEntryForTime.

Summary

Constructors
publicTilesTimelineCacheInternal(TimelineProto.Timeline timeline)

Methods
public TimelineProto.TimelineEntryfindClosestTimelineEntry(long timeMillis)

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

public longfindCurrentTimelineEntryExpiry(TimelineProto.TimelineEntry entry, long fromTimeMillis)

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

public TimelineProto.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 TilesTimelineCacheInternal(TimelineProto.Timeline timeline)

Methods

public TimelineProto.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 TimelineProto.TimelineEntry findClosestTimelineEntry(long timeMillis)

A (very) inexact version of TilesTimelineCacheInternal.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(TimelineProto.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.internal;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.wear.protolayout.proto.TimelineProto.TimeInterval;
import androidx.wear.protolayout.proto.TimelineProto.Timeline;
import androidx.wear.protolayout.proto.TimelineProto.TimelineEntry;

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

    public TilesTimelineCacheInternal(@NonNull Timeline timeline) {
        this.mTimeline = timeline;
    }

    /**
     * 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 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;
                }
            }
        }

        return currentEntry;
    }

    /**
     * A (very) inexact version of {@link TilesTimelineCacheInternal#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 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;
            }
        }

        return 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.
     */
    @MainThread
    public long findCurrentTimelineEntryExpiry(@NonNull TimelineEntry entry, long fromTimeMillis) {
        long currentSmallestExpiry = Long.MAX_VALUE;
        long entryValidityLength = Long.MAX_VALUE;

        if (entry.hasValidity() && entry.getValidity().getEndMillis() > fromTimeMillis) {
            currentSmallestExpiry = entry.getValidity().getEndMillis();
            entryValidityLength =
                    entry.getValidity().getEndMillis() - entry.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)) {
                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 (entry.hasValidity()) {
                if (nextEntryValidity.getStartMillis() > entry.getValidity().getEndMillis()
                        || nextEntryValidity.getStartMillis()
                                < entry.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();
    }
}