Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.timezone; |
| 18 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 19 | import com.android.internal.annotations.GuardedBy; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 20 | import com.android.internal.util.FastXmlSerializer; |
| 21 | |
| 22 | import org.xmlpull.v1.XmlPullParser; |
| 23 | import org.xmlpull.v1.XmlPullParserException; |
| 24 | import org.xmlpull.v1.XmlSerializer; |
| 25 | |
| 26 | import android.util.AtomicFile; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 27 | import android.util.Slog; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 28 | import android.util.Xml; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 29 | |
| 30 | import java.io.File; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 31 | import java.io.FileInputStream; |
| 32 | import java.io.FileOutputStream; |
| 33 | import java.io.IOException; |
| 34 | import java.nio.charset.StandardCharsets; |
| 35 | import java.text.ParseException; |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 36 | import java.io.PrintWriter; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 37 | |
| 38 | import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE; |
| 39 | import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS; |
| 40 | import static com.android.server.timezone.PackageStatus.CHECK_STARTED; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 41 | import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; |
| 42 | import static org.xmlpull.v1.XmlPullParser.START_TAG; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 43 | |
| 44 | /** |
| 45 | * Storage logic for accessing/mutating the Android system's persistent state related to time zone |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 46 | * update checking. There is expected to be a single instance. All non-private methods are thread |
| 47 | * safe. |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 48 | */ |
| 49 | final class PackageStatusStorage { |
| 50 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 51 | private static final String LOG_TAG = "timezone.PackageStatusStorage"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 52 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 53 | private static final String TAG_PACKAGE_STATUS = "PackageStatus"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 54 | |
| 55 | /** |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 56 | * Attribute that stores a monotonically increasing lock ID, used to detect concurrent update |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 57 | * issues without on-line locks. Incremented on every write. |
| 58 | */ |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 59 | private static final String ATTRIBUTE_OPTIMISTIC_LOCK_ID = "optimisticLockId"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 60 | |
| 61 | /** |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 62 | * Attribute that stores the current "check status" of the time zone update application |
| 63 | * packages. |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 64 | */ |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 65 | private static final String ATTRIBUTE_CHECK_STATUS = "checkStatus"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 66 | |
| 67 | /** |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 68 | * Attribute that stores the version of the time zone rules update application being checked |
| 69 | * / last checked. |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 70 | */ |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 71 | private static final String ATTRIBUTE_UPDATE_APP_VERSION = "updateAppPackageVersion"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 72 | |
| 73 | /** |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 74 | * Attribute that stores the version of the time zone rules data application being checked |
| 75 | * / last checked. |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 76 | */ |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 77 | private static final String ATTRIBUTE_DATA_APP_VERSION = "dataAppPackageVersion"; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 78 | |
Dianne Hackborn | 3accca0 | 2013-09-20 09:32:11 -0700 | [diff] [blame] | 79 | private static final long UNKNOWN_PACKAGE_VERSION = -1; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 80 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 81 | private final AtomicFile mPackageStatusFile; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 82 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 83 | PackageStatusStorage(File storageDir) { |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 84 | mPackageStatusFile = new AtomicFile(new File(storageDir, "package-status.xml")); |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Initialize any storage, as needed. |
| 89 | * |
| 90 | * @throws IOException if the storage could not be initialized |
| 91 | */ |
| 92 | void initialize() throws IOException { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 93 | if (!mPackageStatusFile.getBaseFile().exists()) { |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 94 | insertInitialPackageStatus(); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 95 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 96 | } |
| 97 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 98 | void deleteFileForTests() { |
| 99 | synchronized(this) { |
| 100 | mPackageStatusFile.delete(); |
| 101 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Obtain the current check status of the application packages. Returns {@code null} the first |
| 106 | * time it is called, or after {@link #resetCheckState()}. |
| 107 | */ |
| 108 | PackageStatus getPackageStatus() { |
| 109 | synchronized (this) { |
| 110 | try { |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 111 | return getPackageStatusLocked(); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 112 | } catch (ParseException e) { |
| 113 | // This means that data exists in the file but it was bad. |
| 114 | Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 115 | |
| 116 | // Reset the storage so it is in a good state again. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 117 | recoverFromBadData(e); |
| 118 | try { |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 119 | return getPackageStatusLocked(); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 120 | } catch (ParseException e2) { |
| 121 | throw new IllegalStateException("Recovery from bad file failed", e2); |
| 122 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 127 | @GuardedBy("this") |
| 128 | private PackageStatus getPackageStatusLocked() throws ParseException { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 129 | try (FileInputStream fis = mPackageStatusFile.openRead()) { |
| 130 | XmlPullParser parser = parseToPackageStatusTag(fis); |
| 131 | Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS); |
| 132 | if (checkStatus == null) { |
| 133 | return null; |
| 134 | } |
| 135 | int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION); |
| 136 | int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION); |
| 137 | return new PackageStatus(checkStatus, |
| 138 | new PackageVersions(updateAppVersion, dataAppVersion)); |
| 139 | } catch (IOException e) { |
| 140 | ParseException e2 = new ParseException("Error reading package status", 0); |
| 141 | e2.initCause(e); |
| 142 | throw e2; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 143 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 144 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 145 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 146 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 147 | private int recoverFromBadData(Exception cause) { |
| 148 | mPackageStatusFile.delete(); |
| 149 | try { |
| 150 | return insertInitialPackageStatus(); |
| 151 | } catch (IOException e) { |
| 152 | IllegalStateException fatal = new IllegalStateException(e); |
| 153 | fatal.addSuppressed(cause); |
| 154 | throw fatal; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 155 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 156 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 157 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 158 | /** Insert the initial data, returning the optimistic lock ID */ |
| 159 | private int insertInitialPackageStatus() throws IOException { |
| 160 | // Doesn't matter what it is, but we avoid the obvious starting value each time the data |
| 161 | // is reset to ensure that old tokens are unlikely to work. |
| 162 | final int initialOptimisticLockId = (int) System.currentTimeMillis(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 163 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 164 | writePackageStatusLocked(null /* status */, initialOptimisticLockId, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 165 | null /* packageVersions */); |
| 166 | return initialOptimisticLockId; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Generate a new {@link CheckToken} that can be passed to the time zone rules update |
| 171 | * application. |
| 172 | */ |
| 173 | CheckToken generateCheckToken(PackageVersions currentInstalledVersions) { |
| 174 | if (currentInstalledVersions == null) { |
| 175 | throw new NullPointerException("currentInstalledVersions == null"); |
| 176 | } |
| 177 | |
| 178 | synchronized (this) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 179 | int optimisticLockId; |
| 180 | try { |
| 181 | optimisticLockId = getCurrentOptimisticLockId(); |
| 182 | } catch (ParseException e) { |
| 183 | Slog.w(LOG_TAG, "Unable to find optimistic lock ID from package status"); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 184 | |
| 185 | // Recover. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 186 | optimisticLockId = recoverFromBadData(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | int newOptimisticLockId = optimisticLockId + 1; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 190 | try { |
| 191 | boolean statusUpdated = writePackageStatusWithOptimisticLockCheck( |
| 192 | optimisticLockId, newOptimisticLockId, CHECK_STARTED, |
| 193 | currentInstalledVersions); |
| 194 | if (!statusUpdated) { |
| 195 | throw new IllegalStateException("Unable to update status to CHECK_STARTED." |
| 196 | + " synchronization failure?"); |
| 197 | } |
| 198 | return new CheckToken(newOptimisticLockId, currentInstalledVersions); |
| 199 | } catch (IOException e) { |
| 200 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 201 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 202 | } |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Reset the current device state to "unknown". |
| 207 | */ |
| 208 | void resetCheckState() { |
| 209 | synchronized(this) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 210 | int optimisticLockId; |
| 211 | try { |
| 212 | optimisticLockId = getCurrentOptimisticLockId(); |
| 213 | } catch (ParseException e) { |
| 214 | Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package" |
| 215 | + " status"); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 216 | // Attempt to recover the storage state. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 217 | optimisticLockId = recoverFromBadData(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | int newOptimisticLockId = optimisticLockId + 1; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 221 | try { |
| 222 | if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId, |
| 223 | newOptimisticLockId, null /* status */, null /* packageVersions */)) { |
| 224 | throw new IllegalStateException("resetCheckState: Unable to reset package" |
| 225 | + " status, newOptimisticLockId=" + newOptimisticLockId); |
| 226 | } |
| 227 | } catch (IOException e) { |
| 228 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Update the current device state if possible. Returns true if the update was successful. |
| 235 | * {@code false} indicates the storage has been changed since the {@link CheckToken} was |
| 236 | * generated and the update was discarded. |
| 237 | */ |
| 238 | boolean markChecked(CheckToken checkToken, boolean succeeded) { |
| 239 | synchronized (this) { |
| 240 | int optimisticLockId = checkToken.mOptimisticLockId; |
| 241 | int newOptimisticLockId = optimisticLockId + 1; |
| 242 | int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 243 | try { |
| 244 | return writePackageStatusWithOptimisticLockCheck(optimisticLockId, |
| 245 | newOptimisticLockId, status, checkToken.mPackageVersions); |
| 246 | } catch (IOException e) { |
| 247 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 248 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 249 | } |
| 250 | } |
| 251 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 252 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 253 | private int getCurrentOptimisticLockId() throws ParseException { |
| 254 | try (FileInputStream fis = mPackageStatusFile.openRead()) { |
| 255 | XmlPullParser parser = parseToPackageStatusTag(fis); |
| 256 | return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID); |
| 257 | } catch (IOException e) { |
| 258 | ParseException e2 = new ParseException("Unable to read file", 0); |
| 259 | e2.initCause(e); |
| 260 | throw e2; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | /** Returns a parser or throws ParseException, never returns null. */ |
| 265 | private static XmlPullParser parseToPackageStatusTag(FileInputStream fis) |
| 266 | throws ParseException { |
| 267 | try { |
| 268 | XmlPullParser parser = Xml.newPullParser(); |
| 269 | parser.setInput(fis, StandardCharsets.UTF_8.name()); |
| 270 | int type; |
| 271 | while ((type = parser.next()) != END_DOCUMENT) { |
| 272 | final String tag = parser.getName(); |
| 273 | if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) { |
| 274 | return parser; |
| 275 | } |
| 276 | } |
| 277 | throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0); |
| 278 | } catch (XmlPullParserException e) { |
| 279 | throw new IllegalStateException("Unable to configure parser", e); |
| 280 | } catch (IOException e) { |
| 281 | ParseException e2 = new ParseException("Error reading XML", 0); |
| 282 | e.initCause(e); |
| 283 | throw e2; |
| 284 | } |
| 285 | } |
| 286 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 287 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 288 | private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId, |
| 289 | int newOptimisticLockId, Integer status, PackageVersions packageVersions) |
| 290 | throws IOException { |
| 291 | |
| 292 | int currentOptimisticLockId; |
| 293 | try { |
| 294 | currentOptimisticLockId = getCurrentOptimisticLockId(); |
| 295 | if (currentOptimisticLockId != optimisticLockId) { |
| 296 | return false; |
| 297 | } |
| 298 | } catch (ParseException e) { |
| 299 | recoverFromBadData(e); |
| 300 | return false; |
| 301 | } |
| 302 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 303 | writePackageStatusLocked(status, newOptimisticLockId, packageVersions); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 304 | return true; |
| 305 | } |
| 306 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 307 | @GuardedBy("this") |
| 308 | private void writePackageStatusLocked(Integer status, int optimisticLockId, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 309 | PackageVersions packageVersions) throws IOException { |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 310 | if ((status == null) != (packageVersions == null)) { |
| 311 | throw new IllegalArgumentException( |
| 312 | "Provide both status and packageVersions, or neither."); |
| 313 | } |
| 314 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 315 | FileOutputStream fos = null; |
| 316 | try { |
| 317 | fos = mPackageStatusFile.startWrite(); |
| 318 | XmlSerializer serializer = new FastXmlSerializer(); |
| 319 | serializer.setOutput(fos, StandardCharsets.UTF_8.name()); |
| 320 | serializer.startDocument(null /* encoding */, true /* standalone */); |
| 321 | final String namespace = null; |
| 322 | serializer.startTag(namespace, TAG_PACKAGE_STATUS); |
| 323 | String statusAttributeValue = status == null ? "" : Integer.toString(status); |
| 324 | serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue); |
| 325 | serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID, |
| 326 | Integer.toString(optimisticLockId)); |
Dianne Hackborn | 3accca0 | 2013-09-20 09:32:11 -0700 | [diff] [blame] | 327 | long updateAppVersion = status == null |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 328 | ? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion; |
| 329 | serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION, |
Dianne Hackborn | 3accca0 | 2013-09-20 09:32:11 -0700 | [diff] [blame] | 330 | Long.toString(updateAppVersion)); |
| 331 | long dataAppVersion = status == null |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 332 | ? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion; |
| 333 | serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION, |
Dianne Hackborn | 3accca0 | 2013-09-20 09:32:11 -0700 | [diff] [blame] | 334 | Long.toString(dataAppVersion)); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 335 | serializer.endTag(namespace, TAG_PACKAGE_STATUS); |
| 336 | serializer.endDocument(); |
| 337 | serializer.flush(); |
| 338 | mPackageStatusFile.finishWrite(fos); |
| 339 | } catch (IOException e) { |
| 340 | if (fos != null) { |
| 341 | mPackageStatusFile.failWrite(fos); |
| 342 | } |
| 343 | throw e; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 344 | } |
| 345 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 346 | } |
| 347 | |
| 348 | /** Only used during tests to force a known table state. */ |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 349 | public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) |
| 350 | throws IOException { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 351 | synchronized (this) { |
| 352 | try { |
Neil Fuller | 3582259 | 2017-12-11 14:39:03 +0000 | [diff] [blame] | 353 | final int initialOptimisticLockId = (int) System.currentTimeMillis(); |
| 354 | writePackageStatusLocked(checkStatus, initialOptimisticLockId, packageVersions); |
| 355 | } catch (IOException e) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 356 | throw new IllegalStateException(e); |
| 357 | } |
| 358 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 359 | } |
| 360 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 361 | private static Integer getNullableIntAttribute(XmlPullParser parser, String attributeName) |
| 362 | throws ParseException { |
| 363 | String attributeValue = parser.getAttributeValue(null, attributeName); |
| 364 | try { |
| 365 | if (attributeValue == null) { |
| 366 | throw new ParseException("Attribute " + attributeName + " missing", 0); |
| 367 | } else if (attributeValue.isEmpty()) { |
| 368 | return null; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 369 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 370 | return Integer.parseInt(attributeValue); |
| 371 | } catch (NumberFormatException e) { |
| 372 | throw new ParseException( |
| 373 | "Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 374 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 375 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 376 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 377 | private static int getIntAttribute(XmlPullParser parser, String attributeName) |
| 378 | throws ParseException { |
| 379 | Integer value = getNullableIntAttribute(parser, attributeName); |
| 380 | if (value == null) { |
| 381 | throw new ParseException("Missing attribute " + attributeName, 0); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 382 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 383 | return value; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 384 | } |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 385 | |
| 386 | public void dump(PrintWriter printWriter) { |
| 387 | printWriter.println("Package status: " + getPackageStatus()); |
| 388 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 389 | } |