• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }