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.mdnsoffloadcmd;
18 
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.app.Service;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.os.Binder;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.widget.Toast;
35 import android.util.Log;
36 
37 import androidx.annotation.Nullable;
38 import androidx.core.app.NotificationCompat;
39 import androidx.core.content.ContextCompat;
40 
41 import java.util.HexFormat;
42 
43 import device.google.atv.mdns_offload.IMdnsOffloadManager;
44 
45 public class MdnsOffloadCmdService extends Service {
46     private static final String TAG = MdnsOffloadCmdService.class.getSimpleName();
47 
48     private static final String ACTION_OFFLOAD_COMMAND =
49             "com.android.tv.mdnsoffloadcmd.OFFLOAD_COMMAND";
50 
51     private static final String CHANNEL_ID = "MdnsOffloadCmdService";
52 
53     private IMdnsOffloadManager mMdnsOffloadManagerService;
54     private IBinder mBinder;
55 
56     @Nullable
57     @Override
onBind(Intent intent)58     public IBinder onBind(Intent intent) {
59         //We don't want any app to bind to this service.
60         return null;
61     }
62 
63     @Override
onCreate()64     public void onCreate() {
65         super.onCreate();
66         mBinder = new Binder();
67         setupCommandBroadcastReceiver();
68     }
69 
70     @Override
onStartCommand(Intent intent, int flags, int startId)71     public int onStartCommand(Intent intent, int flags, int startId) {
72         createNotificationChannel();
73 
74         Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
75                 .setContentTitle("Foreground Service")
76                 .setSmallIcon(R.drawable.notification_template_icon_low_bg)
77                 .build();
78 
79         bindMdnsOffloadManager();
80 
81         startForeground(1, notification);
82         return START_NOT_STICKY;
83     }
84 
createNotificationChannel()85     private void createNotificationChannel() {
86         NotificationChannel serviceChannel = new NotificationChannel(
87                 CHANNEL_ID,
88                 "Foreground Service Channel",
89                 NotificationManager.IMPORTANCE_DEFAULT
90         );
91 
92         NotificationManager manager = getSystemService(NotificationManager.class);
93         manager.createNotificationChannel(serviceChannel);
94     }
95 
registerProtocolResponse(String rawHexPacket, String iface)96     private void registerProtocolResponse(String rawHexPacket, String iface) {
97         Log.d(TAG, "Registering on iface{" + iface + "} :" + rawHexPacket);
98         IMdnsOffloadManager.OffloadServiceInfo info =
99                 new IMdnsOffloadManager.OffloadServiceInfo();
100         info.rawOffloadPacket = HexFormat.of().parseHex(rawHexPacket);
101         try {
102             if (mMdnsOffloadManagerService == null) {
103                 Log.e(TAG, "Offload Manager not connected");
104                 return;
105             }
106             int recordKey = mMdnsOffloadManagerService.addProtocolResponses(iface, info, mBinder);
107             Log.d(TAG, "Packet offloaded with recordKey=" + recordKey);
108         } catch (RemoteException e) {
109             Log.e(TAG, "Error while registering debug packet", e);
110         }
111     }
112 
registerPassthrough(String qname, String iface)113     private void registerPassthrough(String qname, String iface) {
114         Log.d(TAG, "Registering on iface{" + iface + "} passthrough:" + qname);
115         try {
116             if (mMdnsOffloadManagerService == null) {
117                 Log.e(TAG, "Offload Manager not connected");
118                 return;
119             }
120             mMdnsOffloadManagerService.addToPassthroughList(iface, qname, mBinder);
121         } catch (RemoteException e) {
122             Log.e(TAG, "Error while adding passthrough qname", e);
123         }
124     }
125 
removeProtocolResponse(int recordKey)126     private void removeProtocolResponse(int recordKey) {
127         IMdnsOffloadManager.OffloadServiceInfo info =
128                 new IMdnsOffloadManager.OffloadServiceInfo();
129         try {
130             if (mMdnsOffloadManagerService == null) {
131                 Log.e(TAG, "Offload Manager not connected");
132                 return;
133             }
134             mMdnsOffloadManagerService.removeProtocolResponses(recordKey, mBinder);
135             Log.d(TAG, "Removed record " + recordKey);
136         } catch (RemoteException e) {
137             Log.e(TAG, "Error while registering debug packet", e);
138         }
139     }
140 
removePassthrough(String qname, String iface)141     private void removePassthrough(String qname, String iface) {
142         Log.d(TAG, "Removing passthrough:" + qname + " on iface{" + iface + "}");
143         try {
144             if (mMdnsOffloadManagerService == null) {
145                 Log.e(TAG, "Offload Manager not connected");
146                 return;
147             }
148             mMdnsOffloadManagerService.removeFromPassthroughList(iface, qname, mBinder);
149         } catch (RemoteException e) {
150             Log.e(TAG, "Error while removing passthrough qname", e);
151         }
152     }
153 
setupCommandBroadcastReceiver()154     private void setupCommandBroadcastReceiver() {
155         BroadcastReceiver receiver = new CommandBroadcastReceiver();
156         IntentFilter filter = new IntentFilter();
157         filter.addAction(ACTION_OFFLOAD_COMMAND);
158         registerReceiver(receiver, filter, ContextCompat.RECEIVER_EXPORTED);
159     }
160 
161     private class CommandBroadcastReceiver extends BroadcastReceiver {
162         @Override
onReceive(Context context, Intent intent)163         public void onReceive(Context context, Intent intent) {
164             String action = intent.getStringExtra("action");
165             switch (action) {
166                 case "ADD_OFFLOAD": {
167                     String iface = intent.getStringExtra("iface");
168                     String rawHexPacket = intent.getStringExtra("raw_hex_packet");
169                     if (rawHexPacket != null && iface != null) {
170                         registerProtocolResponse(rawHexPacket, iface);
171                     } else {
172                         Log.d(TAG, "Bad parameters for ADD_OFFLOAD command");
173                     }
174                     break;
175                 }
176                 case "REMOVE_OFFLOAD": {
177                     int recordKey = intent.getIntExtra("recordKey", -1);
178                     if (recordKey >= 0) {
179                         removeProtocolResponse(recordKey);
180                     } else {
181                         Log.d(TAG, "Bad parameters for REMOVE_OFFLOAD command");
182                     }
183                     break;
184                 }
185                 case "ADD_PASSTHROUGH": {
186                     String iface = intent.getStringExtra("iface");
187                     String qname = intent.getStringExtra("qname");
188                     if (iface != null && qname != null) {
189                         registerPassthrough(qname, iface);
190                     } else {
191                         Log.d(TAG, "Bad parameters for ADD_PASSTHROUGH command");
192                     }
193                     break;
194                 }
195                 case "REMOVE_PASSTHROUGH": {
196                     String iface = intent.getStringExtra("iface");
197                     String qname = intent.getStringExtra("qname");
198                     if (iface != null && qname != null) {
199                         removePassthrough(qname, iface);
200                     } else {
201                         Log.d(TAG, "Bad parameters for REMOVE_PASSTHROUGH command");
202                     }
203                     break;
204                 }
205             }
206         }
207     }
208 
209 
bindMdnsOffloadManager()210     private void bindMdnsOffloadManager() {
211         ComponentName componentName = ComponentName.unflattenFromString(
212                 "com.android.tv.mdnsoffloadmanager/.MdnsOffloadManagerService");
213         Intent explicitIntent = new Intent();
214         explicitIntent.setComponent(componentName);
215         boolean bindingSuccessful = bindService(explicitIntent,
216                 mMdnsOffloadManagerServiceConnection, Context.BIND_AUTO_CREATE);
217         if (!bindingSuccessful) {
218             String msg = "Failed to bind MdnsOffloadManager.";
219             Log.e(TAG, msg);
220             throw new IllegalStateException(msg);
221         }
222     }
223 
224 
225     private final ServiceConnection mMdnsOffloadManagerServiceConnection = new ServiceConnection() {
226         @Override
227         public void onServiceConnected(ComponentName className, IBinder service) {
228             Log.i(TAG, "IMdnsOffloadManager service bound successfully.");
229             mMdnsOffloadManagerService = IMdnsOffloadManager.Stub.asInterface(service);
230         }
231 
232         public void onServiceDisconnected(ComponentName className) {
233             Log.e(TAG, "IMdnsOffloadManager service has unexpectedly disconnected.");
234             mMdnsOffloadManagerService = null;
235         }
236     };
237 
238 }
239