blob: d2015d19158fc4248e4184b5bc68f33fc54088ec [file] [log] [blame]
/*
* 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.appsearch.builtintypes;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.provider.Settings;
import androidx.annotation.DoNotInline;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appsearch.annotation.Document;
import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
import androidx.core.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* AppSearch document representing a {@link Timer} entity.
*/
@Document(name = "builtin:Timer")
public final class Timer {
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({STATUS_UNKNOWN, STATUS_STARTED, STATUS_PAUSED, STATUS_EXPIRED, STATUS_MISSED,
STATUS_RESET})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {}
/** The {@link Timer} is in an unknown error state. */
public static final int STATUS_UNKNOWN = 0;
/** The {@link Timer} is started. */
public static final int STATUS_STARTED = 1;
/** The {@link Timer} is paused. */
public static final int STATUS_PAUSED = 2;
/** The {@link Timer} is expired. */
public static final int STATUS_EXPIRED = 3;
/** The {@link Timer} is missed. */
public static final int STATUS_MISSED = 4;
/** The {@link Timer} is reset to its initial value. */
public static final int STATUS_RESET = 5;
@Document.Namespace
private final String mNamespace;
@Document.Id
private final String mId;
@Document.Score
private final int mDocumentScore;
@Document.CreationTimestampMillis
private final long mCreationTimestampMillis;
@Document.TtlMillis
private final long mDocumentTtlMillis;
@Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES)
private final String mName;
@Document.LongProperty
private final long mDurationMillis;
@Document.LongProperty
private final long mOriginalDurationMillis;
@Document.LongProperty
private final long mStartTimeMillis;
@Document.LongProperty
private final long mStartTimeMillisInElapsedRealtime;
@Document.LongProperty
private final int mBootCount;
@Document.LongProperty
private final long mRemainingTimeMillisSinceUpdate;
@Document.StringProperty
private final String mRingtone;
@Document.LongProperty
private final int mStatus;
@Document.BooleanProperty
private final boolean mShouldVibrate;
Timer(@NonNull String namespace, @NonNull String id, int documentScore,
long creationTimestampMillis, long documentTtlMillis, @Nullable String name,
long durationMillis, long originalDurationMillis, long startTimeMillis,
long startTimeMillisInElapsedRealtime, int bootCount,
long remainingTimeMillisSinceUpdate, @Nullable String ringtone, int status,
boolean shouldVibrate) {
mNamespace = Preconditions.checkNotNull(namespace);
mId = Preconditions.checkNotNull(id);
mDocumentScore = documentScore;
mCreationTimestampMillis = creationTimestampMillis;
mDocumentTtlMillis = documentTtlMillis;
mName = name;
mDurationMillis = durationMillis;
mOriginalDurationMillis = originalDurationMillis;
mStartTimeMillis = startTimeMillis;
mStartTimeMillisInElapsedRealtime = startTimeMillisInElapsedRealtime;
mBootCount = bootCount;
mRemainingTimeMillisSinceUpdate = remainingTimeMillisSinceUpdate;
mRingtone = ringtone;
mStatus = status;
mShouldVibrate = shouldVibrate;
}
/** Returns the namespace. */
@NonNull
public String getNamespace() {
return mNamespace;
}
/** Returns the unique identifier. */
@NonNull
public String getId() {
return mId;
}
/**
* Returns the user-provided opaque document score of the current AppSearch document, which can
* be used for ranking using
* {@link androidx.appsearch.app.SearchSpec.RankingStrategy#RANKING_STRATEGY_DOCUMENT_SCORE}.
*
* <p>See {@link Document.Score} for more information on score.
*/
public int getDocumentScore() {
return mDocumentScore;
}
/**
* Returns the creation timestamp for the current AppSearch entity, in milliseconds using the
* {@link System#currentTimeMillis()} time base.
*
* <p>This timestamp refers to the creation time of the AppSearch entity, not when the
* document is written into AppSearch.
*
* <p>If not set, then the current timestamp will be used.
*
* <p>See {@link androidx.appsearch.annotation.Document.CreationTimestampMillis} for more
* information on creation timestamp.
*/
public long getCreationTimestampMillis() {
return mCreationTimestampMillis;
}
/**
* Returns the time-to-live (TTL) for the current AppSearch document as a duration in
* milliseconds.
*
* <p>The document will be automatically deleted when the TTL expires.
*
* <p>See {@link Document.TtlMillis} for more information on TTL.
*/
public long getDocumentTtlMillis() {
return mDocumentTtlMillis;
}
/** Returns the name. */
@Nullable
public String getName() {
return mName;
}
/**
* Returns the total duration in milliseconds, including additional time added by the user.
*
* <p>Applications may allow the user to add additional durations. The durationMillis will
* always return the new updated duration.
*/
public long getDurationMillis() {
return mDurationMillis;
}
/**
* Returns the original duration in milliseconds when the {@link Timer} was first created.
*
* <p>Applications may allow the user to add additional durations. The
* originalDurationMillis will always return the original duration before any change has
* taken place.
*/
public long getOriginalDurationMillis() {
return mOriginalDurationMillis;
}
/**
* Returns the most recent time that the status transitioned to {@link #STATUS_STARTED}. In
* milliseconds using the {@link System#currentTimeMillis()} time base.
*
* <p>If the status is not {@link #STATUS_STARTED}, then this value is undefined, and
* should not be used.
*
* <p>This value is used to calculate {@link #getExpirationTimeMillis(Context)}.
*/
public long getStartTimeMillis() {
return mStartTimeMillis;
}
/**
* Returns the most recent real time that the status transitioned to {@link #STATUS_STARTED}.
* In milliseconds using the {@link android.os.SystemClock#elapsedRealtime()} time base.
*
* <p>If the status is not {@link #STATUS_STARTED}, then this value is undefined, and
* should not be used.
*
* <p>This value is used to calculate {@link #getExpirationTimeMillis(Context)}.
*/
public long getStartTimeMillisInElapsedRealtime() {
return mStartTimeMillisInElapsedRealtime;
}
/**
* Returns the boot count of the device when this document is last updated.
*
* <p>The boot count of the device can be accessed from Global Settings. See
* {@link android.provider.Settings.Global#BOOT_COUNT}.
*
* <p>On older APIs where boot count is not available, this value should not be used.
*
* <p>If available, this value is used to calculate {@link #getExpirationTimeMillis(Context)}
* and {@link #getCurrentRemainingTime(Context)}.
*/
public int getBootCount() {
return mBootCount;
}
/**
* Returns the amount of time remaining in milliseconds for the {@link Timer} since it was
* started, paused or reset.
*
* <p>If it is in the {@link #STATUS_STARTED} state, then the current remaining time will be
* different from this value. To get the current remaining time, use
* {@link #getCurrentRemainingTime(Context)}.
*/
public long getRemainingTimeMillisSinceUpdate() {
return mRemainingTimeMillisSinceUpdate;
}
/**
* Returns the ringtone as a content URI to be played, or
* {@link android.provider.AlarmClock#VALUE_RINGTONE_SILENT} if no ringtone will be played.
*/
@Nullable
public String getRingtone() {
return mRingtone;
}
/**
* Returns the current status.
*
* <p>Status can be {@link #STATUS_UNKNOWN}, {@link #STATUS_STARTED}, {@link #STATUS_PAUSED},
* {@link #STATUS_EXPIRED}, {@link #STATUS_MISSED}, or {@link #STATUS_RESET}.
*/
@Status
public int getStatus() {
return mStatus;
}
/** Returns whether or not to activate the device vibrator when the {@link Timer} expires. */
public boolean shouldVibrate() {
return mShouldVibrate;
}
/**
* Calculates the expire time in milliseconds in the {@link System#currentTimeMillis()} time
* base.
*
* <p>{@link Long#MAX_VALUE} will be returned if the {@link Timer} is {@link #STATUS_PAUSED}
* or {@link #STATUS_RESET}.
*
* <p>A negative value may be returned if the {@link Timer} is {@link #STATUS_MISSED} or
* {@link #STATUS_EXPIRED} to indicate it expired in the past.
*
* @param context The app context
*/
public long getExpirationTimeMillis(@NonNull Context context) {
if (mStatus == STATUS_PAUSED || mStatus == STATUS_RESET) {
return Long.MAX_VALUE;
}
int currentBootCount = -1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
currentBootCount = Api17Impl.getCurrentBootCount(context);
}
if (currentBootCount == -1 || currentBootCount != mBootCount) {
// Boot count doesn't exist or doesn't match current device boot count. Use wall
// clock time since elapsed realtime is no longer valid.
return mStartTimeMillis + mRemainingTimeMillisSinceUpdate;
} else {
// Boot count matches current device boot count. Therefore we can use elapsed
// realtime to do calculations.
long elapsedTime = SystemClock.elapsedRealtime() - mStartTimeMillisInElapsedRealtime;
return System.currentTimeMillis() + mRemainingTimeMillisSinceUpdate - elapsedTime;
}
}
/**
* Calculates the current remaining time in milliseconds.
*
* <p>A negative value may be returned if the {@link Timer} is {@link #STATUS_MISSED} or
* {@link #STATUS_EXPIRED} to indicate it has already fired.
*
* @param context The app context
*/
public long getCurrentRemainingTime(@NonNull Context context) {
if (mStatus == STATUS_PAUSED || mStatus == STATUS_RESET) {
// The timer has not started, so the remaining time is the same as the last updated one.
return mRemainingTimeMillisSinceUpdate;
}
int currentBootCount = -1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
currentBootCount = Api17Impl.getCurrentBootCount(context);
}
long elapsedTime;
if (currentBootCount == -1 || currentBootCount != mBootCount) {
// Boot count doesn't exist or doesn't match current device boot count. Use wall
// clock time since elapsed realtime is no longer valid.
elapsedTime = System.currentTimeMillis() - mStartTimeMillis;
} else {
// Boot count matches current device boot count. Therefore we can use elapsed
// realtime to do calculations.
elapsedTime = SystemClock.elapsedRealtime() - mStartTimeMillisInElapsedRealtime;
}
return mRemainingTimeMillisSinceUpdate - elapsedTime;
}
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static final class Api17Impl {
@DoNotInline
static int getCurrentBootCount(@NonNull Context context) {
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.BOOT_COUNT, -1);
}
private Api17Impl() {}
}
/** Builder for {@link Timer}. */
public static final class Builder extends BaseBuiltinTypeBuilder<Builder> {
private String mName;
private long mDurationMillis;
private long mOriginalDurationMillis;
private long mStartTimeMillis;
private long mStartTimeMillisInElapsedRealtime;
private int mBootCount;
private long mRemainingTimeMillisSinceUpdate;
private String mRingtone;
private int mStatus;
private boolean mShouldVibrate;
/**
* Constructor for {@link Timer.Builder}.
*
* @param namespace Namespace for the Document. See {@link Document.Namespace}.
* @param id Unique identifier for the Document. See {@link Document.Id}.
*/
public Builder(@NonNull String namespace, @NonNull String id) {
super(namespace, id);
}
/**
* Constructor for {@link Timer.Builder} with all the existing values.
*/
public Builder(@NonNull Timer timer) {
this(timer.getNamespace(), timer.getId());
mDocumentScore = timer.getDocumentScore();
mCreationTimestampMillis = timer.getCreationTimestampMillis();
mDocumentTtlMillis = timer.getDocumentTtlMillis();
mName = timer.getName();
mDurationMillis = timer.getDurationMillis();
mOriginalDurationMillis = timer.getOriginalDurationMillis();
mStartTimeMillis = timer.getStartTimeMillis();
mStartTimeMillisInElapsedRealtime = timer.getStartTimeMillisInElapsedRealtime();
mBootCount = timer.getBootCount();
mRemainingTimeMillisSinceUpdate = timer.getRemainingTimeMillisSinceUpdate();
mRingtone = timer.getRingtone();
mStatus = timer.getStatus();
mShouldVibrate = timer.shouldVibrate();
}
/** Sets the name. */
@NonNull
public Builder setName(@Nullable String name) {
mName = name;
return this;
}
/**
* Sets the total duration in milliseconds, including additional time added by the user.
*/
@NonNull
public Builder setDurationMillis(long durationMillis) {
mDurationMillis = durationMillis;
return this;
}
/**
* Sets the original duration in milliseconds when the {@link Timer} was first created.
*/
@NonNull
public Builder setOriginalDurationMillis(long originalDurationMillis) {
mOriginalDurationMillis = originalDurationMillis;
return this;
}
/**
* Sets the most recent time that the status transitioned to {@link #STATUS_STARTED}.
*
* <p> Start time should be sampled in both the {@link System#currentTimeMillis()} and
* {@link android.os.SystemClock#elapsedRealtime()} time base. In addition, the boot
* count of the device is needed to check if the
* {@link android.os.SystemClock#elapsedRealtime()} time base is valid.
*
* @param startTimeMillis The start time in milliseconds using the
* {@link System#currentTimeMillis()} time base.
* @param startTimeMillisInElapsedRealtime The start time in milliseconds using the
* {@link android.os.SystemClock#elapsedRealtime()} time base.
* @param bootCount The current boot count of the device. See
* {@link android.provider.Settings.Global#BOOT_COUNT}.
*/
@NonNull
public Builder setStartTimeMillis(long startTimeMillis,
long startTimeMillisInElapsedRealtime, int bootCount) {
mStartTimeMillis = startTimeMillis;
mStartTimeMillisInElapsedRealtime = startTimeMillisInElapsedRealtime;
mBootCount = bootCount;
return this;
}
/**
* Sets the most recent time that the status transitioned to {@link #STATUS_STARTED}.
*
* <p>See {@link #setStartTimeMillis(long, long, int)}.
*
* @param context The app context used to fetch boot count.
* @param startTimeMillis The start time in milliseconds using the
* {@link System#currentTimeMillis()} time base.
* @param startTimeMillisInElapsedRealtime The start time in milliseconds using the
* {@link android.os.SystemClock#elapsedRealtime()} time base.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@NonNull
public Builder setStartTimeMillis(@NonNull Context context, long startTimeMillis,
long startTimeMillisInElapsedRealtime) {
int bootCount = Api17Impl.getCurrentBootCount(context);
return setStartTimeMillis(startTimeMillis, startTimeMillisInElapsedRealtime, bootCount);
}
/**
* Sets the amount of time remaining in milliseconds for the {@link Timer} since it was
* started, paused or reset.
*/
@NonNull
public Builder setRemainingTimeMillisSinceUpdate(long remainingTimeMillisSinceUpdate) {
mRemainingTimeMillisSinceUpdate = remainingTimeMillisSinceUpdate;
return this;
}
/**
* Sets the content URI for the ringtone to be played, or
* {@link android.provider.AlarmClock#VALUE_RINGTONE_SILENT} if no ringtone will be played.
*/
@NonNull
public Builder setRingtone(@Nullable String ringtone) {
mRingtone = ringtone;
return this;
}
/**
* Sets the current status.
*
* <p>Status can be {@link #STATUS_UNKNOWN}, {@link #STATUS_STARTED},
* {@link #STATUS_PAUSED}, {@link #STATUS_EXPIRED}, {@link #STATUS_MISSED}, or
* {@link #STATUS_RESET}.
*/
@NonNull
public Builder setStatus(@Status int status) {
mStatus = status;
return this;
}
/** Sets whether or not to activate the device vibrator when the {@link Timer} expires. */
@NonNull
public Builder setShouldVibrate(boolean shouldVibrate) {
mShouldVibrate = shouldVibrate;
return this;
}
/** Builds the {@link Timer}. */
@NonNull
public Timer build() {
return new Timer(mNamespace, mId, mDocumentScore,
mCreationTimestampMillis, mDocumentTtlMillis, mName, mDurationMillis,
mOriginalDurationMillis, mStartTimeMillis,
mStartTimeMillisInElapsedRealtime, mBootCount,
mRemainingTimeMillisSinceUpdate, mRingtone, mStatus, mShouldVibrate);
}
}
}