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