• 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 static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 
22 import static com.android.tv.mdnsoffloadmanager.TestHelpers.SERVICE_AIRPLAY;
23 import static com.android.tv.mdnsoffloadmanager.TestHelpers.SERVICE_ATV;
24 import static com.android.tv.mdnsoffloadmanager.TestHelpers.SERVICE_GOOGLECAST;
25 import static com.android.tv.mdnsoffloadmanager.TestHelpers.SERVICE_GTV;
26 import static com.android.tv.mdnsoffloadmanager.TestHelpers.makeIntent;
27 import static com.android.tv.mdnsoffloadmanager.TestHelpers.makeLinkProperties;
28 import static com.android.tv.mdnsoffloadmanager.TestHelpers.makeLowPowerStandbyPolicy;
29 import static com.android.tv.mdnsoffloadmanager.TestHelpers.verifyOffloadedServices;
30 import static com.android.tv.mdnsoffloadmanager.TestHelpers.verifyPassthroughQNames;
31 
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertFalse;
34 import static org.junit.Assert.assertNotNull;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assert.fail;
37 import static org.mockito.ArgumentMatchers.any;
38 import static org.mockito.ArgumentMatchers.anyByte;
39 import static org.mockito.ArgumentMatchers.anyInt;
40 import static org.mockito.ArgumentMatchers.anyString;
41 import static org.mockito.ArgumentMatchers.argThat;
42 import static org.mockito.ArgumentMatchers.eq;
43 import static org.mockito.ArgumentMatchers.notNull;
44 import static org.mockito.Mockito.atLeastOnce;
45 import static org.mockito.Mockito.mock;
46 import static org.mockito.Mockito.never;
47 import static org.mockito.Mockito.reset;
48 import static org.mockito.Mockito.verify;
49 import static org.mockito.Mockito.when;
50 
51 import android.content.BroadcastReceiver;
52 import android.content.ComponentName;
53 import android.content.Context;
54 import android.content.Intent;
55 import android.content.IntentFilter;
56 import android.content.ServiceConnection;
57 import android.content.pm.PackageManager;
58 import android.content.res.Resources;
59 import android.net.ConnectivityManager;
60 import android.net.ConnectivityManager.NetworkCallback;
61 import android.net.Network;
62 import android.os.IBinder;
63 import android.os.Looper;
64 import android.os.PowerManager;
65 import android.os.RemoteException;
66 import android.os.test.TestLooper;
67 import android.util.Log;
68 
69 import androidx.test.filters.SmallTest;
70 
71 import com.android.tv.mdnsoffloadmanager.MdnsOffloadManagerService.Injector;
72 import com.android.tv.mdnsoffloadmanager.util.WakeLockWrapper;
73 
74 import org.junit.Before;
75 import org.junit.Test;
76 import org.mockito.ArgumentCaptor;
77 import org.mockito.Captor;
78 import org.mockito.Mock;
79 import org.mockito.MockitoAnnotations;
80 import org.mockito.Spy;
81 
82 import java.io.PrintWriter;
83 import java.io.StringWriter;
84 import java.util.Arrays;
85 import java.util.List;
86 
87 import device.google.atv.mdns_offload.IMdnsOffload.MdnsProtocolData;
88 import device.google.atv.mdns_offload.IMdnsOffload.MdnsProtocolData.MatchCriteria;
89 import device.google.atv.mdns_offload.IMdnsOffload.PassthroughBehavior;
90 import device.google.atv.mdns_offload.IMdnsOffloadManager;
91 
92 @SmallTest
93 public class MdnsOffloadManagerTest {
94 
95     private static final String TAG = MdnsOffloadManagerTest.class.getSimpleName();
96     private static final ComponentName VENDOR_SERVICE_COMPONENT =
97             ComponentName.unflattenFromString("test.vendor.offloadservice/.TestOffloadService");
98     private static final String[] PRIORITY_LIST = {
99             "_googlecast._tcp.local.",
100             "_some._other._svc.local."
101     };
102     private static final String IFC_0 = "imaginaryif0";
103     private static final String IFC_1 = "imaginaryif1";
104     private static final int APP_UID_0 = 1234;
105     private static final int SECONDARY_USER_APP_UID_0 = 101234;
106     private static final int APP_UID_1 = 1235;
107     private static final String APP_PACKAGE_0 = "first.app.package";
108     private static final String APP_PACKAGE_1 = "some.other.package";
109 
110     @Mock
111     Resources mResources;
112     @Mock
113     IBinder mClientBinder0;
114     @Mock
115     IBinder mClientBinder1;
116     @Mock
117     ConnectivityManager mConnectivityManager;
118     @Mock
119     Network mNetwork0;
120     @Mock
121     Network mNetwork1;
122     @Mock
123     PackageManager mPackageManager;
124     @Mock
125     WakeLockWrapper mWakeLock;
126     @Spy
127     FakeMdnsOffloadService mVendorService = new FakeMdnsOffloadService();
128     @Captor
129     ArgumentCaptor<NetworkCallback> mNetworkCallbackCaptor;
130     @Captor
131     ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
132 
133     TestLooper mTestLooper;
134     ServiceConnection mCapturedVendorServiceConnection;
135     BroadcastReceiver mCapturedScreenBroadcastReceiver;
136     BroadcastReceiver mCapturedLowPowerStandbyPolicyReceiver;
137     MdnsOffloadManagerService mOffloadManagerService;
138     IMdnsOffloadManager mOffloadManagerBinder;
139     boolean mIsInteractive;
140     int mCallingUid;
141     PowerManager.LowPowerStandbyPolicy mLowPowerStandbyPolicy;
142 
143     @Before
setup()144     public void setup() throws PackageManager.NameNotFoundException {
145         mTestLooper = new TestLooper();
146         MockitoAnnotations.initMocks(this);
147         when(mResources.getString(eq(R.string.config_mdnsOffloadVendorServiceComponent)))
148                 .thenReturn(VENDOR_SERVICE_COMPONENT.flattenToShortString());
149         when(mResources.getStringArray(eq(R.array.config_mdnsOffloadPriorityQnames)))
150                 .thenReturn(PRIORITY_LIST);
151         when(mPackageManager.getPackageUid(eq(APP_PACKAGE_0), anyInt())).thenReturn(APP_UID_0);
152         when(mPackageManager.getPackageUid(eq(APP_PACKAGE_1), anyInt())).thenReturn(APP_UID_1);
153         mLowPowerStandbyPolicy = makeLowPowerStandbyPolicy(APP_PACKAGE_0);
154         mCallingUid = APP_UID_0;
155         mIsInteractive = true;
156     }
157 
createOffloadManager()158     private void createOffloadManager() {
159         mOffloadManagerService = new MdnsOffloadManagerService(new Injector() {
160             @Override
161             Resources getResources() {
162                 return mResources;
163             }
164 
165             @Override
166             synchronized Looper getLooper() {
167                 return mTestLooper.getLooper();
168             }
169 
170             @Override
171             boolean isInteractive() {
172                 return mIsInteractive;
173             }
174 
175             @Override
176             int getCallingUid() {
177                 return mCallingUid;
178             }
179 
180             @Override
181             ConnectivityManager getConnectivityManager() {
182                 return mConnectivityManager;
183             }
184 
185             @Override
186             PowerManager.LowPowerStandbyPolicy getLowPowerStandbyPolicy() {
187                 return mLowPowerStandbyPolicy;
188             }
189 
190             @Override
191             WakeLockWrapper newWakeLock() {
192                 return mWakeLock;
193             }
194 
195             @Override
196             PackageManager getPackageManager() {
197                 return mPackageManager;
198             }
199 
200             @Override
201             boolean bindService(Intent intent, ServiceConnection connection, int flags) {
202                 if (!VENDOR_SERVICE_COMPONENT.equals(intent.getComponent())) {
203                     fail("MDNS offload manager is expected to bind to the component provided in " +
204                             "the resources.");
205                 }
206                 if (flags != Context.BIND_AUTO_CREATE) {
207                     fail("MDNS offload manager is expected to set BIND_AUTO_CREATE flag when " +
208                             "binding to vendor service.");
209                 }
210                 mCapturedVendorServiceConnection = connection;
211                 return true;
212             }
213 
214             @Override
215             void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
216                 if (filter.countActions() == 2 &&
217                         filter.hasAction(Intent.ACTION_SCREEN_ON) &&
218                         filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
219                     mCapturedScreenBroadcastReceiver = receiver;
220                     return;
221                 } else if (filter.countActions() == 1 &&
222                         filter.hasAction(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED)) {
223                     mCapturedLowPowerStandbyPolicyReceiver = receiver;
224                     return;
225                 }
226                 fail("Unexpected broadcast receiver registered.");
227             }
228         });
229 
230         mOffloadManagerService.onCreate();
231         mOffloadManagerBinder = (IMdnsOffloadManager) mOffloadManagerService.onBind(null);
232         verify(mConnectivityManager).registerNetworkCallback(
233                 argThat(request ->
234                         Arrays.stream(request.getTransportTypes())
235                                 .boxed()
236                                 .toList()
237                                 .containsAll(List.of(TRANSPORT_ETHERNET, TRANSPORT_WIFI))),
238                 mNetworkCallbackCaptor.capture());
239     }
240 
bindVendorService()241     private void bindVendorService() {
242         mCapturedVendorServiceConnection.onServiceConnected(
243                 VENDOR_SERVICE_COMPONENT, mVendorService);
244         mTestLooper.dispatchAll();
245     }
246 
unbindVendorService()247     private void unbindVendorService() {
248         mCapturedVendorServiceConnection.onServiceDisconnected(VENDOR_SERVICE_COMPONENT);
249         mTestLooper.dispatchAll();
250     }
251 
registerNetwork(Network network, String networkInterface)252     private void registerNetwork(Network network, String networkInterface) {
253         mNetworkCallbackCaptor.getValue().onLinkPropertiesChanged(network,
254                 makeLinkProperties(networkInterface));
255         mTestLooper.dispatchAll();
256     }
257 
unregisterNetwork(Network network)258     private void unregisterNetwork(Network network) {
259         mNetworkCallbackCaptor.getValue().onLost(network);
260         mTestLooper.dispatchAll();
261     }
262 
setupDefaultOffloadManager()263     private void setupDefaultOffloadManager() {
264         createOffloadManager();
265         bindVendorService();
266         registerNetwork(mNetwork0, IFC_0);
267     }
268 
269     @Test
whenCreated_setsListenersAndBindsService()270     public void whenCreated_setsListenersAndBindsService() throws RemoteException {
271         setupDefaultOffloadManager();
272 
273         assertNotNull(mCapturedVendorServiceConnection);
274         assertNotNull(mCapturedScreenBroadcastReceiver);
275         verify(mConnectivityManager).registerNetworkCallback(any(), (NetworkCallback) notNull());
276         // Vendor offload is reset on binding.
277         verify(mVendorService).resetAll();
278         assertFalse(mVendorService.mOffloadState);
279         assertEquals(0, mVendorService.getOffloadData(IFC_0).offloadedRecords.size());
280     }
281 
282 
283     @Test
whenOffloadingRecord_propagatesToVendorService()284     public void whenOffloadingRecord_propagatesToVendorService() throws RemoteException {
285         setupDefaultOffloadManager();
286 
287         int recordKey = mOffloadManagerBinder.addProtocolResponses(
288                 IFC_0, SERVICE_ATV, mClientBinder0);
289         mTestLooper.dispatchAll();
290 
291         assertTrue("Expected a valid record key", recordKey > 0);
292         FakeMdnsOffloadService.OffloadData offloadData = mVendorService.getOffloadData(IFC_0);
293         assertEquals(1, offloadData.offloadedRecords.size());
294         MdnsProtocolData protocolData = offloadData.offloadedRecords.get(0);
295         assertEquals(SERVICE_ATV.rawOffloadPacket, protocolData.rawOffloadPacket);
296         assertEquals(1, protocolData.matchCriteriaList.size());
297         MatchCriteria matchCriteria = protocolData.matchCriteriaList.get(0);
298         assertEquals(0x01, matchCriteria.type); // Type A response
299         assertEquals(12, matchCriteria.nameOffset);
300     }
301 
302     /**
303      * Multiple records must be offloaded in the correct order, as memory for offloaded records is
304      * limited and the first records must take precedence over the subsequent ones.
305      */
306     @Test
offloadingOrderIsMaintained()307     public void offloadingOrderIsMaintained() throws RemoteException {
308         setupDefaultOffloadManager();
309 
310         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
311         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GTV, mClientBinder0);
312         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_AIRPLAY, mClientBinder0);
313         mTestLooper.dispatchAll();
314 
315         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
316     }
317 
318     /**
319      * When removing a record, offload of the rest of the records is re-applied in the same order
320      * as before.
321      */
322     @Test
removingOffloadedRecord_maintainsOrder()323     public void removingOffloadedRecord_maintainsOrder() throws RemoteException {
324         setupDefaultOffloadManager();
325         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
326         int gtvRecordKey = mOffloadManagerBinder.addProtocolResponses(
327                 IFC_0, SERVICE_GTV, mClientBinder0);
328         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_AIRPLAY, mClientBinder0);
329         mTestLooper.dispatchAll();
330         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
331 
332         mOffloadManagerBinder.removeProtocolResponses(gtvRecordKey, mClientBinder0);
333         mTestLooper.dispatchAll();
334 
335         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_AIRPLAY);
336     }
337 
338     @Test
removingInvalidRecordKey_doesNothing()339     public void removingInvalidRecordKey_doesNothing() throws RemoteException {
340         setupDefaultOffloadManager();
341         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
342         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GTV, mClientBinder0);
343         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_AIRPLAY, mClientBinder0);
344         mTestLooper.dispatchAll();
345         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
346 
347         mOffloadManagerBinder.removeProtocolResponses(1337, mClientBinder0);
348         mTestLooper.dispatchAll();
349 
350         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
351     }
352 
353     @Test
removingRecordHoldingInvalidClientBinder_doesNothing()354     public void removingRecordHoldingInvalidClientBinder_doesNothing() throws RemoteException {
355         setupDefaultOffloadManager();
356         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
357         int recordKey = mOffloadManagerBinder.addProtocolResponses(
358                 IFC_0, SERVICE_GTV, mClientBinder0);
359         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_AIRPLAY, mClientBinder0);
360         mTestLooper.dispatchAll();
361         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
362 
363         mOffloadManagerBinder.removeProtocolResponses(recordKey, mClientBinder1);
364         mTestLooper.dispatchAll();
365 
366         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV, SERVICE_AIRPLAY);
367     }
368 
369     @Test
recordsFromPriorityListAreOffloadedFirst()370     public void recordsFromPriorityListAreOffloadedFirst() throws RemoteException {
371         setupDefaultOffloadManager();
372         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
373         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GTV, mClientBinder0);
374         mTestLooper.dispatchAll();
375         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV, SERVICE_GTV);
376 
377         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GOOGLECAST, mClientBinder0);
378         mTestLooper.dispatchAll();
379 
380         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_GOOGLECAST, SERVICE_ATV,
381                 SERVICE_GTV);
382     }
383 
384     @Test
whenOutOfMemoryCapacity_priorityListRecordsAreNotEvicted()385     public void whenOutOfMemoryCapacity_priorityListRecordsAreNotEvicted() throws RemoteException {
386         setupDefaultOffloadManager();
387         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
388         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GTV, mClientBinder0);
389         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_AIRPLAY, mClientBinder0);
390         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GOOGLECAST, mClientBinder0);
391         mTestLooper.dispatchAll();
392 
393         verifyOffloadedServices(
394                 mVendorService, IFC_0, SERVICE_GOOGLECAST, SERVICE_ATV, SERVICE_GTV);
395     }
396 
397     @Test
priorityListNamesAreCanonicalized()398     public void priorityListNamesAreCanonicalized() throws RemoteException {
399         when(mResources.getStringArray(eq(R.array.config_mdnsOffloadPriorityQnames)))
400                 .thenReturn(new String[]{"_googlecast._tcp.local"}); // Trailing dot is missing.
401         setupDefaultOffloadManager();
402         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
403         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GOOGLECAST, mClientBinder0);
404         mTestLooper.dispatchAll();
405 
406         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_GOOGLECAST, SERVICE_ATV);
407     }
408 
409     @Test
addingToPassthroughList_setsPassthroughBehaviorAndPropagatesToVendorService()410     public void addingToPassthroughList_setsPassthroughBehaviorAndPropagatesToVendorService()
411             throws RemoteException {
412         setupDefaultOffloadManager();
413         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
414         mTestLooper.dispatchAll();
415 
416         assertEquals(
417                 PassthroughBehavior.PASSTHROUGH_LIST,
418                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
419         verifyPassthroughQNames(mVendorService, IFC_0, "atv");
420     }
421 
422     /**
423      * Order of passthrough QNames must be maintained, as the vendor service will drop passthrough
424      * QNames if the chip runs out of memory.
425      */
426     @Test
passthroughOrderIsMaintained()427     public void passthroughOrderIsMaintained() throws RemoteException {
428         setupDefaultOffloadManager();
429 
430         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
431         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
432         mOffloadManagerBinder.addToPassthroughList(IFC_0, "airplay", mClientBinder0);
433         mTestLooper.dispatchAll();
434 
435         assertEquals(
436                 PassthroughBehavior.PASSTHROUGH_LIST,
437                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
438         verifyPassthroughQNames(mVendorService, IFC_0, "atv", "gtv", "airplay");
439     }
440 
441     @Test
removingPassthroughQName_maintainsOrder()442     public void removingPassthroughQName_maintainsOrder() throws RemoteException {
443         setupDefaultOffloadManager();
444         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
445         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
446         mOffloadManagerBinder.addToPassthroughList(IFC_0, "airplay", mClientBinder0);
447         mTestLooper.dispatchAll();
448 
449         mOffloadManagerBinder.removeFromPassthroughList(IFC_0, "gtv", mClientBinder0);
450         mTestLooper.dispatchAll();
451 
452         assertEquals(
453                 PassthroughBehavior.PASSTHROUGH_LIST,
454                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
455         verifyPassthroughQNames(mVendorService, IFC_0, "atv", "airplay");
456     }
457 
458     @Test
removingNonexistentPassthroughQName_doesNothing()459     public void removingNonexistentPassthroughQName_doesNothing() throws RemoteException {
460         setupDefaultOffloadManager();
461         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
462         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
463         mOffloadManagerBinder.addToPassthroughList(IFC_0, "airplay", mClientBinder0);
464         mTestLooper.dispatchAll();
465         reset(mVendorService); // Forget previous calls related to passthrough.
466 
467         mOffloadManagerBinder.removeFromPassthroughList(
468                 IFC_0, "otherservice", mClientBinder0);
469         mTestLooper.dispatchAll();
470 
471         verify(mVendorService, never()).setPassthroughBehavior(eq(IFC_0), anyByte());
472         verify(mVendorService, never()).removeFromPassthroughList(eq(IFC_0), anyString());
473         assertEquals(
474                 PassthroughBehavior.PASSTHROUGH_LIST,
475                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
476         verifyPassthroughQNames(mVendorService, IFC_0, "atv", "gtv", "airplay");
477     }
478 
479     @Test
removingPassthroughQNameHoldingInvalidClientBinder_doesNothing()480     public void removingPassthroughQNameHoldingInvalidClientBinder_doesNothing()
481             throws RemoteException {
482         setupDefaultOffloadManager();
483         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
484         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
485         mOffloadManagerBinder.addToPassthroughList(IFC_0, "airplay", mClientBinder0);
486         mTestLooper.dispatchAll();
487 
488         mOffloadManagerBinder.removeFromPassthroughList(IFC_0, "gtv", mClientBinder1);
489         mTestLooper.dispatchAll();
490 
491         verifyPassthroughQNames(mVendorService, IFC_0, "atv", "gtv", "airplay");
492     }
493 
494     @Test
removingAllEntriesFromPassthroughList_disablesPassthroughBehavior()495     public void removingAllEntriesFromPassthroughList_disablesPassthroughBehavior()
496             throws RemoteException {
497         setupDefaultOffloadManager();
498         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
499         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
500         mTestLooper.dispatchAll();
501         assertEquals(
502                 PassthroughBehavior.PASSTHROUGH_LIST,
503                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
504 
505         mOffloadManagerBinder.removeFromPassthroughList(IFC_0, "atv", mClientBinder0);
506         mOffloadManagerBinder.removeFromPassthroughList(IFC_0, "gtv", mClientBinder0);
507         mTestLooper.dispatchAll();
508 
509         assertEquals(
510                 PassthroughBehavior.DROP_ALL,
511                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
512     }
513 
514     @Test
passthroughQNamesFromPriorityListTakePrecedence()515     public void passthroughQNamesFromPriorityListTakePrecedence() throws RemoteException {
516         setupDefaultOffloadManager();
517         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
518         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
519         mTestLooper.dispatchAll();
520         verifyPassthroughQNames(mVendorService, IFC_0, "atv", "gtv");
521 
522         mOffloadManagerBinder.addToPassthroughList(
523                 IFC_0, "_googlecast._tcp.local", mClientBinder0);
524         mTestLooper.dispatchAll();
525 
526         verifyPassthroughQNames(mVendorService, IFC_0, "_googlecast._tcp.local", "atv", "gtv");
527     }
528 
529     /**
530      * TODO(b/271353749#comment43) Remove this requirement once vendor implementations support
531      * case-insensitive string comparisons.
532      */
533     @Test
passthroughPreservesCase()534     public void passthroughPreservesCase() throws RemoteException {
535         setupDefaultOffloadManager();
536 
537         mOffloadManagerBinder.addToPassthroughList(IFC_0, "_SERVICE012._gtv.local", mClientBinder0);
538         mTestLooper.dispatchAll();
539 
540         verifyPassthroughQNames(mVendorService, IFC_0, "_SERVICE012._gtv.local");
541     }
542 
543     @Test
whenOutOfMemoryCapacity_priorityListQNamesAreNotEvicted()544     public void whenOutOfMemoryCapacity_priorityListQNamesAreNotEvicted() throws RemoteException {
545         setupDefaultOffloadManager();
546         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
547         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
548         mOffloadManagerBinder.addToPassthroughList(IFC_0, "another", mClientBinder0);
549         mOffloadManagerBinder.addToPassthroughList(IFC_0, "service", mClientBinder0);
550         mOffloadManagerBinder.addToPassthroughList(
551                 IFC_0, "_googlecast._tcp.local", mClientBinder0);
552         mTestLooper.dispatchAll();
553 
554         verifyPassthroughQNames(
555                 mVendorService, IFC_0, "_googlecast._tcp.local", "atv", "gtv", "another");
556     }
557 
558     @Test
whenVendorServiceBindsLate_offloadsData()559     public void whenVendorServiceBindsLate_offloadsData() throws RemoteException {
560         createOffloadManager();
561         registerNetwork(mNetwork0, IFC_0);
562         int recordKey = mOffloadManagerBinder.addProtocolResponses(
563                 IFC_0, SERVICE_ATV, mClientBinder0);
564         assertTrue("Expected a valid record key", recordKey > 0);
565         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
566         mTestLooper.dispatchAll();
567 
568         bindVendorService();
569 
570         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
571         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
572     }
573 
574     @Test
whenVendorServiceReconnects_restoresOffloadData()575     public void whenVendorServiceReconnects_restoresOffloadData() throws RemoteException {
576         setupDefaultOffloadManager();
577         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
578         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
579         mTestLooper.dispatchAll();
580         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
581         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
582 
583         unbindVendorService();
584         mVendorService = new FakeMdnsOffloadService();
585         bindVendorService();
586 
587         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
588         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
589     }
590 
591     @Test
whenClientDies_cleansUpOffloadData()592     public void whenClientDies_cleansUpOffloadData() throws RemoteException {
593         setupDefaultOffloadManager();
594         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
595         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
596         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_GOOGLECAST, mClientBinder1);
597         mOffloadManagerBinder.addToPassthroughList(IFC_0, "airplay", mClientBinder1);
598         mTestLooper.dispatchAll();
599         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_GOOGLECAST, SERVICE_ATV);
600         verifyPassthroughQNames(mVendorService, IFC_0, "gtv", "airplay");
601 
602         verify(mClientBinder1, atLeastOnce())
603                 .linkToDeath(mDeathRecipientCaptor.capture(), eq(0));
604         mDeathRecipientCaptor.getAllValues().forEach(IBinder.DeathRecipient::binderDied);
605         mTestLooper.dispatchAll();
606 
607         // Offload data owned by other clients is left untouched.
608         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
609         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
610     }
611 
612     @Test
whenNonInteractiveMode_enablesOffload()613     public void whenNonInteractiveMode_enablesOffload() throws RemoteException {
614         setupDefaultOffloadManager();
615         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
616         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
617         mTestLooper.dispatchAll();
618 
619         mCapturedScreenBroadcastReceiver.onReceive(
620                 mock(Context.class), makeIntent(Intent.ACTION_SCREEN_OFF));
621         mTestLooper.dispatchAll();
622 
623         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
624         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
625         assertTrue(mVendorService.mOffloadState);
626     }
627 
628     @Test
whenInteractiveMode_disablesOffloadAndRetrievesMetrics()629     public void whenInteractiveMode_disablesOffloadAndRetrievesMetrics() throws RemoteException {
630         setupDefaultOffloadManager();
631         int recordKey = mOffloadManagerBinder.addProtocolResponses(
632                 IFC_0, SERVICE_ATV, mClientBinder0);
633         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
634         mTestLooper.dispatchAll();
635         mCapturedScreenBroadcastReceiver.onReceive(
636                 mock(Context.class), makeIntent(Intent.ACTION_SCREEN_OFF));
637         mTestLooper.dispatchAll();
638         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
639         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
640         assertTrue(mVendorService.mOffloadState);
641         reset(mVendorService); // Forget previous calls to hit/miss counter methods.
642 
643         mCapturedScreenBroadcastReceiver.onReceive(
644                 mock(Context.class), makeIntent(Intent.ACTION_SCREEN_ON));
645         mTestLooper.dispatchAll();
646 
647         assertFalse(mVendorService.mOffloadState);
648         verify(mVendorService).getAndResetHitCounter(eq(recordKey));
649         verify(mVendorService).getAndResetMissCounter();
650         // Offloaded records are untouched.
651         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
652         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
653     }
654 
655     @Test
whenNetworkNotAvailable_noOffloadOrPassthrough()656     public void whenNetworkNotAvailable_noOffloadOrPassthrough() throws RemoteException {
657         createOffloadManager();
658         bindVendorService();
659 
660         int recordKey = mOffloadManagerBinder.addProtocolResponses(
661                 IFC_0, SERVICE_ATV, mClientBinder0);
662         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
663         mTestLooper.dispatchAll();
664 
665         assertTrue("Expected a valid record key", recordKey > 0);
666         verifyOffloadedServices(mVendorService, IFC_0);
667         verifyPassthroughQNames(mVendorService, IFC_0);
668         assertEquals(
669                 PassthroughBehavior.DROP_ALL,
670                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
671     }
672 
673     @Test
whenNetworkBecomesAvailableLate_offloadsData()674     public void whenNetworkBecomesAvailableLate_offloadsData() throws RemoteException {
675         createOffloadManager();
676         bindVendorService();
677         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
678         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
679         mTestLooper.dispatchAll();
680 
681         registerNetwork(mNetwork0, IFC_0);
682 
683         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
684         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
685     }
686 
687     @Test
whenNetworkLost_removesOffloadData()688     public void whenNetworkLost_removesOffloadData() throws RemoteException {
689         setupDefaultOffloadManager();
690         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
691         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
692         mTestLooper.dispatchAll();
693         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
694         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
695 
696         unregisterNetwork(mNetwork0);
697 
698         verifyOffloadedServices(mVendorService, IFC_0);
699         verifyPassthroughQNames(mVendorService, IFC_0);
700         assertEquals(
701                 PassthroughBehavior.DROP_ALL,
702                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
703     }
704 
705     @Test
whenNetworkLost_maintainsOffloadDataOnOtherInterfaces()706     public void whenNetworkLost_maintainsOffloadDataOnOtherInterfaces() throws RemoteException {
707         setupDefaultOffloadManager();
708         registerNetwork(mNetwork1, IFC_1);
709         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
710         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
711         mOffloadManagerBinder.addProtocolResponses(IFC_1, SERVICE_GOOGLECAST, mClientBinder0);
712         mOffloadManagerBinder.addToPassthroughList(IFC_1, "airplay", mClientBinder0);
713         mTestLooper.dispatchAll();
714         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
715         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
716         verifyOffloadedServices(mVendorService, IFC_1, SERVICE_GOOGLECAST);
717         verifyPassthroughQNames(mVendorService, IFC_1, "airplay");
718 
719         unregisterNetwork(mNetwork1);
720 
721         // Offload data on IFC_0 is left untouched.
722         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
723         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
724         assertEquals(
725                 PassthroughBehavior.PASSTHROUGH_LIST,
726                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
727         verifyOffloadedServices(mVendorService, IFC_1);
728         verifyPassthroughQNames(mVendorService, IFC_1);
729         assertEquals(
730                 PassthroughBehavior.DROP_ALL,
731                 mVendorService.getOffloadData(IFC_1).passthroughBehavior);
732     }
733 
734     @Test
whenNetworkRecovers_restoresOffloadData()735     public void whenNetworkRecovers_restoresOffloadData() throws RemoteException {
736         setupDefaultOffloadManager();
737         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
738         mOffloadManagerBinder.addToPassthroughList(IFC_0, "gtv", mClientBinder0);
739         mTestLooper.dispatchAll();
740         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
741         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
742         unregisterNetwork(mNetwork0);
743         verifyOffloadedServices(mVendorService, IFC_0);
744         verifyPassthroughQNames(mVendorService, IFC_0);
745 
746         registerNetwork(mNetwork0, IFC_0);
747 
748         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
749         verifyPassthroughQNames(mVendorService, IFC_0, "gtv");
750         assertEquals(
751                 PassthroughBehavior.PASSTHROUGH_LIST,
752                 mVendorService.getOffloadData(IFC_0).passthroughBehavior);
753     }
754 
755     @Test
callingPackageNotOnLowPowerExemptedList_dataNotOffloaded()756     public void callingPackageNotOnLowPowerExemptedList_dataNotOffloaded() throws RemoteException {
757         setupDefaultOffloadManager();
758         mCallingUid = APP_UID_1;
759 
760         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
761         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
762         mTestLooper.dispatchAll();
763 
764         verifyOffloadedServices(mVendorService, IFC_0);
765         verifyPassthroughQNames(mVendorService, IFC_0);
766     }
767 
768     @Test
packageRemovedFromLowPowerExemptedList_correspondingDataIsCleared()769     public void packageRemovedFromLowPowerExemptedList_correspondingDataIsCleared()
770             throws RemoteException {
771         setupDefaultOffloadManager();
772         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
773         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
774         mTestLooper.dispatchAll();
775         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
776         verifyPassthroughQNames(mVendorService, IFC_0, "atv");
777 
778         mLowPowerStandbyPolicy = makeLowPowerStandbyPolicy(APP_PACKAGE_1);
779         mCapturedLowPowerStandbyPolicyReceiver.onReceive(
780                 mock(Context.class),
781                 makeIntent(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED));
782         mTestLooper.dispatchAll();
783 
784         verifyOffloadedServices(mVendorService, IFC_0);
785         verifyPassthroughQNames(mVendorService, IFC_0);
786     }
787 
788     @Test
packageAddedToLowPowerExemptedList_dataIsOffloaded()789     public void packageAddedToLowPowerExemptedList_dataIsOffloaded() throws RemoteException {
790         setupDefaultOffloadManager();
791         mCallingUid = APP_UID_1;
792         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
793         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
794         mTestLooper.dispatchAll();
795         verifyOffloadedServices(mVendorService, IFC_0);
796         verifyPassthroughQNames(mVendorService, IFC_0);
797 
798         mLowPowerStandbyPolicy = makeLowPowerStandbyPolicy(APP_PACKAGE_0, APP_PACKAGE_1);
799         mCapturedLowPowerStandbyPolicyReceiver.onReceive(
800                 mock(Context.class),
801                 makeIntent(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED));
802         mTestLooper.dispatchAll();
803 
804         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
805         verifyPassthroughQNames(mVendorService, IFC_0, "atv");
806     }
807 
808     /**
809      * Ensure package allowlist is maintained by app ID, not UID (which is assigned per app & user
810      * combination).
811      */
812     @Test
secondaryUser_packageIsAllowlisted()813     public void secondaryUser_packageIsAllowlisted() throws RemoteException {
814         setupDefaultOffloadManager();
815         mCallingUid = SECONDARY_USER_APP_UID_0;
816 
817         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
818         mOffloadManagerBinder.addToPassthroughList(IFC_0, "atv", mClientBinder0);
819         mTestLooper.dispatchAll();
820 
821         verifyOffloadedServices(mVendorService, IFC_0, SERVICE_ATV);
822         verifyPassthroughQNames(mVendorService, IFC_0, "atv");
823     }
824 
825     @Test
serviceDump_containsDebuggingInfo()826     public void serviceDump_containsDebuggingInfo() throws RemoteException {
827         setupDefaultOffloadManager();
828         mOffloadManagerBinder.addProtocolResponses(IFC_0, SERVICE_ATV, mClientBinder0);
829         mOffloadManagerBinder.addToPassthroughList(
830                 IFC_0, "atv", mClientBinder0);
831         mCallingUid = APP_UID_1;
832         mOffloadManagerBinder.addProtocolResponses(IFC_1, SERVICE_GOOGLECAST, mClientBinder1);
833         mOffloadManagerBinder.addToPassthroughList(
834                 IFC_1, "gtv", mClientBinder1);
835         mTestLooper.dispatchAll();
836 
837         StringWriter resultWriter = new StringWriter();
838         mOffloadManagerService.dump(null, new PrintWriter(resultWriter), null);
839         mTestLooper.dispatchAll();
840         String result = resultWriter.getBuffer().toString();
841 
842         Log.d(TAG, "Service dump:\n" + result);
843         assertTrue(result.contains("""
844                 OffloadIntentStore:
845                 offload intents:
846                 * OffloadIntent{mNetworkInterface='imaginaryif0', mRecordKey=1, mPriority=1, mOwnerAppId=1234}
847                 * OffloadIntent{mNetworkInterface='imaginaryif1', mRecordKey=2, mPriority=-2, mOwnerAppId=1235}
848                 passthrough intents:
849                 * PassthroughIntent{mNetworkInterface='imaginaryif0', mOriginalQName='atv', mCanonicalQName='ATV.', mPriority=0, mOwnerAppId=1234}
850                 * PassthroughIntent{mNetworkInterface='imaginaryif1', mOriginalQName='gtv', mCanonicalQName='GTV.', mPriority=0, mOwnerAppId=1235}
851 
852                 """));
853         assertTrue(result.contains("""
854                 InterfaceOffloadManager[imaginaryif0]:
855                 mIsNetworkAvailable=true
856                 current offload keys:
857                 * 0
858                 current passthrough qnames:
859                 * atv
860 
861                 """));
862         assertTrue(result.contains("""
863                 InterfaceOffloadManager[imaginaryif1]:
864                 mIsNetworkAvailable=false
865                 current offload keys:
866                 current passthrough qnames:
867 
868                 """));
869         assertTrue(result.contains("""
870                 OffloadWriter:
871                 mOffloadState=false
872                 isVendorServiceConnected=true
873 
874                 """));
875         assertTrue(result.contains("""
876                 mRecordKey=1
877                 match criteria:
878                 * MatchCriteria{type=1, nameOffset=12}
879                 raw offload packet:
880                 000000:  00 00 00 00 00 00 00 01 00 00 00 00 03 61 74 76   |  .............atv
881                 000016:  00 00 01 80 01 00 00 00 05 00 04 64 50 28 14      |  ...........dP(.
882                 """));
883         assertTrue(result.contains("""
884                 mRecordKey=2
885                 match criteria:
886                 * MatchCriteria{type=12, nameOffset=12}
887                 * MatchCriteria{type=1, nameOffset=55}
888                 raw offload packet:
889                 000000:  00 00 00 00 00 00 00 02 00 00 00 00 0b 5f 67 6f   |  ............._go
890                 000016:  6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f   |  oglecast._tcp.lo
891                 000032:  63 61 6c 00 00 0c 80 01 00 00 00 05 00 09 06 74   |  cal............t
892                 000048:  76 2d 61 62 63 c0 1d c0 2e 00 01 80 01 00 00 00   |  v-abc...........
893                 000064:  05 00 04 64 50 28 14                              |  ...dP(.
894                 """));
895     }
896 }