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.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