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.phone.satellite.accesscontrol;
18 
19 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
20 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_LOCATION_NOT_AVAILABLE;
21 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
22 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
23 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
24 
25 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.EVENT_CONFIG_DATA_UPDATED;
26 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.GOOGLE_US_SAN_SAT_S2_FILE_NAME;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertNull;
31 import static org.junit.Assert.assertSame;
32 import static org.junit.Assert.assertTrue;
33 import static org.junit.Assert.fail;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.anyBoolean;
36 import static org.mockito.ArgumentMatchers.anyInt;
37 import static org.mockito.ArgumentMatchers.anyString;
38 import static org.mockito.ArgumentMatchers.eq;
39 import static org.mockito.Mockito.clearInvocations;
40 import static org.mockito.Mockito.doAnswer;
41 import static org.mockito.Mockito.doReturn;
42 import static org.mockito.Mockito.mock;
43 import static org.mockito.Mockito.never;
44 import static org.mockito.Mockito.spy;
45 import static org.mockito.Mockito.times;
46 import static org.mockito.Mockito.verify;
47 import static org.mockito.Mockito.when;
48 
49 import android.annotation.Nullable;
50 import android.content.Context;
51 import android.content.SharedPreferences;
52 import android.content.res.Resources;
53 import android.location.Location;
54 import android.location.LocationManager;
55 import android.location.LocationRequest;
56 import android.os.AsyncResult;
57 import android.os.Bundle;
58 import android.os.CancellationSignal;
59 import android.os.DropBoxManager;
60 import android.os.Handler;
61 import android.os.HandlerThread;
62 import android.os.Looper;
63 import android.os.Message;
64 import android.os.ResultReceiver;
65 import android.telecom.TelecomManager;
66 import android.telephony.satellite.SatelliteManager;
67 import android.testing.TestableLooper;
68 import android.util.Log;
69 import android.util.Pair;
70 
71 import androidx.test.ext.junit.runners.AndroidJUnit4;
72 
73 import com.android.internal.telephony.Phone;
74 import com.android.internal.telephony.PhoneFactory;
75 import com.android.internal.telephony.TelephonyCountryDetector;
76 import com.android.internal.telephony.flags.FeatureFlags;
77 import com.android.internal.telephony.satellite.SatelliteConfig;
78 import com.android.internal.telephony.satellite.SatelliteConfigParser;
79 import com.android.internal.telephony.satellite.SatelliteController;
80 import com.android.internal.telephony.satellite.SatelliteModemInterface;
81 
82 import org.junit.After;
83 import org.junit.Before;
84 import org.junit.Test;
85 import org.junit.runner.RunWith;
86 import org.mockito.ArgumentCaptor;
87 import org.mockito.Captor;
88 import org.mockito.Mock;
89 import org.mockito.MockitoAnnotations;
90 
91 import java.io.File;
92 import java.lang.reflect.Field;
93 import java.util.ArrayList;
94 import java.util.Arrays;
95 import java.util.HashMap;
96 import java.util.List;
97 import java.util.Map;
98 import java.util.Set;
99 import java.util.concurrent.Executor;
100 import java.util.concurrent.Semaphore;
101 import java.util.concurrent.TimeUnit;
102 import java.util.function.Consumer;
103 
104 /** Unit test for {@link SatelliteAccessController} */
105 @RunWith(AndroidJUnit4.class)
106 public class SatelliteAccessControllerTest {
107     private static final String TAG = "SatelliteAccessControllerTest";
108     private static final String[] TEST_SATELLITE_COUNTRY_CODES = {"US", "CA", "UK"};
109     private static final String TEST_SATELLITE_S2_FILE = "sat_s2_file.dat";
110     private static final boolean TEST_SATELLITE_ALLOW = true;
111     private static final int TEST_LOCATION_FRESH_DURATION_SECONDS = 10;
112     private static final long TEST_LOCATION_FRESH_DURATION_NANOS =
113             TimeUnit.SECONDS.toNanos(TEST_LOCATION_FRESH_DURATION_SECONDS);
114     private static final long TIMEOUT = 500;
115     private static final List<String> EMPTY_STRING_LIST = new ArrayList<>();
116     private static final List<String> LOCATION_PROVIDERS =
117             listOf(LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
118     private static final int SUB_ID = 0;
119 
120     @Mock
121     private LocationManager mMockLocationManager;
122     @Mock
123     private TelecomManager mMockTelecomManager;
124     @Mock
125     private TelephonyCountryDetector mMockCountryDetector;
126     @Mock
127     private SatelliteController mMockSatelliteController;
128     @Mock
129     private SatelliteModemInterface mMockSatelliteModemInterface;
130     @Mock
131     private DropBoxManager mMockDropBoxManager;
132     @Mock
133     private Context mMockContext;
134     @Mock
135     private Phone mMockPhone;
136     @Mock
137     private Phone mMockPhone2;
138     @Mock
139     private FeatureFlags mMockFeatureFlags;
140     @Mock
141     private Resources mMockResources;
142     @Mock
143     private SatelliteOnDeviceAccessController mMockSatelliteOnDeviceAccessController;
144     @Mock
145     Location mMockLocation0;
146     @Mock
147     Location mMockLocation1;
148     @Mock
149     File mMockSatS2File;
150     @Mock
151     SharedPreferences mMockSharedPreferences;
152     @Mock
153     private SharedPreferences.Editor mMockSharedPreferencesEditor;
154     @Mock
155     private Map<SatelliteOnDeviceAccessController.LocationToken, Boolean>
156             mMockCachedAccessRestrictionMap;
157 
158     private Looper mLooper;
159     private TestableLooper mTestableLooper;
160     private Phone[] mPhones;
161     private TestSatelliteAccessController mSatelliteAccessControllerUT;
162 
163     @Captor
164     private ArgumentCaptor<CancellationSignal> mLocationRequestCancellationSignalCaptor;
165     @Captor
166     private ArgumentCaptor<Consumer<Location>> mLocationRequestConsumerCaptor;
167     @Captor
168     private ArgumentCaptor<Handler> mConfigUpdateHandlerCaptor;
169     @Captor
170     private ArgumentCaptor<Integer> mConfigUpdateIntCaptor;
171     @Captor
172     private ArgumentCaptor<Object> mConfigUpdateObjectCaptor;
173     private boolean mQueriedSatelliteAllowed = false;
174     private int mQueriedSatelliteAllowedResultCode = SATELLITE_RESULT_SUCCESS;
175     private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0);
176     private ResultReceiver mSatelliteAllowedReceiver = new ResultReceiver(null) {
177         @Override
178         protected void onReceiveResult(int resultCode, Bundle resultData) {
179             mQueriedSatelliteAllowedResultCode = resultCode;
180             if (resultCode == SATELLITE_RESULT_SUCCESS) {
181                 if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
182                     mQueriedSatelliteAllowed = resultData.getBoolean(
183                             KEY_SATELLITE_COMMUNICATION_ALLOWED);
184                 } else {
185                     logd("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
186                     mQueriedSatelliteAllowed = false;
187                 }
188             } else {
189                 logd("mSatelliteAllowedReceiver: resultCode=" + resultCode);
190                 mQueriedSatelliteAllowed = false;
191             }
192             try {
193                 mSatelliteAllowedSemaphore.release();
194             } catch (Exception ex) {
195                 fail("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex);
196             }
197         }
198     };
199 
200     @Before
setUp()201     public void setUp() throws Exception {
202         logd("setUp");
203         MockitoAnnotations.initMocks(this);
204 
205         if (Looper.myLooper() == null) {
206             Looper.prepare();
207         }
208 
209         HandlerThread handlerThread = new HandlerThread("SatelliteAccessControllerTest");
210         handlerThread.start();
211         mLooper = handlerThread.getLooper();
212         mTestableLooper = new TestableLooper(mLooper);
213         when(mMockContext.getSystemServiceName(LocationManager.class)).thenReturn(
214                 Context.LOCATION_SERVICE);
215         when(mMockContext.getSystemServiceName(TelecomManager.class)).thenReturn(
216                 Context.TELECOM_SERVICE);
217         when(mMockContext.getSystemServiceName(DropBoxManager.class)).thenReturn(
218                 Context.DROPBOX_SERVICE);
219         when(mMockContext.getSystemService(LocationManager.class)).thenReturn(
220                 mMockLocationManager);
221         when(mMockContext.getSystemService(TelecomManager.class)).thenReturn(
222                 mMockTelecomManager);
223         when(mMockContext.getSystemService(DropBoxManager.class)).thenReturn(
224                 mMockDropBoxManager);
225         mPhones = new Phone[]{mMockPhone, mMockPhone2};
226         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
227         replaceInstance(SatelliteController.class, "sInstance", null,
228                 mMockSatelliteController);
229         replaceInstance(SatelliteModemInterface.class, "sInstance", null,
230                 mMockSatelliteModemInterface);
231         replaceInstance(TelephonyCountryDetector.class, "sInstance", null,
232                 mMockCountryDetector);
233         when(mMockContext.getResources()).thenReturn(mMockResources);
234         when(mMockResources.getStringArray(
235                 com.android.internal.R.array.config_oem_enabled_satellite_country_codes))
236                 .thenReturn(TEST_SATELLITE_COUNTRY_CODES);
237         when(mMockResources.getBoolean(
238                 com.android.internal.R.bool.config_oem_enabled_satellite_access_allow))
239                 .thenReturn(TEST_SATELLITE_ALLOW);
240         when(mMockResources.getString(
241                 com.android.internal.R.string.config_oem_enabled_satellite_s2cell_file))
242                 .thenReturn(TEST_SATELLITE_S2_FILE);
243         when(mMockResources.getInteger(com.android.internal.R.integer
244                 .config_oem_enabled_satellite_location_fresh_duration))
245                 .thenReturn(TEST_LOCATION_FRESH_DURATION_SECONDS);
246 
247         when(mMockLocationManager.getProviders(true)).thenReturn(LOCATION_PROVIDERS);
248         when(mMockLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER))
249                 .thenReturn(mMockLocation0);
250         when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
251                 .thenReturn(mMockLocation1);
252         when(mMockLocation0.getLatitude()).thenReturn(0.0);
253         when(mMockLocation0.getLongitude()).thenReturn(0.0);
254         when(mMockLocation1.getLatitude()).thenReturn(1.0);
255         when(mMockLocation1.getLongitude()).thenReturn(1.0);
256         when(mMockSatelliteOnDeviceAccessController.isSatCommunicationAllowedAtLocation(
257                 any(SatelliteOnDeviceAccessController.LocationToken.class))).thenReturn(true);
258 
259         when(mMockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(
260                 mMockSharedPreferences);
261         when(mMockSharedPreferences.getBoolean(anyString(), anyBoolean())).thenReturn(true);
262         when(mMockSharedPreferences.getStringSet(anyString(), any()))
263                 .thenReturn(Set.of(TEST_SATELLITE_COUNTRY_CODES));
264         doReturn(mMockSharedPreferencesEditor).when(mMockSharedPreferences).edit();
265         doReturn(mMockSharedPreferencesEditor).when(mMockSharedPreferencesEditor)
266                 .putBoolean(anyString(), anyBoolean());
267         doReturn(mMockSharedPreferencesEditor).when(mMockSharedPreferencesEditor)
268                 .putStringSet(anyString(), any());
269 
270         when(mMockFeatureFlags.satellitePersistentLogging()).thenReturn(true);
271 
272         mSatelliteAccessControllerUT = new TestSatelliteAccessController(mMockContext,
273                 mMockFeatureFlags, mLooper, mMockLocationManager, mMockTelecomManager,
274                 mMockSatelliteOnDeviceAccessController, mMockSatS2File);
275         mTestableLooper.processAllMessages();
276     }
277 
278     @After
tearDown()279     public void tearDown() throws Exception {
280         logd("tearDown");
281         if (mTestableLooper != null) {
282             mTestableLooper.destroy();
283             mTestableLooper = null;
284         }
285 
286         if (mLooper != null) {
287             mLooper.quit();
288             mLooper = null;
289         }
290     }
291 
292     @Test
testGetInstance()293     public void testGetInstance() {
294         SatelliteAccessController inst1 =
295                 SatelliteAccessController.getOrCreateInstance(mMockContext, mMockFeatureFlags);
296         SatelliteAccessController inst2 =
297                 SatelliteAccessController.getOrCreateInstance(mMockContext, mMockFeatureFlags);
298         assertEquals(inst1, inst2);
299     }
300 
301     @Test
testRequestIsSatelliteCommunicationAllowedForCurrentLocation()302     public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() throws Exception {
303         // OEM-enabled satellite is not supported
304         when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
305         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
306                 SUB_ID, mSatelliteAllowedReceiver);
307         mTestableLooper.processAllMessages();
308         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
309                 mSatelliteAllowedSemaphore, 1));
310         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
311 
312         // OEM-enabled satellite is supported
313         when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
314 
315         // Satellite is not supported
316         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
317         clearAllInvocations();
318         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
319                 SUB_ID, mSatelliteAllowedReceiver);
320         mTestableLooper.processAllMessages();
321         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
322                 mSatelliteAllowedSemaphore, 1));
323         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
324         assertFalse(mQueriedSatelliteAllowed);
325 
326         // Failed to query whether satellite is supported or not
327         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_MODEM_ERROR);
328         clearAllInvocations();
329         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
330                 SUB_ID, mSatelliteAllowedReceiver);
331         mTestableLooper.processAllMessages();
332         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
333                 mSatelliteAllowedSemaphore, 1));
334         assertEquals(SATELLITE_RESULT_MODEM_ERROR, mQueriedSatelliteAllowedResultCode);
335 
336         // Network country codes are not available. TelecomManager.isInEmergencyCall() returns true.
337         // On-device access controller will be used. Last known location is available and fresh.
338         clearAllInvocations();
339         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
340         setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
341         when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
342         when(mMockTelecomManager.isInEmergencyCall()).thenReturn(true);
343         mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
344         when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(2L);
345         when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
346         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
347                 SUB_ID, mSatelliteAllowedReceiver);
348         mTestableLooper.processAllMessages();
349         assertTrue(
350                 mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
351         verify(mMockSatelliteOnDeviceAccessController).isSatCommunicationAllowedAtLocation(
352                 any(SatelliteOnDeviceAccessController.LocationToken.class));
353         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
354                 mSatelliteAllowedSemaphore, 1));
355         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
356         assertTrue(mQueriedSatelliteAllowed);
357 
358         // Move time forward and verify resources are cleaned up
359         clearAllInvocations();
360         mTestableLooper.moveTimeForward(mSatelliteAccessControllerUT
361                 .getKeepOnDeviceAccessControllerResourcesTimeoutMillis());
362         mTestableLooper.processAllMessages();
363         assertFalse(
364                 mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
365         assertTrue(mSatelliteAccessControllerUT.isSatelliteOnDeviceAccessControllerReset());
366         verify(mMockSatelliteOnDeviceAccessController).close();
367 
368         // Restore SatelliteOnDeviceAccessController for next verification
369         mSatelliteAccessControllerUT.setSatelliteOnDeviceAccessController(
370                 mMockSatelliteOnDeviceAccessController);
371 
372         // Network country codes are not available. TelecomManager.isInEmergencyCall() returns
373         // false. Phone0 is in ECM. On-device access controller will be used. Last known location is
374         // not fresh.
375         clearAllInvocations();
376         when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
377         when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
378         when(mMockPhone.isInEcm()).thenReturn(true);
379         mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
380         when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
381         when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
382         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
383                 SUB_ID, mSatelliteAllowedReceiver);
384         mTestableLooper.processAllMessages();
385         assertFalse(
386                 mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
387         verify(mMockLocationManager).getCurrentLocation(eq(LocationManager.GPS_PROVIDER),
388                 any(LocationRequest.class), mLocationRequestCancellationSignalCaptor.capture(),
389                 any(Executor.class), mLocationRequestConsumerCaptor.capture());
390         assertTrue(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
391         sendLocationRequestResult(mMockLocation0);
392         assertFalse(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
393         // The LocationToken should be already in the cache
394         verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
395                 any(SatelliteOnDeviceAccessController.LocationToken.class));
396         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
397                 mSatelliteAllowedSemaphore, 1));
398         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
399         assertTrue(mQueriedSatelliteAllowed);
400 
401         // Timed out to wait for current location. No cached allowed state.
402         clearAllInvocations();
403         mSatelliteAccessControllerUT.setIsSatelliteCommunicationAllowedForCurrentLocationCache(
404                 "cache_clear_and_not_allowed");
405         when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
406         when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
407         when(mMockPhone.isInEcm()).thenReturn(true);
408         mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
409         when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
410         when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
411         when(mMockCountryDetector.getCachedLocationCountryIsoInfo()).thenReturn(new Pair<>("", 0L));
412         when(mMockCountryDetector.getCachedNetworkCountryIsoInfo()).thenReturn(new HashMap<>());
413         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
414                 SUB_ID, mSatelliteAllowedReceiver);
415         mTestableLooper.processAllMessages();
416         assertFalse(
417                 mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
418         verify(mMockLocationManager).getCurrentLocation(anyString(), any(LocationRequest.class),
419                 any(CancellationSignal.class), any(Executor.class), any(Consumer.class));
420         assertTrue(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
421         // Timed out
422         mTestableLooper.moveTimeForward(
423                 mSatelliteAccessControllerUT.getWaitForCurrentLocationTimeoutMillis());
424         mTestableLooper.processAllMessages();
425         assertFalse(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
426         verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
427                 any(SatelliteOnDeviceAccessController.LocationToken.class));
428         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
429                 mSatelliteAllowedSemaphore, 1));
430         assertEquals(SATELLITE_RESULT_LOCATION_NOT_AVAILABLE, mQueriedSatelliteAllowedResultCode);
431 
432         // Network country codes are not available. TelecomManager.isInEmergencyCall() returns
433         // false. No phone is in ECM. Last known location is not fresh. Cached country codes should
434         // be used for verifying satellite allow. No cached country codes are available.
435         clearAllInvocations();
436         when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
437         when(mMockCountryDetector.getCachedLocationCountryIsoInfo()).thenReturn(new Pair<>("", 0L));
438         when(mMockCountryDetector.getCachedNetworkCountryIsoInfo()).thenReturn(new HashMap<>());
439         when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
440         when(mMockPhone.isInEcm()).thenReturn(false);
441         when(mMockPhone2.isInEcm()).thenReturn(false);
442         mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
443         when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
444         when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
445         mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
446                 SUB_ID, mSatelliteAllowedReceiver);
447         mTestableLooper.processAllMessages();
448         verify(mMockLocationManager, never()).getCurrentLocation(anyString(),
449                 any(LocationRequest.class), any(CancellationSignal.class), any(Executor.class),
450                 any(Consumer.class));
451         verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
452                 any(SatelliteOnDeviceAccessController.LocationToken.class));
453         assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
454                 mSatelliteAllowedSemaphore, 1));
455         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
456         assertFalse(mQueriedSatelliteAllowed);
457     }
458 
459     @Test
testUpdateSatelliteConfigData()460     public void testUpdateSatelliteConfigData() throws Exception {
461         verify(mMockSatelliteController).registerForConfigUpdateChanged(
462                 mConfigUpdateHandlerCaptor.capture(), mConfigUpdateIntCaptor.capture(),
463                 mConfigUpdateObjectCaptor.capture());
464 
465         assertSame(mConfigUpdateHandlerCaptor.getValue(), mSatelliteAccessControllerUT);
466         assertSame(mConfigUpdateIntCaptor.getValue(), EVENT_CONFIG_DATA_UPDATED);
467         assertSame(mConfigUpdateObjectCaptor.getValue(), mMockContext);
468 
469         replaceInstance(SatelliteAccessController.class, "mCachedAccessRestrictionMap",
470                 mSatelliteAccessControllerUT, mMockCachedAccessRestrictionMap);
471 
472         // These APIs are executed during loadRemoteConfigs
473         verify(mMockSharedPreferences, times(1)).getStringSet(anyString(), any());
474         verify(mMockSharedPreferences, times(1)).getBoolean(anyString(), anyBoolean());
475 
476         // satelliteConfig is null
477         SatelliteConfigParser spyConfigParser =
478                 spy(new SatelliteConfigParser("test".getBytes()));
479         doReturn(spyConfigParser).when(mMockSatelliteController).getSatelliteConfigParser();
480         assertNull(spyConfigParser.getConfig());
481 
482         sendConfigUpdateChangedEvent(mMockContext);
483         verify(mMockSharedPreferences, never()).edit();
484         verify(mMockCachedAccessRestrictionMap, never()).clear();
485 
486         // satelliteConfig has invalid country codes
487         SatelliteConfig mockConfig = mock(SatelliteConfig.class);
488         doReturn(List.of("USA", "JAP")).when(mockConfig).getDeviceSatelliteCountryCodes();
489         doReturn(mockConfig).when(mMockSatelliteController).getSatelliteConfig();
490         doReturn(false).when(mockConfig).isSatelliteDataForAllowedRegion();
491 
492         sendConfigUpdateChangedEvent(mMockContext);
493         verify(mMockSharedPreferences, never()).edit();
494         verify(mMockCachedAccessRestrictionMap, never()).clear();
495 
496         // satelliteConfig does not have is_allow_access_control data
497         doReturn(List.of(TEST_SATELLITE_COUNTRY_CODES))
498                 .when(mockConfig).getDeviceSatelliteCountryCodes();
499         doReturn(null).when(mockConfig).isSatelliteDataForAllowedRegion();
500 
501         sendConfigUpdateChangedEvent(mMockContext);
502         verify(mMockSharedPreferences, never()).edit();
503         verify(mMockCachedAccessRestrictionMap, never()).clear();
504 
505         // satelliteConfig doesn't have S2CellFile
506         File mockFile = mock(File.class);
507         doReturn(false).when(mockFile).exists();
508         doReturn(List.of(TEST_SATELLITE_COUNTRY_CODES))
509                 .when(mockConfig).getDeviceSatelliteCountryCodes();
510         doReturn(true).when(mockConfig).isSatelliteDataForAllowedRegion();
511         doReturn(mockFile).when(mockConfig).getSatelliteS2CellFile(mMockContext);
512 
513         sendConfigUpdateChangedEvent(mMockContext);
514         verify(mMockSharedPreferences, never()).edit();
515         verify(mMockCachedAccessRestrictionMap, never()).clear();
516 
517         // satelliteConfig has valid data
518         doReturn(mockConfig).when(mMockSatelliteController).getSatelliteConfig();
519         File testS2File = mSatelliteAccessControllerUT
520                 .getTestSatelliteS2File(GOOGLE_US_SAN_SAT_S2_FILE_NAME);
521         doReturn(List.of(TEST_SATELLITE_COUNTRY_CODES))
522                 .when(mockConfig).getDeviceSatelliteCountryCodes();
523         doReturn(true).when(mockConfig).isSatelliteDataForAllowedRegion();
524         doReturn(testS2File).when(mockConfig).getSatelliteS2CellFile(mMockContext);
525 
526         sendConfigUpdateChangedEvent(mMockContext);
527         verify(mMockSharedPreferences, times(2)).edit();
528         verify(mMockCachedAccessRestrictionMap, times(1)).clear();
529     }
530 
sendConfigUpdateChangedEvent(Context context)531     private void sendConfigUpdateChangedEvent(Context context) {
532         Message msg = mSatelliteAccessControllerUT.obtainMessage(EVENT_CONFIG_DATA_UPDATED);
533         msg.obj = new AsyncResult(context, SATELLITE_RESULT_SUCCESS, null);
534         msg.sendToTarget();
535         mTestableLooper.processAllMessages();
536     }
537 
clearAllInvocations()538     private void clearAllInvocations() {
539         clearInvocations(mMockSatelliteController);
540         clearInvocations(mMockSatelliteOnDeviceAccessController);
541         clearInvocations(mMockLocationManager);
542         clearInvocations(mMockCountryDetector);
543     }
544 
verifyCountryDetectorApisCalled()545     private void verifyCountryDetectorApisCalled() {
546         verify(mMockCountryDetector).getCurrentNetworkCountryIso();
547         verify(mMockCountryDetector).getCachedLocationCountryIsoInfo();
548         verify(mMockCountryDetector).getCachedLocationCountryIsoInfo();
549     }
550 
waitForRequestIsSatelliteAllowedForCurrentLocationResult(Semaphore semaphore, int expectedNumberOfEvents)551     private boolean waitForRequestIsSatelliteAllowedForCurrentLocationResult(Semaphore semaphore,
552             int expectedNumberOfEvents) {
553         for (int i = 0; i < expectedNumberOfEvents; i++) {
554             try {
555                 if (!semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
556                     logd("Timeout to receive "
557                             + "requestIsCommunicationAllowedForCurrentLocation()"
558                             + " callback");
559                     return false;
560                 }
561             } catch (Exception ex) {
562                 logd("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex);
563                 return false;
564             }
565         }
566         return true;
567     }
568 
sendLocationRequestResult(Location location)569     private void sendLocationRequestResult(Location location) {
570         mLocationRequestConsumerCaptor.getValue().accept(location);
571         mTestableLooper.processAllMessages();
572     }
573 
setUpResponseForRequestIsSatelliteSupported( boolean isSatelliteSupported, @SatelliteManager.SatelliteResult int error)574     private void setUpResponseForRequestIsSatelliteSupported(
575             boolean isSatelliteSupported, @SatelliteManager.SatelliteResult int error) {
576         doAnswer(invocation -> {
577             ResultReceiver resultReceiver = invocation.getArgument(1);
578             if (error == SATELLITE_RESULT_SUCCESS) {
579                 Bundle bundle = new Bundle();
580                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, isSatelliteSupported);
581                 resultReceiver.send(error, bundle);
582             } else {
583                 resultReceiver.send(error, Bundle.EMPTY);
584             }
585             return null;
586         }).when(mMockSatelliteController).requestIsSatelliteSupported(anyInt(),
587                 any(ResultReceiver.class));
588     }
589 
setUpResponseForRequestIsSatelliteProvisioned( boolean isSatelliteProvisioned, @SatelliteManager.SatelliteResult int error)590     private void setUpResponseForRequestIsSatelliteProvisioned(
591             boolean isSatelliteProvisioned, @SatelliteManager.SatelliteResult int error) {
592         doAnswer(invocation -> {
593             ResultReceiver resultReceiver = invocation.getArgument(1);
594             if (error == SATELLITE_RESULT_SUCCESS) {
595                 Bundle bundle = new Bundle();
596                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
597                         isSatelliteProvisioned);
598                 resultReceiver.send(error, bundle);
599             } else {
600                 resultReceiver.send(error, Bundle.EMPTY);
601             }
602             return null;
603         }).when(mMockSatelliteController).requestIsSatelliteProvisioned(anyInt(),
604                 any(ResultReceiver.class));
605     }
606 
607     @SafeVarargs
listOf(E... values)608     private static <E> List<E> listOf(E... values) {
609         return Arrays.asList(values);
610     }
611 
logd(String message)612     private static void logd(String message) {
613         Log.d(TAG, message);
614     }
615 
replaceInstance(final Class c, final String instanceName, final Object obj, final Object newValue)616     private static void replaceInstance(final Class c,
617             final String instanceName, final Object obj, final Object newValue) throws Exception {
618         Field field = c.getDeclaredField(instanceName);
619         field.setAccessible(true);
620         field.set(obj, newValue);
621     }
622 
623     private static class TestSatelliteAccessController extends SatelliteAccessController {
624         public long elapsedRealtimeNanos = 0;
625 
626         /**
627          * Create a SatelliteAccessController instance.
628          *
629          * @param context                           The context associated with the
630          *                                          {@link SatelliteAccessController} instance.
631          * @param featureFlags                      The FeatureFlags that are supported.
632          * @param looper                            The Looper to run the SatelliteAccessController
633          *                                          on.
634          * @param locationManager                   The LocationManager for querying current
635          *                                          location of the
636          *                                          device.
637          * @param satelliteOnDeviceAccessController The on-device satellite access controller
638          *                                          instance.
639          */
TestSatelliteAccessController(Context context, FeatureFlags featureFlags, Looper looper, LocationManager locationManager, TelecomManager telecomManager, SatelliteOnDeviceAccessController satelliteOnDeviceAccessController, File s2CellFile)640         protected TestSatelliteAccessController(Context context, FeatureFlags featureFlags,
641                 Looper looper, LocationManager locationManager, TelecomManager telecomManager,
642                 SatelliteOnDeviceAccessController satelliteOnDeviceAccessController,
643                 File s2CellFile) {
644             super(context, featureFlags, looper, locationManager, telecomManager,
645                     satelliteOnDeviceAccessController, s2CellFile);
646         }
647 
648         @Override
getElapsedRealtimeNanos()649         protected long getElapsedRealtimeNanos() {
650             return elapsedRealtimeNanos;
651         }
652 
isKeepOnDeviceAccessControllerResourcesTimerStarted()653         public boolean isKeepOnDeviceAccessControllerResourcesTimerStarted() {
654             return hasMessages(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT);
655         }
656 
isSatelliteOnDeviceAccessControllerReset()657         public boolean isSatelliteOnDeviceAccessControllerReset() {
658             synchronized (mLock) {
659                 return (mSatelliteOnDeviceAccessController == null);
660             }
661         }
662 
setSatelliteOnDeviceAccessController( @ullable SatelliteOnDeviceAccessController accessController)663         public void setSatelliteOnDeviceAccessController(
664                 @Nullable SatelliteOnDeviceAccessController accessController) {
665             synchronized (mLock) {
666                 mSatelliteOnDeviceAccessController = accessController;
667             }
668         }
669 
getKeepOnDeviceAccessControllerResourcesTimeoutMillis()670         public long getKeepOnDeviceAccessControllerResourcesTimeoutMillis() {
671             return KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT_MILLIS;
672         }
673 
getWaitForCurrentLocationTimeoutMillis()674         public long getWaitForCurrentLocationTimeoutMillis() {
675             return WAIT_FOR_CURRENT_LOCATION_TIMEOUT_MILLIS;
676         }
677 
isWaitForCurrentLocationTimerStarted()678         public boolean isWaitForCurrentLocationTimerStarted() {
679             return hasMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT);
680         }
681     }
682 }
683