blob: 58bdeb9d11ec5fc8243719f23c9dd4fe21270e60 [file] [log] [blame]
Neil Fuller68f66662017-03-16 18:32:21 +00001/*
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
17package com.android.server.timezone;
18
19import com.android.internal.annotations.VisibleForTesting;
20import com.android.server.SystemService;
Neil Fullera6a71d02017-06-13 15:12:17 +010021import com.android.timezone.distro.DistroException;
22import com.android.timezone.distro.DistroVersion;
Neil Fullerfe3b1182017-06-19 12:56:08 +010023import com.android.timezone.distro.TimeZoneDistro;
Neil Fullera6a71d02017-06-13 15:12:17 +010024import com.android.timezone.distro.StagedDistroOperation;
Neil Fuller68f66662017-03-16 18:32:21 +000025
26import android.app.timezone.Callback;
27import android.app.timezone.DistroFormatVersion;
28import android.app.timezone.DistroRulesVersion;
29import android.app.timezone.ICallback;
30import android.app.timezone.IRulesManager;
31import android.app.timezone.RulesManager;
32import android.app.timezone.RulesState;
33import android.content.Context;
34import android.os.ParcelFileDescriptor;
35import android.os.RemoteException;
36import android.util.Slog;
37
38import java.io.File;
39import java.io.IOException;
40import java.util.Arrays;
41import java.util.concurrent.Executor;
42import java.util.concurrent.atomic.AtomicBoolean;
Neil Fuller68f66662017-03-16 18:32:21 +000043import libcore.tzdata.update2.TimeZoneDistroInstaller;
44
45// TODO(nfuller) Add EventLog calls where useful in the system server.
46// TODO(nfuller) Check logging best practices in the system server.
47// TODO(nfuller) Check error handling best practices in the system server.
48public final class RulesManagerService extends IRulesManager.Stub {
49
50 private static final String TAG = "timezone.RulesManagerService";
51
52 /** The distro format supported by this device. */
53 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
54 static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
55 new DistroFormatVersion(
56 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
57 DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
58
59 public static class Lifecycle extends SystemService {
60 private RulesManagerService mService;
61
62 public Lifecycle(Context context) {
63 super(context);
64 }
65
66 @Override
67 public void onStart() {
68 mService = RulesManagerService.create(getContext());
69 mService.start();
70
71 publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
72 }
73 }
74
75 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
76 static final String REQUIRED_UPDATER_PERMISSION =
77 android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
78 private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
79 private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
80
81 private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
82 private final PermissionHelper mPermissionHelper;
83 private final PackageTracker mPackageTracker;
84 private final Executor mExecutor;
85 private final TimeZoneDistroInstaller mInstaller;
86 private final FileDescriptorHelper mFileDescriptorHelper;
87
88 private static RulesManagerService create(Context context) {
89 RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
90 return new RulesManagerService(
91 helper /* permissionHelper */,
92 helper /* executor */,
93 helper /* fileDescriptorHelper */,
94 PackageTracker.create(context),
95 new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
96 }
97
98 // A constructor that can be used by tests to supply mocked / faked dependencies.
99 RulesManagerService(PermissionHelper permissionHelper,
100 Executor executor,
101 FileDescriptorHelper fileDescriptorHelper, PackageTracker packageTracker,
102 TimeZoneDistroInstaller timeZoneDistroInstaller) {
103 mPermissionHelper = permissionHelper;
104 mExecutor = executor;
105 mFileDescriptorHelper = fileDescriptorHelper;
106 mPackageTracker = packageTracker;
107 mInstaller = timeZoneDistroInstaller;
108 }
109
110 public void start() {
111 mPackageTracker.start();
112 }
113
114 @Override // Binder call
115 public RulesState getRulesState() {
116 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
117
118 synchronized(this) {
119 String systemRulesVersion;
120 try {
121 systemRulesVersion = mInstaller.getSystemRulesVersion();
122 } catch (IOException e) {
123 Slog.w(TAG, "Failed to read system rules", e);
124 return null;
125 }
126
127 boolean operationInProgress = this.mOperationInProgress.get();
128
129 // Determine the staged operation status, if possible.
130 DistroRulesVersion stagedDistroRulesVersion = null;
131 int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
132 if (!operationInProgress) {
133 StagedDistroOperation stagedDistroOperation;
134 try {
135 stagedDistroOperation = mInstaller.getStagedDistroOperation();
136 if (stagedDistroOperation == null) {
137 stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
138 } else if (stagedDistroOperation.isUninstall) {
139 stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
140 } else {
141 // Must be an install.
142 stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
143 DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
144 stagedDistroRulesVersion = new DistroRulesVersion(
145 stagedDistroVersion.rulesVersion,
146 stagedDistroVersion.revision);
147 }
148 } catch (DistroException | IOException e) {
149 Slog.w(TAG, "Failed to read staged distro.", e);
150 }
151 }
152
153 // Determine the installed distro state, if possible.
154 DistroVersion installedDistroVersion;
155 int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
156 DistroRulesVersion installedDistroRulesVersion = null;
157 if (!operationInProgress) {
158 try {
159 installedDistroVersion = mInstaller.getInstalledDistroVersion();
160 if (installedDistroVersion == null) {
161 distroStatus = RulesState.DISTRO_STATUS_NONE;
162 installedDistroRulesVersion = null;
163 } else {
164 distroStatus = RulesState.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 return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
174 operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
175 distroStatus, installedDistroRulesVersion);
176 }
177 }
178
179 @Override
180 public int requestInstall(
181 ParcelFileDescriptor timeZoneDistro, byte[] checkTokenBytes, ICallback callback) {
182 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
183
184 CheckToken checkToken = null;
185 if (checkTokenBytes != null) {
186 checkToken = createCheckTokenOrThrow(checkTokenBytes);
187 }
188 synchronized (this) {
189 if (timeZoneDistro == null) {
190 throw new NullPointerException("timeZoneDistro == null");
191 }
192 if (callback == null) {
193 throw new NullPointerException("observer == null");
194 }
195 if (mOperationInProgress.get()) {
196 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
197 }
198 mOperationInProgress.set(true);
199
200 // Execute the install asynchronously.
201 mExecutor.execute(new InstallRunnable(timeZoneDistro, checkToken, callback));
202
203 return RulesManager.SUCCESS;
204 }
205 }
206
207 private class InstallRunnable implements Runnable {
208
209 private final ParcelFileDescriptor mTimeZoneDistro;
210 private final CheckToken mCheckToken;
211 private final ICallback mCallback;
212
213 InstallRunnable(
214 ParcelFileDescriptor timeZoneDistro, CheckToken checkToken, ICallback callback) {
215 mTimeZoneDistro = timeZoneDistro;
216 mCheckToken = checkToken;
217 mCallback = callback;
218 }
219
220 @Override
221 public void run() {
222 // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
223 // when we are done.
224 boolean success = false;
225 try {
226 byte[] distroBytes =
227 RulesManagerService.this.mFileDescriptorHelper.readFully(mTimeZoneDistro);
Neil Fullerfe3b1182017-06-19 12:56:08 +0100228 TimeZoneDistro distro = new TimeZoneDistro(distroBytes);
229 int installerResult = mInstaller.stageInstallWithErrorCode(distro);
Neil Fuller68f66662017-03-16 18:32:21 +0000230 int resultCode = mapInstallerResultToApiCode(installerResult);
231 sendFinishedStatus(mCallback, resultCode);
232
233 // All the installer failure modes are currently non-recoverable and won't be
234 // improved by trying again. Therefore success = true.
235 success = true;
236 } catch (Exception e) {
237 Slog.w(TAG, "Failed to install distro.", e);
238 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
239 } finally {
240 // Notify the package tracker that the operation is now complete.
241 mPackageTracker.recordCheckResult(mCheckToken, success);
242
243 mOperationInProgress.set(false);
244 }
245 }
246
247 private int mapInstallerResultToApiCode(int installerResult) {
248 switch (installerResult) {
249 case TimeZoneDistroInstaller.INSTALL_SUCCESS:
250 return Callback.SUCCESS;
251 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
252 return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
253 case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
254 return Callback.ERROR_INSTALL_RULES_TOO_OLD;
255 case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
256 return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
257 case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
258 return Callback.ERROR_INSTALL_VALIDATION_ERROR;
259 default:
260 return Callback.ERROR_UNKNOWN_FAILURE;
261 }
262 }
263 }
264
265 @Override
266 public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
267 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
268
269 CheckToken checkToken = null;
270 if (checkTokenBytes != null) {
271 checkToken = createCheckTokenOrThrow(checkTokenBytes);
272 }
273 synchronized(this) {
274 if (callback == null) {
275 throw new NullPointerException("callback == null");
276 }
277
278 if (mOperationInProgress.get()) {
279 return RulesManager.ERROR_OPERATION_IN_PROGRESS;
280 }
281 mOperationInProgress.set(true);
282
283 // Execute the uninstall asynchronously.
284 mExecutor.execute(new UninstallRunnable(checkToken, callback));
285
286 return RulesManager.SUCCESS;
287 }
288 }
289
290 private class UninstallRunnable implements Runnable {
291
292 private final CheckToken mCheckToken;
293 private final ICallback mCallback;
294
295 public UninstallRunnable(CheckToken checkToken, ICallback callback) {
296 mCheckToken = checkToken;
297 mCallback = callback;
298 }
299
300 @Override
301 public void run() {
302 boolean success = false;
303 try {
304 success = mInstaller.stageUninstall();
305 // Right now we just have success (0) / failure (1). All clients should be checking
306 // against SUCCESS. More granular failures may be added in future.
307 int resultCode = success ? Callback.SUCCESS
308 : Callback.ERROR_UNKNOWN_FAILURE;
309 sendFinishedStatus(mCallback, resultCode);
310 } catch (Exception e) {
311 Slog.w(TAG, "Failed to uninstall distro.", e);
312 sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
313 } finally {
314 // Notify the package tracker that the operation is now complete.
315 mPackageTracker.recordCheckResult(mCheckToken, success);
316
317 mOperationInProgress.set(false);
318 }
319 }
320 }
321
322 private void sendFinishedStatus(ICallback callback, int resultCode) {
323 try {
324 callback.onFinished(resultCode);
325 } catch (RemoteException e) {
326 Slog.e(TAG, "Unable to notify observer of result", e);
327 }
328 }
329
330 @Override
331 public void requestNothing(byte[] checkTokenBytes, boolean success) {
332 mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
333 CheckToken checkToken = null;
334 if (checkTokenBytes != null) {
335 checkToken = createCheckTokenOrThrow(checkTokenBytes);
336 }
337 mPackageTracker.recordCheckResult(checkToken, success);
338 }
339
340 private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
341 CheckToken checkToken;
342 try {
343 checkToken = CheckToken.fromByteArray(checkTokenBytes);
344 } catch (IOException e) {
345 throw new IllegalArgumentException("Unable to read token bytes "
346 + Arrays.toString(checkTokenBytes), e);
347 }
348 return checkToken;
349 }
350}