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