diff options
5 files changed, 155 insertions, 14 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 12058c15458e..d1eef085a506 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4273,7 +4273,11 @@ <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing check after reboot or airplane mode toggling --> <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool> + <!-- Boolean indicating that the system will use autoSuspend. If set to false, autoSuspend is not used and the system will only suspend upon an explicit request. --> <bool translatable="false" name="config_enableAutoSuspend">true</bool> + + <!-- Class name of the custom country detector to be used. --> + <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 29f90fb192c5..1c387baf7826 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3689,6 +3689,8 @@ <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" /> + <java-symbol type="string" name="config_customCountryDetector" /> + <!-- For Foldables --> <java-symbol type="bool" name="config_lidControlsDisplayFold" /> <java-symbol type="string" name="config_foldedArea" /> diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java index 861c731c69e0..b0132d35fa3b 100644 --- a/services/core/java/com/android/server/CountryDetectorService.java +++ b/services/core/java/com/android/server/CountryDetectorService.java @@ -24,21 +24,29 @@ import android.location.ICountryListener; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.text.TextUtils; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.location.ComprehensiveCountryDetector; +import com.android.server.location.CountryDetectorBase; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; /** - * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}. + * This class detects the country that the user is in. The default country detection is made through + * {@link com.android.server.location.ComprehensiveCountryDetector}. It is possible to overlay the + * detection algorithm by overlaying the attribute R.string.config_customCountryDetector with the + * custom class name to use instead. The custom class must extend + * {@link com.android.server.location.CountryDetectorBase} * * @hide */ @@ -88,7 +96,7 @@ public class CountryDetectorService extends ICountryDetector.Stub { private final HashMap<IBinder, Receiver> mReceivers; private final Context mContext; - private ComprehensiveCountryDetector mCountryDetector; + private CountryDetectorBase mCountryDetector; private boolean mSystemReady; private Handler mHandler; private CountryListener mLocationBasedDetectorListener; @@ -184,8 +192,17 @@ public class CountryDetectorService extends ICountryDetector.Stub { }); } - private void initialize() { - mCountryDetector = new ComprehensiveCountryDetector(mContext); + @VisibleForTesting + void initialize() { + final String customCountryClass = mContext.getString(R.string.config_customCountryDetector); + if (!TextUtils.isEmpty(customCountryClass)) { + mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass); + } + + if (mCountryDetector == null) { + Slog.d(TAG, "Using default country detector"); + mCountryDetector = new ComprehensiveCountryDetector(mContext); + } mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country)); } @@ -194,10 +211,32 @@ public class CountryDetectorService extends ICountryDetector.Stub { } @VisibleForTesting + CountryDetectorBase getCountryDetector() { + return mCountryDetector; + } + + @VisibleForTesting boolean isSystemReady() { return mSystemReady; } + private CountryDetectorBase loadCustomCountryDetectorIfAvailable( + final String customCountryClass) { + CountryDetectorBase customCountryDetector = null; + + Slog.d(TAG, "Using custom country detector class: " + customCountryClass); + try { + customCountryDetector = Class.forName(customCountryClass).asSubclass( + CountryDetectorBase.class).getConstructor(Context.class).newInstance( + mContext); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | NoSuchMethodException | InvocationTargetException e) { + Slog.e(TAG, "Could not instantiate the custom country detector class"); + } + + return customCountryDetector; + } + @SuppressWarnings("unused") @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -206,9 +245,10 @@ public class CountryDetectorService extends ICountryDetector.Stub { try { final Printer p = new PrintWriterPrinter(fout); p.println("CountryDetectorService state:"); + p.println("Country detector class=" + mCountryDetector.getClass().getName()); p.println(" Number of listeners=" + mReceivers.keySet().size()); if (mCountryDetector == null) { - p.println(" ComprehensiveCountryDetector not initialized"); + p.println(" CountryDetector not initialized"); } else { p.println(" " + mCountryDetector.toString()); } diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java index e9c5ce7127de..d5483ffa5445 100644 --- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java @@ -19,8 +19,11 @@ package com.android.server; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.location.Country; import android.location.CountryListener; import android.location.ICountryListener; @@ -31,6 +34,10 @@ import android.os.RemoteException; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.R; +import com.android.server.location.ComprehensiveCountryDetector; +import com.android.server.location.CustomCountryDetectorTestClass; + import com.google.common.truth.Expect; import org.junit.Before; @@ -38,12 +45,18 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class CountryDetectorServiceTest { + private static final String VALID_CUSTOM_TEST_CLASS = + "com.android.server.location.CustomCountryDetectorTestClass"; + private static final String INVALID_CUSTOM_TEST_CLASS = + "com.android.server.location.MissingCountryDetectorTestClass"; + private static class CountryListenerTester extends ICountryListener.Stub { private Country mCountry; @@ -83,12 +96,11 @@ public class CountryDetectorServiceTest { } } - @Rule - public final Expect expect = Expect.create(); - @Spy - private Context mContext = ApplicationProvider.getApplicationContext(); - @Spy - private Handler mHandler = new Handler(Looper.myLooper()); + @Rule public final Expect expect = Expect.create(); + @Spy private Context mContext = ApplicationProvider.getApplicationContext(); + @Spy private Handler mHandler = new Handler(Looper.myLooper()); + @Mock private Resources mResources; + private CountryDetectorServiceTester mCountryDetectorService; @BeforeClass @@ -108,10 +120,12 @@ public class CountryDetectorServiceTest { message.getCallback().run(); return true; }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong()); + + doReturn(mResources).when(mContext).getResources(); } @Test - public void countryListener_add_successful() throws RemoteException { + public void addCountryListener_validListener_listenerAdded() throws RemoteException { CountryListenerTester countryListener = new CountryListenerTester(); mCountryDetectorService.systemRunning(); @@ -122,7 +136,7 @@ public class CountryDetectorServiceTest { } @Test - public void countryListener_remove_successful() throws RemoteException { + public void removeCountryListener_validListener_listenerRemoved() throws RemoteException { CountryListenerTester countryListener = new CountryListenerTester(); mCountryDetectorService.systemRunning(); @@ -133,8 +147,31 @@ public class CountryDetectorServiceTest { expect.that(mCountryDetectorService.isListenerSet()).isFalse(); } + @Test(expected = RemoteException.class) + public void addCountryListener_serviceNotReady_throwsException() throws RemoteException { + CountryListenerTester countryListener = new CountryListenerTester(); + + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + mCountryDetectorService.addCountryListener(countryListener); + } + + @Test(expected = RemoteException.class) + public void removeCountryListener_serviceNotReady_throwsException() throws RemoteException { + CountryListenerTester countryListener = new CountryListenerTester(); + + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + mCountryDetectorService.removeCountryListener(countryListener); + } + @Test - public void countryListener_notify_successful() throws RemoteException { + public void detectCountry_serviceNotReady_returnNull() { + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + + expect.that(mCountryDetectorService.detectCountry()).isNull(); + } + + @Test + public void notifyReceivers_twoListenersRegistered_bothNotified() throws RemoteException { CountryListenerTester countryListenerA = new CountryListenerTester(); CountryListenerTester countryListenerB = new CountryListenerTester(); Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK); @@ -151,4 +188,26 @@ public class CountryDetectorServiceTest { expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue(); expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue(); } + + @Test + public void initialize_deviceWithCustomDetector_useCustomDetectorClass() { + when(mResources.getString(R.string.config_customCountryDetector)) + .thenReturn(VALID_CUSTOM_TEST_CLASS); + + mCountryDetectorService.initialize(); + + expect.that(mCountryDetectorService.getCountryDetector()) + .isInstanceOf(CustomCountryDetectorTestClass.class); + } + + @Test + public void initialize_deviceWithInvalidCustomDetector_useDefaultDetector() { + when(mResources.getString(R.string.config_customCountryDetector)) + .thenReturn(INVALID_CUSTOM_TEST_CLASS); + + mCountryDetectorService.initialize(); + + expect.that(mCountryDetectorService.getCountryDetector()) + .isInstanceOf(ComprehensiveCountryDetector.class); + } } diff --git a/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java new file mode 100644 index 000000000000..e159012f1b54 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import android.content.Context; +import android.location.Country; + +public class CustomCountryDetectorTestClass extends CountryDetectorBase { + public CustomCountryDetectorTestClass(Context ctx) { + super(ctx); + } + + @Override + public Country detectCountry() { + return null; + } + + @Override + public void stop() { + + } +} |