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 | |
| 79 | private static final int UNKNOWN_PACKAGE_VERSION = -1; |
| 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 | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 85 | if (!mPackageStatusFile.getBaseFile().exists()) { |
| 86 | try { |
| 87 | insertInitialPackageStatus(); |
| 88 | } catch (IOException e) { |
| 89 | throw new IllegalStateException(e); |
| 90 | } |
| 91 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 92 | } |
| 93 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 94 | void deleteFileForTests() { |
| 95 | synchronized(this) { |
| 96 | mPackageStatusFile.delete(); |
| 97 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Obtain the current check status of the application packages. Returns {@code null} the first |
| 102 | * time it is called, or after {@link #resetCheckState()}. |
| 103 | */ |
| 104 | PackageStatus getPackageStatus() { |
| 105 | synchronized (this) { |
| 106 | try { |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 107 | return getPackageStatusLocked(); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 108 | } catch (ParseException e) { |
| 109 | // This means that data exists in the file but it was bad. |
| 110 | Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 111 | |
| 112 | // Reset the storage so it is in a good state again. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 113 | recoverFromBadData(e); |
| 114 | try { |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 115 | return getPackageStatusLocked(); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 116 | } catch (ParseException e2) { |
| 117 | throw new IllegalStateException("Recovery from bad file failed", e2); |
| 118 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 123 | @GuardedBy("this") |
| 124 | private PackageStatus getPackageStatusLocked() throws ParseException { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 125 | try (FileInputStream fis = mPackageStatusFile.openRead()) { |
| 126 | XmlPullParser parser = parseToPackageStatusTag(fis); |
| 127 | Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS); |
| 128 | if (checkStatus == null) { |
| 129 | return null; |
| 130 | } |
| 131 | int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION); |
| 132 | int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION); |
| 133 | return new PackageStatus(checkStatus, |
| 134 | new PackageVersions(updateAppVersion, dataAppVersion)); |
| 135 | } catch (IOException e) { |
| 136 | ParseException e2 = new ParseException("Error reading package status", 0); |
| 137 | e2.initCause(e); |
| 138 | throw e2; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 139 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 140 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 141 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 142 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 143 | private int recoverFromBadData(Exception cause) { |
| 144 | mPackageStatusFile.delete(); |
| 145 | try { |
| 146 | return insertInitialPackageStatus(); |
| 147 | } catch (IOException e) { |
| 148 | IllegalStateException fatal = new IllegalStateException(e); |
| 149 | fatal.addSuppressed(cause); |
| 150 | throw fatal; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 151 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 152 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 153 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 154 | /** Insert the initial data, returning the optimistic lock ID */ |
| 155 | private int insertInitialPackageStatus() throws IOException { |
| 156 | // Doesn't matter what it is, but we avoid the obvious starting value each time the data |
| 157 | // is reset to ensure that old tokens are unlikely to work. |
| 158 | final int initialOptimisticLockId = (int) System.currentTimeMillis(); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 159 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 160 | writePackageStatusLocked(null /* status */, initialOptimisticLockId, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 161 | null /* packageVersions */); |
| 162 | return initialOptimisticLockId; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Generate a new {@link CheckToken} that can be passed to the time zone rules update |
| 167 | * application. |
| 168 | */ |
| 169 | CheckToken generateCheckToken(PackageVersions currentInstalledVersions) { |
| 170 | if (currentInstalledVersions == null) { |
| 171 | throw new NullPointerException("currentInstalledVersions == null"); |
| 172 | } |
| 173 | |
| 174 | synchronized (this) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 175 | int optimisticLockId; |
| 176 | try { |
| 177 | optimisticLockId = getCurrentOptimisticLockId(); |
| 178 | } catch (ParseException e) { |
| 179 | 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] | 180 | |
| 181 | // Recover. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 182 | optimisticLockId = recoverFromBadData(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 183 | } |
| 184 | |
| 185 | int newOptimisticLockId = optimisticLockId + 1; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 186 | try { |
| 187 | boolean statusUpdated = writePackageStatusWithOptimisticLockCheck( |
| 188 | optimisticLockId, newOptimisticLockId, CHECK_STARTED, |
| 189 | currentInstalledVersions); |
| 190 | if (!statusUpdated) { |
| 191 | throw new IllegalStateException("Unable to update status to CHECK_STARTED." |
| 192 | + " synchronization failure?"); |
| 193 | } |
| 194 | return new CheckToken(newOptimisticLockId, currentInstalledVersions); |
| 195 | } catch (IOException e) { |
| 196 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 197 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 198 | } |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Reset the current device state to "unknown". |
| 203 | */ |
| 204 | void resetCheckState() { |
| 205 | synchronized(this) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 206 | int optimisticLockId; |
| 207 | try { |
| 208 | optimisticLockId = getCurrentOptimisticLockId(); |
| 209 | } catch (ParseException e) { |
| 210 | Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package" |
| 211 | + " status"); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 212 | // Attempt to recover the storage state. |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 213 | optimisticLockId = recoverFromBadData(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | int newOptimisticLockId = optimisticLockId + 1; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 217 | try { |
| 218 | if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId, |
| 219 | newOptimisticLockId, null /* status */, null /* packageVersions */)) { |
| 220 | throw new IllegalStateException("resetCheckState: Unable to reset package" |
| 221 | + " status, newOptimisticLockId=" + newOptimisticLockId); |
| 222 | } |
| 223 | } catch (IOException e) { |
| 224 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /** |
| 230 | * Update the current device state if possible. Returns true if the update was successful. |
| 231 | * {@code false} indicates the storage has been changed since the {@link CheckToken} was |
| 232 | * generated and the update was discarded. |
| 233 | */ |
| 234 | boolean markChecked(CheckToken checkToken, boolean succeeded) { |
| 235 | synchronized (this) { |
| 236 | int optimisticLockId = checkToken.mOptimisticLockId; |
| 237 | int newOptimisticLockId = optimisticLockId + 1; |
| 238 | int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE; |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 239 | try { |
| 240 | return writePackageStatusWithOptimisticLockCheck(optimisticLockId, |
| 241 | newOptimisticLockId, status, checkToken.mPackageVersions); |
| 242 | } catch (IOException e) { |
| 243 | throw new IllegalStateException(e); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 244 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 245 | } |
| 246 | } |
| 247 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 248 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 249 | private int getCurrentOptimisticLockId() throws ParseException { |
| 250 | try (FileInputStream fis = mPackageStatusFile.openRead()) { |
| 251 | XmlPullParser parser = parseToPackageStatusTag(fis); |
| 252 | return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID); |
| 253 | } catch (IOException e) { |
| 254 | ParseException e2 = new ParseException("Unable to read file", 0); |
| 255 | e2.initCause(e); |
| 256 | throw e2; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /** Returns a parser or throws ParseException, never returns null. */ |
| 261 | private static XmlPullParser parseToPackageStatusTag(FileInputStream fis) |
| 262 | throws ParseException { |
| 263 | try { |
| 264 | XmlPullParser parser = Xml.newPullParser(); |
| 265 | parser.setInput(fis, StandardCharsets.UTF_8.name()); |
| 266 | int type; |
| 267 | while ((type = parser.next()) != END_DOCUMENT) { |
| 268 | final String tag = parser.getName(); |
| 269 | if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) { |
| 270 | return parser; |
| 271 | } |
| 272 | } |
| 273 | throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0); |
| 274 | } catch (XmlPullParserException e) { |
| 275 | throw new IllegalStateException("Unable to configure parser", e); |
| 276 | } catch (IOException e) { |
| 277 | ParseException e2 = new ParseException("Error reading XML", 0); |
| 278 | e.initCause(e); |
| 279 | throw e2; |
| 280 | } |
| 281 | } |
| 282 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 283 | @GuardedBy("this") |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 284 | private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId, |
| 285 | int newOptimisticLockId, Integer status, PackageVersions packageVersions) |
| 286 | throws IOException { |
| 287 | |
| 288 | int currentOptimisticLockId; |
| 289 | try { |
| 290 | currentOptimisticLockId = getCurrentOptimisticLockId(); |
| 291 | if (currentOptimisticLockId != optimisticLockId) { |
| 292 | return false; |
| 293 | } |
| 294 | } catch (ParseException e) { |
| 295 | recoverFromBadData(e); |
| 296 | return false; |
| 297 | } |
| 298 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 299 | writePackageStatusLocked(status, newOptimisticLockId, packageVersions); |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 300 | return true; |
| 301 | } |
| 302 | |
Neil Fuller | 316fb607 | 2017-07-11 11:34:25 +0100 | [diff] [blame] | 303 | @GuardedBy("this") |
| 304 | private void writePackageStatusLocked(Integer status, int optimisticLockId, |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 305 | PackageVersions packageVersions) throws IOException { |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 306 | if ((status == null) != (packageVersions == null)) { |
| 307 | throw new IllegalArgumentException( |
| 308 | "Provide both status and packageVersions, or neither."); |
| 309 | } |
| 310 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 311 | FileOutputStream fos = null; |
| 312 | try { |
| 313 | fos = mPackageStatusFile.startWrite(); |
| 314 | XmlSerializer serializer = new FastXmlSerializer(); |
| 315 | serializer.setOutput(fos, StandardCharsets.UTF_8.name()); |
| 316 | serializer.startDocument(null /* encoding */, true /* standalone */); |
| 317 | final String namespace = null; |
| 318 | serializer.startTag(namespace, TAG_PACKAGE_STATUS); |
| 319 | String statusAttributeValue = status == null ? "" : Integer.toString(status); |
| 320 | serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue); |
| 321 | serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID, |
| 322 | Integer.toString(optimisticLockId)); |
| 323 | int updateAppVersion = status == null |
| 324 | ? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion; |
| 325 | serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION, |
| 326 | Integer.toString(updateAppVersion)); |
| 327 | int dataAppVersion = status == null |
| 328 | ? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion; |
| 329 | serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION, |
| 330 | Integer.toString(dataAppVersion)); |
| 331 | serializer.endTag(namespace, TAG_PACKAGE_STATUS); |
| 332 | serializer.endDocument(); |
| 333 | serializer.flush(); |
| 334 | mPackageStatusFile.finishWrite(fos); |
| 335 | } catch (IOException e) { |
| 336 | if (fos != null) { |
| 337 | mPackageStatusFile.failWrite(fos); |
| 338 | } |
| 339 | throw e; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 340 | } |
| 341 | |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 342 | } |
| 343 | |
| 344 | /** Only used during tests to force a known table state. */ |
| 345 | public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) { |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 346 | synchronized (this) { |
| 347 | try { |
| 348 | int optimisticLockId = getCurrentOptimisticLockId(); |
| 349 | writePackageStatusWithOptimisticLockCheck(optimisticLockId, optimisticLockId, |
| 350 | checkStatus, packageVersions); |
| 351 | } catch (IOException | ParseException e) { |
| 352 | throw new IllegalStateException(e); |
| 353 | } |
| 354 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 355 | } |
| 356 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 357 | private static Integer getNullableIntAttribute(XmlPullParser parser, String attributeName) |
| 358 | throws ParseException { |
| 359 | String attributeValue = parser.getAttributeValue(null, attributeName); |
| 360 | try { |
| 361 | if (attributeValue == null) { |
| 362 | throw new ParseException("Attribute " + attributeName + " missing", 0); |
| 363 | } else if (attributeValue.isEmpty()) { |
| 364 | return null; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 365 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 366 | return Integer.parseInt(attributeValue); |
| 367 | } catch (NumberFormatException e) { |
| 368 | throw new ParseException( |
| 369 | "Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 370 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 371 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 372 | |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 373 | private static int getIntAttribute(XmlPullParser parser, String attributeName) |
| 374 | throws ParseException { |
| 375 | Integer value = getNullableIntAttribute(parser, attributeName); |
| 376 | if (value == null) { |
| 377 | throw new ParseException("Missing attribute " + attributeName, 0); |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 378 | } |
Neil Fuller | 5f6750f | 2017-05-17 04:43:12 +0100 | [diff] [blame] | 379 | return value; |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 380 | } |
Neil Fuller | 87b1128 | 2017-06-23 16:43:45 +0100 | [diff] [blame] | 381 | |
| 382 | public void dump(PrintWriter printWriter) { |
| 383 | printWriter.println("Package status: " + getPackageStatus()); |
| 384 | } |
Neil Fuller | 68f6666 | 2017-03-16 18:32:21 +0000 | [diff] [blame] | 385 | } |