Merge "Remove TZUvA feature."
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user