1 /* 2 * Copyright (C) 2023 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.tv.mdnsoffloadmanager; 18 19 import android.app.Service; 20 import android.content.BroadcastReceiver; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.ServiceConnection; 26 import android.content.pm.PackageManager; 27 import android.content.res.Resources; 28 import android.net.ConnectivityManager; 29 import android.net.LinkProperties; 30 import android.net.Network; 31 import android.net.NetworkCapabilities; 32 import android.net.NetworkRequest; 33 import android.os.Binder; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.PowerManager; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.util.Log; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.VisibleForTesting; 45 import androidx.annotation.WorkerThread; 46 47 import com.android.tv.mdnsoffloadmanager.util.WakeLockWrapper; 48 49 import java.io.FileDescriptor; 50 import java.io.PrintWriter; 51 import java.util.HashMap; 52 import java.util.Map; 53 import java.util.Objects; 54 import java.util.Set; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 import java.util.stream.Collectors; 58 59 import device.google.atv.mdns_offload.IMdnsOffload; 60 import device.google.atv.mdns_offload.IMdnsOffloadManager; 61 62 63 public class MdnsOffloadManagerService extends Service { 64 65 private static final String TAG = MdnsOffloadManagerService.class.getSimpleName(); 66 private static final int VENDOR_SERVICE_COMPONENT_ID = 67 R.string.config_mdnsOffloadVendorServiceComponent; 68 private static final int AWAIT_DUMP_SECONDS = 5; 69 70 private final ConnectivityManager.NetworkCallback mNetworkCallback = 71 new ConnectivityManagerNetworkCallback(); 72 private final Map<String, InterfaceOffloadManager> mInterfaceOffloadManagers = new HashMap<>(); 73 private final Injector mInjector; 74 private Handler mHandler; 75 private PriorityListManager mPriorityListManager; 76 private OffloadIntentStore mOffloadIntentStore; 77 private OffloadWriter mOffloadWriter; 78 private ConnectivityManager mConnectivityManager; 79 private PackageManager mPackageManager; 80 private WakeLockWrapper mWakeLock; 81 MdnsOffloadManagerService()82 public MdnsOffloadManagerService() { 83 this(new Injector()); 84 } 85 86 @VisibleForTesting MdnsOffloadManagerService(@onNull Injector injector)87 MdnsOffloadManagerService(@NonNull Injector injector) { 88 super(); 89 injector.setContext(this); 90 mInjector = injector; 91 } 92 93 @VisibleForTesting 94 static class Injector { 95 96 private Context mContext = null; 97 private Looper mLooper = null; 98 setContext(Context context)99 void setContext(Context context) { 100 mContext = context; 101 } 102 getLooper()103 synchronized Looper getLooper() { 104 if (mLooper == null) { 105 HandlerThread ht = new HandlerThread("MdnsOffloadManager"); 106 ht.start(); 107 mLooper = ht.getLooper(); 108 } 109 return mLooper; 110 } 111 getResources()112 Resources getResources() { 113 return mContext.getResources(); 114 } 115 getConnectivityManager()116 ConnectivityManager getConnectivityManager() { 117 return mContext.getSystemService(ConnectivityManager.class); 118 } 119 120 getLowPowerStandbyPolicy()121 PowerManager.LowPowerStandbyPolicy getLowPowerStandbyPolicy() { 122 return mContext.getSystemService(PowerManager.class).getLowPowerStandbyPolicy(); 123 } 124 newWakeLock()125 WakeLockWrapper newWakeLock() { 126 return new WakeLockWrapper( 127 mContext.getSystemService(PowerManager.class) 128 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)); 129 } 130 getPackageManager()131 PackageManager getPackageManager() { 132 return mContext.getPackageManager(); 133 } 134 isInteractive()135 boolean isInteractive() { 136 return mContext.getSystemService(PowerManager.class).isInteractive(); 137 } 138 bindService(Intent intent, ServiceConnection connection, int flags)139 boolean bindService(Intent intent, ServiceConnection connection, int flags) { 140 return mContext.bindService(intent, connection, flags); 141 } 142 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags)143 void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { 144 mContext.registerReceiver(receiver, filter, flags); 145 } 146 getCallingUid()147 int getCallingUid() { 148 return Binder.getCallingUid(); 149 } 150 } 151 152 @Override onCreate()153 public void onCreate() { 154 super.onCreate(); 155 mHandler = new Handler(mInjector.getLooper()); 156 mPriorityListManager = new PriorityListManager(mInjector.getResources()); 157 mOffloadIntentStore = new OffloadIntentStore(mPriorityListManager); 158 mOffloadWriter = new OffloadWriter(); 159 mConnectivityManager = mInjector.getConnectivityManager(); 160 mPackageManager = mInjector.getPackageManager(); 161 mWakeLock = mInjector.newWakeLock(); 162 bindVendorService(); 163 setupScreenBroadcastReceiver(); 164 setupConnectivityListener(); 165 setupStandbyPolicyListener(); 166 } 167 bindVendorService()168 private void bindVendorService() { 169 String vendorServicePath = mInjector.getResources().getString(VENDOR_SERVICE_COMPONENT_ID); 170 171 if (vendorServicePath.isEmpty()) { 172 String msg = "vendorServicePath is empty. Bind cannot proceed."; 173 Log.e(TAG, msg); 174 throw new IllegalArgumentException(msg); 175 } 176 ComponentName componentName = ComponentName.unflattenFromString(vendorServicePath); 177 if (componentName == null) { 178 String msg = "componentName cannot be extracted from vendorServicePath." 179 + " Bind cannot proceed."; 180 Log.e(TAG, msg); 181 throw new IllegalArgumentException(msg); 182 } 183 184 Log.d(TAG, "IMdnsOffloadManager is binding to: " + componentName); 185 186 Intent explicitIntent = new Intent(); 187 explicitIntent.setComponent(componentName); 188 boolean bindingSuccessful = mInjector.bindService( 189 explicitIntent, mVendorServiceConnection, Context.BIND_AUTO_CREATE); 190 if (!bindingSuccessful) { 191 String msg = "Failed to bind to vendor service at {" + vendorServicePath + "}."; 192 Log.e(TAG, msg); 193 throw new IllegalStateException(msg); 194 } 195 } 196 setupScreenBroadcastReceiver()197 private void setupScreenBroadcastReceiver() { 198 BroadcastReceiver receiver = new ScreenBroadcastReceiver(); 199 IntentFilter filter = new IntentFilter(); 200 filter.addAction(Intent.ACTION_SCREEN_ON); 201 filter.addAction(Intent.ACTION_SCREEN_OFF); 202 mInjector.registerReceiver(receiver, filter, 0); 203 mHandler.post(() -> mOffloadWriter.setOffloadState(!mInjector.isInteractive())); 204 } 205 setupConnectivityListener()206 private void setupConnectivityListener() { 207 NetworkRequest networkRequest = new NetworkRequest.Builder() 208 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 209 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 210 .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) 211 .build(); 212 mConnectivityManager.registerNetworkCallback(networkRequest, mNetworkCallback); 213 } 214 setupStandbyPolicyListener()215 private void setupStandbyPolicyListener() { 216 BroadcastReceiver receiver = new LowPowerStandbyPolicyReceiver(); 217 IntentFilter filter = new IntentFilter(); 218 filter.addAction(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED); 219 mInjector.registerReceiver(receiver, filter, 0); 220 refreshAppIdAllowlist(); 221 } 222 refreshAppIdAllowlist()223 private void refreshAppIdAllowlist() { 224 PowerManager.LowPowerStandbyPolicy standbyPolicy = mInjector.getLowPowerStandbyPolicy(); 225 Set<Integer> allowedAppIds = standbyPolicy.getExemptPackages() 226 .stream() 227 .map(pkg -> { 228 try { 229 return mPackageManager.getPackageUid(pkg, 0); 230 } catch (PackageManager.NameNotFoundException e) { 231 Log.w(TAG, "Unable to get UID of package {" + pkg + "}."); 232 return null; 233 } 234 }) 235 .filter(Objects::nonNull) 236 .map(UserHandle::getAppId) 237 .collect(Collectors.toSet()); 238 mHandler.post(() -> { 239 mOffloadIntentStore.setAppIdAllowlist(allowedAppIds); 240 mInterfaceOffloadManagers.values() 241 .forEach(InterfaceOffloadManager::onAppIdAllowlistUpdated); 242 }); 243 } 244 245 @Override onBind(Intent intent)246 public IBinder onBind(Intent intent) { 247 return mOffloadManagerBinder; 248 } 249 250 @Override dump(FileDescriptor fileDescriptor, PrintWriter printWriter, String[] strings)251 protected void dump(FileDescriptor fileDescriptor, PrintWriter printWriter, String[] strings) { 252 CountDownLatch doneSignal = new CountDownLatch(1); 253 mHandler.post(() -> { 254 dump(printWriter); 255 doneSignal.countDown(); 256 }); 257 boolean success = false; 258 try { 259 success = doneSignal.await(AWAIT_DUMP_SECONDS, TimeUnit.SECONDS); 260 } catch (InterruptedException ignored) { 261 } 262 if (!success) { 263 Log.e(TAG, "Failed to dump state on handler thread"); 264 } 265 } 266 267 @WorkerThread dump(PrintWriter writer)268 private void dump(PrintWriter writer) { 269 mOffloadIntentStore.dump(writer); 270 mInterfaceOffloadManagers.values().forEach(manager -> manager.dump(writer)); 271 mOffloadWriter.dump(writer); 272 mOffloadIntentStore.dumpProtocolData(writer); 273 } 274 275 private final IMdnsOffloadManager.Stub mOffloadManagerBinder = new IMdnsOffloadManager.Stub() { 276 @Override 277 public int addProtocolResponses(@NonNull String networkInterface, 278 @NonNull OffloadServiceInfo serviceOffloadData, 279 @NonNull IBinder clientToken) { 280 Objects.requireNonNull(networkInterface); 281 Objects.requireNonNull(serviceOffloadData); 282 Objects.requireNonNull(clientToken); 283 int callerUid = mInjector.getCallingUid(); 284 OffloadIntentStore.OffloadIntent offloadIntent = 285 mOffloadIntentStore.registerOffloadIntent( 286 networkInterface, serviceOffloadData, clientToken, callerUid); 287 try { 288 offloadIntent.mClientToken.linkToDeath( 289 () -> removeProtocolResponses(offloadIntent.mRecordKey, clientToken), 0); 290 } catch (RemoteException e) { 291 String msg = "Error while setting a callback for linkToDeath binder" + 292 " {" + offloadIntent.mClientToken + "} in addProtocolResponses."; 293 Log.e(TAG, msg, e); 294 return offloadIntent.mRecordKey; 295 } 296 mHandler.post(() -> { 297 getInterfaceOffloadManager(networkInterface).refreshProtocolResponses(); 298 }); 299 return offloadIntent.mRecordKey; 300 } 301 302 @Override 303 public void removeProtocolResponses(int recordKey, @NonNull IBinder clientToken) { 304 if (recordKey <= 0) { 305 throw new IllegalArgumentException("recordKey must be positive"); 306 } 307 Objects.requireNonNull(clientToken); 308 mHandler.post(() -> { 309 OffloadIntentStore.OffloadIntent offloadIntent = 310 mOffloadIntentStore.getAndRemoveOffloadIntent(recordKey, clientToken); 311 if (offloadIntent == null) { 312 return; 313 } 314 getInterfaceOffloadManager(offloadIntent.mNetworkInterface) 315 .refreshProtocolResponses(); 316 }); 317 } 318 319 @Override 320 public void addToPassthroughList( 321 @NonNull String networkInterface, 322 @NonNull String qname, 323 @NonNull IBinder clientToken) { 324 Objects.requireNonNull(networkInterface); 325 Objects.requireNonNull(qname); 326 Objects.requireNonNull(clientToken); 327 int callerUid = mInjector.getCallingUid(); 328 mHandler.post(() -> { 329 OffloadIntentStore.PassthroughIntent ptIntent = 330 mOffloadIntentStore.registerPassthroughIntent( 331 networkInterface, qname, clientToken, callerUid); 332 IBinder token = ptIntent.mClientToken; 333 try { 334 token.linkToDeath( 335 () -> removeFromPassthroughList( 336 networkInterface, ptIntent.mCanonicalQName, token), 0); 337 } catch (RemoteException e) { 338 String msg = "Error while setting a callback for linkToDeath binder {" 339 + token + "} in addToPassthroughList."; 340 Log.e(TAG, msg, e); 341 return; 342 } 343 getInterfaceOffloadManager(networkInterface).refreshPassthroughList(); 344 }); 345 } 346 347 @Override 348 public void removeFromPassthroughList( 349 @NonNull String networkInterface, 350 @NonNull String qname, 351 @NonNull IBinder clientToken) { 352 Objects.requireNonNull(networkInterface); 353 Objects.requireNonNull(qname); 354 Objects.requireNonNull(clientToken); 355 mHandler.post(() -> { 356 boolean removed = mOffloadIntentStore.removePassthroughIntent(qname, clientToken); 357 if (removed) { 358 getInterfaceOffloadManager(networkInterface).refreshPassthroughList(); 359 } 360 }); 361 } 362 363 @Override 364 public int getInterfaceVersion() { 365 return super.VERSION; 366 } 367 368 @Override 369 public String getInterfaceHash() { 370 return super.HASH; 371 } 372 }; 373 getInterfaceOffloadManager(String networkInterface)374 private InterfaceOffloadManager getInterfaceOffloadManager(String networkInterface) { 375 return mInterfaceOffloadManagers.computeIfAbsent( 376 networkInterface, 377 iface -> new InterfaceOffloadManager(iface, mOffloadIntentStore, mOffloadWriter)); 378 } 379 380 private final ServiceConnection mVendorServiceConnection = new ServiceConnection() { 381 @Override 382 public void onServiceConnected(ComponentName className, IBinder service) { 383 Log.i(TAG, "IMdnsOffload service bound successfully."); 384 IMdnsOffload vendorService = IMdnsOffload.Stub.asInterface(service); 385 mHandler.post(() -> { 386 mOffloadWriter.setVendorService(vendorService); 387 mOffloadWriter.resetAll(); 388 mInterfaceOffloadManagers.values() 389 .forEach(InterfaceOffloadManager::onVendorServiceConnected); 390 mOffloadWriter.applyOffloadState(); 391 }); 392 } 393 394 public void onServiceDisconnected(ComponentName className) { 395 Log.e(TAG, "IMdnsOffload service has unexpectedly disconnected."); 396 mHandler.post(() -> { 397 mOffloadWriter.setVendorService(null); 398 mInterfaceOffloadManagers.values() 399 .forEach(InterfaceOffloadManager::onVendorServiceDisconnected); 400 }); 401 } 402 }; 403 404 private class ScreenBroadcastReceiver extends BroadcastReceiver { 405 @Override onReceive(Context context, Intent intent)406 public void onReceive(Context context, Intent intent) { 407 // Note: Screen on/off here is actually historical naming for the overall interactive 408 // state of the device: 409 // https://developer.android.com/reference/android/os/PowerManager#isInteractive() 410 String action = intent.getAction(); 411 mHandler.post(() -> { 412 if (Intent.ACTION_SCREEN_ON.equals(action)) { 413 mOffloadWriter.setOffloadState(false); 414 mOffloadWriter.retrieveAndClearMetrics(mOffloadIntentStore.getRecordKeys()); 415 } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { 416 try { 417 mWakeLock.acquire(5000); 418 mOffloadWriter.setOffloadState(true); 419 } finally { 420 mWakeLock.release(); 421 } 422 } 423 }); 424 } 425 } 426 427 private class LowPowerStandbyPolicyReceiver extends BroadcastReceiver { 428 @Override onReceive(Context context, Intent intent)429 public void onReceive(Context context, Intent intent) { 430 if (!PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED.equals(intent.getAction())) { 431 return; 432 } 433 refreshAppIdAllowlist(); 434 } 435 } 436 437 private class ConnectivityManagerNetworkCallback extends ConnectivityManager.NetworkCallback { 438 private final Map<Network, LinkProperties> mLinkProperties = new HashMap<>(); 439 440 @Override onLinkPropertiesChanged(Network network, LinkProperties linkProperties)441 public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { 442 // We only want to know the interface name of a network. This method is 443 // called right after onAvailable() or any other important change during the lifecycle 444 // of the network. 445 mHandler.post(() -> { 446 LinkProperties previousProperties = mLinkProperties.put(network, linkProperties); 447 if (previousProperties != null && 448 !previousProperties.getInterfaceName().equals( 449 linkProperties.getInterfaceName())) { 450 // This means that the interface changed names, which may happen 451 // but very rarely. 452 InterfaceOffloadManager offloadManager = 453 getInterfaceOffloadManager(previousProperties.getInterfaceName()); 454 offloadManager.onNetworkLost(); 455 } 456 457 // We trigger an onNetworkAvailable even if the existing is the same in case 458 // anything needs to be refreshed due to the LinkProperties change. 459 InterfaceOffloadManager offloadManager = 460 getInterfaceOffloadManager(linkProperties.getInterfaceName()); 461 offloadManager.onNetworkAvailable(); 462 }); 463 } 464 465 @Override onLost(@onNull Network network)466 public void onLost(@NonNull Network network) { 467 mHandler.post(() -> { 468 // Network object is guaranteed to match a network object from a previous 469 // onLinkPropertiesChanged() so the LinkProperties must be available to retrieve 470 // the associated iface. 471 LinkProperties previousProperties = mLinkProperties.remove(network); 472 if (previousProperties == null){ 473 Log.w(TAG,"Network "+ network + " lost before being available."); 474 return; 475 } 476 InterfaceOffloadManager offloadManager = 477 getInterfaceOffloadManager(previousProperties.getInterfaceName()); 478 offloadManager.onNetworkLost(); 479 }); 480 } 481 } 482 } 483