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