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 19 import com.android.internal.annotations.VisibleForTesting; 20 import com.android.server.EventLogTags; 21 import com.android.server.SystemService; 22 import com.android.timezone.distro.DistroException; 23 import com.android.timezone.distro.DistroVersion; 24 import com.android.timezone.distro.StagedDistroOperation; 25 import com.android.timezone.distro.TimeZoneDistro; 26 import com.android.timezone.distro.installer.TimeZoneDistroInstaller; 27 28 import android.app.timezone.Callback; 29 import android.app.timezone.DistroFormatVersion; 30 import android.app.timezone.DistroRulesVersion; 31 import android.app.timezone.ICallback; 32 import android.app.timezone.IRulesManager; 33 import android.app.timezone.RulesManager; 34 import android.app.timezone.RulesState; 35 import android.content.Context; 36 import android.os.ParcelFileDescriptor; 37 import android.os.RemoteException; 38 import android.util.Slog; 39 40 import java.io.File; 41 import java.io.FileDescriptor; 42 import java.io.FileInputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.PrintWriter; 46 import java.util.Arrays; 47 import java.util.concurrent.Executor; 48 import java.util.concurrent.atomic.AtomicBoolean; 49 import libcore.icu.ICU; 50 import libcore.util.TimeZoneFinder; 51 import libcore.util.ZoneInfoDB; 52 53 import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED; 54 import static android.app.timezone.RulesState.DISTRO_STATUS_NONE; 55 import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN; 56 import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL; 57 import static android.app.timezone.RulesState.STAGED_OPERATION_NONE; 58 import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL; 59 import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN; 60 61 public final class RulesManagerService extends IRulesManager.Stub { 62 63 private static final String TAG = "timezone.RulesManagerService"; 64 65 /** The distro format supported by this device. */ 66 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 67 static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED = 68 new DistroFormatVersion( 69 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION, 70 DistroVersion.CURRENT_FORMAT_MINOR_VERSION); 71 72 public static class Lifecycle extends SystemService { 73 public Lifecycle(Context context) { 74 super(context); 75 } 76 77 @Override 78 public void onStart() { 79 RulesManagerService service = RulesManagerService.create(getContext()); 80 service.start(); 81 82 // Publish the binder service so it can be accessed from other (appropriately 83 // permissioned) processes. 84 publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service); 85 86 // Publish the service instance locally so we can use it directly from within the system 87 // server from TimeZoneUpdateIdler. 88 publishLocalService(RulesManagerService.class, service); 89 } 90 } 91 92 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 93 static final String REQUIRED_UPDATER_PERMISSION = 94 android.Manifest.permission.UPDATE_TIME_ZONE_RULES; 95 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 96 static final String REQUIRED_QUERY_PERMISSION = 97 android.Manifest.permission.QUERY_TIME_ZONE_RULES; 98 private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata"); 99 private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); 100 101 private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false); 102 private final PermissionHelper mPermissionHelper; 103 private final PackageTracker mPackageTracker; 104 private final Executor mExecutor; 105 private final RulesManagerIntentHelper mIntentHelper; 106 private final TimeZoneDistroInstaller mInstaller; 107 108 private static RulesManagerService create(Context context) { 109 RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context); 110 return new RulesManagerService( 111 helper /* permissionHelper */, 112 helper /* executor */, 113 helper /* intentHelper */, 114 PackageTracker.create(context), 115 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR)); 116 } 117 118 // A constructor that can be used by tests to supply mocked / faked dependencies. 119 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) 120 RulesManagerService(PermissionHelper permissionHelper, Executor executor, 121 RulesManagerIntentHelper intentHelper, PackageTracker packageTracker, 122 TimeZoneDistroInstaller timeZoneDistroInstaller) { 123 mPermissionHelper = permissionHelper; 124 mExecutor = executor; 125 mIntentHelper = intentHelper; 126 mPackageTracker = packageTracker; 127 mInstaller = timeZoneDistroInstaller; 128 } 129 130 public void start() { 131 // Return value deliberately ignored: no action required on failure to start. 132 mPackageTracker.start(); 133 } 134 135 @Override // Binder call 136 public RulesState getRulesState() { 137 mPermissionHelper.enforceCallerHasPermission(REQUIRED_QUERY_PERMISSION); 138 139 return getRulesStateInternal(); 140 } 141 142 /** Like {@link #getRulesState()} without the permission check. */ 143 private RulesState getRulesStateInternal() { 144 synchronized(this) { 145 String systemRulesVersion; 146 try { 147 systemRulesVersion = mInstaller.getSystemRulesVersion(); 148 } catch (IOException e) { 149 Slog.w(TAG, "Failed to read system rules", e); 150 return null; 151 } 152 153 // Determine the installed distro state. This should be possible regardless of whether 154 // there's an operation in progress. 155 DistroVersion installedDistroVersion; 156 int distroStatus = DISTRO_STATUS_UNKNOWN; 157 DistroRulesVersion installedDistroRulesVersion = null; 158 try { 159 installedDistroVersion = mInstaller.getInstalledDistroVersion(); 160 if (installedDistroVersion == null) { 161 distroStatus = DISTRO_STATUS_NONE; 162 installedDistroRulesVersion = null; 163 } else { 164 distroStatus = DISTRO_STATUS_INSTALLED; 165 installedDistroRulesVersion = new DistroRulesVersion( 166 installedDistroVersion.rulesVersion, 167 installedDistroVersion.revision); 168 } 169 } catch (DistroException | IOException e) { 170 Slog.w(TAG, "Failed to read installed distro.", e); 171 } 172 173 boolean operationInProgress = this.mOperationInProgress.get(); 174 175 // Determine the staged operation status, if possible. 176 DistroRulesVersion stagedDistroRulesVersion = null; 177 int stagedOperationStatus = STAGED_OPERATION_UNKNOWN; 178 if (!operationInProgress) { 179 StagedDistroOperation stagedDistroOperation; 180 try { 181 stagedDistroOperation = mInstaller.getStagedDistroOperation(); 182 if (stagedDistroOperation == null) { 183 stagedOperationStatus = STAGED_OPERATION_NONE; 184 } else if (stagedDistroOperation.isUninstall) { 185 stagedOperationStatus = STAGED_OPERATION_UNINSTALL; 186 } else { 187 // Must be an install. 188 stagedOperationStatus = STAGED_OPERATION_INSTALL; 189 DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion; 190 stagedDistroRulesVersion = new DistroRulesVersion( 191 stagedDistroVersion.rulesVersion, 192 stagedDistroVersion.revision); 193 } 194 } catch (DistroException | IOException e) { 195 Slog.w(TAG, "Failed to read staged distro.", e); 196 } 197 } 198 return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED, 199 operationInProgress, stagedOperationStatus, stagedDistroRulesVersion, 200 distroStatus, installedDistroRulesVersion); 201 } 202 } 203 204 @Override 205 public int requestInstall(ParcelFileDescriptor distroParcelFileDescriptor, 206 byte[] checkTokenBytes, ICallback callback) { 207 208 boolean closeParcelFileDescriptorOnExit = true; 209 try { 210 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 211 212 CheckToken checkToken = null; 213 if (checkTokenBytes != null) { 214 checkToken = createCheckTokenOrThrow(checkTokenBytes); 215 } 216 EventLogTags.writeTimezoneRequestInstall(toStringOrNull(checkToken)); 217 218 synchronized (this) { 219 if (distroParcelFileDescriptor == null) { 220 throw new NullPointerException("distroParcelFileDescriptor == null"); 221 } 222 if (callback == null) { 223 throw new NullPointerException("observer == null"); 224 } 225 if (mOperationInProgress.get()) { 226 return RulesManager.ERROR_OPERATION_IN_PROGRESS; 227 } 228 mOperationInProgress.set(true); 229 230 // Execute the install asynchronously. 231 mExecutor.execute( 232 new InstallRunnable(distroParcelFileDescriptor, checkToken, callback)); 233 234 // The InstallRunnable now owns the ParcelFileDescriptor, so it will close it after 235 // it executes (and we do not have to). 236 closeParcelFileDescriptorOnExit = false; 237 238 return RulesManager.SUCCESS; 239 } 240 } finally { 241 // We should close() the local ParcelFileDescriptor we were passed if it hasn't been 242 // passed to another thread to handle. 243 if (distroParcelFileDescriptor != null && closeParcelFileDescriptorOnExit) { 244 try { 245 distroParcelFileDescriptor.close(); 246 } catch (IOException e) { 247 Slog.w(TAG, "Failed to close distroParcelFileDescriptor", e); 248 } 249 } 250 } 251 } 252 253 private class InstallRunnable implements Runnable { 254 255 private final ParcelFileDescriptor mDistroParcelFileDescriptor; 256 private final CheckToken mCheckToken; 257 private final ICallback mCallback; 258 259 InstallRunnable(ParcelFileDescriptor distroParcelFileDescriptor, CheckToken checkToken, 260 ICallback callback) { 261 mDistroParcelFileDescriptor = distroParcelFileDescriptor; 262 mCheckToken = checkToken; 263 mCallback = callback; 264 } 265 266 @Override 267 public void run() { 268 EventLogTags.writeTimezoneInstallStarted(toStringOrNull(mCheckToken)); 269 270 boolean success = false; 271 // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed 272 // when we are done. 273 try (ParcelFileDescriptor pfd = mDistroParcelFileDescriptor) { 274 // The ParcelFileDescriptor owns the underlying FileDescriptor and we'll close 275 // it at the end of the try-with-resources. 276 final boolean isFdOwner = false; 277 InputStream is = new FileInputStream(pfd.getFileDescriptor(), isFdOwner); 278 279 TimeZoneDistro distro = new TimeZoneDistro(is); 280 int installerResult = mInstaller.stageInstallWithErrorCode(distro); 281 282 // Notify interested parties that something is staged. 283 sendInstallNotificationIntentIfRequired(installerResult); 284 285 int resultCode = mapInstallerResultToApiCode(installerResult); 286 EventLogTags.writeTimezoneInstallComplete(toStringOrNull(mCheckToken), resultCode); 287 sendFinishedStatus(mCallback, resultCode); 288 289 // All the installer failure modes are currently non-recoverable and won't be 290 // improved by trying again. Therefore success = true. 291 success = true; 292 } catch (Exception e) { 293 Slog.w(TAG, "Failed to install distro.", e); 294 EventLogTags.writeTimezoneInstallComplete( 295 toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE); 296 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE); 297 } finally { 298 // Notify the package tracker that the operation is now complete. 299 mPackageTracker.recordCheckResult(mCheckToken, success); 300 301 mOperationInProgress.set(false); 302 } 303 } 304 305 private void sendInstallNotificationIntentIfRequired(int installerResult) { 306 if (installerResult == TimeZoneDistroInstaller.INSTALL_SUCCESS) { 307 mIntentHelper.sendTimeZoneOperationStaged(); 308 } 309 } 310 311 private int mapInstallerResultToApiCode(int installerResult) { 312 switch (installerResult) { 313 case TimeZoneDistroInstaller.INSTALL_SUCCESS: 314 return Callback.SUCCESS; 315 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE: 316 return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE; 317 case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD: 318 return Callback.ERROR_INSTALL_RULES_TOO_OLD; 319 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION: 320 return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION; 321 case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR: 322 return Callback.ERROR_INSTALL_VALIDATION_ERROR; 323 default: 324 return Callback.ERROR_UNKNOWN_FAILURE; 325 } 326 } 327 } 328 329 @Override 330 public int requestUninstall(byte[] checkTokenBytes, ICallback callback) { 331 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 332 333 CheckToken checkToken = null; 334 if (checkTokenBytes != null) { 335 checkToken = createCheckTokenOrThrow(checkTokenBytes); 336 } 337 EventLogTags.writeTimezoneRequestUninstall(toStringOrNull(checkToken)); 338 synchronized(this) { 339 if (callback == null) { 340 throw new NullPointerException("callback == null"); 341 } 342 343 if (mOperationInProgress.get()) { 344 return RulesManager.ERROR_OPERATION_IN_PROGRESS; 345 } 346 mOperationInProgress.set(true); 347 348 // Execute the uninstall asynchronously. 349 mExecutor.execute(new UninstallRunnable(checkToken, callback)); 350 351 return RulesManager.SUCCESS; 352 } 353 } 354 355 private class UninstallRunnable implements Runnable { 356 357 private final CheckToken mCheckToken; 358 private final ICallback mCallback; 359 360 UninstallRunnable(CheckToken checkToken, ICallback callback) { 361 mCheckToken = checkToken; 362 mCallback = callback; 363 } 364 365 @Override 366 public void run() { 367 EventLogTags.writeTimezoneUninstallStarted(toStringOrNull(mCheckToken)); 368 boolean packageTrackerStatus = false; 369 try { 370 int uninstallResult = mInstaller.stageUninstall(); 371 372 // Notify interested parties that something is staged. 373 sendUninstallNotificationIntentIfRequired(uninstallResult); 374 375 packageTrackerStatus = (uninstallResult == TimeZoneDistroInstaller.UNINSTALL_SUCCESS 376 || uninstallResult == TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED); 377 378 // Right now we just have Callback.SUCCESS / Callback.ERROR_UNKNOWN_FAILURE for 379 // uninstall. All clients should be checking against SUCCESS. More granular failures 380 // may be added in future. 381 int callbackResultCode = 382 packageTrackerStatus ? Callback.SUCCESS : Callback.ERROR_UNKNOWN_FAILURE; 383 EventLogTags.writeTimezoneUninstallComplete( 384 toStringOrNull(mCheckToken), callbackResultCode); 385 sendFinishedStatus(mCallback, callbackResultCode); 386 } catch (Exception e) { 387 EventLogTags.writeTimezoneUninstallComplete( 388 toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE); 389 Slog.w(TAG, "Failed to uninstall distro.", e); 390 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE); 391 } finally { 392 // Notify the package tracker that the operation is now complete. 393 mPackageTracker.recordCheckResult(mCheckToken, packageTrackerStatus); 394 395 mOperationInProgress.set(false); 396 } 397 } 398 399 private void sendUninstallNotificationIntentIfRequired(int uninstallResult) { 400 switch (uninstallResult) { 401 case TimeZoneDistroInstaller.UNINSTALL_SUCCESS: 402 mIntentHelper.sendTimeZoneOperationStaged(); 403 break; 404 case TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED: 405 mIntentHelper.sendTimeZoneOperationUnstaged(); 406 break; 407 case TimeZoneDistroInstaller.UNINSTALL_FAIL: 408 default: 409 // No-op - unknown or nothing to notify about. 410 } 411 } 412 } 413 414 private void sendFinishedStatus(ICallback callback, int resultCode) { 415 try { 416 callback.onFinished(resultCode); 417 } catch (RemoteException e) { 418 Slog.e(TAG, "Unable to notify observer of result", e); 419 } 420 } 421 422 @Override 423 public void requestNothing(byte[] checkTokenBytes, boolean success) { 424 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION); 425 CheckToken checkToken = null; 426 if (checkTokenBytes != null) { 427 checkToken = createCheckTokenOrThrow(checkTokenBytes); 428 } 429 EventLogTags.writeTimezoneRequestNothing(toStringOrNull(checkToken)); 430 mPackageTracker.recordCheckResult(checkToken, success); 431 EventLogTags.writeTimezoneNothingComplete(toStringOrNull(checkToken)); 432 } 433 434 @Override 435 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 436 if (!mPermissionHelper.checkDumpPermission(TAG, pw)) { 437 return; 438 } 439 440 RulesState rulesState = getRulesStateInternal(); 441 if (args != null && args.length == 2) { 442 // Formatting options used for automated tests. The format is less free-form than 443 // the -format options, which are intended to be easier to parse. 444 if ("-format_state".equals(args[0]) && args[1] != null) { 445 for (char c : args[1].toCharArray()) { 446 switch (c) { 447 case 'p': { 448 // Report operation in progress 449 String value = "Unknown"; 450 if (rulesState != null) { 451 value = Boolean.toString(rulesState.isOperationInProgress()); 452 } 453 pw.println("Operation in progress: " + value); 454 break; 455 } 456 case 's': { 457 // Report system image rules version 458 String value = "Unknown"; 459 if (rulesState != null) { 460 value = rulesState.getSystemRulesVersion(); 461 } 462 pw.println("System rules version: " + value); 463 break; 464 } 465 case 'c': { 466 // Report current installation state 467 String value = "Unknown"; 468 if (rulesState != null) { 469 value = distroStatusToString(rulesState.getDistroStatus()); 470 } 471 pw.println("Current install state: " + value); 472 break; 473 } 474 case 'i': { 475 // Report currently installed version 476 String value = "Unknown"; 477 if (rulesState != null) { 478 DistroRulesVersion installedRulesVersion = 479 rulesState.getInstalledDistroRulesVersion(); 480 if (installedRulesVersion == null) { 481 value = "<None>"; 482 } else { 483 value = installedRulesVersion.toDumpString(); 484 } 485 } 486 pw.println("Installed rules version: " + value); 487 break; 488 } 489 case 'o': { 490 // Report staged operation type 491 String value = "Unknown"; 492 if (rulesState != null) { 493 int stagedOperationType = rulesState.getStagedOperationType(); 494 value = stagedOperationToString(stagedOperationType); 495 } 496 pw.println("Staged operation: " + value); 497 break; 498 } 499 case 't': { 500 // Report staged version (i.e. the one that will be installed next boot 501 // if the staged operation is an install). 502 String value = "Unknown"; 503 if (rulesState != null) { 504 DistroRulesVersion stagedDistroRulesVersion = 505 rulesState.getStagedDistroRulesVersion(); 506 if (stagedDistroRulesVersion == null) { 507 value = "<None>"; 508 } else { 509 value = stagedDistroRulesVersion.toDumpString(); 510 } 511 } 512 pw.println("Staged rules version: " + value); 513 break; 514 } 515 case 'a': { 516 // Report the active rules version (i.e. the rules in use by the current 517 // process). 518 pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " 519 + ICU.getTZDataVersion() + "," 520 + ZoneInfoDB.getInstance().getVersion() + "," 521 + TimeZoneFinder.getInstance().getIanaVersion()); 522 break; 523 } 524 default: { 525 pw.println("Unknown option: " + c); 526 } 527 } 528 } 529 return; 530 } 531 } 532 533 pw.println("RulesManagerService state: " + toString()); 534 pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " 535 + ICU.getTZDataVersion() + "," 536 + ZoneInfoDB.getInstance().getVersion() + "," 537 + TimeZoneFinder.getInstance().getIanaVersion()); 538 pw.println("Distro state: " + rulesState.toString()); 539 mPackageTracker.dump(pw); 540 } 541 542 /** 543 * Called when the device is considered idle. 544 */ 545 void notifyIdle() { 546 // No package has changed: we are just triggering because the device is idle and there 547 // *might* be work to do. 548 final boolean packageChanged = false; 549 mPackageTracker.triggerUpdateIfNeeded(packageChanged); 550 } 551 552 @Override 553 public String toString() { 554 return "RulesManagerService{" + 555 "mOperationInProgress=" + mOperationInProgress + 556 '}'; 557 } 558 559 private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) { 560 CheckToken checkToken; 561 try { 562 checkToken = CheckToken.fromByteArray(checkTokenBytes); 563 } catch (IOException e) { 564 throw new IllegalArgumentException("Unable to read token bytes " 565 + Arrays.toString(checkTokenBytes), e); 566 } 567 return checkToken; 568 } 569 570 private static String distroStatusToString(int distroStatus) { 571 switch(distroStatus) { 572 case DISTRO_STATUS_NONE: 573 return "None"; 574 case DISTRO_STATUS_INSTALLED: 575 return "Installed"; 576 case DISTRO_STATUS_UNKNOWN: 577 default: 578 return "Unknown"; 579 } 580 } 581 582 private static String stagedOperationToString(int stagedOperationType) { 583 switch(stagedOperationType) { 584 case STAGED_OPERATION_NONE: 585 return "None"; 586 case STAGED_OPERATION_UNINSTALL: 587 return "Uninstall"; 588 case STAGED_OPERATION_INSTALL: 589 return "Install"; 590 case STAGED_OPERATION_UNKNOWN: 591 default: 592 return "Unknown"; 593 } 594 } 595 596 private static String toStringOrNull(Object obj) { 597 return obj == null ? null : obj.toString(); 598 } 599 } 600