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 android.app.timezone;
     18 
     19 import android.annotation.IntDef;
     20 import android.content.Context;
     21 import android.os.Handler;
     22 import android.os.ParcelFileDescriptor;
     23 import android.os.RemoteException;
     24 import android.os.ServiceManager;
     25 import android.util.Log;
     26 
     27 import java.io.IOException;
     28 import java.lang.annotation.Retention;
     29 import java.lang.annotation.RetentionPolicy;
     30 import java.util.Arrays;
     31 
     32 /**
     33  * The interface through which a time zone update application interacts with the Android system
     34  * to handle time zone rule updates.
     35  *
     36  * <p>This interface is intended for use with the default APK-based time zone rules update
     37  * application but it can also be used by OEMs if that mechanism is turned off using configuration.
     38  * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
     39  * permission.
     40  *
     41  * <p>When using the default mechanism, when properly configured the Android system will send a
     42  * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
     43  * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
     44  * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
     45  * updater application is then responsible for calling one of
     46  * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
     47  * {@link #requestUninstall(byte[], Callback)} or
     48  * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
     49  * distro should be installed, the current distro should be uninstalled, or there is nothing to do
     50  * (or that the correct operation could not be determined due to an error). In each case the updater
     51  * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
     52  * back so the system in the {@code checkToken} parameter.
     53  *
     54  * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
     55  * rather than an APK, then they should disable the default triggering mechanism in config and are
     56  * responsible for triggering their own update checks / installs / uninstalls. In this case the
     57  * "check token" parameter can be left null and there is never any need to call
     58  * {@link #requestNothing(byte[], boolean)}.
     59  *
     60  * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
     61  * unnecessary checks being triggered.
     62  *
     63  * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
     64  * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
     65  * @hide
     66  */
     67 public final class RulesManager {
     68     private static final String TAG = "timezone.RulesManager";
     69     private static final boolean DEBUG = false;
     70 
     71     @Retention(RetentionPolicy.SOURCE)
     72     @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS})
     73     public @interface ResultCode {}
     74 
     75     /**
     76      * Indicates that an operation succeeded.
     77      */
     78     public static final int SUCCESS = 0;
     79 
     80     /**
     81      * Indicates that an install/uninstall cannot be initiated because there is one already in
     82      * progress.
     83      */
     84     public static final int ERROR_OPERATION_IN_PROGRESS = 1;
     85 
     86     /**
     87      * Indicates an install / uninstall did not fully succeed for an unknown reason.
     88      */
     89     public static final int ERROR_UNKNOWN_FAILURE = 2;
     90 
     91     private final Context mContext;
     92     private final IRulesManager mIRulesManager;
     93 
     94     public RulesManager(Context context) {
     95         mContext = context;
     96         mIRulesManager = IRulesManager.Stub.asInterface(
     97                 ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
     98     }
     99 
    100     /**
    101      * Returns information about the current time zone rules state such as the IANA version of
    102      * the system and any currently installed distro. This method is intended to allow clients to
    103      * determine if the current state can be improved; for example by passing the information to a
    104      * server that may provide a new distro for download.
    105      */
    106     public RulesState getRulesState() {
    107         try {
    108             logDebug("sIRulesManager.getRulesState()");
    109             RulesState rulesState = mIRulesManager.getRulesState();
    110             logDebug("sIRulesManager.getRulesState() returned " + rulesState);
    111             return rulesState;
    112         } catch (RemoteException e) {
    113             throw e.rethrowFromSystemServer();
    114         }
    115     }
    116 
    117     /**
    118      * Requests installation of the supplied distro. The distro must have been checked for integrity
    119      * by the caller or have been received via a trusted mechanism.
    120      *
    121      * @param distroFileDescriptor the file descriptor for the distro
    122      * @param checkToken an optional token provided if the install was triggered in response to a
    123      *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
    124      * @param callback the {@link Callback} to receive callbacks related to the installation
    125      * @return {@link #SUCCESS} if the installation will be attempted
    126      */
    127     @ResultCode
    128     public int requestInstall(
    129             ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
    130             throws IOException {
    131 
    132         ICallback iCallback = new CallbackWrapper(mContext, callback);
    133         try {
    134             logDebug("sIRulesManager.requestInstall()");
    135             return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
    136         } catch (RemoteException e) {
    137             throw e.rethrowFromSystemServer();
    138         }
    139     }
    140 
    141     /**
    142      * Requests uninstallation of the currently installed distro (leaving the device with no
    143      * distro installed).
    144      *
    145      * @param checkToken an optional token provided if the uninstall was triggered in response to a
    146      *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
    147      * @param callback the {@link Callback} to receive callbacks related to the uninstall
    148      * @return {@link #SUCCESS} if the uninstallation will be attempted
    149      */
    150     @ResultCode
    151     public int requestUninstall(byte[] checkToken, Callback callback) {
    152         ICallback iCallback = new CallbackWrapper(mContext, callback);
    153         try {
    154             logDebug("sIRulesManager.requestUninstall()");
    155             return mIRulesManager.requestUninstall(checkToken, iCallback);
    156         } catch (RemoteException e) {
    157             throw e.rethrowFromSystemServer();
    158         }
    159     }
    160 
    161     /*
    162      * We wrap incoming binder calls with a private class implementation that
    163      * redirects them into main-thread actions.  This serializes the backup
    164      * progress callbacks nicely within the usual main-thread lifecycle pattern.
    165      */
    166     private class CallbackWrapper extends ICallback.Stub {
    167         final Handler mHandler;
    168         final Callback mCallback;
    169 
    170         CallbackWrapper(Context context, Callback callback) {
    171             mCallback = callback;
    172             mHandler = new Handler(context.getMainLooper());
    173         }
    174 
    175         // Binder calls into this object just enqueue on the main-thread handler
    176         @Override
    177         public void onFinished(int status) {
    178             logDebug("mCallback.onFinished(status), status=" + status);
    179             mHandler.post(() -> mCallback.onFinished(status));
    180         }
    181     }
    182 
    183     /**
    184      * Requests the system does not modify the currently installed time zone distro, if any. This
    185      * method records the fact that a time zone check operation triggered by the system is now
    186      * complete and there was nothing to do. The token passed should be the one presented when the
    187      * check was triggered.
    188      *
    189      * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
    190      * should be careful not to pass false if the failure is unlikely to resolve by itself.
    191      *
    192      * @param checkToken an optional token provided if the install was triggered in response to a
    193      *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
    194      * @param succeeded true if the check was successful, false if it was not successful but may
    195      *     succeed if it is retried
    196      */
    197     public void requestNothing(byte[] checkToken, boolean succeeded) {
    198         try {
    199             logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
    200             mIRulesManager.requestNothing(checkToken, succeeded);
    201         } catch (RemoteException e) {
    202             throw e.rethrowFromSystemServer();
    203         }
    204     }
    205 
    206     static void logDebug(String msg) {
    207         if (DEBUG) {
    208             Log.v(TAG, msg);
    209         }
    210     }
    211 }
    212