summaryrefslogtreecommitdiff
path: root/android/app/timezone/RulesManager.java
blob: 0a38eb9ae777272e1cc81866ff9a0f39723a97ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.timezone;

import android.annotation.IntDef;
import android.content.Context;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;

/**
 * The interface through which a time zone update application interacts with the Android system
 * to handle time zone rule updates.
 *
 * <p>This interface is intended for use with the default APK-based time zone rules update
 * application but it can also be used by OEMs if that mechanism is turned off using configuration.
 * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
 * permission.
 *
 * <p>When using the default mechanism, when properly configured the Android system will send a
 * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
 * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
 * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
 * updater application is then responsible for calling one of
 * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
 * {@link #requestUninstall(byte[], Callback)} or
 * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
 * distro should be installed, the current distro should be uninstalled, or there is nothing to do
 * (or that the correct operation could not be determined due to an error). In each case the updater
 * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
 * back so the system in the {@code checkToken} parameter.
 *
 * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
 * rather than an APK, then they should disable the default triggering mechanism in config and are
 * responsible for triggering their own update checks / installs / uninstalls. In this case the
 * "check token" parameter can be left null and there is never any need to call
 * {@link #requestNothing(byte[], boolean)}.
 *
 * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
 * unnecessary checks being triggered.
 *
 * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
 * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
 * @hide
 */
public final class RulesManager {
    private static final String TAG = "timezone.RulesManager";
    private static final boolean DEBUG = false;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
            SUCCESS,
            ERROR_UNKNOWN_FAILURE,
            ERROR_OPERATION_IN_PROGRESS
    })
    public @interface ResultCode {}

    /**
     * Indicates that an operation succeeded.
     */
    public static final int SUCCESS = 0;

    /**
     * Indicates that an install/uninstall cannot be initiated because there is one already in
     * progress.
     */
    public static final int ERROR_OPERATION_IN_PROGRESS = 1;

    /**
     * Indicates an install / uninstall did not fully succeed for an unknown reason.
     */
    public static final int ERROR_UNKNOWN_FAILURE = 2;

    private final Context mContext;
    private final IRulesManager mIRulesManager;

    public RulesManager(Context context) {
        mContext = context;
        mIRulesManager = IRulesManager.Stub.asInterface(
                ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
    }

    /**
     * Returns information about the current time zone rules state such as the IANA version of
     * the system and any currently installed distro. This method is intended to allow clients to
     * determine if the current state can be improved; for example by passing the information to a
     * server that may provide a new distro for download.
     */
    public RulesState getRulesState() {
        try {
            logDebug("mIRulesManager.getRulesState()");
            RulesState rulesState = mIRulesManager.getRulesState();
            logDebug("mIRulesManager.getRulesState() returned " + rulesState);
            return rulesState;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests installation of the supplied distro. The distro must have been checked for integrity
     * by the caller or have been received via a trusted mechanism.
     *
     * @param distroFileDescriptor the file descriptor for the distro
     * @param checkToken an optional token provided if the install was triggered in response to a
     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
     * @param callback the {@link Callback} to receive callbacks related to the installation
     * @return {@link #SUCCESS} if the installation will be attempted
     */
    @ResultCode
    public int requestInstall(
            ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
            throws IOException {

        ICallback iCallback = new CallbackWrapper(mContext, callback);
        try {
            logDebug("mIRulesManager.requestInstall()");
            return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests uninstallation of the currently installed distro (leaving the device with no
     * distro installed).
     *
     * @param checkToken an optional token provided if the uninstall was triggered in response to a
     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
     * @param callback the {@link Callback} to receive callbacks related to the uninstall
     * @return {@link #SUCCESS} if the uninstallation will be attempted
     */
    @ResultCode
    public int requestUninstall(byte[] checkToken, Callback callback) {
        ICallback iCallback = new CallbackWrapper(mContext, callback);
        try {
            logDebug("mIRulesManager.requestUninstall()");
            return mIRulesManager.requestUninstall(checkToken, iCallback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /*
     * We wrap incoming binder calls with a private class implementation that
     * redirects them into main-thread actions.  This serializes the backup
     * progress callbacks nicely within the usual main-thread lifecycle pattern.
     */
    private class CallbackWrapper extends ICallback.Stub {
        final Handler mHandler;
        final Callback mCallback;

        CallbackWrapper(Context context, Callback callback) {
            mCallback = callback;
            mHandler = new Handler(context.getMainLooper());
        }

        // Binder calls into this object just enqueue on the main-thread handler
        @Override
        public void onFinished(int status) {
            logDebug("mCallback.onFinished(status), status=" + status);
            mHandler.post(() -> mCallback.onFinished(status));
        }
    }

    /**
     * Requests the system does not modify the currently installed time zone distro, if any. This
     * method records the fact that a time zone check operation triggered by the system is now
     * complete and there was nothing to do. The token passed should be the one presented when the
     * check was triggered.
     *
     * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
     * should be careful not to pass false if the failure is unlikely to resolve by itself.
     *
     * @param checkToken an optional token provided if the install was triggered in response to a
     *     {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
     * @param succeeded true if the check was successful, false if it was not successful but may
     *     succeed if it is retried
     */
    public void requestNothing(byte[] checkToken, boolean succeeded) {
        try {
            logDebug("mIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
            mIRulesManager.requestNothing(checkToken, succeeded);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    static void logDebug(String msg) {
        if (DEBUG) {
            Log.v(TAG, msg);
        }
    }
}