blob: f50364d70bc786795d3c5f31f398a5a0c153f354 [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;
42
43import java.util.List;
44import java.util.function.Consumer;
45
46/**
47 * Controls the connections with {@link ExplicitHealthCheckService}.
48 */
49class ExplicitHealthCheckController {
50 private static final String TAG = "ExplicitHealthCheckController";
51 private final Object mLock = new Object();
52 private final Context mContext;
53 @GuardedBy("mLock") @Nullable private StateCallback mStateCallback;
54 @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
55 @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
56 @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
57
58 ExplicitHealthCheckController(Context context) {
59 mContext = context;
60 }
61
62 /**
63 * Requests an explicit health check for {@code packageName}.
64 * After this request, the callback registered on {@link startService} can receive explicit
65 * health check passed results.
66 *
67 * @throws IllegalStateException if the service is not started
68 */
69 public void request(String packageName) throws RemoteException {
70 synchronized (mLock) {
71 enforceServiceReadyLocked();
72 mRemoteService.request(packageName);
73 }
74 }
75
76 /**
77 * Cancels all explicit health checks for {@code packageName}.
78 * After this request, the callback registered on {@link startService} can no longer receive
79 * explicit health check passed results.
80 *
81 * @throws IllegalStateException if the service is not started
82 */
83 public void cancel(String packageName) throws RemoteException {
84 synchronized (mLock) {
85 enforceServiceReadyLocked();
86 mRemoteService.cancel(packageName);
87 }
88 }
89
90 /**
91 * Returns the packages that we can request explicit health checks for.
92 * The packages will be returned to the {@code consumer}.
93 *
94 * @throws IllegalStateException if the service is not started
95 */
96 public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
97 synchronized (mLock) {
98 enforceServiceReadyLocked();
99 if (mSupportedPackages == null) {
100 mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
101 mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
102 consumer.accept(mSupportedPackages);
103 }));
104 } else {
105 consumer.accept(mSupportedPackages);
106 }
107 }
108 }
109
110 /**
111 * Returns the packages for which health checks are currently in progress.
112 * The packages will be returned to the {@code consumer}.
113 *
114 * @throws IllegalStateException if the service is not started
115 */
116 public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
117 synchronized (mLock) {
118 enforceServiceReadyLocked();
119 mRemoteService.getRequestedPackages(new RemoteCallback(
120 result -> consumer.accept(
121 result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
122 }
123 }
124
125 /**
126 * Starts the explicit health check service.
127 *
128 * @param stateCallback will receive important state changes changes
129 * @param passedConsumer will accept packages that pass explicit health checks
130 *
131 * @throws IllegalStateException if the service is already started
132 */
133 public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) {
134 synchronized (mLock) {
135 if (mRemoteService != null) {
136 throw new IllegalStateException("Explicit health check service already started.");
137 }
138 mStateCallback = stateCallback;
139 mConnection = new ServiceConnection() {
140 @Override
141 public void onServiceConnected(ComponentName name, IBinder service) {
142 synchronized (mLock) {
143 mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
144 try {
145 mRemoteService.setCallback(new RemoteCallback(result -> {
146 String packageName =
147 result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
148 if (!TextUtils.isEmpty(packageName)) {
149 passedConsumer.accept(packageName);
150 } else {
151 Slog.w(TAG, "Empty package passed explicit health check?");
152 }
153 }));
154 mStateCallback.onStart();
155 Slog.i(TAG, "Explicit health check service is connected " + name);
156 } catch (RemoteException e) {
157 Slog.wtf(TAG, "Coud not setCallback on explicit health check service");
158 }
159 }
160 }
161
162 @Override
163 @MainThread
164 public void onServiceDisconnected(ComponentName name) {
165 resetState();
166 Slog.i(TAG, "Explicit health check service is disconnected " + name);
167 }
168
169 @Override
170 public void onBindingDied(ComponentName name) {
171 resetState();
172 Slog.i(TAG, "Explicit health check service binding is dead " + name);
173 }
174
175 @Override
176 public void onNullBinding(ComponentName name) {
177 resetState();
178 Slog.i(TAG, "Explicit health check service binding is null " + name);
179 }
180 };
181
182 ComponentName component = getServiceComponentNameLocked();
183 if (component != null) {
184 Intent intent = new Intent();
185 intent.setComponent(component);
186 mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
187 UserHandle.of(UserHandle.USER_SYSTEM));
188 }
189 }
190 }
191
192 // TODO: Differentiate between expected vs unexpected stop?
193 /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */
194 abstract static class StateCallback {
195 /** The controller is ready and we can request explicit health checks for packages */
196 public void onStart() {}
197
198 /** The controller is not ready and we cannot request explicit health checks for packages */
199 public void onStop() {}
200 }
201
202 /** Stops the explicit health check service. */
203 public void stopService() {
204 synchronized (mLock) {
205 if (mRemoteService != null) {
206 mContext.unbindService(mConnection);
207 }
208 }
209 }
210
211 @GuardedBy("mLock")
212 @Nullable
213 private ServiceInfo getServiceInfoLocked() {
214 final String packageName =
215 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
216 if (packageName == null) {
217 Slog.w(TAG, "no external services package!");
218 return null;
219 }
220
221 final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
222 intent.setPackage(packageName);
223 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
224 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
225 if (resolveInfo == null || resolveInfo.serviceInfo == null) {
226 Slog.w(TAG, "No valid components found.");
227 return null;
228 }
229 return resolveInfo.serviceInfo;
230 }
231
232 @GuardedBy("mLock")
233 @Nullable
234 private ComponentName getServiceComponentNameLocked() {
235 final ServiceInfo serviceInfo = getServiceInfoLocked();
236 if (serviceInfo == null) {
237 return null;
238 }
239
240 final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
241 if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
242 .equals(serviceInfo.permission)) {
243 Slog.w(TAG, name.flattenToShortString() + " does not require permission "
244 + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
245 return null;
246 }
247 return name;
248 }
249
250 private void resetState() {
251 synchronized (mLock) {
252 mStateCallback.onStop();
253 mStateCallback = null;
254 mSupportedPackages = null;
255 mRemoteService = null;
256 mConnection = null;
257 }
258 }
259
260 @GuardedBy("mLock")
261 private void enforceServiceReadyLocked() {
262 if (mRemoteService == null) {
263 throw new IllegalStateException("Explicit health check service not ready");
264 }
265 }
266}