Home | History | Annotate | Download | only in server
      1 /*
      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 
     17 package com.android.server;
     18 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
     19 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
     20 import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
     21 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
     22 
     23 import android.Manifest;
     24 import android.annotation.MainThread;
     25 import android.annotation.Nullable;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.ServiceConnection;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.ResolveInfo;
     32 import android.content.pm.ServiceInfo;
     33 import android.os.IBinder;
     34 import android.os.RemoteCallback;
     35 import android.os.RemoteException;
     36 import android.os.UserHandle;
     37 import android.service.watchdog.ExplicitHealthCheckService;
     38 import android.service.watchdog.IExplicitHealthCheckService;
     39 import android.text.TextUtils;
     40 import android.util.ArraySet;
     41 import android.util.Slog;
     42 
     43 import com.android.internal.annotations.GuardedBy;
     44 import com.android.internal.util.Preconditions;
     45 
     46 import java.util.Collection;
     47 import java.util.Collections;
     48 import java.util.Iterator;
     49 import java.util.List;
     50 import java.util.Set;
     51 import java.util.function.Consumer;
     52 
     53 // TODO(b/120598832): Add tests
     54 /**
     55  * Controls the connections with {@link ExplicitHealthCheckService}.
     56  */
     57 class ExplicitHealthCheckController {
     58     private static final String TAG = "ExplicitHealthCheckController";
     59     private final Object mLock = new Object();
     60     private final Context mContext;
     61 
     62     // Called everytime a package passes the health check, so the watchdog is notified of the
     63     // passing check. In practice, should never be null after it has been #setEnabled.
     64     // To prevent deadlocks between the controller and watchdog threads, we have
     65     // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
     66     // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
     67     @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
     68     // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
     69     // supporting health checks and update its internal state. In practice, should never be null
     70     // after it has been #setEnabled.
     71     // To prevent deadlocks between the controller and watchdog threads, we have
     72     // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
     73     // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
     74     @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
     75     // Called everytime we need to notify the watchdog to sync requests between itself and the
     76     // health check service. In practice, should never be null after it has been #setEnabled.
     77     // To prevent deadlocks between the controller and watchdog threads, we have
     78     // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
     79     // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
     80     @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
     81     // Actual binder object to the explicit health check service.
     82     @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
     83     // Connection to the explicit health check service, necessary to unbind.
     84     // We should only try to bind if mConnection is null, non-null indicates we
     85     // are connected or at least connecting.
     86     @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
     87     // Bind state of the explicit health check service.
     88     @GuardedBy("mLock") private boolean mEnabled;
     89 
     90     ExplicitHealthCheckController(Context context) {
     91         mContext = context;
     92     }
     93 
     94     /** Enables or disables explicit health checks. */
     95     public void setEnabled(boolean enabled) {
     96         synchronized (mLock) {
     97             Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
     98             mEnabled = enabled;
     99         }
    100     }
    101 
    102     /**
    103      * Sets callbacks to listen to important events from the controller.
    104      *
    105      * <p> Should be called once at initialization before any other calls to the controller to
    106      * ensure a happens-before relationship of the set parameters and visibility on other threads.
    107      */
    108     public void setCallbacks(Consumer<String> passedConsumer,
    109             Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
    110         synchronized (mLock) {
    111             if (mPassedConsumer != null || mSupportedConsumer != null
    112                     || mNotifySyncRunnable != null) {
    113                 Slog.wtf(TAG, "Resetting health check controller callbacks");
    114             }
    115 
    116             mPassedConsumer = Preconditions.checkNotNull(passedConsumer);
    117             mSupportedConsumer = Preconditions.checkNotNull(supportedConsumer);
    118             mNotifySyncRunnable = Preconditions.checkNotNull(notifySyncRunnable);
    119         }
    120     }
    121 
    122     /**
    123      * Calls the health check service to request or cancel packages based on
    124      * {@code newRequestedPackages}.
    125      *
    126      * <p> Supported packages in {@code newRequestedPackages} that have not been previously
    127      * requested will be requested while supported packages not in {@code newRequestedPackages}
    128      * but were previously requested will be cancelled.
    129      *
    130      * <p> This handles binding and unbinding to the health check service as required.
    131      *
    132      * <p> Note, calling this may modify {@code newRequestedPackages}.
    133      *
    134      * <p> Note, this method is not thread safe, all calls should be serialized.
    135      */
    136     public void syncRequests(Set<String> newRequestedPackages) {
    137         boolean enabled;
    138         synchronized (mLock) {
    139             enabled = mEnabled;
    140         }
    141 
    142         if (!enabled) {
    143             Slog.i(TAG, "Health checks disabled, no supported packages");
    144             // Call outside lock
    145             mSupportedConsumer.accept(Collections.emptyList());
    146             return;
    147         }
    148 
    149         getSupportedPackages(supportedPackageConfigs -> {
    150             // Notify the watchdog without lock held
    151             mSupportedConsumer.accept(supportedPackageConfigs);
    152             getRequestedPackages(previousRequestedPackages -> {
    153                 synchronized (mLock) {
    154                     // Hold lock so requests and cancellations are sent atomically.
    155                     // It is important we don't mix requests from multiple threads.
    156 
    157                     Set<String> supportedPackages = new ArraySet<>();
    158                     for (PackageConfig config : supportedPackageConfigs) {
    159                         supportedPackages.add(config.getPackageName());
    160                     }
    161                     // Note, this may modify newRequestedPackages
    162                     newRequestedPackages.retainAll(supportedPackages);
    163 
    164                     // Cancel packages no longer requested
    165                     actOnDifference(previousRequestedPackages,
    166                             newRequestedPackages, p -> cancel(p));
    167                     // Request packages not yet requested
    168                     actOnDifference(newRequestedPackages,
    169                             previousRequestedPackages, p -> request(p));
    170 
    171                     if (newRequestedPackages.isEmpty()) {
    172                         Slog.i(TAG, "No more health check requests, unbinding...");
    173                         unbindService();
    174                         return;
    175                     }
    176                 }
    177             });
    178         });
    179     }
    180 
    181     private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
    182             Consumer<String> action) {
    183         Iterator<String> iterator = collection1.iterator();
    184         while (iterator.hasNext()) {
    185             String packageName = iterator.next();
    186             if (!collection2.contains(packageName)) {
    187                 action.accept(packageName);
    188             }
    189         }
    190     }
    191 
    192     /**
    193      * Requests an explicit health check for {@code packageName}.
    194      * After this request, the callback registered on {@link #setCallbacks} can receive explicit
    195      * health check passed results.
    196      */
    197     private void request(String packageName) {
    198         synchronized (mLock) {
    199             if (!prepareServiceLocked("request health check for " + packageName)) {
    200                 return;
    201             }
    202 
    203             Slog.i(TAG, "Requesting health check for package " + packageName);
    204             try {
    205                 mRemoteService.request(packageName);
    206             } catch (RemoteException e) {
    207                 Slog.w(TAG, "Failed to request health check for package " + packageName, e);
    208             }
    209         }
    210     }
    211 
    212     /**
    213      * Cancels all explicit health checks for {@code packageName}.
    214      * After this request, the callback registered on {@link #setCallbacks} can no longer receive
    215      * explicit health check passed results.
    216      */
    217     private void cancel(String packageName) {
    218         synchronized (mLock) {
    219             if (!prepareServiceLocked("cancel health check for " + packageName)) {
    220                 return;
    221             }
    222 
    223             Slog.i(TAG, "Cancelling health check for package " + packageName);
    224             try {
    225                 mRemoteService.cancel(packageName);
    226             } catch (RemoteException e) {
    227                 // Do nothing, if the service is down, when it comes up, we will sync requests,
    228                 // if there's some other error, retrying wouldn't fix anyways.
    229                 Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
    230             }
    231         }
    232     }
    233 
    234     /**
    235      * Returns the packages that we can request explicit health checks for.
    236      * The packages will be returned to the {@code consumer}.
    237      */
    238     private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
    239         synchronized (mLock) {
    240             if (!prepareServiceLocked("get health check supported packages")) {
    241                 return;
    242             }
    243 
    244             Slog.d(TAG, "Getting health check supported packages");
    245             try {
    246                 mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
    247                     List<PackageConfig> packages =
    248                             result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES);
    249                     Slog.i(TAG, "Explicit health check supported packages " + packages);
    250                     consumer.accept(packages);
    251                 }));
    252             } catch (RemoteException e) {
    253                 // Request failed, treat as if all observed packages are supported, if any packages
    254                 // expire during this period, we may incorrectly treat it as failing health checks
    255                 // even if we don't support health checks for the package.
    256                 Slog.w(TAG, "Failed to get health check supported packages", e);
    257             }
    258         }
    259     }
    260 
    261     /**
    262      * Returns the packages for which health checks are currently in progress.
    263      * The packages will be returned to the {@code consumer}.
    264      */
    265     private void getRequestedPackages(Consumer<List<String>> consumer) {
    266         synchronized (mLock) {
    267             if (!prepareServiceLocked("get health check requested packages")) {
    268                 return;
    269             }
    270 
    271             Slog.d(TAG, "Getting health check requested packages");
    272             try {
    273                 mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
    274                     List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
    275                     Slog.i(TAG, "Explicit health check requested packages " + packages);
    276                     consumer.accept(packages);
    277                 }));
    278             } catch (RemoteException e) {
    279                 // Request failed, treat as if we haven't requested any packages, if any packages
    280                 // were actually requested, they will not be cancelled now. May be cancelled later
    281                 Slog.w(TAG, "Failed to get health check requested packages", e);
    282             }
    283         }
    284     }
    285 
    286     /**
    287      * Binds to the explicit health check service if the controller is enabled and
    288      * not already bound.
    289      */
    290     private void bindService() {
    291         synchronized (mLock) {
    292             if (!mEnabled || mConnection != null || mRemoteService != null) {
    293                 if (!mEnabled) {
    294                     Slog.i(TAG, "Not binding to service, service disabled");
    295                 } else if (mRemoteService != null) {
    296                     Slog.i(TAG, "Not binding to service, service already connected");
    297                 } else {
    298                     Slog.i(TAG, "Not binding to service, service already connecting");
    299                 }
    300                 return;
    301             }
    302             ComponentName component = getServiceComponentNameLocked();
    303             if (component == null) {
    304                 Slog.wtf(TAG, "Explicit health check service not found");
    305                 return;
    306             }
    307 
    308             Intent intent = new Intent();
    309             intent.setComponent(component);
    310             mConnection = new ServiceConnection() {
    311                 @Override
    312                 public void onServiceConnected(ComponentName name, IBinder service) {
    313                     Slog.i(TAG, "Explicit health check service is connected " + name);
    314                     initState(service);
    315                 }
    316 
    317                 @Override
    318                 @MainThread
    319                 public void onServiceDisconnected(ComponentName name) {
    320                     // Service crashed or process was killed, #onServiceConnected will be called.
    321                     // Don't need to re-bind.
    322                     Slog.i(TAG, "Explicit health check service is disconnected " + name);
    323                     synchronized (mLock) {
    324                         mRemoteService = null;
    325                     }
    326                 }
    327 
    328                 @Override
    329                 public void onBindingDied(ComponentName name) {
    330                     // Application hosting service probably got updated
    331                     // Need to re-bind.
    332                     Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
    333                     unbindService();
    334                     bindService();
    335                 }
    336 
    337                 @Override
    338                 public void onNullBinding(ComponentName name) {
    339                     // Should never happen. Service returned null from #onBind.
    340                     Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
    341                 }
    342             };
    343 
    344             mContext.bindServiceAsUser(intent, mConnection,
    345                     Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM));
    346             Slog.i(TAG, "Explicit health check service is bound");
    347         }
    348     }
    349 
    350     /** Unbinds the explicit health check service. */
    351     private void unbindService() {
    352         synchronized (mLock) {
    353             if (mRemoteService != null) {
    354                 mContext.unbindService(mConnection);
    355                 mRemoteService = null;
    356                 mConnection = null;
    357             }
    358             Slog.i(TAG, "Explicit health check service is unbound");
    359         }
    360     }
    361 
    362     @GuardedBy("mLock")
    363     @Nullable
    364     private ServiceInfo getServiceInfoLocked() {
    365         final String packageName =
    366                 mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
    367         if (packageName == null) {
    368             Slog.w(TAG, "no external services package!");
    369             return null;
    370         }
    371 
    372         final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
    373         intent.setPackage(packageName);
    374         final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
    375                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
    376         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
    377             Slog.w(TAG, "No valid components found.");
    378             return null;
    379         }
    380         return resolveInfo.serviceInfo;
    381     }
    382 
    383     @GuardedBy("mLock")
    384     @Nullable
    385     private ComponentName getServiceComponentNameLocked() {
    386         final ServiceInfo serviceInfo = getServiceInfoLocked();
    387         if (serviceInfo == null) {
    388             return null;
    389         }
    390 
    391         final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
    392         if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
    393                 .equals(serviceInfo.permission)) {
    394             Slog.w(TAG, name.flattenToShortString() + " does not require permission "
    395                     + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
    396             return null;
    397         }
    398         return name;
    399     }
    400 
    401     private void initState(IBinder service) {
    402         synchronized (mLock) {
    403             if (!mEnabled) {
    404                 Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
    405                 // Very unlikely, but we disabled the service after binding but before we connected
    406                 unbindService();
    407                 return;
    408             }
    409             mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
    410             try {
    411                 mRemoteService.setCallback(new RemoteCallback(result -> {
    412                     String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
    413                     if (!TextUtils.isEmpty(packageName)) {
    414                         if (mPassedConsumer == null) {
    415                             Slog.wtf(TAG, "Health check passed for package " + packageName
    416                                     + "but no consumer registered.");
    417                         } else {
    418                             // Call without lock held
    419                             mPassedConsumer.accept(packageName);
    420                         }
    421                     } else {
    422                         Slog.wtf(TAG, "Empty package passed explicit health check?");
    423                     }
    424                 }));
    425                 Slog.i(TAG, "Service initialized, syncing requests");
    426             } catch (RemoteException e) {
    427                 Slog.wtf(TAG, "Could not setCallback on explicit health check service");
    428             }
    429         }
    430         // Calling outside lock
    431         mNotifySyncRunnable.run();
    432     }
    433 
    434     /**
    435      * Prepares the health check service to receive requests.
    436      *
    437      * @return {@code true} if it is ready and we can proceed with a request,
    438      * {@code false} otherwise. If it is not ready, and the service is enabled,
    439      * we will bind and the request should be automatically attempted later.
    440      */
    441     @GuardedBy("mLock")
    442     private boolean prepareServiceLocked(String action) {
    443         if (mRemoteService != null && mEnabled) {
    444             return true;
    445         }
    446         Slog.i(TAG, "Service not ready to " + action
    447                 + (mEnabled ? ". Binding..." : ". Disabled"));
    448         if (mEnabled) {
    449             bindService();
    450         }
    451         return false;
    452     }
    453 }
    454