Merge "Remove TZUvA feature."

This commit is contained in:
Almaz Mingaleev
2022-06-23 07:47:26 +00:00
committed by Gerrit Code Review
27 changed files with 0 additions and 5618 deletions

View File

@@ -6325,10 +6325,6 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.server.usage.UsageStatsIdleService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>

View File

@@ -1644,49 +1644,6 @@
suggestions to TimeDetector service. See also config_autoTimeSourcesPriority. -->
<bool name="config_enableGnssTimeUpdateService">false</bool>
<!-- Enables the TimeZoneRuleManager service. This is the global switch for the updateable time
zone update mechanism. -->
<bool name="config_enableUpdateableTimeZoneRules">false</bool>
<!-- Enables APK-based time zone update triggering. Set this to false when updates are triggered
via external events and not by APK updates. For example, if an updater checks with a server
on a regular schedule.
[This is only used if config_enableUpdateableTimeZoneRules is true.] -->
<bool name="config_timeZoneRulesUpdateTrackingEnabled">false</bool>
<!-- The package of the time zone rules updater application. Expected to be the same
for all Android devices that support APK-based time zone rule updates.
A package-targeted com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
will be sent to the updater app if the system server detects an update to the updater or
data app packages.
The package referenced here must have the android.permission.UPDATE_TIME_ZONE_RULES
permission.
[This is only used if config_enableUpdateableTimeZoneRules and
config_timeZoneRulesUpdateTrackingEnabled are true.] -->
<string name="config_timeZoneRulesUpdaterPackage" translatable="false">com.android.timezone.updater</string>
<!-- The package of the time zone rules data application. Expected to be configured
by OEMs to reference their own priv-app APK package.
A package-targeted com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
will be sent to the updater app if the system server detects an update to the updater or
data app packages.
[This is only used if config_enableUpdateableTimeZoneRules and
config_timeZoneRulesUpdateTrackingEnabled are true.] -->
<string name="config_timeZoneRulesDataPackage" translatable="false"></string>
<!-- The allowed time in milliseconds between an update check intent being broadcast and the
response being considered overdue. Reliability triggers will not fire in this time.
[This is only used if config_enableUpdateableTimeZoneRules and
config_timeZoneRulesUpdateTrackingEnabled are true.] -->
<!-- 5 minutes -->
<integer name="config_timeZoneRulesCheckTimeMillisAllowed">300000</integer>
<!-- The number of times a time zone update check is allowed to fail before the system will stop
reacting to reliability triggers.
[This is only used if config_enableUpdateableTimeZoneRules and
config_timeZoneRulesUpdateTrackingEnabled are true.] -->
<integer name="config_timeZoneRulesCheckRetryCount">5</integer>
<!-- Whether the geolocation time zone detection feature is enabled. Setting this to false means
the feature cannot be used. Setting this to true means system server components can be
tested and location time zone detection may be used if other configuration allows (see

View File

@@ -323,12 +323,6 @@
<java-symbol type="bool" name="config_checkWallpaperAtBoot" />
<java-symbol type="string" name="config_wallpaperManagerServiceName" />
<java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" />
<java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
<java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
<java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
<java-symbol type="string" name="config_timeZoneRulesDataPackage" />
<java-symbol type="integer" name="config_timeZoneRulesCheckTimeMillisAllowed" />
<java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
<java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
<java-symbol type="bool" name="config_enableScreenshotChord" />
<java-symbol type="bool" name="config_enableWifiDisplay" />

View File

@@ -132,8 +132,6 @@ java_library_static {
],
static_libs: [
"time_zone_distro",
"time_zone_distro_installer",
"android.hardware.authsecret-V1.0-java",
"android.hardware.boot-V1.0-java",
"android.hardware.boot-V1.1-java",

View File

@@ -20,21 +20,13 @@ import android.content.Context;
import android.os.Binder;
import android.service.runtime.DebugEntryProto;
import android.service.runtime.RuntimeServiceInfoProto;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.i18n.timezone.DebugInfo;
import com.android.i18n.timezone.I18nModuleDebug;
import com.android.i18n.timezone.TimeZoneDataFiles;
import com.android.internal.util.DumpUtils;
import com.android.timezone.distro.DistroException;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.FileUtils;
import com.android.timezone.distro.TimeZoneDistro;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -61,7 +53,6 @@ public class RuntimeService extends Binder {
ProtoOutputStream proto = null;
DebugInfo i18nLibraryDebugInfo = I18nModuleDebug.getDebugInfo();
addTimeZoneApkDebugInfo(i18nLibraryDebugInfo);
if (protoFormat) {
proto = new ProtoOutputStream(fd);
@@ -85,20 +76,6 @@ public class RuntimeService extends Binder {
return false;
}
/**
* Add information to {@link DebugInfo} about the time zone data supplied by the
* "Time zone updates via APK" feature.
*/
private static void addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo) {
// Add /data tz data set using the DistroVersion class (which libcore cannot use).
// This update mechanism will be removed after the time zone APEX is launched so this
// untidiness will disappear with it.
String debugKeyPrefix = "core_library.timezone.source.data_";
String versionFileName = TimeZoneDataFiles.getDataTimeZoneFile(
TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
addDistroVersionDebugInfo(versionFileName, debugKeyPrefix, coreLibraryDebugInfo);
}
/**
* Prints {@code coreLibraryDebugInfo} to {@code pw}.
*
@@ -131,42 +108,4 @@ public class RuntimeService extends Binder {
protoStream.end(entryToken);
}
}
/**
* Adds version information to {@code debugInfo} from the distro_version file that may exist
* at {@code distroVersionFileName}. If the file does not exist or cannot be read this is
* reported as debug information too.
*/
private static void addDistroVersionDebugInfo(String distroVersionFileName,
String debugKeyPrefix, DebugInfo debugInfo) {
File file = new File(distroVersionFileName);
String statusKey = debugKeyPrefix + "status";
if (file.exists()) {
try {
byte[] versionBytes =
FileUtils.readBytes(file, DistroVersion.DISTRO_VERSION_FILE_LENGTH);
DistroVersion distroVersion = DistroVersion.fromBytes(versionBytes);
String formatVersionString = distroVersion.formatMajorVersion + "."
+ distroVersion.formatMinorVersion;
debugInfo.addStringEntry(statusKey, "OK")
.addStringEntry(debugKeyPrefix + "formatVersion", formatVersionString)
.addStringEntry(debugKeyPrefix + "rulesVersion",
distroVersion.rulesVersion)
.addStringEntry(debugKeyPrefix + "revision",
distroVersion.revision);
} catch (IOException | DistroException e) {
debugInfo.addStringEntry(statusKey, "ERROR");
debugInfo.addStringEntry(debugKeyPrefix + "exception_class",
e.getClass().getName());
debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage());
logMessage("Error reading " + file, e);
}
} else {
debugInfo.addStringEntry(statusKey, "NOT_FOUND");
}
}
private static void logMessage(String msg, Throwable t) {
Slog.v(TAG, msg, t);
}
}

View File

@@ -1,98 +0,0 @@
/*
* 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 com.android.server.timezone;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
/**
* A deserialized version of the byte[] sent to the time zone update application to identify a
* triggered time zone update check. It encodes the optimistic lock ID used to detect
* concurrent checks and the minimal package versions that will have been checked.
*/
final class CheckToken {
final int mOptimisticLockId;
final PackageVersions mPackageVersions;
CheckToken(int optimisticLockId, PackageVersions packageVersions) {
this.mOptimisticLockId = optimisticLockId;
if (packageVersions == null) {
throw new NullPointerException("packageVersions == null");
}
this.mPackageVersions = packageVersions;
}
byte[] toByteArray() {
ByteArrayOutputStream baos = new ByteArrayOutputStream(12 /* (3 * sizeof(int)) */);
try (DataOutputStream dos = new DataOutputStream(baos)) {
dos.writeInt(mOptimisticLockId);
dos.writeLong(mPackageVersions.mUpdateAppVersion);
dos.writeLong(mPackageVersions.mDataAppVersion);
} catch (IOException e) {
throw new RuntimeException("Unable to write into a ByteArrayOutputStream", e);
}
return baos.toByteArray();
}
static CheckToken fromByteArray(byte[] tokenBytes) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(tokenBytes);
try (DataInputStream dis = new DataInputStream(bais)) {
int versionId = dis.readInt();
long updateAppVersion = dis.readLong();
long dataAppVersion = dis.readLong();
return new CheckToken(versionId, new PackageVersions(updateAppVersion, dataAppVersion));
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CheckToken checkToken = (CheckToken) o;
if (mOptimisticLockId != checkToken.mOptimisticLockId) {
return false;
}
return mPackageVersions.equals(checkToken.mPackageVersions);
}
@Override
public int hashCode() {
int result = mOptimisticLockId;
result = 31 * result + mPackageVersions.hashCode();
return result;
}
@Override
public String toString() {
return "Token{" +
"mOptimisticLockId=" + mOptimisticLockId +
", mPackageVersions=" + mPackageVersions +
'}';
}
}

View File

@@ -1,34 +0,0 @@
/*
* 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 com.android.server.timezone;
/**
* An easy-to-mock interface around device config for use by {@link PackageTracker}; it is not
* possible to test various states with the real one because config is fixed in the system image.
*/
interface ConfigHelper {
boolean isTrackingEnabled();
String getUpdateAppPackageName();
String getDataAppPackageName();
int getCheckTimeAllowedMillis();
int getFailedCheckRetryCount();
}

View File

@@ -1,41 +0,0 @@
/*
* 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 com.android.server.timezone;
import android.content.Intent;
import android.content.pm.PackageManager;
/**
* An easy-to-mock facade around PackageManager for use by {@link PackageTracker}; it is not
* possible to test various cases with the real one because of the need to simulate package versions
* and manifest configurations.
*/
interface PackageManagerHelper {
long getInstalledPackageVersion(String packageName)
throws PackageManager.NameNotFoundException;
boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException;
boolean usesPermission(String packageName, String requiredPermissionName)
throws PackageManager.NameNotFoundException;
boolean contentProviderRegistered(String authority, String requiredPackageName);
boolean receiverRegistered(Intent intent, String requiredPermissionName)
throws PackageManager.NameNotFoundException;
}

View File

@@ -1,89 +0,0 @@
/*
* 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 com.android.server.timezone;
import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Information about the status of the time zone update / data packages that are persisted by the
* Android system.
*/
final class PackageStatus {
@Retention(RetentionPolicy.SOURCE)
@IntDef({ CHECK_STARTED, CHECK_COMPLETED_SUCCESS, CHECK_COMPLETED_FAILURE })
@interface CheckStatus {}
/** A time zone update check has been started but not yet completed. */
static final int CHECK_STARTED = 1;
/** A time zone update check has been completed and succeeded. */
static final int CHECK_COMPLETED_SUCCESS = 2;
/** A time zone update check has been completed and failed. */
static final int CHECK_COMPLETED_FAILURE = 3;
@CheckStatus
final int mCheckStatus;
// Non-null
final PackageVersions mVersions;
PackageStatus(@CheckStatus int checkStatus, PackageVersions versions) {
this.mCheckStatus = checkStatus;
if (checkStatus < 1 || checkStatus > 3) {
throw new IllegalArgumentException("Unknown checkStatus " + checkStatus);
}
if (versions == null) {
throw new NullPointerException("versions == null");
}
this.mVersions = versions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PackageStatus that = (PackageStatus) o;
if (mCheckStatus != that.mCheckStatus) {
return false;
}
return mVersions.equals(that.mVersions);
}
@Override
public int hashCode() {
int result = mCheckStatus;
result = 31 * result + mVersions.hashCode();
return result;
}
@Override
public String toString() {
return "PackageStatus{" +
"mCheckStatus=" + mCheckStatus +
", mVersions=" + mVersions +
'}';
}
}

View File

@@ -1,389 +0,0 @@
/*
* 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 com.android.server.timezone;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.io.PrintWriter;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
/**
* Storage logic for accessing/mutating the Android system's persistent state related to time zone
* update checking. There is expected to be a single instance. All non-private methods are thread
* safe.
*/
final class PackageStatusStorage {
private static final String LOG_TAG = "timezone.PackageStatusStorage";
private static final String TAG_PACKAGE_STATUS = "PackageStatus";
/**
* Attribute that stores a monotonically increasing lock ID, used to detect concurrent update
* issues without on-line locks. Incremented on every write.
*/
private static final String ATTRIBUTE_OPTIMISTIC_LOCK_ID = "optimisticLockId";
/**
* Attribute that stores the current "check status" of the time zone update application
* packages.
*/
private static final String ATTRIBUTE_CHECK_STATUS = "checkStatus";
/**
* Attribute that stores the version of the time zone rules update application being checked
* / last checked.
*/
private static final String ATTRIBUTE_UPDATE_APP_VERSION = "updateAppPackageVersion";
/**
* Attribute that stores the version of the time zone rules data application being checked
* / last checked.
*/
private static final String ATTRIBUTE_DATA_APP_VERSION = "dataAppPackageVersion";
private static final long UNKNOWN_PACKAGE_VERSION = -1;
private final AtomicFile mPackageStatusFile;
PackageStatusStorage(File storageDir) {
mPackageStatusFile = new AtomicFile(new File(storageDir, "package-status.xml"), "timezone-status");
}
/**
* Initialize any storage, as needed.
*
* @throws IOException if the storage could not be initialized
*/
void initialize() throws IOException {
if (!mPackageStatusFile.getBaseFile().exists()) {
insertInitialPackageStatus();
}
}
void deleteFileForTests() {
synchronized(this) {
mPackageStatusFile.delete();
}
}
/**
* Obtain the current check status of the application packages. Returns {@code null} the first
* time it is called, or after {@link #resetCheckState()}.
*/
PackageStatus getPackageStatus() {
synchronized (this) {
try {
return getPackageStatusLocked();
} catch (ParseException e) {
// This means that data exists in the file but it was bad.
Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e);
// Reset the storage so it is in a good state again.
recoverFromBadData(e);
try {
return getPackageStatusLocked();
} catch (ParseException e2) {
throw new IllegalStateException("Recovery from bad file failed", e2);
}
}
}
}
@GuardedBy("this")
private PackageStatus getPackageStatusLocked() throws ParseException {
try (FileInputStream fis = mPackageStatusFile.openRead()) {
TypedXmlPullParser parser = parseToPackageStatusTag(fis);
Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS);
if (checkStatus == null) {
return null;
}
int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION);
int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION);
return new PackageStatus(checkStatus,
new PackageVersions(updateAppVersion, dataAppVersion));
} catch (IOException e) {
ParseException e2 = new ParseException("Error reading package status", 0);
e2.initCause(e);
throw e2;
}
}
@GuardedBy("this")
private int recoverFromBadData(Exception cause) {
mPackageStatusFile.delete();
try {
return insertInitialPackageStatus();
} catch (IOException e) {
IllegalStateException fatal = new IllegalStateException(e);
fatal.addSuppressed(cause);
throw fatal;
}
}
/** Insert the initial data, returning the optimistic lock ID */
private int insertInitialPackageStatus() throws IOException {
// Doesn't matter what it is, but we avoid the obvious starting value each time the data
// is reset to ensure that old tokens are unlikely to work.
final int initialOptimisticLockId = (int) System.currentTimeMillis();
writePackageStatusLocked(null /* status */, initialOptimisticLockId,
null /* packageVersions */);
return initialOptimisticLockId;
}
/**
* Generate a new {@link CheckToken} that can be passed to the time zone rules update
* application.
*/
CheckToken generateCheckToken(PackageVersions currentInstalledVersions) {
if (currentInstalledVersions == null) {
throw new NullPointerException("currentInstalledVersions == null");
}
synchronized (this) {
int optimisticLockId;
try {
optimisticLockId = getCurrentOptimisticLockId();
} catch (ParseException e) {
Slog.w(LOG_TAG, "Unable to find optimistic lock ID from package status");
// Recover.
optimisticLockId = recoverFromBadData(e);
}
int newOptimisticLockId = optimisticLockId + 1;
try {
boolean statusUpdated = writePackageStatusWithOptimisticLockCheck(
optimisticLockId, newOptimisticLockId, CHECK_STARTED,
currentInstalledVersions);
if (!statusUpdated) {
throw new IllegalStateException("Unable to update status to CHECK_STARTED."
+ " synchronization failure?");
}
return new CheckToken(newOptimisticLockId, currentInstalledVersions);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* Reset the current device state to "unknown".
*/
void resetCheckState() {
synchronized(this) {
int optimisticLockId;
try {
optimisticLockId = getCurrentOptimisticLockId();
} catch (ParseException e) {
Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+ " status");
// Attempt to recover the storage state.
optimisticLockId = recoverFromBadData(e);
}
int newOptimisticLockId = optimisticLockId + 1;
try {
if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId,
newOptimisticLockId, null /* status */, null /* packageVersions */)) {
throw new IllegalStateException("resetCheckState: Unable to reset package"
+ " status, newOptimisticLockId=" + newOptimisticLockId);
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* Update the current device state if possible. Returns true if the update was successful.
* {@code false} indicates the storage has been changed since the {@link CheckToken} was
* generated and the update was discarded.
*/
boolean markChecked(CheckToken checkToken, boolean succeeded) {
synchronized (this) {
int optimisticLockId = checkToken.mOptimisticLockId;
int newOptimisticLockId = optimisticLockId + 1;
int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
try {
return writePackageStatusWithOptimisticLockCheck(optimisticLockId,
newOptimisticLockId, status, checkToken.mPackageVersions);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@GuardedBy("this")
private int getCurrentOptimisticLockId() throws ParseException {
try (FileInputStream fis = mPackageStatusFile.openRead()) {
TypedXmlPullParser parser = parseToPackageStatusTag(fis);
return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID);
} catch (IOException e) {
ParseException e2 = new ParseException("Unable to read file", 0);
e2.initCause(e);
throw e2;
}
}
/** Returns a parser or throws ParseException, never returns null. */
private static TypedXmlPullParser parseToPackageStatusTag(FileInputStream fis)
throws ParseException {
try {
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
int type;
while ((type = parser.next()) != END_DOCUMENT) {
final String tag = parser.getName();
if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) {
return parser;
}
}
throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0);
} catch (XmlPullParserException e) {
throw new IllegalStateException("Unable to configure parser", e);
} catch (IOException e) {
ParseException e2 = new ParseException("Error reading XML", 0);
e.initCause(e);
throw e2;
}
}
@GuardedBy("this")
private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId,
int newOptimisticLockId, Integer status, PackageVersions packageVersions)
throws IOException {
int currentOptimisticLockId;
try {
currentOptimisticLockId = getCurrentOptimisticLockId();
if (currentOptimisticLockId != optimisticLockId) {
return false;
}
} catch (ParseException e) {
recoverFromBadData(e);
return false;
}
writePackageStatusLocked(status, newOptimisticLockId, packageVersions);
return true;
}
@GuardedBy("this")
private void writePackageStatusLocked(Integer status, int optimisticLockId,
PackageVersions packageVersions) throws IOException {
if ((status == null) != (packageVersions == null)) {
throw new IllegalArgumentException(
"Provide both status and packageVersions, or neither.");
}
FileOutputStream fos = null;
try {
fos = mPackageStatusFile.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
serializer.startDocument(null /* encoding */, true /* standalone */);
final String namespace = null;
serializer.startTag(namespace, TAG_PACKAGE_STATUS);
String statusAttributeValue = status == null ? "" : Integer.toString(status);
serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue);
serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID,
Integer.toString(optimisticLockId));
long updateAppVersion = status == null
? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion;
serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION,
Long.toString(updateAppVersion));
long dataAppVersion = status == null
? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion;
serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION,
Long.toString(dataAppVersion));
serializer.endTag(namespace, TAG_PACKAGE_STATUS);
serializer.endDocument();
serializer.flush();
mPackageStatusFile.finishWrite(fos);
} catch (IOException e) {
if (fos != null) {
mPackageStatusFile.failWrite(fos);
}
throw e;
}
}
/** Only used during tests to force a known table state. */
public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions)
throws IOException {
synchronized (this) {
try {
final int initialOptimisticLockId = (int) System.currentTimeMillis();
writePackageStatusLocked(checkStatus, initialOptimisticLockId, packageVersions);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
private static Integer getNullableIntAttribute(TypedXmlPullParser parser, String attributeName)
throws ParseException {
String attributeValue = parser.getAttributeValue(null, attributeName);
try {
if (attributeValue == null) {
throw new ParseException("Attribute " + attributeName + " missing", 0);
} else if (attributeValue.isEmpty()) {
return null;
}
return Integer.parseInt(attributeValue);
} catch (NumberFormatException e) {
throw new ParseException(
"Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0);
}
}
private static int getIntAttribute(TypedXmlPullParser parser, String attributeName)
throws ParseException {
Integer value = getNullableIntAttribute(parser, attributeName);
if (value == null) {
throw new ParseException("Missing attribute " + attributeName, 0);
}
return value;
}
public void dump(PrintWriter printWriter) {
printWriter.println("Package status: " + getPackageStatus());
}
}

View File

@@ -1,545 +0,0 @@
/*
* 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 com.android.server.timezone;
import com.android.internal.annotations.VisibleForTesting;
import android.app.timezone.RulesUpdaterContract;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.SystemClock;
import android.provider.TimeZoneRulesDataContract;
import android.util.Slog;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Clock;
/**
* Monitors the installed applications associated with time zone updates. If the app packages are
* updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
* broadcast intent is used to trigger the time zone updater app.
*
* <p>The "update triggering" behavior of this component can be disabled via device configuration.
*
* <p>The package tracker listens for package updates of the time zone "updater app" and "data app".
* It also listens for "reliability" triggers. Reliability triggers are there to ensure that the
* package tracker handles failures reliably and are "idle maintenance" events or something similar.
* Reliability triggers can cause a time zone update check to take place if the current state is
* unclear. For example, it can be unclear after boot or after a failure. If there are repeated
* failures reliability updates are halted until the next boot.
*
* <p>This component keeps persistent track of the most recent app packages checked to avoid
* unnecessary expense from broadcasting intents (which will cause other app processes to spawn).
* The current status is also stored to detect whether the most recently-generated check is
* complete successfully. For example, if the device was interrupted while doing a check and never
* acknowledged a check then a check will be retried the next time a "reliability trigger" event
* happens.
*/
// Also made non-final so it can be mocked.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class PackageTracker {
private static final String TAG = "timezone.PackageTracker";
private final PackageManagerHelper mPackageManagerHelper;
private final PackageTrackerIntentHelper mIntentHelper;
private final ConfigHelper mConfigHelper;
private final PackageStatusStorage mPackageStatusStorage;
private final Clock mElapsedRealtimeClock;
// False if tracking is disabled.
private boolean mTrackingEnabled;
// These fields may be null if package tracking is disabled.
private String mUpdateAppPackageName;
private String mDataAppPackageName;
// The time a triggered check is allowed to take before it is considered overdue.
private int mCheckTimeAllowedMillis;
// The number of failed checks in a row before reliability checks should stop happening.
private long mFailedCheckRetryCount;
/*
* The minimum delay between a successive reliability triggers / other operations. Should to be
* larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package
* update checks.
*/
private int mDelayBeforeReliabilityCheckMillis;
// Reliability check state: If a check was triggered but not acknowledged within
// mCheckTimeAllowedMillis then another one can be triggered.
private Long mLastTriggerTimestamp = null;
// Reliability check state: Whether any checks have been triggered at all.
private boolean mCheckTriggered;
// Reliability check state: A count of how many failures have occurred consecutively.
private int mCheckFailureCount;
/** Creates the {@link PackageTracker} for normal use. */
static PackageTracker create(Context context) {
Clock elapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
File storageDir = FileUtils.createDir(Environment.getDataSystemDirectory(), "timezone");
return new PackageTracker(
elapsedRealtimeClock /* elapsedRealtimeClock */,
helperImpl /* configHelper */,
helperImpl /* packageManagerHelper */,
new PackageStatusStorage(storageDir),
new PackageTrackerIntentHelperImpl(context));
}
// A constructor that can be used by tests to supply mocked / faked dependencies.
PackageTracker(Clock elapsedRealtimeClock, ConfigHelper configHelper,
PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage,
PackageTrackerIntentHelper intentHelper) {
mElapsedRealtimeClock = elapsedRealtimeClock;
mConfigHelper = configHelper;
mPackageManagerHelper = packageManagerHelper;
mPackageStatusStorage = packageStatusStorage;
mIntentHelper = intentHelper;
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected synchronized boolean start() {
mTrackingEnabled = mConfigHelper.isTrackingEnabled();
if (!mTrackingEnabled) {
Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled.");
return false;
}
mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName();
mDataAppPackageName = mConfigHelper.getDataAppPackageName();
mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000);
// Validate the device configuration including the application packages.
// The manifest entries in the apps themselves are not validated until use as they can
// change and we don't want to prevent the system server starting due to a bad application.
throwIfDeviceSettingsOrAppsAreBad();
// Explicitly start in a reliability state where reliability triggering will do something.
mCheckTriggered = false;
mCheckFailureCount = 0;
// Initialize the storage, as needed.
try {
mPackageStatusStorage.initialize();
} catch (IOException e) {
Slog.w(TAG, "PackageTracker storage could not be initialized.", e);
return false;
}
// Initialize the intent helper.
mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
// Schedule a reliability trigger so we will have at least one after boot. This will allow
// us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok
// to delay for a while before doing this even if idle.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
Slog.i(TAG, "Time zone updater / data package tracking enabled");
return true;
}
/**
* Performs checks that confirm the system image has correctly configured package
* tracking configuration. Only called if package tracking is enabled. Throws an exception if
* the device is configured badly which will prevent the device booting.
*/
private void throwIfDeviceSettingsOrAppsAreBad() {
// None of the checks below can be based on application manifest settings, otherwise a bad
// update could leave the device in an unbootable state. See validateDataAppManifest() and
// validateUpdaterAppManifest() for softer errors.
throwRuntimeExceptionIfNullOrEmpty(
mUpdateAppPackageName, "Update app package name missing.");
throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing.");
if (mFailedCheckRetryCount < 1) {
throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null);
}
if (mCheckTimeAllowedMillis < 1000) {
throw logAndThrowRuntimeException(
"mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null);
}
// Validate the updater application package.
try {
if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) {
throw logAndThrowRuntimeException(
"Update app " + mUpdateAppPackageName + " must be a priv-app.", null);
}
} catch (PackageManager.NameNotFoundException e) {
throw logAndThrowRuntimeException("Could not determine update app package details for "
+ mUpdateAppPackageName, e);
}
Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid.");
// Validate the data application package.
try {
if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) {
throw logAndThrowRuntimeException(
"Data app " + mDataAppPackageName + " must be a priv-app.", null);
}
} catch (PackageManager.NameNotFoundException e) {
throw logAndThrowRuntimeException("Could not determine data app package details for "
+ mDataAppPackageName, e);
}
Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid.");
}
/**
* Inspects the current in-memory state, installed packages and storage state to determine if an
* update check is needed and then trigger if it is.
*
* @param packageChanged true if this method was called because a known packaged definitely
* changed, false if the cause is a reliability trigger
*/
public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
if (!mTrackingEnabled) {
throw new IllegalStateException("Unexpected call. Tracking is disabled.");
}
// Validate the applications' current manifest entries: make sure they are configured as
// they should be. These are not fatal and just means that no update is triggered: we don't
// want to take down the system server if an OEM or Google have pushed a bad update to
// an application.
boolean updaterAppManifestValid = validateUpdaterAppManifest();
boolean dataAppManifestValid = validateDataAppManifest();
if (!updaterAppManifestValid || !dataAppManifestValid) {
Slog.e(TAG, "No update triggered due to invalid application manifest entries."
+ " updaterApp=" + updaterAppManifestValid
+ ", dataApp=" + dataAppManifestValid);
// There's no point in doing any reliability triggers if the current packages are bad.
mIntentHelper.unscheduleReliabilityTrigger();
return;
}
if (!packageChanged) {
// This call was made because the device is doing a "reliability" check.
// 4 possible cases:
// 1) No check has previously triggered since restart. We want to trigger in this case.
// 2) A check has previously triggered and it is in progress. We want to trigger if
// the response is overdue.
// 3) A check has previously triggered and it failed. We want to trigger, but only if
// we're not in a persistent failure state.
// 4) A check has previously triggered and it succeeded.
// We don't want to trigger, and want to stop future triggers.
if (!mCheckTriggered) {
// Case 1.
Slog.d(TAG, "triggerUpdateIfNeeded: First reliability trigger.");
} else if (isCheckInProgress()) {
// Case 2.
if (!isCheckResponseOverdue()) {
// A check is in progress but hasn't been given time to succeed.
Slog.d(TAG,
"triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+ " Not triggering.");
// Don't do any work now but we do schedule a future reliability trigger.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
} else if (mCheckFailureCount > mFailedCheckRetryCount) {
// Case 3. If the system is in some kind of persistent failure state we don't want
// to keep checking, so just stop.
Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+ " exceeded. Stopping reliability triggers until next reboot or package"
+ " update.");
mIntentHelper.unscheduleReliabilityTrigger();
return;
} else if (mCheckFailureCount == 0) {
// Case 4.
Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+ " successful.");
mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
// Read the currently installed data / updater package versions.
PackageVersions currentInstalledVersions = lookupInstalledPackageVersions();
if (currentInstalledVersions == null) {
// This should not happen if the device is configured in a valid way.
Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
mIntentHelper.unscheduleReliabilityTrigger();
return;
}
// Establish the current state using package manager and stored state. Determine if we have
// already successfully checked the installed versions.
PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
if (packageStatus == null) {
// This can imply corrupt, uninitialized storage state (e.g. first check ever on a
// device) or after some kind of reset.
Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed.");
} else if (!packageStatus.mVersions.equals(currentInstalledVersions)) {
// The stored package version information differs from the installed version.
// Trigger the check in all cases.
Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions="
+ packageStatus.mVersions + ", do not match current package versions="
+ currentInstalledVersions + ". Triggering check.");
} else {
Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently"
+ " installed versions, currentInstalledVersions=" + currentInstalledVersions
+ ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus);
if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) {
// The last check succeeded and nothing has changed. Do nothing and disable
// reliability checks.
Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
// Generate a token to send to the updater app.
CheckToken checkToken =
mPackageStatusStorage.generateCheckToken(currentInstalledVersions);
if (checkToken == null) {
Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+ " Not sending check request.");
// Trigger again later: perhaps we'll have better luck.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
// Trigger the update check.
mIntentHelper.sendTriggerUpdateCheck(checkToken);
mCheckTriggered = true;
// Update the reliability check state in case the update fails.
setCheckInProgress();
// Schedule a reliability trigger in case the update check doesn't succeed and there is no
// response at all. It will be cancelled if the check is successful in recordCheckResult.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
}
/**
* Used to record the result of a check. Can be called even if active package tracking is
* disabled.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) {
Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success);
// If package tracking is disabled it means no record-keeping is required. However, we do
// want to clear out any stored state to make it clear that the current state is unknown and
// should tracking become enabled again (perhaps through an OTA) we'd need to perform an
// update check.
if (!mTrackingEnabled) {
// This means an updater has spontaneously modified time zone data without having been
// triggered. This can happen if the OEM is handling their own updates, but we don't
// need to do any tracking in this case.
if (checkToken == null) {
// This is the expected case if tracking is disabled but an OEM is handling time
// zone installs using their own mechanism.
Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been"
+ " provided. Resetting tracking state.");
} else {
// This is unexpected. If tracking is disabled then no check token should have been
// generated by the package tracker. An updater should never create its own token.
// This could be a bug in the updater.
Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken
+ " has been unexpectedly provided. Resetting tracking state.");
}
mPackageStatusStorage.resetCheckState();
return;
}
if (checkToken == null) {
/*
* If the checkToken is null it suggests an install / uninstall / acknowledgement has
* occurred without a prior trigger (or the client didn't return the token it was given
* for some reason, perhaps a bug).
*
* This shouldn't happen under normal circumstances:
*
* If package tracking is enabled, we assume it is the package tracker responsible for
* triggering updates and a token should have been produced and returned.
*
* If the OEM is handling time zone updates case package tracking should be disabled.
*
* This could happen in tests. The device should recover back to a known state by
* itself rather than be left in an invalid state.
*
* We treat this as putting the device into an unknown state and make sure that
* reliability triggering is enabled so we should recover.
*/
Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting"
+ " storage state.");
mPackageStatusStorage.resetCheckState();
// Schedule a reliability trigger and reset the failure count so we know that the
// next reliability trigger will do something.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount = 0;
} else {
// This is the expected case when tracking is enabled: a check was triggered and it has
// completed.
boolean recordedCheckCompleteSuccessfully =
mPackageStatusStorage.markChecked(checkToken, success);
if (recordedCheckCompleteSuccessfully) {
// If we have recorded the result (whatever it was) we know there is no check in
// progress.
setCheckComplete();
if (success) {
// Since the check was successful, no reliability trigger is required until
// there is a package change.
mIntentHelper.unscheduleReliabilityTrigger();
mCheckFailureCount = 0;
} else {
// Enable schedule a reliability trigger to check again in future.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
} else {
// The failure to record the check means an optimistic lock failure and suggests
// that another check was triggered after the token was generated.
Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+ " with success=" + success + ". Optimistic lock failure");
// Schedule a reliability trigger to potentially try again in future.
mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
}
}
/** Access to consecutive failure counts for use in tests. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected int getCheckFailureCountForTests() {
return mCheckFailureCount;
}
private void setCheckInProgress() {
mLastTriggerTimestamp = mElapsedRealtimeClock.millis();
}
private void setCheckComplete() {
mLastTriggerTimestamp = null;
}
private boolean isCheckInProgress() {
return mLastTriggerTimestamp != null;
}
private boolean isCheckResponseOverdue() {
if (mLastTriggerTimestamp == null) {
return false;
}
// Risk of overflow, but highly unlikely given the implementation and not problematic.
return mElapsedRealtimeClock.millis() > mLastTriggerTimestamp + mCheckTimeAllowedMillis;
}
private PackageVersions lookupInstalledPackageVersions() {
long updatePackageVersion;
long dataPackageVersion;
try {
updatePackageVersion =
mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName);
dataPackageVersion =
mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package"
+ " versions", e);
return null;
}
return new PackageVersions(updatePackageVersion, dataPackageVersion);
}
private boolean validateDataAppManifest() {
// We only want to talk to a provider that exposed by the known data app package
// so we look up the providers exposed by that app and check the well-known authority is
// there. This prevents the case where *even if* the data app doesn't expose the provider
// required, another app cannot expose one to replace it.
if (!mPackageManagerHelper.contentProviderRegistered(
TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) {
// Error! Found the package but it didn't expose the correct provider.
Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName
+ " does not expose the required provider with authority="
+ TimeZoneRulesDataContract.AUTHORITY);
return false;
}
return true;
}
private boolean validateUpdaterAppManifest() {
try {
// The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission.
// The updater app is expected to have a receiver for the intent we are going to trigger
// and require the TRIGGER_TIME_ZONE_RULES_CHECK.
if (!mPackageManagerHelper.usesPermission(
mUpdateAppPackageName,
RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) {
Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ " does not use permission="
+ RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
return false;
}
if (!mPackageManagerHelper.receiverRegistered(
RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName),
RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) {
return false;
}
return true;
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ " does not expose the required broadcast receiver.", e);
return false;
}
}
private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) {
if (value == null || value.trim().isEmpty()) {
throw logAndThrowRuntimeException(message, null);
}
}
private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) {
Slog.wtf(TAG, message, cause);
throw new RuntimeException(message, cause);
}
public void dump(PrintWriter fout) {
fout.println("PackageTrackerState: " + toString());
mPackageStatusStorage.dump(fout);
}
@Override
public String toString() {
return "PackageTracker{" +
"mTrackingEnabled=" + mTrackingEnabled +
", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
", mDataAppPackageName='" + mDataAppPackageName + '\'' +
", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis +
", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
", mCheckTriggered=" + mCheckTriggered +
", mCheckFailureCount=" + mCheckFailureCount +
'}';
}
}

View File

@@ -1,148 +0,0 @@
/*
* 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 com.android.server.timezone;
import com.android.internal.R;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.UserHandle;
import android.util.Slog;
import java.util.List;
/**
* A single class that implements multiple helper interfaces for use by {@link PackageTracker}.
*/
final class PackageTrackerHelperImpl implements ConfigHelper, PackageManagerHelper {
private static final String TAG = "PackageTrackerHelperImpl";
private final Context mContext;
private final PackageManager mPackageManager;
PackageTrackerHelperImpl(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
}
@Override
public boolean isTrackingEnabled() {
return mContext.getResources().getBoolean(R.bool.config_timeZoneRulesUpdateTrackingEnabled);
}
@Override
public String getUpdateAppPackageName() {
return mContext.getResources().getString(R.string.config_timeZoneRulesUpdaterPackage);
}
@Override
public String getDataAppPackageName() {
Resources resources = mContext.getResources();
return resources.getString(R.string.config_timeZoneRulesDataPackage);
}
@Override
public int getCheckTimeAllowedMillis() {
return mContext.getResources().getInteger(
R.integer.config_timeZoneRulesCheckTimeMillisAllowed);
}
@Override
public int getFailedCheckRetryCount() {
return mContext.getResources().getInteger(R.integer.config_timeZoneRulesCheckRetryCount);
}
@Override
public long getInstalledPackageVersion(String packageName)
throws PackageManager.NameNotFoundException {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
return packageInfo.getLongVersionCode();
}
@Override
public boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
return packageInfo.applicationInfo.isPrivilegedApp();
}
@Override
public boolean usesPermission(String packageName, String requiredPermissionName)
throws PackageManager.NameNotFoundException {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
| PackageManager.GET_PERMISSIONS;
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
if (packageInfo.requestedPermissions == null) {
return false;
}
for (String requestedPermission : packageInfo.requestedPermissions) {
if (requiredPermissionName.equals(requestedPermission)) {
return true;
}
}
return false;
}
@Override
public boolean contentProviderRegistered(String authority, String requiredPackageName) {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser(
authority, flags, UserHandle.SYSTEM.getIdentifier());
if (providerInfo == null) {
Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
+ authority);
return false;
}
boolean packageMatches =
requiredPackageName.equals(providerInfo.applicationInfo.packageName);
if (!packageMatches) {
Slog.i(TAG, "contentProviderRegistered: App with packageName=" + requiredPackageName
+ " does not expose the a content provider with authority=" + authority);
return false;
}
return true;
}
@Override
public boolean receiverRegistered(Intent intent, String requiredPermissionName)
throws PackageManager.NameNotFoundException {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser(
intent, flags, UserHandle.SYSTEM);
if (resolveInfo.size() != 1) {
Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
+ " intent=" + intent + ", found=" + resolveInfo);
return false;
}
ResolveInfo matched = resolveInfo.get(0);
boolean requiresPermission = requiredPermissionName.equals(matched.activityInfo.permission);
if (!requiresPermission) {
Slog.i(TAG, "receiverRegistered: Broadcast receiver registered for intent="
+ intent + " must require permission " + requiredPermissionName);
}
return requiresPermission;
}
}

View File

@@ -1,44 +0,0 @@
/*
* 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 com.android.server.timezone;
/**
* An easy-to-mock interface around intent sending / receiving for use by {@link PackageTracker};
* it is not possible to test various cases with the real one because of the need to simulate
* receiving and broadcasting intents.
*/
interface PackageTrackerIntentHelper {
void initialize(String updateAppPackageName, String dataAppPackageName,
PackageTracker packageTracker);
void sendTriggerUpdateCheck(CheckToken checkToken);
/**
* Schedule a "reliability trigger" after at least minimumDelayMillis, replacing any existing
* scheduled one. A reliability trigger ensures that the {@link PackageTracker} can pick up
* reliably if a previous update check did not complete for some reason. It can happen when
* the device is idle. The trigger is expected to call
* {@link PackageTracker#triggerUpdateIfNeeded(boolean)} with a {@code false} value.
*/
void scheduleReliabilityTrigger(long minimumDelayMillis);
/**
* Make sure there is no reliability trigger scheduled. No-op if there wasn't one.
*/
void unscheduleReliabilityTrigger();
}

View File

@@ -1,116 +0,0 @@
/*
* 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 com.android.server.timezone;
import com.android.server.EventLogTags;
import android.app.timezone.RulesUpdaterContract;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PatternMatcher;
import android.os.UserHandle;
import android.util.Slog;
/**
* The bona fide implementation of {@link PackageTrackerIntentHelper}.
*/
final class PackageTrackerIntentHelperImpl implements PackageTrackerIntentHelper {
private final static String TAG = "timezone.PackageTrackerIntentHelperImpl";
private final Context mContext;
private String mUpdaterAppPackageName;
PackageTrackerIntentHelperImpl(Context context) {
mContext = context;
}
@Override
public void initialize(String updaterAppPackageName, String dataAppPackageName,
PackageTracker packageTracker) {
mUpdaterAppPackageName = updaterAppPackageName;
// Register for events of interest.
// The intent filter that triggers when package update events happen that indicate there may
// be work to do.
IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addDataScheme("package");
packageIntentFilter.addDataSchemeSpecificPart(
updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
packageIntentFilter.addDataSchemeSpecificPart(
dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
// ACTION_PACKAGE_ADDED is fired when a package is upgraded or downgraded (in addition to
// ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED). A system/priv-app can never be
// removed entirely so we do not need to trigger on ACTION_PACKAGE_REMOVED or
// ACTION_PACKAGE_FULLY_REMOVED.
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
// ACTION_PACKAGE_CHANGED is used when a package is disabled / re-enabled. It is not
// strictly necessary to trigger on this but it won't hurt anything and may catch some cases
// where a package has changed while disabled.
// Note: ACTION_PACKAGE_CHANGED is not fired when updating a suspended app, but
// ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED are (and the app
// is left in an unsuspended state after this).
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
// We do not register for ACTION_PACKAGE_RESTARTED because it doesn't imply an update.
// We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
// not expected to need local data.
Receiver packageUpdateReceiver = new Receiver(packageTracker);
mContext.registerReceiverAsUser(
packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter,
null /* broadcastPermission */, null /* default handler */);
}
/** Sends an intent to trigger an update check. */
@Override
public void sendTriggerUpdateCheck(CheckToken checkToken) {
RulesUpdaterContract.sendBroadcast(
mContext, mUpdaterAppPackageName, checkToken.toByteArray());
EventLogTags.writeTimezoneTriggerCheck(checkToken.toString());
}
@Override
public synchronized void scheduleReliabilityTrigger(long minimumDelayMillis) {
TimeZoneUpdateIdler.schedule(mContext, minimumDelayMillis);
}
@Override
public synchronized void unscheduleReliabilityTrigger() {
TimeZoneUpdateIdler.unschedule(mContext);
}
private static class Receiver extends BroadcastReceiver {
private final PackageTracker mPackageTracker;
private Receiver(PackageTracker packageTracker) {
mPackageTracker = packageTracker;
}
@Override
public void onReceive(Context context, Intent intent) {
Slog.d(TAG, "Received intent: " + intent.toString());
mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
}
}
}

View File

@@ -1,63 +0,0 @@
/*
* 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 com.android.server.timezone;
/**
* Package version information about the time zone updater and time zone data application packages.
*/
final class PackageVersions {
final long mUpdateAppVersion;
final long mDataAppVersion;
PackageVersions(long updateAppVersion, long dataAppVersion) {
this.mUpdateAppVersion = updateAppVersion;
this.mDataAppVersion = dataAppVersion;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PackageVersions that = (PackageVersions) o;
if (mUpdateAppVersion != that.mUpdateAppVersion) {
return false;
}
return mDataAppVersion == that.mDataAppVersion;
}
@Override
public int hashCode() {
int result = Long.hashCode(mUpdateAppVersion);
result = 31 * result + Long.hashCode(mDataAppVersion);
return result;
}
@Override
public String toString() {
return "PackageVersions{" +
"mUpdateAppVersion=" + mUpdateAppVersion +
", mDataAppVersion=" + mDataAppVersion +
'}';
}
}

View File

@@ -1,29 +0,0 @@
/*
* 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 com.android.server.timezone;
import java.io.PrintWriter;
/**
* An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
*/
public interface PermissionHelper {
void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
boolean checkDumpPermission(String tag, PrintWriter printWriter);
}

View File

@@ -1,35 +0,0 @@
/*
* 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 com.android.server.timezone;
/**
* An easy-to-mock interface around intent sending / receiving for use by
* {@link RulesManagerService}; it is not possible to test various cases with the real one because
* of the need to simulate broadcasting intents.
*/
interface RulesManagerIntentHelper {
/**
* Send a broadcast informing listeners that a time zone operation is staged.
*/
void sendTimeZoneOperationStaged();
/**
* Send a broadcast informing listeners that a time zone operation is no longer staged.
*/
void sendTimeZoneOperationUnstaged();
}

View File

@@ -1,602 +0,0 @@
/*
* 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 com.android.server.timezone;
import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
import static android.app.timezone.RulesState.DISTRO_STATUS_NONE;
import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN;
import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL;
import static android.app.timezone.RulesState.STAGED_OPERATION_NONE;
import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL;
import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN;
import android.app.timezone.Callback;
import android.app.timezone.DistroFormatVersion;
import android.app.timezone.DistroRulesVersion;
import android.app.timezone.ICallback;
import android.app.timezone.IRulesManager;
import android.app.timezone.RulesManager;
import android.app.timezone.RulesState;
import android.content.Context;
import android.icu.util.TimeZone;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import com.android.i18n.timezone.TimeZoneDataFiles;
import com.android.i18n.timezone.TimeZoneFinder;
import com.android.i18n.timezone.TzDataSetVersion;
import com.android.i18n.timezone.ZoneInfoDb;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.timezone.distro.DistroException;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.StagedDistroOperation;
import com.android.timezone.distro.TimeZoneDistro;
import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
public final class RulesManagerService extends IRulesManager.Stub {
private static final String TAG = "timezone.RulesManagerService";
/** The distro format supported by this device. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
new DistroFormatVersion(
TzDataSetVersion.currentFormatMajorVersion(),
TzDataSetVersion.currentFormatMinorVersion());
public static class Lifecycle extends SystemService {
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
RulesManagerService service = RulesManagerService.create(getContext());
service.start();
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service);
// Publish the service instance locally so we can use it directly from within the system
// server from TimeZoneUpdateIdler.
publishLocalService(RulesManagerService.class, service);
}
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static final String REQUIRED_UPDATER_PERMISSION =
android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static final String REQUIRED_QUERY_PERMISSION =
android.Manifest.permission.QUERY_TIME_ZONE_RULES;
private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
private final PermissionHelper mPermissionHelper;
private final PackageTracker mPackageTracker;
private final Executor mExecutor;
private final RulesManagerIntentHelper mIntentHelper;
private final TimeZoneDistroInstaller mInstaller;
private static RulesManagerService create(Context context) {
RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
File baseVersionFile = new File(TimeZoneDataFiles.getTimeZoneModuleTzVersionFile());
File tzDataDir = new File(TimeZoneDataFiles.getDataTimeZoneRootDir());
return new RulesManagerService(
helper /* permissionHelper */,
helper /* executor */,
helper /* intentHelper */,
PackageTracker.create(context),
new TimeZoneDistroInstaller(TAG, baseVersionFile, tzDataDir));
}
// A constructor that can be used by tests to supply mocked / faked dependencies.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
RulesManagerService(PermissionHelper permissionHelper, Executor executor,
RulesManagerIntentHelper intentHelper, PackageTracker packageTracker,
TimeZoneDistroInstaller timeZoneDistroInstaller) {
mPermissionHelper = permissionHelper;
mExecutor = executor;
mIntentHelper = intentHelper;
mPackageTracker = packageTracker;
mInstaller = timeZoneDistroInstaller;
}
public void start() {
// Return value deliberately ignored: no action required on failure to start.
mPackageTracker.start();
}
@Override // Binder call
public RulesState getRulesState() {
mPermissionHelper.enforceCallerHasPermission(REQUIRED_QUERY_PERMISSION);
return getRulesStateInternal();
}
/** Like {@link #getRulesState()} without the permission check. */
private RulesState getRulesStateInternal() {
synchronized(this) {
TzDataSetVersion baseVersion;
try {
baseVersion = mInstaller.readBaseVersion();
} catch (IOException e) {
Slog.w(TAG, "Failed to read base rules version", e);
return null;
}
// Determine the installed distro state. This should be possible regardless of whether
// there's an operation in progress.
DistroVersion installedDistroVersion;
int distroStatus = DISTRO_STATUS_UNKNOWN;
DistroRulesVersion installedDistroRulesVersion = null;
try {
installedDistroVersion = mInstaller.getInstalledDistroVersion();
if (installedDistroVersion == null) {
distroStatus = DISTRO_STATUS_NONE;
installedDistroRulesVersion = null;
} else {
distroStatus = DISTRO_STATUS_INSTALLED;
installedDistroRulesVersion = new DistroRulesVersion(
installedDistroVersion.rulesVersion,
installedDistroVersion.revision);
}
} catch (DistroException | IOException e) {
Slog.w(TAG, "Failed to read installed distro.", e);
}
boolean operationInProgress = this.mOperationInProgress.get();
// Determine the staged operation status, if possible.
DistroRulesVersion stagedDistroRulesVersion = null;
int stagedOperationStatus = STAGED_OPERATION_UNKNOWN;
if (!operationInProgress) {
StagedDistroOperation stagedDistroOperation;
try {
stagedDistroOperation = mInstaller.getStagedDistroOperation();
if (stagedDistroOperation == null) {
stagedOperationStatus = STAGED_OPERATION_NONE;
} else if (stagedDistroOperation.isUninstall) {
stagedOperationStatus = STAGED_OPERATION_UNINSTALL;
} else {
// Must be an install.
stagedOperationStatus = STAGED_OPERATION_INSTALL;
DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
stagedDistroRulesVersion = new DistroRulesVersion(
stagedDistroVersion.rulesVersion,
stagedDistroVersion.revision);
}
} catch (DistroException | IOException e) {
Slog.w(TAG, "Failed to read staged distro.", e);
}
}
return new RulesState(baseVersion.getRulesVersion(), DISTRO_FORMAT_VERSION_SUPPORTED,
operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
distroStatus, installedDistroRulesVersion);
}
}
@Override
public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor,
byte[] checkTokenBytes, ICallback callback) {
boolean closeParcelFileDescriptorOnExit = true;
try {
mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
CheckToken checkToken = null;
if (checkTokenBytes != null) {
checkToken = createCheckTokenOrThrow(checkTokenBytes);
}
EventLogTags.writeTimezoneRequestInstall(toStringOrNull(checkToken));
synchronized (this) {
if (distroParcelFileDescriptor == null) {
throw new NullPointerException("distroParcelFileDescriptor == null");
}
if (callback == null) {
throw new NullPointerException("observer == null");
}
if (mOperationInProgress.get()) {
return RulesManager.ERROR_OPERATION_IN_PROGRESS;
}
mOperationInProgress.set(true);
// Execute the install asynchronously.
mExecutor.execute(
new InstallRunnable(distroParcelFileDescriptor, checkToken, callback));
// The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after
// it executes (and we do not have to).
closeParcelFileDescriptorOnExit = false;
return RulesManager.SUCCESS;
}
} finally {
// We should close() the local ParcelFileDescriptor we were passed if it hasn't been
// passed to another thread to handle.
if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) {
try {
distroParcelFileDescriptor.close();
} catch (IOException e) {
Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e);
}
}
}
}
private class InstallRunnable implements Runnable {
private final ParcelFileDescriptor mDistroParcelFileDescriptor;
private final CheckToken mCheckToken;
private final ICallback mCallback;
InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken,
ICallback callback) {
mDistroParcelFileDescriptor = distroParcelFileDescriptor;
mCheckToken = checkToken;
mCallback = callback;
}
@Override
public void run() {
EventLogTags.writeTimezoneInstallStarted(toStringOrNull(mCheckToken));
boolean success = false;
// Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
// when we are done.
try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) {
// The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close
// it at the end of the try-with-resources.
final boolean isFdOwner = false;
InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner);
TimeZoneDistro distro = new TimeZoneDistro(is);
int installerResult = mInstaller.stageInstallWithErrorCode(distro);
// Notify interested parties that something is staged.
sendInstallNotificationIntentIfRequired(installerResult);
int resultCode = mapInstallerResultToApiCode(installerResult);
EventLogTags.writeTimezoneInstallComplete(toStringOrNull(mCheckToken), resultCode);
sendFinishedStatus(mCallback, resultCode);
// All the installer failure modes are currently non-recoverable and won't be
// improved by trying again. Therefore success = true.
success = true;
} catch (Exception e) {
Slog.w(TAG, "Failed to install distro.", e);
EventLogTags.writeTimezoneInstallComplete(
toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE);
sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
} finally {
// Notify the package tracker that the operation is now complete.
mPackageTracker.recordCheckResult(mCheckToken, success);
mOperationInProgress.set(false);
}
}
private void sendInstallNotificationIntentIfRequired(int installerResult) {
if (installerResult == TimeZoneDistroInstaller.INSTALL_SUCCESS) {
mIntentHelper.sendTimeZoneOperationStaged();
}
}
private int mapInstallerResultToApiCode(int installerResult) {
switch (installerResult) {
case TimeZoneDistroInstaller.INSTALL_SUCCESS:
return Callback.SUCCESS;
case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
return Callback.ERROR_INSTALL_RULES_TOO_OLD;
case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
return Callback.ERROR_INSTALL_VALIDATION_ERROR;
default:
return Callback.ERROR_UNKNOWN_FAILURE;
}
}
}
@Override
public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
CheckToken checkToken = null;
if (checkTokenBytes != null) {
checkToken = createCheckTokenOrThrow(checkTokenBytes);
}
EventLogTags.writeTimezoneRequestUninstall(toStringOrNull(checkToken));
synchronized(this) {
if (callback == null) {
throw new NullPointerException("callback == null");
}
if (mOperationInProgress.get()) {
return RulesManager.ERROR_OPERATION_IN_PROGRESS;
}
mOperationInProgress.set(true);
// Execute the uninstall asynchronously.
mExecutor.execute(new UninstallRunnable(checkToken, callback));
return RulesManager.SUCCESS;
}
}
private class UninstallRunnable implements Runnable {
private final CheckToken mCheckToken;
private final ICallback mCallback;
UninstallRunnable(CheckToken checkToken, ICallback callback) {
mCheckToken = checkToken;
mCallback = callback;
}
@Override
public void run() {
EventLogTags.writeTimezoneUninstallStarted(toStringOrNull(mCheckToken));
boolean packageTrackerStatus = false;
try {
int uninstallResult = mInstaller.stageUninstall();
// Notify interested parties that something is staged.
sendUninstallNotificationIntentIfRequired(uninstallResult);
packageTrackerStatus = (uninstallResult == TimeZoneDistroInstaller.UNINSTALL_SUCCESS
|| uninstallResult == TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED);
// Right now we just have Callback.SUCCESS / Callback.ERROR_UNKNOWN_FAILURE for
// uninstall. All clients should be checking against SUCCESS. More granular failures
// may be added in future.
int callbackResultCode =
packageTrackerStatus ? Callback.SUCCESS : Callback.ERROR_UNKNOWN_FAILURE;
EventLogTags.writeTimezoneUninstallComplete(
toStringOrNull(mCheckToken), callbackResultCode);
sendFinishedStatus(mCallback, callbackResultCode);
} catch (Exception e) {
EventLogTags.writeTimezoneUninstallComplete(
toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE);
Slog.w(TAG, "Failed to uninstall distro.", e);
sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
} finally {
// Notify the package tracker that the operation is now complete.
mPackageTracker.recordCheckResult(mCheckToken, packageTrackerStatus);
mOperationInProgress.set(false);
}
}
private void sendUninstallNotificationIntentIfRequired(int uninstallResult) {
switch (uninstallResult) {
case TimeZoneDistroInstaller.UNINSTALL_SUCCESS:
mIntentHelper.sendTimeZoneOperationStaged();
break;
case TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED:
mIntentHelper.sendTimeZoneOperationUnstaged();
break;
case TimeZoneDistroInstaller.UNINSTALL_FAIL:
default:
// No-op - unknown or nothing to notify about.
}
}
}
private void sendFinishedStatus(ICallback callback, int resultCode) {
try {
callback.onFinished(resultCode);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify observer of result", e);
}
}
@Override
public void requestNothing(byte[] checkTokenBytes, boolean success) {
mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
CheckToken checkToken = null;
if (checkTokenBytes != null) {
checkToken = createCheckTokenOrThrow(checkTokenBytes);
}
EventLogTags.writeTimezoneRequestNothing(toStringOrNull(checkToken));
mPackageTracker.recordCheckResult(checkToken, success);
EventLogTags.writeTimezoneNothingComplete(toStringOrNull(checkToken));
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mPermissionHelper.checkDumpPermission(TAG, pw)) {
return;
}
RulesState rulesState = getRulesStateInternal();
if (args != null && args.length == 2) {
// Formatting options used for automated tests. The format is less free-form than
// the -format options, which are intended to be easier to parse.
if ("-format_state".equals(args[0]) && args[1] != null) {
for (char c : args[1].toCharArray()) {
switch (c) {
case 'p': {
// Report operation in progress
String value = "Unknown";
if (rulesState != null) {
value = Boolean.toString(rulesState.isOperationInProgress());
}
pw.println("Operation in progress: " + value);
break;
}
case 'b': {
// Report base rules version
String value = "Unknown";
if (rulesState != null) {
value = rulesState.getBaseRulesVersion();
}
pw.println("Base rules version: " + value);
break;
}
case 'c': {
// Report current installation state
String value = "Unknown";
if (rulesState != null) {
value = distroStatusToString(rulesState.getDistroStatus());
}
pw.println("Current install state: " + value);
break;
}
case 'i': {
// Report currently installed version
String value = "Unknown";
if (rulesState != null) {
DistroRulesVersion installedRulesVersion =
rulesState.getInstalledDistroRulesVersion();
if (installedRulesVersion == null) {
value = "<None>";
} else {
value = installedRulesVersion.toDumpString();
}
}
pw.println("Installed rules version: " + value);
break;
}
case 'o': {
// Report staged operation type
String value = "Unknown";
if (rulesState != null) {
int stagedOperationType = rulesState.getStagedOperationType();
value = stagedOperationToString(stagedOperationType);
}
pw.println("Staged operation: " + value);
break;
}
case 't': {
// Report staged version (i.e. the one that will be installed next boot
// if the staged operation is an install).
String value = "Unknown";
if (rulesState != null) {
DistroRulesVersion stagedDistroRulesVersion =
rulesState.getStagedDistroRulesVersion();
if (stagedDistroRulesVersion == null) {
value = "<None>";
} else {
value = stagedDistroRulesVersion.toDumpString();
}
}
pw.println("Staged rules version: " + value);
break;
}
case 'a': {
// Report the active rules version (i.e. the rules in use by the current
// process).
pw.println("Active rules version (ICU, ZoneInfoDb, TimeZoneFinder): "
+ TimeZone.getTZDataVersion() + ","
+ ZoneInfoDb.getInstance().getVersion() + ","
+ TimeZoneFinder.getInstance().getIanaVersion());
break;
}
default: {
pw.println("Unknown option: " + c);
}
}
}
return;
}
}
pw.println("RulesManagerService state: " + toString());
pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+ TimeZone.getTZDataVersion() + ","
+ ZoneInfoDb.getInstance().getVersion() + ","
+ TimeZoneFinder.getInstance().getIanaVersion());
pw.println("Distro state: " + rulesState.toString());
mPackageTracker.dump(pw);
}
/**
* Called when the device is considered idle.
*/
void notifyIdle() {
// No package has changed: we are just triggering because the device is idle and there
// *might* be work to do.
final boolean packageChanged = false;
mPackageTracker.triggerUpdateIfNeeded(packageChanged);
}
@Override
public String toString() {
return "RulesManagerService{" +
"mOperationInProgress=" + mOperationInProgress +
'}';
}
private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
CheckToken checkToken;
try {
checkToken = CheckToken.fromByteArray(checkTokenBytes);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to read token bytes "
+ Arrays.toString(checkTokenBytes), e);
}
return checkToken;
}
private static String distroStatusToString(int distroStatus) {
switch(distroStatus) {
case DISTRO_STATUS_NONE:
return "None";
case DISTRO_STATUS_INSTALLED:
return "Installed";
case DISTRO_STATUS_UNKNOWN:
default:
return "Unknown";
}
}
private static String stagedOperationToString(int stagedOperationType) {
switch(stagedOperationType) {
case STAGED_OPERATION_NONE:
return "None";
case STAGED_OPERATION_UNINSTALL:
return "Uninstall";
case STAGED_OPERATION_INSTALL:
return "Install";
case STAGED_OPERATION_UNKNOWN:
default:
return "Unknown";
}
}
private static String toStringOrNull(Object obj) {
return obj == null ? null : obj.toString();
}
}

View File

@@ -1,74 +0,0 @@
/*
* 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 com.android.server.timezone;
import com.android.internal.util.DumpUtils;
import android.app.timezone.RulesManager;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.UserHandle;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
/**
* A single class that implements multiple helper interfaces for use by {@link RulesManagerService}.
*/
final class RulesManagerServiceHelperImpl
implements PermissionHelper, Executor, RulesManagerIntentHelper {
private final Context mContext;
RulesManagerServiceHelperImpl(Context context) {
mContext = context;
}
@Override
public void enforceCallerHasPermission(String requiredPermission) {
mContext.enforceCallingPermission(requiredPermission, null /* message */);
}
@Override
public boolean checkDumpPermission(String tag, PrintWriter pw) {
return DumpUtils.checkDumpPermission(mContext, tag, pw);
}
@Override
public void execute(Runnable runnable) {
AsyncTask.execute(runnable);
}
@Override
public void sendTimeZoneOperationStaged() {
sendOperationIntent(true /* staged */);
}
@Override
public void sendTimeZoneOperationUnstaged() {
sendOperationIntent(false /* staged */);
}
private void sendOperationIntent(boolean staged) {
Intent intent = new Intent(RulesManager.ACTION_RULES_UPDATE_OPERATION);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.putExtra(RulesManager.EXTRA_OPERATION_STAGED, staged);
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
}

View File

@@ -1,100 +0,0 @@
/*
* 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 com.android.server.timezone;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.util.Slog;
import com.android.server.LocalServices;
/**
* A JobService used to trigger time zone rules update work when a device falls idle.
*/
public final class TimeZoneUpdateIdler extends JobService {
private static final String TAG = "timezone.TimeZoneUpdateIdler";
/** The static job ID used to handle on-idle work. */
// Must be unique within UID (system service)
private static final int TIME_ZONE_UPDATE_IDLE_JOB_ID = 27042305;
@Override
public boolean onStartJob(JobParameters params) {
RulesManagerService rulesManagerService =
LocalServices.getService(RulesManagerService.class);
Slog.d(TAG, "onStartJob() called");
// Note: notifyIdle() explicitly handles canceling / re-scheduling so no need to reschedule
// here.
rulesManagerService.notifyIdle();
// Everything is handled synchronously. We are done.
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
// Reschedule if stopped unless it was cancelled due to unschedule().
boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP;
Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
return reschedule;
}
/**
* Schedules the TimeZoneUpdateIdler job service to run once.
*
* @param context Context to use to get a job scheduler.
*/
public static void schedule(Context context, long minimumDelayMillis) {
// Request that the JobScheduler tell us when the device falls idle.
JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// The TimeZoneUpdateIdler will send an intent that will trigger the Receiver.
ComponentName idlerJobServiceName =
new ComponentName(context, TimeZoneUpdateIdler.class);
// We require the device is idle, but also that it is charging to be as non-invasive as
// we can.
JobInfo.Builder jobInfoBuilder =
new JobInfo.Builder(TIME_ZONE_UPDATE_IDLE_JOB_ID, idlerJobServiceName)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setMinimumLatency(minimumDelayMillis);
Slog.d(TAG, "schedule() called: minimumDelayMillis=" + minimumDelayMillis);
jobScheduler.schedule(jobInfoBuilder.build());
}
/**
* Unschedules the TimeZoneUpdateIdler job service.
*
* @param context Context to use to get a job scheduler.
*/
public static void unschedule(Context context) {
JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
Slog.d(TAG, "unschedule() called");
jobScheduler.cancel(TIME_ZONE_UPDATE_IDLE_JOB_ID);
}
}

View File

@@ -326,8 +326,6 @@ public final class SystemServer implements Dumpable {
"com.android.server.systemcaptions.SystemCaptionsManagerService";
private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
"com.android.server.texttospeech.TextToSpeechManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String IOT_SERVICE_CLASS =
"com.android.things.server.IoTSystemService";
private static final String SLICE_MANAGER_SERVICE_CLASS =
@@ -2183,19 +2181,6 @@ public final class SystemServer implements Dumpable {
}
t.traceEnd();
// timezone.RulesManagerService will prevent a device starting up if the chain of trust
// required for safe time zone updates might be broken. RuleManagerService cannot do
// this check when mOnlyCore == true, so we don't enable the service in this case.
// This service requires that JobSchedulerService is already started when it starts.
final boolean startRulesManagerService =
!mOnlyCore && context.getResources().getBoolean(
R.bool.config_enableUpdateableTimeZoneRules);
if (startRulesManagerService) {
t.traceBegin("StartTimeZoneRulesManagerService");
mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
t.traceEnd();
}
if (!isWatch && !disableNetworkTime) {
t.traceBegin("StartNetworkTimeUpdateService");
try {

View File

@@ -1,75 +0,0 @@
/*
* 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 com.android.server.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import java.io.IOException;
@SmallTest
public class CheckTokenTest {
@Test
public void toByteArray() throws Exception {
PackageVersions packageVersions =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
CheckToken originalToken = new CheckToken(1 /* optimisticLockId */, packageVersions);
assertEquals(originalToken, CheckToken.fromByteArray(originalToken.toByteArray()));
}
@Test
public void fromByteArray() {
PackageVersions packageVersions =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
CheckToken token = new CheckToken(1, packageVersions);
byte[] validTokenBytes = token.toByteArray();
byte[] shortTokenBytes = new byte[validTokenBytes.length - 1];
System.arraycopy(validTokenBytes, 0, shortTokenBytes, 0, shortTokenBytes.length);
try {
CheckToken.fromByteArray(shortTokenBytes);
fail();
} catch (IOException expected) {}
}
@Test
public void equals() {
PackageVersions packageVersions1 =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
PackageVersions packageVersions2 =
new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
assertFalse(packageVersions1.equals(packageVersions2));
CheckToken baseline = new CheckToken(1, packageVersions1);
assertEquals(baseline, baseline);
CheckToken deepEqual = new CheckToken(1, packageVersions1);
assertEquals(baseline, deepEqual);
CheckToken differentOptimisticLockId = new CheckToken(2, packageVersions1);
assertFalse(differentOptimisticLockId.equals(baseline));
CheckToken differentPackageVersions = new CheckToken(1, packageVersions2);
assertFalse(differentPackageVersions.equals(baseline));
}
}

View File

@@ -1,271 +0,0 @@
/*
* 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 com.android.server.timezone;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@SmallTest
public class PackageStatusStorageTest {
private static final PackageVersions VALID_PACKAGE_VERSIONS = new PackageVersions(1, 2);
private PackageStatusStorage mPackageStatusStorage;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getContext();
File dataDir = context.getFilesDir();
// Using the instrumentation context means the database is created in a test app-specific
// directory.
mPackageStatusStorage = new PackageStatusStorage(dataDir);
mPackageStatusStorage.initialize();
}
@After
public void tearDown() throws Exception {
mPackageStatusStorage.deleteFileForTests();
}
@Test
public void initialize_fail() {
File readOnlyDir = new File("/system/does/not/exist");
PackageStatusStorage packageStatusStorage = new PackageStatusStorage(readOnlyDir);
try {
packageStatusStorage.initialize();
fail();
} catch (IOException expected) {}
}
@Test
public void getPackageStatus_initialState() {
assertNull(mPackageStatusStorage.getPackageStatus());
}
@Test
public void resetCheckState() {
// Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus());
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
// There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
// Now clear the state.
mPackageStatusStorage.resetCheckState();
// After reset, there should be no package state again.
assertNull(mPackageStatusStorage.getPackageStatus());
CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
// Token after a reset should still be distinct.
assertFalse(token1.equals(token2));
// Now clear the state again.
mPackageStatusStorage.resetCheckState();
// After reset, there should be no package state again.
assertNull(mPackageStatusStorage.getPackageStatus());
CheckToken token3 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
// A CheckToken generated after a reset should still be distinct.
assertFalse(token2.equals(token3));
}
@Test
public void generateCheckToken_missingFileBehavior() {
// Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus());
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertNotNull(token1);
// There should now be state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the data by removing the file.
mPackageStatusStorage.deleteFileForTests();
// Check that generateCheckToken recovers.
assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
}
@Test
public void getPackageStatus_missingFileBehavior() {
// Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus());
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertNotNull(token1);
// There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the data by removing the file.
mPackageStatusStorage.deleteFileForTests();
assertNull(mPackageStatusStorage.getPackageStatus());
}
@Test
public void markChecked_missingFileBehavior() {
// Assert initial state.
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertNotNull(token1);
// There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
// Corrupt the data by removing the file.
mPackageStatusStorage.deleteFileForTests();
// The missing file should mean token1 is now considered invalid, so we should get a false.
assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
// The storage should have recovered and we should be able to carry on like before.
CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertTrue(mPackageStatusStorage.markChecked(token2, true /* succeeded */));
}
@Test
public void checkToken_tokenIsUnique() {
PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
PackageStatus expectedPackageStatus =
new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions);
CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
assertEquals(packageVersions, token1.mPackageVersions);
PackageStatus actualPackageStatus1 = mPackageStatusStorage.getPackageStatus();
assertEquals(expectedPackageStatus, actualPackageStatus1);
CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
assertEquals(packageVersions, token1.mPackageVersions);
assertFalse(token1.mOptimisticLockId == token2.mOptimisticLockId);
assertFalse(token1.equals(token2));
}
@Test
public void markChecked_checkSucceeded() {
PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
boolean writeOk = mPackageStatusStorage.markChecked(token, true /* succeeded */);
assertTrue(writeOk);
PackageStatus expectedPackageStatus =
new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
}
@Test
public void markChecked_checkFailed() {
PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
boolean writeOk = mPackageStatusStorage.markChecked(token, false /* succeeded */);
assertTrue(writeOk);
PackageStatus expectedPackageStatus =
new PackageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
}
@Test
public void markChecked_optimisticLocking_multipleToken() {
PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
PackageStatus packageStatusBeforeChecked = mPackageStatusStorage.getPackageStatus();
boolean writeOk1 = mPackageStatusStorage.markChecked(token1, true /* succeeded */);
// Generation of token2 should mean that token1 is no longer valid.
assertFalse(writeOk1);
assertEquals(packageStatusBeforeChecked, mPackageStatusStorage.getPackageStatus());
boolean writeOk2 = mPackageStatusStorage.markChecked(token2, true /* succeeded */);
// token2 should still be valid, and the attempt with token1 should have had no effect.
assertTrue(writeOk2);
PackageStatus expectedPackageStatus =
new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
}
@Test
public void markChecked_optimisticLocking_repeatedTokenUse() {
PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
boolean writeOk1 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
assertTrue(writeOk1);
PackageStatus expectedPackageStatus =
new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
// token cannot be reused.
boolean writeOk2 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
assertFalse(writeOk2);
assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
}
@Test
public void dump() {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
// Dump initial state.
mPackageStatusStorage.dump(printWriter);
// No crash and it does something.
assertFalse(stringWriter.toString().isEmpty());
// Reset
stringWriter.getBuffer().setLength(0);
assertTrue(stringWriter.toString().isEmpty());
// Store something.
mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
mPackageStatusStorage.dump(printWriter);
assertFalse(stringWriter.toString().isEmpty());
}
}

View File

@@ -1,53 +0,0 @@
/*
* 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 com.android.server.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@SmallTest
public class PackageStatusTest {
@Test
public void equals() {
PackageVersions packageVersions1 =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
PackageVersions packageVersions2 =
new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
assertFalse(packageVersions1.equals(packageVersions2));
PackageStatus baseline =
new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
assertEquals(baseline, baseline);
PackageStatus deepEqual =
new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
assertEquals(baseline, deepEqual);
PackageStatus differentStatus =
new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
assertFalse(differentStatus.equals(baseline));
PackageStatus differentPackageVersions =
new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
assertFalse(differentPackageVersions.equals(baseline));
}
}

View File

@@ -1,47 +0,0 @@
/*
* 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 com.android.server.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@SmallTest
public class PackageVersionsTest {
@Test
public void equals() {
PackageVersions baseline =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
assertEquals(baseline, baseline);
PackageVersions deepEqual =
new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
assertEquals(baseline, deepEqual);
PackageVersions differentUpdateAppVersion =
new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
assertFalse(baseline.equals(differentUpdateAppVersion));
PackageVersions differentDataAppVersion =
new PackageVersions(1 /* updateAppVersion */, 2 /* dataAppVersion */);
assertFalse(baseline.equals(differentDataAppVersion));
}
}