summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2019-03-26 19:11:34 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-03-26 19:11:34 +0000
commita908be90badb36dd33c5e62a77d94f55bf5231df (patch)
treee6f6488981f9b567d2227fc3d2aad6699d0ba60b
parentddd19b3537c58b854c6156f3abe4526573a5fb68 (diff)
parent0de99f36e2acd80b1e71d263df8713fa3a23f621 (diff)
Merge "Add ExplicitHealthCheckController"
-rw-r--r--services/core/java/com/android/server/ExplicitHealthCheckController.java266
1 files changed, 266 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
new file mode 100644
index 000000000000..f50364d70bc7
--- /dev/null
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.watchdog.ExplicitHealthCheckService;
+import android.service.watchdog.IExplicitHealthCheckService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Controls the connections with {@link ExplicitHealthCheckService}.
+ */
+class ExplicitHealthCheckController {
+ private static final String TAG = "ExplicitHealthCheckController";
+ private final Object mLock = new Object();
+ private final Context mContext;
+ @GuardedBy("mLock") @Nullable private StateCallback mStateCallback;
+ @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
+ @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+ @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
+
+ ExplicitHealthCheckController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Requests an explicit health check for {@code packageName}.
+ * After this request, the callback registered on {@link startService} can receive explicit
+ * health check passed results.
+ *
+ * @throws IllegalStateException if the service is not started
+ */
+ public void request(String packageName) throws RemoteException {
+ synchronized (mLock) {
+ enforceServiceReadyLocked();
+ mRemoteService.request(packageName);
+ }
+ }
+
+ /**
+ * Cancels all explicit health checks for {@code packageName}.
+ * After this request, the callback registered on {@link startService} can no longer receive
+ * explicit health check passed results.
+ *
+ * @throws IllegalStateException if the service is not started
+ */
+ public void cancel(String packageName) throws RemoteException {
+ synchronized (mLock) {
+ enforceServiceReadyLocked();
+ mRemoteService.cancel(packageName);
+ }
+ }
+
+ /**
+ * Returns the packages that we can request explicit health checks for.
+ * The packages will be returned to the {@code consumer}.
+ *
+ * @throws IllegalStateException if the service is not started
+ */
+ public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ synchronized (mLock) {
+ enforceServiceReadyLocked();
+ if (mSupportedPackages == null) {
+ mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
+ mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
+ consumer.accept(mSupportedPackages);
+ }));
+ } else {
+ consumer.accept(mSupportedPackages);
+ }
+ }
+ }
+
+ /**
+ * Returns the packages for which health checks are currently in progress.
+ * The packages will be returned to the {@code consumer}.
+ *
+ * @throws IllegalStateException if the service is not started
+ */
+ public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ synchronized (mLock) {
+ enforceServiceReadyLocked();
+ mRemoteService.getRequestedPackages(new RemoteCallback(
+ result -> consumer.accept(
+ result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
+ }
+ }
+
+ /**
+ * Starts the explicit health check service.
+ *
+ * @param stateCallback will receive important state changes changes
+ * @param passedConsumer will accept packages that pass explicit health checks
+ *
+ * @throws IllegalStateException if the service is already started
+ */
+ public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) {
+ synchronized (mLock) {
+ if (mRemoteService != null) {
+ throw new IllegalStateException("Explicit health check service already started.");
+ }
+ mStateCallback = stateCallback;
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
+ try {
+ mRemoteService.setCallback(new RemoteCallback(result -> {
+ String packageName =
+ result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
+ if (!TextUtils.isEmpty(packageName)) {
+ passedConsumer.accept(packageName);
+ } else {
+ Slog.w(TAG, "Empty package passed explicit health check?");
+ }
+ }));
+ mStateCallback.onStart();
+ Slog.i(TAG, "Explicit health check service is connected " + name);
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Coud not setCallback on explicit health check service");
+ }
+ }
+ }
+
+ @Override
+ @MainThread
+ public void onServiceDisconnected(ComponentName name) {
+ resetState();
+ Slog.i(TAG, "Explicit health check service is disconnected " + name);
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ resetState();
+ Slog.i(TAG, "Explicit health check service binding is dead " + name);
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ resetState();
+ Slog.i(TAG, "Explicit health check service binding is null " + name);
+ }
+ };
+
+ ComponentName component = getServiceComponentNameLocked();
+ if (component != null) {
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+ }
+ }
+
+ // TODO: Differentiate between expected vs unexpected stop?
+ /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */
+ abstract static class StateCallback {
+ /** The controller is ready and we can request explicit health checks for packages */
+ public void onStart() {}
+
+ /** The controller is not ready and we cannot request explicit health checks for packages */
+ public void onStop() {}
+ }
+
+ /** Stops the explicit health check service. */
+ public void stopService() {
+ synchronized (mLock) {
+ if (mRemoteService != null) {
+ mContext.unbindService(mConnection);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ServiceInfo getServiceInfoLocked() {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
+
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ComponentName getServiceComponentNameLocked() {
+ final ServiceInfo serviceInfo = getServiceInfoLocked();
+ if (serviceInfo == null) {
+ return null;
+ }
+
+ final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
+ .equals(serviceInfo.permission)) {
+ Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+ + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
+ return null;
+ }
+ return name;
+ }
+
+ private void resetState() {
+ synchronized (mLock) {
+ mStateCallback.onStop();
+ mStateCallback = null;
+ mSupportedPackages = null;
+ mRemoteService = null;
+ mConnection = null;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void enforceServiceReadyLocked() {
+ if (mRemoteService == null) {
+ throw new IllegalStateException("Explicit health check service not ready");
+ }
+ }
+}