Merge "Time zone update API classes"

am: cfe0c2f1ec

Change-Id: I6442aee1844ec9c6ef1017a4744936252ef45243
This commit is contained in:
Neil Fuller
2017-05-04 14:01:23 +00:00
committed by android-build-merger
17 changed files with 1601 additions and 0 deletions

View File

@@ -103,6 +103,8 @@ LOCAL_SRC_FILES += \
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
core/java/android/app/timezone/ICallback.aidl \
core/java/android/app/timezone/IRulesManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 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 android.app.timezone;
import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Callback interface for receiving information about an async time zone operation.
* The methods will be called on your application's main thread.
*
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public abstract class Callback {
@Retention(RetentionPolicy.SOURCE)
@IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_INSTALL_BAD_DISTRO_STRUCTURE,
ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, ERROR_INSTALL_RULES_TOO_OLD,
ERROR_INSTALL_VALIDATION_ERROR})
public @interface AsyncResultCode {}
/**
* Indicates that an operation succeeded.
*/
public static final int SUCCESS = 0;
/**
* Indicates an install / uninstall did not fully succeed for an unknown reason.
*/
public static final int ERROR_UNKNOWN_FAILURE = 1;
/**
* Indicates an install failed because of a structural issue with the provided distro,
* e.g. it wasn't in the right format or the contents were structured incorrectly.
*/
public static final int ERROR_INSTALL_BAD_DISTRO_STRUCTURE = 2;
/**
* Indicates an install failed because of a versioning issue with the provided distro,
* e.g. it was created for a different version of Android.
*/
public static final int ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION = 3;
/**
* Indicates an install failed because the rules provided are too old for the device,
* e.g. the Android device shipped with a newer rules version.
*/
public static final int ERROR_INSTALL_RULES_TOO_OLD = 4;
/**
* Indicates an install failed because the distro contents failed validation.
*/
public static final int ERROR_INSTALL_VALIDATION_ERROR = 5;
/**
* Reports the result of an async time zone operation.
*/
public abstract void onFinished(@AsyncResultCode int status);
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 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 android.app.timezone;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Versioning information about a distro's format or a device's supported format.
*
* <p>The following properties are included:
* <dl>
* <dt>majorVersion</dt>
* <dd>the major distro format version. Major versions differences are not compatible - e.g.
* 2 is not compatible with 1 or 3.</dd>
* <dt>minorVersion</dt>
* <dd>the minor distro format version. Minor versions should be backwards compatible iff the
* major versions match exactly, i.e. version 2.2 will be compatible with 2.1 devices but not
* 2.3 devices.</dd>
* </dl>
*
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public final class DistroFormatVersion implements Parcelable {
private final int mMajorVersion;
private final int mMinorVersion;
public DistroFormatVersion(int majorVersion, int minorVersion) {
mMajorVersion = Utils.validateVersion("major", majorVersion);
mMinorVersion = Utils.validateVersion("minor", minorVersion);
}
public static final Creator<DistroFormatVersion> CREATOR = new Creator<DistroFormatVersion>() {
public DistroFormatVersion createFromParcel(Parcel in) {
int majorVersion = in.readInt();
int minorVersion = in.readInt();
return new DistroFormatVersion(majorVersion, minorVersion);
}
public DistroFormatVersion[] newArray(int size) {
return new DistroFormatVersion[size];
}
};
public int getMajorVersion() {
return mMajorVersion;
}
public int getMinorVersion() {
return mMinorVersion;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mMajorVersion);
out.writeInt(mMinorVersion);
}
/**
* If this object describes a device's supported version and the parameter describes a distro's
* version, this method returns whether the device would accept the distro.
*/
public boolean supports(DistroFormatVersion distroFormatVersion) {
return mMajorVersion == distroFormatVersion.mMajorVersion
&& mMinorVersion <= distroFormatVersion.mMinorVersion;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DistroFormatVersion that = (DistroFormatVersion) o;
if (mMajorVersion != that.mMajorVersion) {
return false;
}
return mMinorVersion == that.mMinorVersion;
}
@Override
public int hashCode() {
int result = mMajorVersion;
result = 31 * result + mMinorVersion;
return result;
}
@Override
public String toString() {
return "DistroFormatVersion{"
+ "mMajorVersion=" + mMajorVersion
+ ", mMinorVersion=" + mMinorVersion
+ '}';
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 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 android.app.timezone;
import static android.app.timezone.Utils.validateRulesVersion;
import static android.app.timezone.Utils.validateVersion;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Versioning information about a set of time zone rules.
*
* <p>The following properties are included:
* <dl>
* <dt>rulesVersion</dt>
* <dd>the IANA rules version. e.g. "2017a"</dd>
* <dt>revision</dt>
* <dd>the revision for the rules. Allows there to be several revisions for a given IANA rules
* release. Numerically higher is newer.</dd>
* </dl>
*
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public final class DistroRulesVersion implements Parcelable {
private final String mRulesVersion;
private final int mRevision;
public DistroRulesVersion(String rulesVersion, int revision) {
mRulesVersion = validateRulesVersion("rulesVersion", rulesVersion);
mRevision = validateVersion("revision", revision);
}
public static final Creator<DistroRulesVersion> CREATOR = new Creator<DistroRulesVersion>() {
public DistroRulesVersion createFromParcel(Parcel in) {
String rulesVersion = in.readString();
int revision = in.readInt();
return new DistroRulesVersion(rulesVersion, revision);
}
public DistroRulesVersion[] newArray(int size) {
return new DistroRulesVersion[size];
}
};
public String getRulesVersion() {
return mRulesVersion;
}
public int getRevision() {
return mRevision;
}
/**
* Returns true if this DistroRulesVersion is older than the one supplied. It returns false if
* it is the same or newer. This method compares the {@code rulesVersion} and the
* {@code revision}.
*/
public boolean isOlderThan(DistroRulesVersion distroRulesVersion) {
int rulesComparison = mRulesVersion.compareTo(distroRulesVersion.mRulesVersion);
if (rulesComparison < 0) {
return true;
}
if (rulesComparison > 0) {
return false;
}
return mRevision < distroRulesVersion.mRevision;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mRulesVersion);
out.writeInt(mRevision);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DistroRulesVersion that = (DistroRulesVersion) o;
if (mRevision != that.mRevision) {
return false;
}
return mRulesVersion.equals(that.mRulesVersion);
}
@Override
public int hashCode() {
int result = mRulesVersion.hashCode();
result = 31 * result + mRevision;
return result;
}
@Override
public String toString() {
return "DistroRulesVersion{"
+ "mRulesVersion='" + mRulesVersion + '\''
+ ", mRevision='" + mRevision + '\''
+ '}';
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 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 android.app.timezone;
/**
* Callback interface for a timezone updater to receive information about the success or failure of
* an installation/uninstallation attempt.
*
* {@hide}
*/
oneway interface ICallback {
void onFinished(int error);
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 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 android.app.timezone;
import android.app.timezone.ICallback;
import android.app.timezone.RulesState;
import android.os.ParcelFileDescriptor;
/**
* Interface to the TimeZone Rules Manager Service.
*
* <p>This interface is only intended for system apps to call. They should use the
* {@link android.app.timezone.RulesManager} class rather than going through this
* Binder interface directly. See {@link android.app.timezone.RulesManager} for more complete
* documentation.
*
* {@hide}
*/
interface IRulesManager {
/**
* Returns information about the current time zone rules state such as the IANA version of
* the system and any currently installed distro. This method is intended to allow clients to
* determine if the current state can be improved; for example by passing the information to a
* server that may provide a new distro for download.
*/
RulesState getRulesState();
/**
* Requests installation of the supplied distro. The distro must have been checked for integrity
* by the caller or have been received via a trusted mechanism.
*
* @param distroFileDescriptor the file descriptor for the distro
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link ICallback} to receive callbacks related to the
* installation
* @return zero if the installation will be attempted; nonzero on error
*/
int requestInstall(in ParcelFileDescriptor distroFileDescriptor, in byte[] checkToken,
ICallback callback);
/**
* Requests uninstallation of the currently installed distro (leaving the device with no
* distro installed).
*
* @param checkToken an optional token provided if the uninstall was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link ICallback} to receive callbacks related to the
* uninstall
* @return zero if the uninstallation will be attempted; nonzero on error
*/
int requestUninstall(in byte[] checkToken, ICallback callback);
/**
* Requests the system does not modify the currently installed time zone distro, if any. This
* method records the fact that a time zone check operation triggered by the system is now
* complete and there was nothing to do. The token passed should be the one presented when the
* check was triggered.
*
* <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
* should be careful not to pass false if the failure is unlikely to resolve by itself.
*
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param success true if the check was successful, false if it was not successful but may
* succeed if it is retried
*/
void requestNothing(in byte[] token, boolean success);
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (C) 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 android.app.timezone;
import android.annotation.IntDef;
import android.content.Context;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
* The interface through which a time zone update application interacts with the Android system
* to handle time zone rule updates.
*
* <p>This interface is intended for use with the default APK-based time zone rules update
* application but it can also be used by OEMs if that mechanism is turned off using configuration.
* All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
* permission.
*
* <p>When using the default mechanism, when properly configured the Android system will send a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
* {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
* when it detects that it or the OEM's APK containing time zone rules data has been modified. The
* updater application is then responsible for calling one of
* {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
* {@link #requestUninstall(byte[], Callback)} or
* {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
* distro should be installed, the current distro should be uninstalled, or there is nothing to do
* (or that the correct operation could not be determined due to an error). In each case the updater
* must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
* back so the system in the {@code checkToken} parameter.
*
* <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
* rather than an APK, then they should disable the default triggering mechanism in config and are
* responsible for triggering their own update checks / installs / uninstalls. In this case the
* "check token" parameter can be left null and there is never any need to call
* {@link #requestNothing(byte[], boolean)}.
*
* <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
* unnecessary checks being triggered.
*
* <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
* {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public final class RulesManager {
private static final String TAG = "timezone.RulesManager";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS})
public @interface ResultCode {}
/**
* Indicates that an operation succeeded.
*/
public static final int SUCCESS = 0;
/**
* Indicates that an install/uninstall cannot be initiated because there is one already in
* progress.
*/
public static final int ERROR_OPERATION_IN_PROGRESS = 1;
/**
* Indicates an install / uninstall did not fully succeed for an unknown reason.
*/
public static final int ERROR_UNKNOWN_FAILURE = 2;
private final Context mContext;
private final IRulesManager mIRulesManager;
public RulesManager(Context context) {
mContext = context;
mIRulesManager = IRulesManager.Stub.asInterface(
ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
}
/**
* Returns information about the current time zone rules state such as the IANA version of
* the system and any currently installed distro. This method is intended to allow clients to
* determine if the current state can be improved; for example by passing the information to a
* server that may provide a new distro for download.
*/
public RulesState getRulesState() {
try {
logDebug("sIRulesManager.getRulesState()");
RulesState rulesState = mIRulesManager.getRulesState();
logDebug("sIRulesManager.getRulesState() returned " + rulesState);
return rulesState;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Requests installation of the supplied distro. The distro must have been checked for integrity
* by the caller or have been received via a trusted mechanism.
*
* @param distroFileDescriptor the file descriptor for the distro
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link Callback} to receive callbacks related to the installation
* @return {@link #SUCCESS} if the installation will be attempted
*/
@ResultCode
public int requestInstall(
ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
throws IOException {
ICallback iCallback = new CallbackWrapper(mContext, callback);
try {
logDebug("sIRulesManager.requestInstall()");
return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Requests uninstallation of the currently installed distro (leaving the device with no
* distro installed).
*
* @param checkToken an optional token provided if the uninstall was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param callback the {@link Callback} to receive callbacks related to the uninstall
* @return {@link #SUCCESS} if the uninstallation will be attempted
*/
@ResultCode
public int requestUninstall(byte[] checkToken, Callback callback) {
ICallback iCallback = new CallbackWrapper(mContext, callback);
try {
logDebug("sIRulesManager.requestUninstall()");
return mIRulesManager.requestUninstall(checkToken, iCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
* progress callbacks nicely within the usual main-thread lifecycle pattern.
*/
private class CallbackWrapper extends ICallback.Stub {
final Handler mHandler;
final Callback mCallback;
CallbackWrapper(Context context, Callback callback) {
mCallback = callback;
mHandler = new Handler(context.getMainLooper());
}
// Binder calls into this object just enqueue on the main-thread handler
@Override
public void onFinished(int status) {
logDebug("mCallback.onFinished(status), status=" + status);
mHandler.post(() -> mCallback.onFinished(status));
}
}
/**
* Requests the system does not modify the currently installed time zone distro, if any. This
* method records the fact that a time zone check operation triggered by the system is now
* complete and there was nothing to do. The token passed should be the one presented when the
* check was triggered.
*
* <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
* should be careful not to pass false if the failure is unlikely to resolve by itself.
*
* @param checkToken an optional token provided if the install was triggered in response to a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
* @param succeeded true if the check was successful, false if it was not successful but may
* succeed if it is retried
*/
public void requestNothing(byte[] checkToken, boolean succeeded) {
try {
logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
mIRulesManager.requestNothing(checkToken, succeeded);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
static void logDebug(String msg) {
if (DEBUG) {
Log.v(TAG, msg);
}
}
}

View File

@@ -0,0 +1,17 @@
/*
* Copyright (C) 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.
*/
parcelable RulesState;

View File

@@ -0,0 +1,319 @@
/*
* Copyright (C) 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 android.app.timezone;
import static android.app.timezone.Utils.validateConditionalNull;
import static android.app.timezone.Utils.validateNotNull;
import static android.app.timezone.Utils.validateRulesVersion;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Description of the state of time zone rules on a device.
*
* <p>The following properties are included:
* <dl>
* <dt>systemRulesVersion</dt>
* <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
* <dt>distroFormatVersionSupported</dt>
* <dd>the distro format version supported by this device. Always present.</dd>
* <dt>operationInProgress</dt>
* <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
* <dt>stagedOperationType</dt>
* <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
* {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
* there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
* used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
* require a reboot to become active.</dd>
* <dt>stagedDistroRulesVersion</dt>
* <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
* currently staged for installation.</dd>
* <dt>distroStatus</dt>
* <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
* {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
* {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
* </dd>
* <dt>installedDistroRulesVersion</dt>
* <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
* installed and active distro.</dd>
* </dl>
*
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public final class RulesState implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STAGED_OPERATION_UNKNOWN,
STAGED_OPERATION_NONE,
STAGED_OPERATION_UNINSTALL,
STAGED_OPERATION_INSTALL })
private @interface StagedOperationType {}
/** Staged state could not be determined. */
public static final int STAGED_OPERATION_UNKNOWN = 0;
/** Nothing is staged. */
public static final int STAGED_OPERATION_NONE = 1;
/** An uninstall is staged. */
public static final int STAGED_OPERATION_UNINSTALL = 2;
/** An install is staged. */
public static final int STAGED_OPERATION_INSTALL = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DISTRO_STATUS_UNKNOWN,
DISTRO_STATUS_NONE,
DISTRO_STATUS_INSTALLED })
private @interface DistroStatus {}
/** The current distro status could not be determined. */
public static final int DISTRO_STATUS_UNKNOWN = 0;
/** There is no active installed time zone distro. */
public static final int DISTRO_STATUS_NONE = 1;
/** The is an active, installed time zone distro. */
public static final int DISTRO_STATUS_INSTALLED = 2;
private static final byte BYTE_FALSE = 0;
private static final byte BYTE_TRUE = 1;
private final String mSystemRulesVersion;
private final DistroFormatVersion mDistroFormatVersionSupported;
private final boolean mOperationInProgress;
@StagedOperationType private final int mStagedOperationType;
@Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
@DistroStatus private final int mDistroStatus;
@Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
public RulesState(String systemRulesVersion, DistroFormatVersion distroFormatVersionSupported,
boolean operationInProgress,
@StagedOperationType int stagedOperationType,
@Nullable DistroRulesVersion stagedDistroRulesVersion,
@DistroStatus int distroStatus,
@Nullable DistroRulesVersion installedDistroRulesVersion) {
this.mSystemRulesVersion = validateRulesVersion("systemRulesVersion", systemRulesVersion);
this.mDistroFormatVersionSupported =
validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
this.mOperationInProgress = operationInProgress;
if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
throw new IllegalArgumentException(
"stagedOperationType != STAGED_OPERATION_UNKNOWN");
}
this.mStagedOperationType = validateStagedOperation(stagedOperationType);
this.mStagedDistroRulesVersion = validateConditionalNull(
mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
"stagedDistroRulesVersion", stagedDistroRulesVersion);
if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) {
throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN");
}
this.mDistroStatus = validateDistroStatus(distroStatus);
this.mInstalledDistroRulesVersion = validateConditionalNull(
mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
"installedDistroRulesVersion", installedDistroRulesVersion);
}
public String getSystemRulesVersion() {
return mSystemRulesVersion;
}
public boolean isOperationInProgress() {
return mOperationInProgress;
}
public @StagedOperationType int getStagedOperationType() {
return mStagedOperationType;
}
/**
* Returns the staged rules version when {@link #getStagedOperationType()} is
* {@link #STAGED_OPERATION_INSTALL}.
*/
public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
return mStagedDistroRulesVersion;
}
public @DistroStatus int getDistroStatus() {
return mDistroStatus;
}
/**
* Returns the installed rules version when {@link #getDistroStatus()} is
* {@link #DISTRO_STATUS_INSTALLED}.
*/
public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
return mInstalledDistroRulesVersion;
}
/**
* Returns true if a distro in the specified format is supported on this device.
*/
public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
return mDistroFormatVersionSupported.supports(distroFormatVersion);
}
/**
* Returns true if the distro IANA rules version supplied is newer or the same as the version in
* the system image data files.
*/
public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
}
public boolean isDistroInstalled() {
return mDistroStatus == DISTRO_STATUS_INSTALLED;
}
/**
* Returns true if the rules version supplied is newer than the one currently installed. If
* there is no installed distro this method throws IllegalStateException.
*/
public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
if (mOperationInProgress) {
throw new IllegalStateException("Distro state not known: operation in progress.");
}
if (!isDistroInstalled()) {
throw new IllegalStateException("No distro installed.");
}
return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
}
public static final Parcelable.Creator<RulesState> CREATOR =
new Parcelable.Creator<RulesState>() {
public RulesState createFromParcel(Parcel in) {
return RulesState.createFromParcel(in);
}
public RulesState[] newArray(int size) {
return new RulesState[size];
}
};
private static RulesState createFromParcel(Parcel in) {
String systemRulesVersion = in.readString();
DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
boolean operationInProgress = in.readByte() == BYTE_TRUE;
int distroStagedState = in.readByte();
DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
int installedDistroStatus = in.readByte();
DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
return new RulesState(systemRulesVersion, distroFormatVersionSupported, operationInProgress,
distroStagedState, stagedDistroRulesVersion,
installedDistroStatus, installedDistroRulesVersion);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mSystemRulesVersion);
out.writeParcelable(mDistroFormatVersionSupported, 0);
out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
out.writeByte((byte) mStagedOperationType);
out.writeParcelable(mStagedDistroRulesVersion, 0);
out.writeByte((byte) mDistroStatus);
out.writeParcelable(mInstalledDistroRulesVersion, 0);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RulesState that = (RulesState) o;
if (mOperationInProgress != that.mOperationInProgress) {
return false;
}
if (mStagedOperationType != that.mStagedOperationType) {
return false;
}
if (mDistroStatus != that.mDistroStatus) {
return false;
}
if (!mSystemRulesVersion.equals(that.mSystemRulesVersion)) {
return false;
}
if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
return false;
}
if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
.equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
return false;
}
return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
.equals(that.mInstalledDistroRulesVersion)
: that.mInstalledDistroRulesVersion == null;
}
@Override
public int hashCode() {
int result = mSystemRulesVersion.hashCode();
result = 31 * result + mDistroFormatVersionSupported.hashCode();
result = 31 * result + (mOperationInProgress ? 1 : 0);
result = 31 * result + mStagedOperationType;
result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
.hashCode()
: 0);
result = 31 * result + mDistroStatus;
result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "RulesState{"
+ "mSystemRulesVersion='" + mSystemRulesVersion + '\''
+ ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
+ ", mOperationInProgress=" + mOperationInProgress
+ ", mStagedOperationType=" + mStagedOperationType
+ ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
+ ", mDistroStatus=" + mDistroStatus
+ ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
+ '}';
}
private static int validateStagedOperation(int stagedOperationType) {
if (stagedOperationType < STAGED_OPERATION_UNKNOWN
|| stagedOperationType > STAGED_OPERATION_INSTALL) {
throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
}
return stagedOperationType;
}
private static int validateDistroStatus(int distroStatus) {
if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
}
return distroStatus;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 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 android.app.timezone;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelFileDescriptor;
/**
* Constants related to the contract between the Android system and the privileged time zone updater
* application.
*
* @hide
*/
// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
public final class RulesUpdaterContract {
/**
* The system permission possessed by the Android system that allows it to trigger time zone
* update checks. The updater should be configured to require this permission when registering
* for {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intents.
*/
public static final String TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION =
android.Manifest.permission.TRIGGER_TIME_ZONE_RULES_CHECK;
/**
* The system permission possessed by the time zone rules updater app that allows it to update
* device time zone rules. The Android system requires this permission for calls made to
* {@link RulesManager}.
*/
public static final String UPDATE_TIME_ZONE_RULES_PERMISSION =
android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
/**
* The action of the intent that the Android system will broadcast. The intent will be targeted
* at the configured updater application's package meaning the term "broadcast" only loosely
* applies.
*/
public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
"android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
/**
* The extra containing the {@code byte[]} that should be passed to
* {@link RulesManager#requestInstall(ParcelFileDescriptor, byte[], Callback)},
* {@link RulesManager#requestUninstall(byte[], Callback)} and
* {@link RulesManager#requestNothing(byte[], boolean)} methods when the
* {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
*/
public static final String EXTRA_CHECK_TOKEN =
"android.intent.extra.timezone.CHECK_TOKEN";
/**
* Creates an intent that would trigger a time zone rules update check.
*/
public static Intent createUpdaterIntent(String updaterPackageName) {
Intent intent = new Intent(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK);
intent.setPackage(updaterPackageName);
intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
return intent;
}
/**
* Broadcasts an {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with the
* {@link #EXTRA_CHECK_TOKEN} that triggers an update check, including the required receiver
* permission.
*/
public static void sendBroadcast(Context context, String updaterAppPackageName,
byte[] checkTokenBytes) {
Intent intent = createUpdaterIntent(updaterAppPackageName);
intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 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 android.app.timezone;
/**
* Shared code for android.app.timezone classes.
*/
final class Utils {
private Utils() {}
static int validateVersion(String type, int version) {
if (version < 0 || version > 999) {
throw new IllegalArgumentException("Invalid " + type + " version=" + version);
}
return version;
}
static String validateRulesVersion(String type, String rulesVersion) {
validateNotNull(type, rulesVersion);
if (rulesVersion.isEmpty()) {
throw new IllegalArgumentException(type + " must not be empty");
}
return rulesVersion;
}
/** Validates that {@code object} is not null. Always returns {@code object}. */
static <T> T validateNotNull(String type, T object) {
if (object == null) {
throw new NullPointerException(type + " == null");
}
return object;
}
/**
* If {@code requireNotNull} is {@code true} calls {@link #validateNotNull(String, Object)},
* and {@link #validateNull(String, Object)} otherwise. Returns {@code object}.
*/
static <T> T validateConditionalNull(boolean requireNotNull, String type, T object) {
if (requireNotNull) {
return validateNotNull(type, object);
} else {
return validateNull(type, object);
}
}
/** Validates that {@code object} is null. Always returns null. */
static <T> T validateNull(String type, T object) {
if (object != null) {
throw new IllegalArgumentException(type + " != null");
}
return null;
}
}

View File

@@ -2656,6 +2656,7 @@ public abstract class Context {
SENSOR_SERVICE,
STORAGE_SERVICE,
WALLPAPER_SERVICE,
TIME_ZONE_RULES_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
@@ -3665,6 +3666,15 @@ public abstract class Context {
*/
public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService";
/**
* Use with {@link #getSystemService} to retrieve an
* {@link android.app.timezone.ITimeZoneRulesManager}.
* @hide
*
* @see #getSystemService
*/
public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.

View File

@@ -2094,6 +2094,22 @@
<permission android:name="android.permission.UPDATE_CONFIG"
android:protectionLevel="signature|privileged" />
<!-- Allows a time zone rule updater application to request
the system installs / uninstalls timezone rules.
<p>An application requesting this permission is responsible for
verifying the source and integrity of the update before passing
it off to the installer components.
@hide -->
<permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
android:protectionLevel="signature|privileged" />
<!-- Must be required by a time zone rule updater application,
to ensure that only the system can trigger it.
@hide -->
<permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"/>
<!-- Allows the system to reset throttling in shortcut manager.
@hide -->
<permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 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 android.app.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import org.junit.Test;
/**
* Tests for {@link DistroFormatVersion}.
*/
// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
public class DistroFormatVersionTest {
@Test
public void equalsAndHashCode() {
DistroFormatVersion one = new DistroFormatVersion(1, 2);
assertEqualsContract(one, one);
DistroFormatVersion two = new DistroFormatVersion(1, 2);
assertEqualsContract(one, two);
DistroFormatVersion three = new DistroFormatVersion(2, 1);
assertFalse(one.equals(three));
}
@Test
public void parcelable() {
DistroFormatVersion version = new DistroFormatVersion(2, 3);
Parcel parcel = Parcel.obtain();
version.writeToParcel(parcel, 0 /* flags */);
parcel.setDataPosition(0);
DistroFormatVersion newVersion = DistroFormatVersion.CREATOR.createFromParcel(parcel);
assertEquals(version, newVersion);
}
@Test
public void supportsVersion() {
DistroFormatVersion deviceVersion = new DistroFormatVersion(2, 2);
assertTrue(deviceVersion.supports(deviceVersion));
DistroFormatVersion sameVersion = new DistroFormatVersion(2, 2);
assertTrue(deviceVersion.supports(sameVersion));
// Minor versions are backwards compatible.
DistroFormatVersion sameMajorNewerMinor = new DistroFormatVersion(2, 3);
assertTrue(deviceVersion.supports(sameMajorNewerMinor));
DistroFormatVersion sameMajorOlderMinor = new DistroFormatVersion(2, 1);
assertFalse(deviceVersion.supports(sameMajorOlderMinor));
// Major versions are not backwards compatible.
DistroFormatVersion newerMajor = new DistroFormatVersion(1, 2);
assertFalse(deviceVersion.supports(newerMajor));
DistroFormatVersion olderMajor = new DistroFormatVersion(3, 2);
assertFalse(deviceVersion.supports(olderMajor));
}
private static void assertEqualsContract(DistroFormatVersion one, DistroFormatVersion two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 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 android.app.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import org.junit.Test;
/**
* Tests for {@link DistroRulesVersion}.
*/
// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
public class DistroRulesVersionTest {
@Test
public void equalsAndHashCode() {
DistroRulesVersion one = new DistroRulesVersion("2016a", 2);
assertEqualsContract(one, one);
DistroRulesVersion two = new DistroRulesVersion("2016a", 2);
assertEqualsContract(one, two);
DistroRulesVersion three = new DistroRulesVersion("2016b", 1);
assertFalse(one.equals(three));
}
@Test
public void parcelable() {
DistroRulesVersion version = new DistroRulesVersion("2016a", 2);
Parcel parcel = Parcel.obtain();
version.writeToParcel(parcel, 0 /* flags */);
parcel.setDataPosition(0);
DistroRulesVersion newVersion = DistroRulesVersion.CREATOR.createFromParcel(parcel);
assertEquals(version, newVersion);
}
@Test
public void isOlderThan() {
DistroRulesVersion deviceVersion = new DistroRulesVersion("2016b", 2);
assertFalse(deviceVersion.isOlderThan(deviceVersion));
DistroRulesVersion sameVersion = new DistroRulesVersion("2016b", 2);
assertFalse(deviceVersion.isOlderThan(sameVersion));
DistroRulesVersion sameRulesNewerRevision = new DistroRulesVersion("2016b", 3);
assertTrue(deviceVersion.isOlderThan(sameRulesNewerRevision));
DistroRulesVersion sameRulesOlderRevision = new DistroRulesVersion("2016b", 1);
assertFalse(deviceVersion.isOlderThan(sameRulesOlderRevision));
DistroRulesVersion newerRules = new DistroRulesVersion("2016c", 2);
assertTrue(deviceVersion.isOlderThan(newerRules));
DistroRulesVersion olderRules = new DistroRulesVersion("2016a", 2);
assertFalse(deviceVersion.isOlderThan(olderRules));
}
private static void assertEqualsContract(DistroRulesVersion one, DistroRulesVersion two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 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 android.app.timezone;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import org.junit.Test;
/**
* Tests for {@link RulesState}.
*/
// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
public class RulesStateTest {
@Test
public void equalsAndHashCode() {
RulesState one = new RulesState(
"2016a", formatVersion(1, 2), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertEqualsContract(one, one);
RulesState two = new RulesState(
"2016a", formatVersion(1, 2), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertEqualsContract(one, two);
RulesState differentSystemRules = new RulesState(
"2016b", formatVersion(1, 2), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertFalse(one.equals(differentSystemRules));
RulesState differentFormatVersion = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertFalse(one.equals(differentFormatVersion));
RulesState differentOperationInProgress = new RulesState(
"2016a", formatVersion(1, 1), true /* operationInProgress */,
RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
assertFalse(one.equals(differentOperationInProgress));
RulesState differentStagedOperation = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertFalse(one.equals(differentStagedOperation));
RulesState differentStagedInstallVersion = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 4),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
assertFalse(one.equals(differentStagedInstallVersion));
RulesState differentInstalled = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
assertFalse(one.equals(differentInstalled));
RulesState differentInstalledVersion = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
assertFalse(one.equals(differentInstalledVersion));
}
@Test
public void parcelable() {
RulesState rulesState1 = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016b", 2),
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
checkParcelableRoundTrip(rulesState1);
RulesState rulesStateWithNulls = new RulesState(
"2016a", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
checkParcelableRoundTrip(rulesStateWithNulls);
RulesState rulesStateWithUnknowns = new RulesState(
"2016a", formatVersion(1, 1), true /* operationInProgress */,
RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
checkParcelableRoundTrip(rulesStateWithNulls);
}
private static void checkParcelableRoundTrip(RulesState rulesState) {
Parcel parcel = Parcel.obtain();
rulesState.writeToParcel(parcel, 0 /* flags */);
parcel.setDataPosition(0);
RulesState newVersion = RulesState.CREATOR.createFromParcel(parcel);
assertEquals(rulesState, newVersion);
}
@Test
public void isSystemVersionOlderThan() {
RulesState rulesState = new RulesState(
"2016b", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
}
@Test
public void isInstalledDistroOlderThan() {
RulesState operationInProgress = new RulesState(
"2016b", formatVersion(1, 1), true /* operationInProgress */,
RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
try {
operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
fail();
} catch (IllegalStateException expected) {
}
RulesState nothingInstalled = new RulesState(
"2016b", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
try {
nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
fail();
} catch (IllegalStateException expected) {
}
DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
RulesState rulesStateWithInstalledVersion = new RulesState(
"2016b", formatVersion(1, 1), false /* operationInProgress */,
RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
DistroRulesVersion olderRules = rulesVersion("2016a", 1);
assertEquals(installedVersion.isOlderThan(olderRules),
rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
DistroRulesVersion sameRules = rulesVersion("2016b", 1);
assertEquals(installedVersion.isOlderThan(sameRules),
rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
DistroRulesVersion newerRules = rulesVersion("2016c", 1);
assertEquals(installedVersion.isOlderThan(newerRules),
rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
}
private static void assertEqualsContract(RulesState one, RulesState two) {
assertEquals(one, two);
assertEquals(one.hashCode(), two.hashCode());
}
private static DistroRulesVersion rulesVersion(String rulesVersion, int revision) {
return new DistroRulesVersion(rulesVersion, revision);
}
private static DistroFormatVersion formatVersion(int majorVersion, int minorVersion) {
return new DistroFormatVersion(majorVersion, minorVersion);
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 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 android.app.timezone;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.content.Context;
import android.content.Intent;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;
/**
* Tests for {@link RulesUpdaterContract}.
*/
// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
public class RulesUpdaterContractTest {
@Test
public void createUpdaterIntent() throws Exception {
String packageName = "foobar";
Intent intent = RulesUpdaterContract.createUpdaterIntent(packageName);
assertEquals(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK, intent.getAction());
assertEquals(packageName, intent.getPackage());
assertEquals(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, intent.getFlags());
}
@Test
public void sendBroadcast() throws Exception {
String packageName = "foobar";
byte[] tokenBytes = new byte[] { 1, 2, 3, 4, 5 };
Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(packageName);
expectedIntent.putExtra(RulesUpdaterContract.EXTRA_CHECK_TOKEN, tokenBytes);
Context mockContext = mock(Context.class);
RulesUpdaterContract.sendBroadcast(mockContext, packageName, tokenBytes);
verify(mockContext).sendBroadcast(
filterEquals(expectedIntent),
eq(RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION));
}
/**
* Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
* check the parameter against the intent supplied.
*/
private static Intent filterEquals(final Intent expected) {
final Matcher<Intent> m = new BaseMatcher<Intent>() {
@Override
public boolean matches(Object actual) {
return actual != null && expected.filterEquals((Intent) actual);
}
@Override
public void describeTo(Description description) {
description.appendText(expected.toString());
}
};
return argThat(m);
}
}