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 android.ext.services.watchdog;
18 
19 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
20 
21 import android.content.ComponentName;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.provider.DeviceConfig;
27 import android.service.watchdog.ExplicitHealthCheckService;
28 import android.util.Log;
29 
30 import androidx.annotation.VisibleForTesting;
31 
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.ConcurrentHashMap;
37 
38 /**
39  * Routes explicit health check requests to the appropriate {@link ExplicitHealthChecker}.
40  */
41 public final class ExplicitHealthCheckServiceImpl extends ExplicitHealthCheckService {
42     private static final String TAG = "ExplicitHealthCheckServiceImpl";
43     // TODO: Add build dependency on NetworkStack stable AIDL so we can stop hard coding class name
44     private static final String NETWORK_STACK_CONNECTOR_CLASS =
45             "android.net.INetworkStackConnector";
46     public static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS =
47             "watchdog_request_timeout_millis";
48     // TODO(b/153701690): Use TimeUnit class to get time information instead of using constant.
49     public static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 24 * 60 * 60 * 1000; // 1 day
50     // Modified only #onCreate, using concurrent collection to ensure thread visibility
51     @VisibleForTesting
52     final Map<String, ExplicitHealthChecker> mSupportedCheckers = new ConcurrentHashMap<>();
53 
54     @Override
onCreate()55     public void onCreate() {
56         super.onCreate();
57         initHealthCheckers();
58     }
59 
60     @Override
onRequestHealthCheck(String packageName)61     public void onRequestHealthCheck(String packageName) {
62         ExplicitHealthChecker checker = mSupportedCheckers.get(packageName);
63         if (checker != null) {
64             checker.request();
65         } else {
66             Log.w(TAG, "Ignoring request for explicit health check for unsupported package "
67                     + packageName);
68         }
69     }
70 
71     @Override
onCancelHealthCheck(String packageName)72     public void onCancelHealthCheck(String packageName) {
73         ExplicitHealthChecker checker = mSupportedCheckers.get(packageName);
74         if (checker != null) {
75             checker.cancel();
76         } else {
77             Log.w(TAG, "Ignoring request to cancel explicit health check for unsupported package "
78                     + packageName);
79         }
80     }
81 
82     @Override
onGetSupportedPackages()83     public List<PackageConfig> onGetSupportedPackages() {
84         List<PackageConfig> packages = new ArrayList<>();
85         long requestTimeoutMillis = DeviceConfig.getLong(
86                 DeviceConfig.NAMESPACE_ROLLBACK,
87                 PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
88                 DEFAULT_REQUEST_TIMEOUT_MILLIS);
89         if (requestTimeoutMillis <= 0) {
90             requestTimeoutMillis = DEFAULT_REQUEST_TIMEOUT_MILLIS;
91         }
92         for (ExplicitHealthChecker checker : mSupportedCheckers.values()) {
93             PackageConfig pkg = new PackageConfig(checker.getSupportedPackageName(),
94                     requestTimeoutMillis);
95             packages.add(pkg);
96         }
97         return packages;
98     }
99 
100     @Override
onGetRequestedPackages()101     public List<String> onGetRequestedPackages() {
102         List<String> packages = new ArrayList<>();
103         Iterator<ExplicitHealthChecker> it = mSupportedCheckers.values().iterator();
104         // Could potentially race, where we read a checker#isPending and it changes before we
105         // return list. However, if it races and it is in the list, the caller might call #cancel
106         // which would fail, but that is fine. If it races and it ends up *not* in the list, it was
107         // already cancelled, so there's no need for the caller to cancel it
108         while (it.hasNext()) {
109             ExplicitHealthChecker checker = it.next();
110             if (checker.isPending()) {
111                 packages.add(checker.getSupportedPackageName());
112             }
113         }
114         return packages;
115     }
116 
initHealthCheckers()117     private void initHealthCheckers() {
118         ComponentName comp = resolveSystemService(new Intent(NETWORK_STACK_CONNECTOR_CLASS),
119                 getPackageManager(), 0);
120         if (comp != null) {
121             String networkStackPackageName = comp.getPackageName();
122             mSupportedCheckers.put(networkStackPackageName,
123                     new NetworkChecker(this, networkStackPackageName));
124         } else {
125             // On Go devices, or any device that does not ship the network stack module.
126             // The network stack will live in system_server process, so no need to monitor.
127             Log.i(TAG, "Network stack module not found");
128         }
129     }
130 
resolveSystemService(Intent intent, PackageManager pm, int flags)131     private ComponentName resolveSystemService(Intent intent, PackageManager pm, int flags) {
132         List<ResolveInfo> results =  pm.queryIntentServices(intent, flags);
133         if (results == null) {
134             return null;
135         }
136         ComponentName comp = null;
137         for (int i=0; i<results.size(); i++) {
138             ResolveInfo ri = results.get(i);
139             if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
140                 continue;
141             }
142             ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
143                     ri.serviceInfo.name);
144             if (comp != null) {
145                 throw new IllegalStateException("Multiple system services handle " + this
146                         + ": " + comp + ", " + foundComp);
147             }
148             comp = foundComp;
149         }
150         return comp;
151     }
152 }
153