blob: 27ad208ef8a1f382270f26de3e29c36fc3904975 [file] [log] [blame]
Zimuzo0de99f32019-03-18 13:23:55 +00001/*
2 * Copyright (C) 2019 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;
18import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
19import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
20import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
21
22import android.Manifest;
23import android.annotation.MainThread;
24import android.annotation.Nullable;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.pm.ServiceInfo;
32import android.os.IBinder;
33import android.os.RemoteCallback;
34import android.os.RemoteException;
35import android.os.UserHandle;
36import android.service.watchdog.ExplicitHealthCheckService;
37import android.service.watchdog.IExplicitHealthCheckService;
38import android.text.TextUtils;
39import android.util.Slog;
40
41import com.android.internal.annotations.GuardedBy;
Zimuzocaa435e2019-03-20 11:16:06 +000042import com.android.internal.util.Preconditions;
Zimuzo0de99f32019-03-18 13:23:55 +000043
Zimuzocb148b22019-04-01 18:54:17 +010044import java.util.Collection;
Zimuzocaa435e2019-03-20 11:16:06 +000045import java.util.Collections;
Zimuzocb148b22019-04-01 18:54:17 +010046import java.util.Iterator;
Zimuzo0de99f32019-03-18 13:23:55 +000047import java.util.List;
Zimuzocb148b22019-04-01 18:54:17 +010048import java.util.Set;
Zimuzo0de99f32019-03-18 13:23:55 +000049import java.util.function.Consumer;
50
Zimuzocb148b22019-04-01 18:54:17 +010051// TODO(b/120598832): Add tests
Zimuzo0de99f32019-03-18 13:23:55 +000052/**
53 * Controls the connections with {@link ExplicitHealthCheckService}.
54 */
55class ExplicitHealthCheckController {
56 private static final String TAG = "ExplicitHealthCheckController";
57 private final Object mLock = new Object();
58 private final Context mContext;
Zimuzocaa435e2019-03-20 11:16:06 +000059
Zimuzocaa435e2019-03-20 11:16:06 +000060 // Called everytime a package passes the health check, so the watchdog is notified of the
61 // passing check. In practice, should never be null after it has been #setEnabled.
Zimuzocb148b22019-04-01 18:54:17 +010062 // To prevent deadlocks between the controller and watchdog threads, we have
63 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
64 // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
Zimuzocaa435e2019-03-20 11:16:06 +000065 @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
Zimuzocb148b22019-04-01 18:54:17 +010066 // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
67 // supporting health checks and update its internal state. In practice, should never be null
68 // after it has been #setEnabled.
69 // To prevent deadlocks between the controller and watchdog threads, we have
70 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
71 // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
72 @GuardedBy("mLock") @Nullable private Consumer<List<String>> mSupportedConsumer;
73 // Called everytime we need to notify the watchdog to sync requests between itself and the
74 // health check service. In practice, should never be null after it has been #setEnabled.
75 // To prevent deadlocks between the controller and watchdog threads, we have
76 // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
77 // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
78 @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
Zimuzocaa435e2019-03-20 11:16:06 +000079 // Actual binder object to the explicit health check service.
Zimuzo0de99f32019-03-18 13:23:55 +000080 @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
Zimuzocb148b22019-04-01 18:54:17 +010081 // Connection to the explicit health check service, necessary to unbind.
82 // We should only try to bind if mConnection is null, non-null indicates we
83 // are connected or at least connecting.
Zimuzocaa435e2019-03-20 11:16:06 +000084 @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
85 // Bind state of the explicit health check service.
86 @GuardedBy("mLock") private boolean mEnabled;
Zimuzo0de99f32019-03-18 13:23:55 +000087
88 ExplicitHealthCheckController(Context context) {
89 mContext = context;
90 }
91
Zimuzocb148b22019-04-01 18:54:17 +010092 /** Enables or disables explicit health checks. */
93 public void setEnabled(boolean enabled) {
94 synchronized (mLock) {
95 Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
96 mEnabled = enabled;
97 }
98 }
99
100 /**
101 * Sets callbacks to listen to important events from the controller.
102 *
103 * <p> Should be called once at initialization before any other calls to the controller to
104 * ensure a happens-before relationship of the set parameters and visibility on other threads.
105 */
106 public void setCallbacks(Consumer<String> passedConsumer,
107 Consumer<List<String>> supportedConsumer, Runnable notifySyncRunnable) {
108 synchronized (mLock) {
109 if (mPassedConsumer != null || mSupportedConsumer != null
110 || mNotifySyncRunnable != null) {
111 Slog.wtf(TAG, "Resetting health check controller callbacks");
112 }
113
114 mPassedConsumer = Preconditions.checkNotNull(passedConsumer);
115 mSupportedConsumer = Preconditions.checkNotNull(supportedConsumer);
116 mNotifySyncRunnable = Preconditions.checkNotNull(notifySyncRunnable);
117 }
118 }
119
120 /**
121 * Calls the health check service to request or cancel packages based on
122 * {@code newRequestedPackages}.
123 *
124 * <p> Supported packages in {@code newRequestedPackages} that have not been previously
125 * requested will be requested while supported packages not in {@code newRequestedPackages}
126 * but were previously requested will be cancelled.
127 *
128 * <p> This handles binding and unbinding to the health check service as required.
129 *
130 * <p> Note, calling this may modify {@code newRequestedPackages}.
131 *
132 * <p> Note, this method is not thread safe, all calls should be serialized.
133 */
134 public void syncRequests(Set<String> newRequestedPackages) {
135 boolean enabled;
136 synchronized (mLock) {
137 enabled = mEnabled;
138 }
139
140 if (!enabled) {
141 Slog.i(TAG, "Health checks disabled, no supported packages");
142 // Call outside lock
143 mSupportedConsumer.accept(Collections.emptyList());
144 return;
145 }
146
147 getSupportedPackages(supportedPackages -> {
148 // Notify the watchdog without lock held
149 mSupportedConsumer.accept(supportedPackages);
150 getRequestedPackages(previousRequestedPackages -> {
151 synchronized (mLock) {
152 // Hold lock so requests and cancellations are sent atomically.
153 // It is important we don't mix requests from multiple threads.
154
155 // Note, this may modify newRequestedPackages
156 newRequestedPackages.retainAll(supportedPackages);
157
158 // Cancel packages no longer requested
159 actOnDifference(previousRequestedPackages,
160 newRequestedPackages, p -> cancel(p));
161 // Request packages not yet requested
162 actOnDifference(newRequestedPackages,
163 previousRequestedPackages, p -> request(p));
164
165 if (newRequestedPackages.isEmpty()) {
166 Slog.i(TAG, "No more health check requests, unbinding...");
167 unbindService();
168 return;
169 }
170 }
171 });
172 });
173 }
174
175 private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
176 Consumer<String> action) {
177 Iterator<String> iterator = collection1.iterator();
178 while (iterator.hasNext()) {
179 String packageName = iterator.next();
180 if (!collection2.contains(packageName)) {
181 action.accept(packageName);
182 }
183 }
184 }
185
Zimuzo0de99f32019-03-18 13:23:55 +0000186 /**
187 * Requests an explicit health check for {@code packageName}.
Zimuzocaa435e2019-03-20 11:16:06 +0000188 * After this request, the callback registered on {@link #setCallbacks} can receive explicit
Zimuzo0de99f32019-03-18 13:23:55 +0000189 * health check passed results.
Zimuzo0de99f32019-03-18 13:23:55 +0000190 */
Zimuzocb148b22019-04-01 18:54:17 +0100191 private void request(String packageName) {
Zimuzo0de99f32019-03-18 13:23:55 +0000192 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100193 if (!prepareServiceLocked("request health check for " + packageName)) {
Zimuzocaa435e2019-03-20 11:16:06 +0000194 return;
195 }
196
Zimuzocaa435e2019-03-20 11:16:06 +0000197 Slog.i(TAG, "Requesting health check for package " + packageName);
Zimuzocb148b22019-04-01 18:54:17 +0100198 try {
199 mRemoteService.request(packageName);
200 } catch (RemoteException e) {
201 Slog.w(TAG, "Failed to request health check for package " + packageName, e);
202 }
Zimuzo0de99f32019-03-18 13:23:55 +0000203 }
204 }
205
206 /**
207 * Cancels all explicit health checks for {@code packageName}.
Zimuzocaa435e2019-03-20 11:16:06 +0000208 * After this request, the callback registered on {@link #setCallbacks} can no longer receive
Zimuzo0de99f32019-03-18 13:23:55 +0000209 * explicit health check passed results.
Zimuzo0de99f32019-03-18 13:23:55 +0000210 */
Zimuzocb148b22019-04-01 18:54:17 +0100211 private void cancel(String packageName) {
Zimuzo0de99f32019-03-18 13:23:55 +0000212 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100213 if (!prepareServiceLocked("cancel health check for " + packageName)) {
Zimuzocaa435e2019-03-20 11:16:06 +0000214 return;
215 }
216
Zimuzocaa435e2019-03-20 11:16:06 +0000217 Slog.i(TAG, "Cancelling health check for package " + packageName);
Zimuzocb148b22019-04-01 18:54:17 +0100218 try {
219 mRemoteService.cancel(packageName);
220 } catch (RemoteException e) {
221 // Do nothing, if the service is down, when it comes up, we will sync requests,
222 // if there's some other error, retrying wouldn't fix anyways.
223 Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
224 }
Zimuzo0de99f32019-03-18 13:23:55 +0000225 }
226 }
227
228 /**
229 * Returns the packages that we can request explicit health checks for.
230 * The packages will be returned to the {@code consumer}.
Zimuzo0de99f32019-03-18 13:23:55 +0000231 */
Zimuzocb148b22019-04-01 18:54:17 +0100232 private void getSupportedPackages(Consumer<List<String>> consumer) {
Zimuzo0de99f32019-03-18 13:23:55 +0000233 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100234 if (!prepareServiceLocked("get health check supported packages")) {
Zimuzocaa435e2019-03-20 11:16:06 +0000235 return;
236 }
237
Zimuzocb148b22019-04-01 18:54:17 +0100238 Slog.d(TAG, "Getting health check supported packages");
239 try {
Zimuzo0de99f32019-03-18 13:23:55 +0000240 mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
Zimuzocb148b22019-04-01 18:54:17 +0100241 List<String> packages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
242 Slog.i(TAG, "Explicit health check supported packages " + packages);
243 consumer.accept(packages);
Zimuzo0de99f32019-03-18 13:23:55 +0000244 }));
Zimuzocb148b22019-04-01 18:54:17 +0100245 } catch (RemoteException e) {
246 // Request failed, treat as if all observed packages are supported, if any packages
247 // expire during this period, we may incorrectly treat it as failing health checks
248 // even if we don't support health checks for the package.
249 Slog.w(TAG, "Failed to get health check supported packages", e);
Zimuzo0de99f32019-03-18 13:23:55 +0000250 }
251 }
252 }
253
254 /**
255 * Returns the packages for which health checks are currently in progress.
256 * The packages will be returned to the {@code consumer}.
Zimuzo0de99f32019-03-18 13:23:55 +0000257 */
Zimuzocb148b22019-04-01 18:54:17 +0100258 private void getRequestedPackages(Consumer<List<String>> consumer) {
Zimuzo0de99f32019-03-18 13:23:55 +0000259 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100260 if (!prepareServiceLocked("get health check requested packages")) {
Zimuzocaa435e2019-03-20 11:16:06 +0000261 return;
262 }
263
Zimuzocaa435e2019-03-20 11:16:06 +0000264 Slog.d(TAG, "Getting health check requested packages");
Zimuzocb148b22019-04-01 18:54:17 +0100265 try {
266 mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
267 List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
268 Slog.i(TAG, "Explicit health check requested packages " + packages);
269 consumer.accept(packages);
270 }));
271 } catch (RemoteException e) {
272 // Request failed, treat as if we haven't requested any packages, if any packages
273 // were actually requested, they will not be cancelled now. May be cancelled later
274 Slog.w(TAG, "Failed to get health check requested packages", e);
Zimuzocaa435e2019-03-20 11:16:06 +0000275 }
276 }
277 }
278
Zimuzo0de99f32019-03-18 13:23:55 +0000279 /**
Zimuzocb148b22019-04-01 18:54:17 +0100280 * Binds to the explicit health check service if the controller is enabled and
281 * not already bound.
Zimuzo0de99f32019-03-18 13:23:55 +0000282 */
Zimuzocaa435e2019-03-20 11:16:06 +0000283 private void bindService() {
Zimuzo0de99f32019-03-18 13:23:55 +0000284 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100285 if (!mEnabled || mConnection != null || mRemoteService != null) {
286 if (!mEnabled) {
287 Slog.i(TAG, "Not binding to service, service disabled");
288 } else if (mRemoteService != null) {
289 Slog.i(TAG, "Not binding to service, service already connected");
290 } else {
291 Slog.i(TAG, "Not binding to service, service already connecting");
292 }
Zimuzocaa435e2019-03-20 11:16:06 +0000293 return;
Zimuzo0de99f32019-03-18 13:23:55 +0000294 }
Zimuzocaa435e2019-03-20 11:16:06 +0000295 ComponentName component = getServiceComponentNameLocked();
296 if (component == null) {
297 Slog.wtf(TAG, "Explicit health check service not found");
298 return;
299 }
300
301 Intent intent = new Intent();
302 intent.setComponent(component);
Zimuzo0de99f32019-03-18 13:23:55 +0000303 mConnection = new ServiceConnection() {
304 @Override
305 public void onServiceConnected(ComponentName name, IBinder service) {
Zimuzocaa435e2019-03-20 11:16:06 +0000306 Slog.i(TAG, "Explicit health check service is connected " + name);
Zimuzocb148b22019-04-01 18:54:17 +0100307 initState(service);
Zimuzo0de99f32019-03-18 13:23:55 +0000308 }
309
310 @Override
311 @MainThread
312 public void onServiceDisconnected(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000313 // Service crashed or process was killed, #onServiceConnected will be called.
314 // Don't need to re-bind.
Zimuzo0de99f32019-03-18 13:23:55 +0000315 Slog.i(TAG, "Explicit health check service is disconnected " + name);
Zimuzocb148b22019-04-01 18:54:17 +0100316 synchronized (mLock) {
317 mRemoteService = null;
318 }
Zimuzo0de99f32019-03-18 13:23:55 +0000319 }
320
321 @Override
322 public void onBindingDied(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000323 // Application hosting service probably got updated
324 // Need to re-bind.
Zimuzocb148b22019-04-01 18:54:17 +0100325 Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
326 unbindService();
327 bindService();
Zimuzo0de99f32019-03-18 13:23:55 +0000328 }
329
330 @Override
331 public void onNullBinding(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000332 // Should never happen. Service returned null from #onBind.
333 Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
Zimuzo0de99f32019-03-18 13:23:55 +0000334 }
335 };
336
Zimuzocb148b22019-04-01 18:54:17 +0100337 mContext.bindServiceAsUser(intent, mConnection,
338 Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM));
339 Slog.i(TAG, "Explicit health check service is bound");
Zimuzo0de99f32019-03-18 13:23:55 +0000340 }
341 }
342
Zimuzocaa435e2019-03-20 11:16:06 +0000343 /** Unbinds the explicit health check service. */
344 private void unbindService() {
Zimuzo0de99f32019-03-18 13:23:55 +0000345 synchronized (mLock) {
346 if (mRemoteService != null) {
347 mContext.unbindService(mConnection);
Zimuzocaa435e2019-03-20 11:16:06 +0000348 mRemoteService = null;
Zimuzocb148b22019-04-01 18:54:17 +0100349 mConnection = null;
Zimuzo0de99f32019-03-18 13:23:55 +0000350 }
Zimuzocb148b22019-04-01 18:54:17 +0100351 Slog.i(TAG, "Explicit health check service is unbound");
Zimuzo0de99f32019-03-18 13:23:55 +0000352 }
353 }
354
355 @GuardedBy("mLock")
356 @Nullable
357 private ServiceInfo getServiceInfoLocked() {
358 final String packageName =
359 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
360 if (packageName == null) {
361 Slog.w(TAG, "no external services package!");
362 return null;
363 }
364
365 final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
366 intent.setPackage(packageName);
367 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
368 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
369 if (resolveInfo == null || resolveInfo.serviceInfo == null) {
370 Slog.w(TAG, "No valid components found.");
371 return null;
372 }
373 return resolveInfo.serviceInfo;
374 }
375
376 @GuardedBy("mLock")
377 @Nullable
378 private ComponentName getServiceComponentNameLocked() {
379 final ServiceInfo serviceInfo = getServiceInfoLocked();
380 if (serviceInfo == null) {
381 return null;
382 }
383
384 final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
385 if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
386 .equals(serviceInfo.permission)) {
387 Slog.w(TAG, name.flattenToShortString() + " does not require permission "
388 + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
389 return null;
390 }
391 return name;
392 }
393
Zimuzocaa435e2019-03-20 11:16:06 +0000394 private void initState(IBinder service) {
Zimuzo0de99f32019-03-18 13:23:55 +0000395 synchronized (mLock) {
Zimuzocb148b22019-04-01 18:54:17 +0100396 if (!mEnabled) {
397 Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
398 // Very unlikely, but we disabled the service after binding but before we connected
399 unbindService();
400 return;
401 }
Zimuzocaa435e2019-03-20 11:16:06 +0000402 mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
403 try {
404 mRemoteService.setCallback(new RemoteCallback(result -> {
405 String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
406 if (!TextUtils.isEmpty(packageName)) {
Zimuzocb148b22019-04-01 18:54:17 +0100407 if (mPassedConsumer == null) {
408 Slog.wtf(TAG, "Health check passed for package " + packageName
409 + "but no consumer registered.");
410 } else {
411 // Call without lock held
412 mPassedConsumer.accept(packageName);
Zimuzocaa435e2019-03-20 11:16:06 +0000413 }
414 } else {
Zimuzocb148b22019-04-01 18:54:17 +0100415 Slog.wtf(TAG, "Empty package passed explicit health check?");
Zimuzocaa435e2019-03-20 11:16:06 +0000416 }
417 }));
Zimuzocb148b22019-04-01 18:54:17 +0100418 Slog.i(TAG, "Service initialized, syncing requests");
Zimuzocaa435e2019-03-20 11:16:06 +0000419 } catch (RemoteException e) {
420 Slog.wtf(TAG, "Could not setCallback on explicit health check service");
421 }
Zimuzo0de99f32019-03-18 13:23:55 +0000422 }
Zimuzocb148b22019-04-01 18:54:17 +0100423 // Calling outside lock
424 mNotifySyncRunnable.run();
Zimuzo0de99f32019-03-18 13:23:55 +0000425 }
426
Zimuzocb148b22019-04-01 18:54:17 +0100427 /**
428 * Prepares the health check service to receive requests.
429 *
430 * @return {@code true} if it is ready and we can proceed with a request,
431 * {@code false} otherwise. If it is not ready, and the service is enabled,
432 * we will bind and the request should be automatically attempted later.
433 */
Zimuzo0de99f32019-03-18 13:23:55 +0000434 @GuardedBy("mLock")
Zimuzocb148b22019-04-01 18:54:17 +0100435 private boolean prepareServiceLocked(String action) {
436 if (mRemoteService != null && mEnabled) {
437 return true;
Zimuzo0de99f32019-03-18 13:23:55 +0000438 }
Zimuzocb148b22019-04-01 18:54:17 +0100439 Slog.i(TAG, "Service not ready to " + action
440 + (mEnabled ? ". Binding..." : ". Disabled"));
441 if (mEnabled) {
442 bindService();
443 }
444 return false;
Zimuzo0de99f32019-03-18 13:23:55 +0000445 }
446}