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