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