blob: 164837ab98dd8654a759978368fad2deea75e322 [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
Zimuzocaa435e2019-03-20 11:16:06 +000044import java.util.Collections;
Zimuzo0de99f32019-03-18 13:23:55 +000045import java.util.List;
46import java.util.function.Consumer;
47
48/**
49 * Controls the connections with {@link ExplicitHealthCheckService}.
50 */
51class ExplicitHealthCheckController {
52 private static final String TAG = "ExplicitHealthCheckController";
53 private final Object mLock = new Object();
54 private final Context mContext;
Zimuzocaa435e2019-03-20 11:16:06 +000055
56 // Called everytime the service is connected, so the watchdog can sync it's state with
57 // the health check service. In practice, should never be null after it has been #setEnabled.
58 @GuardedBy("mLock") @Nullable private Runnable mOnConnected;
59 // Called everytime a package passes the health check, so the watchdog is notified of the
60 // passing check. In practice, should never be null after it has been #setEnabled.
61 @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
62 // Actual binder object to the explicit health check service.
Zimuzo0de99f32019-03-18 13:23:55 +000063 @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
Zimuzocaa435e2019-03-20 11:16:06 +000064 // Cache for packages supporting explicit health checks. This cache should not change while
65 // the health check service is running.
Zimuzo0de99f32019-03-18 13:23:55 +000066 @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
Zimuzocaa435e2019-03-20 11:16:06 +000067 // Connection to the explicit health check service, necessary to unbind
68 @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
69 // Bind state of the explicit health check service.
70 @GuardedBy("mLock") private boolean mEnabled;
Zimuzo0de99f32019-03-18 13:23:55 +000071
72 ExplicitHealthCheckController(Context context) {
73 mContext = context;
74 }
75
76 /**
77 * Requests an explicit health check for {@code packageName}.
Zimuzocaa435e2019-03-20 11:16:06 +000078 * After this request, the callback registered on {@link #setCallbacks} can receive explicit
Zimuzo0de99f32019-03-18 13:23:55 +000079 * health check passed results.
80 *
81 * @throws IllegalStateException if the service is not started
82 */
83 public void request(String packageName) throws RemoteException {
84 synchronized (mLock) {
Zimuzocaa435e2019-03-20 11:16:06 +000085 if (!mEnabled) {
86 return;
87 }
88
Zimuzo0de99f32019-03-18 13:23:55 +000089 enforceServiceReadyLocked();
Zimuzocaa435e2019-03-20 11:16:06 +000090
91 Slog.i(TAG, "Requesting health check for package " + packageName);
Zimuzo0de99f32019-03-18 13:23:55 +000092 mRemoteService.request(packageName);
93 }
94 }
95
96 /**
97 * Cancels all explicit health checks for {@code packageName}.
Zimuzocaa435e2019-03-20 11:16:06 +000098 * After this request, the callback registered on {@link #setCallbacks} can no longer receive
Zimuzo0de99f32019-03-18 13:23:55 +000099 * explicit health check passed results.
100 *
101 * @throws IllegalStateException if the service is not started
102 */
103 public void cancel(String packageName) throws RemoteException {
104 synchronized (mLock) {
Zimuzocaa435e2019-03-20 11:16:06 +0000105 if (!mEnabled) {
106 return;
107 }
108
Zimuzo0de99f32019-03-18 13:23:55 +0000109 enforceServiceReadyLocked();
Zimuzocaa435e2019-03-20 11:16:06 +0000110
111 Slog.i(TAG, "Cancelling health check for package " + packageName);
Zimuzo0de99f32019-03-18 13:23:55 +0000112 mRemoteService.cancel(packageName);
113 }
114 }
115
116 /**
117 * Returns the packages that we can request explicit health checks for.
118 * The packages will be returned to the {@code consumer}.
119 *
120 * @throws IllegalStateException if the service is not started
121 */
122 public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
123 synchronized (mLock) {
Zimuzocaa435e2019-03-20 11:16:06 +0000124 if (!mEnabled) {
125 consumer.accept(Collections.emptyList());
126 return;
127 }
128
Zimuzo0de99f32019-03-18 13:23:55 +0000129 enforceServiceReadyLocked();
Zimuzocaa435e2019-03-20 11:16:06 +0000130
Zimuzo0de99f32019-03-18 13:23:55 +0000131 if (mSupportedPackages == null) {
Zimuzocaa435e2019-03-20 11:16:06 +0000132 Slog.d(TAG, "Getting health check supported packages");
Zimuzo0de99f32019-03-18 13:23:55 +0000133 mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
134 mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
135 consumer.accept(mSupportedPackages);
136 }));
137 } else {
Zimuzocaa435e2019-03-20 11:16:06 +0000138 Slog.d(TAG, "Getting cached health check supported packages");
Zimuzo0de99f32019-03-18 13:23:55 +0000139 consumer.accept(mSupportedPackages);
140 }
141 }
142 }
143
144 /**
145 * Returns the packages for which health checks are currently in progress.
146 * The packages will be returned to the {@code consumer}.
147 *
148 * @throws IllegalStateException if the service is not started
149 */
150 public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
151 synchronized (mLock) {
Zimuzocaa435e2019-03-20 11:16:06 +0000152 if (!mEnabled) {
153 consumer.accept(Collections.emptyList());
154 return;
155 }
156
Zimuzo0de99f32019-03-18 13:23:55 +0000157 enforceServiceReadyLocked();
Zimuzocaa435e2019-03-20 11:16:06 +0000158
159 Slog.d(TAG, "Getting health check requested packages");
Zimuzo0de99f32019-03-18 13:23:55 +0000160 mRemoteService.getRequestedPackages(new RemoteCallback(
161 result -> consumer.accept(
162 result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
163 }
164 }
165
Zimuzocaa435e2019-03-20 11:16:06 +0000166 /** Enables or disables explicit health checks. */
167 public void setEnabled(boolean enabled) {
168 synchronized (mLock) {
169 if (enabled == mEnabled) {
170 return;
171 }
172
173 Slog.i(TAG, "Setting explicit health checks enabled " + enabled);
174 mEnabled = enabled;
175 if (enabled) {
176 bindService();
177 } else {
178 unbindService();
179 }
180 }
181 }
182
Zimuzo0de99f32019-03-18 13:23:55 +0000183 /**
Zimuzocaa435e2019-03-20 11:16:06 +0000184 * Sets callbacks to listen to important events from the controller.
185 * Should be called at initialization.
Zimuzo0de99f32019-03-18 13:23:55 +0000186 */
Zimuzocaa435e2019-03-20 11:16:06 +0000187 public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) {
188 Preconditions.checkNotNull(onConnected);
189 Preconditions.checkNotNull(passedConsumer);
190 mOnConnected = onConnected;
191 mPassedConsumer = passedConsumer;
192 }
193
194 /** Binds to the explicit health check service. */
195 private void bindService() {
Zimuzo0de99f32019-03-18 13:23:55 +0000196 synchronized (mLock) {
197 if (mRemoteService != null) {
Zimuzocaa435e2019-03-20 11:16:06 +0000198 return;
Zimuzo0de99f32019-03-18 13:23:55 +0000199 }
Zimuzocaa435e2019-03-20 11:16:06 +0000200 ComponentName component = getServiceComponentNameLocked();
201 if (component == null) {
202 Slog.wtf(TAG, "Explicit health check service not found");
203 return;
204 }
205
206 Intent intent = new Intent();
207 intent.setComponent(component);
208 // TODO: Fix potential race conditions during mConnection state transitions.
209 // E.g after #onServiceDisconected, the mRemoteService object is invalid until
210 // we get an #onServiceConnected.
Zimuzo0de99f32019-03-18 13:23:55 +0000211 mConnection = new ServiceConnection() {
212 @Override
213 public void onServiceConnected(ComponentName name, IBinder service) {
Zimuzocaa435e2019-03-20 11:16:06 +0000214 initState(service);
215 Slog.i(TAG, "Explicit health check service is connected " + name);
Zimuzo0de99f32019-03-18 13:23:55 +0000216 }
217
218 @Override
219 @MainThread
220 public void onServiceDisconnected(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000221 // Service crashed or process was killed, #onServiceConnected will be called.
222 // Don't need to re-bind.
Zimuzo0de99f32019-03-18 13:23:55 +0000223 Slog.i(TAG, "Explicit health check service is disconnected " + name);
224 }
225
226 @Override
227 public void onBindingDied(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000228 // Application hosting service probably got updated
229 // Need to re-bind.
230 synchronized (mLock) {
231 if (mEnabled) {
232 unbindService();
233 bindService();
234 }
235 }
Zimuzo0de99f32019-03-18 13:23:55 +0000236 Slog.i(TAG, "Explicit health check service binding is dead " + name);
237 }
238
239 @Override
240 public void onNullBinding(ComponentName name) {
Zimuzocaa435e2019-03-20 11:16:06 +0000241 // Should never happen. Service returned null from #onBind.
242 Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
Zimuzo0de99f32019-03-18 13:23:55 +0000243 }
244 };
245
Zimuzocaa435e2019-03-20 11:16:06 +0000246 Slog.i(TAG, "Binding to explicit health service");
247 mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
248 UserHandle.of(UserHandle.USER_SYSTEM));
Zimuzo0de99f32019-03-18 13:23:55 +0000249 }
250 }
251
Zimuzocaa435e2019-03-20 11:16:06 +0000252 /** Unbinds the explicit health check service. */
253 private void unbindService() {
Zimuzo0de99f32019-03-18 13:23:55 +0000254 synchronized (mLock) {
255 if (mRemoteService != null) {
Zimuzocaa435e2019-03-20 11:16:06 +0000256 Slog.i(TAG, "Unbinding from explicit health service");
Zimuzo0de99f32019-03-18 13:23:55 +0000257 mContext.unbindService(mConnection);
Zimuzocaa435e2019-03-20 11:16:06 +0000258 mRemoteService = null;
Zimuzo0de99f32019-03-18 13:23:55 +0000259 }
260 }
261 }
262
263 @GuardedBy("mLock")
264 @Nullable
265 private ServiceInfo getServiceInfoLocked() {
266 final String packageName =
267 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
268 if (packageName == null) {
269 Slog.w(TAG, "no external services package!");
270 return null;
271 }
272
273 final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
274 intent.setPackage(packageName);
275 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
276 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
277 if (resolveInfo == null || resolveInfo.serviceInfo == null) {
278 Slog.w(TAG, "No valid components found.");
279 return null;
280 }
281 return resolveInfo.serviceInfo;
282 }
283
284 @GuardedBy("mLock")
285 @Nullable
286 private ComponentName getServiceComponentNameLocked() {
287 final ServiceInfo serviceInfo = getServiceInfoLocked();
288 if (serviceInfo == null) {
289 return null;
290 }
291
292 final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
293 if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
294 .equals(serviceInfo.permission)) {
295 Slog.w(TAG, name.flattenToShortString() + " does not require permission "
296 + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
297 return null;
298 }
299 return name;
300 }
301
Zimuzocaa435e2019-03-20 11:16:06 +0000302 private void initState(IBinder service) {
Zimuzo0de99f32019-03-18 13:23:55 +0000303 synchronized (mLock) {
Zimuzo0de99f32019-03-18 13:23:55 +0000304 mSupportedPackages = null;
Zimuzocaa435e2019-03-20 11:16:06 +0000305 mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
306 try {
307 mRemoteService.setCallback(new RemoteCallback(result -> {
308 String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
309 if (!TextUtils.isEmpty(packageName)) {
310 synchronized (mLock) {
311 if (mPassedConsumer == null) {
312 Slog.w(TAG, "Health check passed for package " + packageName
313 + "but no consumer registered.");
314 } else {
315 mPassedConsumer.accept(packageName);
316 }
317 }
318 } else {
319 Slog.w(TAG, "Empty package passed explicit health check?");
320 }
321 }));
322 if (mOnConnected == null) {
323 Slog.w(TAG, "Health check service connected but no runnable registered.");
324 } else {
325 mOnConnected.run();
326 }
327 } catch (RemoteException e) {
328 Slog.wtf(TAG, "Could not setCallback on explicit health check service");
329 }
Zimuzo0de99f32019-03-18 13:23:55 +0000330 }
331 }
332
333 @GuardedBy("mLock")
334 private void enforceServiceReadyLocked() {
335 if (mRemoteService == null) {
Zimuzocaa435e2019-03-20 11:16:06 +0000336 // TODO: Try to bind to service
Zimuzo0de99f32019-03-18 13:23:55 +0000337 throw new IllegalStateException("Explicit health check service not ready");
338 }
339 }
340}