diff options
7 files changed, 269 insertions, 3 deletions
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java index d244c3739..20c27d70b 100644 --- a/service/java/com/android/server/wifi/FrameworkFacade.java +++ b/service/java/com/android/server/wifi/FrameworkFacade.java @@ -69,6 +69,20 @@ public class FrameworkFacade { return mActivityManager; } + /** + * Mockable setter for Settings.Global + */ + public boolean setIntegerSetting(ContentResolver contentResolver, String name, int value) { + return Settings.Global.putInt(contentResolver, name, value); + } + + /** + * Mockable getter for Settings.Global + */ + public int getIntegerSetting(ContentResolver contentResolver, String name, int def) { + return Settings.Global.getInt(contentResolver, name, def); + } + public boolean setIntegerSetting(Context context, String name, int def) { return Settings.Global.putInt(getContentResolver(context), name, def); } diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 71f8b7073..158d84058 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -159,6 +159,8 @@ public class WifiInjector { private final WifiHealthMonitor mWifiHealthMonitor; private final WifiOemConfigStoreMigrationDataHolder mOemConfigStoreMigrationDataHolder; private final WifiSettingsConfigStore mSettingsConfigStore; + private final WifiScanAlwaysAvailableSettingsCompatibility + mWifiScanAlwaysAvailableSettingsCompatibility; public WifiInjector(Context context) { if (context == null) { @@ -339,6 +341,9 @@ public class WifiInjector { mActiveModeWarden = new ActiveModeWarden(this, wifiLooper, mWifiNative, new DefaultModeManager(mContext), mBatteryStats, mWifiDiagnostics, mContext, mClientModeImpl, mSettingsStore, mFrameworkFacade, mWifiPermissionsUtil); + mWifiScanAlwaysAvailableSettingsCompatibility = + new WifiScanAlwaysAvailableSettingsCompatibility(mContext, wifiHandler, + mSettingsStore, mActiveModeWarden, mFrameworkFacade); mWifiApConfigStore = new WifiApConfigStore( mContext, this, wifiHandler, mBackupManagerProxy, mWifiConfigStore, mWifiConfigManager, mActiveModeWarden); @@ -820,4 +825,9 @@ public class WifiInjector { public WifiSettingsConfigStore getSettingsConfigStore() { return mSettingsConfigStore; } + + public WifiScanAlwaysAvailableSettingsCompatibility + getWifiScanAlwaysAvailableSettingsCompatibility() { + return mWifiScanAlwaysAvailableSettingsCompatibility; + } } diff --git a/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java b/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java new file mode 100644 index 000000000..6abdd3162 --- /dev/null +++ b/service/java/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibility.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 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.wifi; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Note: This is a hack to provide backward compatibility with the + * {@link Settings.Global#WIFI_SCAN_ALWAYS_AVAILABLE} @hide settings usage. We migrated storage + * of the scan always available state from this setting to our internal storage, but need to support + * the existing @hide users. + * TODO(b/149954910): We should find a path to stop supporting this! + */ +public class WifiScanAlwaysAvailableSettingsCompatibility { + private static final String TAG = "WifiScanAlwaysAvailableSettingsCompatibility"; + + /** + * Copy of the settings string. Can't directly use the constant because it is @hide. + */ + @VisibleForTesting + public static final String SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE = + "wifi_scan_always_enabled"; + + private final Context mContext; + private final ContentResolver mContentResolver; + private final Handler mHandler; + private final WifiSettingsStore mWifiSettingsStore; + private final ActiveModeWarden mActiveModeWarden; + private final FrameworkFacade mFrameworkFacade; + + public WifiScanAlwaysAvailableSettingsCompatibility(Context context, + Handler handler, WifiSettingsStore wifiSettingsStore, + ActiveModeWarden activeModeWarden, FrameworkFacade frameworkFacade) { + mContext = context; + mHandler = handler; + mWifiSettingsStore = wifiSettingsStore; + mActiveModeWarden = activeModeWarden; + mFrameworkFacade = frameworkFacade; + + // Cache the content resolver to ensure that we can detect self changes. + mContentResolver = context.getContentResolver(); + } + + /** + * Register settings change observer. + */ + public void initialize() { + ContentObserver contentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + // Ignore any changes we triggered to avoid causing a loop. + if (selfChange) return; + + boolean settingsIsAvailable = + mFrameworkFacade.getIntegerSetting( + mContentResolver, SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE, 0) + == 1; + // Check if the new state is different from our current state. + if (mWifiSettingsStore.isScanAlwaysAvailable() != settingsIsAvailable) { + Log.i(TAG, "settings changed, new value: " + settingsIsAvailable + + ", triggering update"); + mWifiSettingsStore.handleWifiScanAlwaysAvailableToggled(settingsIsAvailable); + mActiveModeWarden.scanAlwaysModeChanged(); + } + } + }; + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE), + false, contentObserver); + } + + /** + * Handle scan always available toggle from {@link android.net.wifi.WifiManager# + * setScanAlwaysAvailable(boolean)} + */ + public void handleWifiScanAlwaysAvailableToggled(boolean isAvailable) { + mFrameworkFacade.setIntegerSetting( + mContentResolver, SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE, isAvailable ? 1 : 0); + } +} diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index e278c125e..acb060dfc 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -348,6 +348,7 @@ public class WifiServiceImpl extends BaseWifiService { Log.i(TAG, "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled")); + mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility().initialize(); mContext.registerReceiver( new BroadcastReceiver() { @Override @@ -1893,6 +1894,8 @@ public class WifiServiceImpl extends BaseWifiService { enforceNetworkSettingsPermission(); mLog.info("setScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush(); mSettingsStore.handleWifiScanAlwaysAvailableToggled(isAvailable); + mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility() + .handleWifiScanAlwaysAvailableToggled(isAvailable); mActiveModeWarden.scanAlwaysModeChanged(); } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java new file mode 100644 index 000000000..2caa813c6 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/WifiScanAlwaysAvailableSettingsCompatibilityTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 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.wifi; + +import static com.android.server.wifi.WifiScanAlwaysAvailableSettingsCompatibility.SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.validateMockitoUsage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link com.android.server.wifi.WifiScanAlwaysAvailableSettingsCompatibility}. + */ +@SmallTest +public class WifiScanAlwaysAvailableSettingsCompatibilityTest extends WifiBaseTest { + @Mock + private Context mContext; + @Mock + private Handler mHandler; + @Mock + private WifiSettingsStore mWifiSettingsStore; + @Mock + private ActiveModeWarden mActiveModeWarden; + @Mock + private FrameworkFacade mFrameworkFacade; + @Mock + private ContentResolver mContentResolver; + + private ArgumentCaptor<ContentObserver> mContentObserverArgumentCaptor = + ArgumentCaptor.forClass(ContentObserver.class); + + private WifiScanAlwaysAvailableSettingsCompatibility mScanAlwaysCompatibility; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mScanAlwaysCompatibility = + new WifiScanAlwaysAvailableSettingsCompatibility(mContext, mHandler, + mWifiSettingsStore, mActiveModeWarden, mFrameworkFacade); + } + + /** + * Called after each test + */ + @After + public void cleanup() { + validateMockitoUsage(); + } + + @Test + public void reactToContentObserverChanges() { + mScanAlwaysCompatibility.initialize(); + verify(mContentResolver).registerContentObserver( + any(), anyBoolean(), mContentObserverArgumentCaptor.capture()); + + ContentObserver contentObserver = mContentObserverArgumentCaptor.getValue(); + assertNotNull(contentObserver); + + when(mWifiSettingsStore.isScanAlwaysAvailable()).thenReturn(false); + when(mFrameworkFacade.getIntegerSetting( + any(ContentResolver.class), + eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE), + anyInt())) + .thenReturn(1); + contentObserver.onChange(false); + + verify(mWifiSettingsStore).handleWifiScanAlwaysAvailableToggled(true); + verify(mActiveModeWarden).scanAlwaysModeChanged(); + + when(mWifiSettingsStore.isScanAlwaysAvailable()).thenReturn(true); + when(mFrameworkFacade.getIntegerSetting( + any(ContentResolver.class), + eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE), + anyInt())) + .thenReturn(0); + contentObserver.onChange(false); + + verify(mWifiSettingsStore).handleWifiScanAlwaysAvailableToggled(false); + verify(mActiveModeWarden, times(2)).scanAlwaysModeChanged(); + } + + + @Test + public void changeSetting() { + mScanAlwaysCompatibility.initialize(); + + mScanAlwaysCompatibility.handleWifiScanAlwaysAvailableToggled(true); + verify(mFrameworkFacade).setIntegerSetting( + any(ContentResolver.class), + eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE), + eq(1)); + + mScanAlwaysCompatibility.handleWifiScanAlwaysAvailableToggled(false); + verify(mFrameworkFacade).setIntegerSetting( + any(ContentResolver.class), + eq(SETTINGS_GLOBAL_WIFI_SCAN_ALWAYS_AVAILABLE), + eq(0)); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index 08380614d..f1e515b9c 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -307,6 +307,7 @@ public class WifiServiceImplTest extends WifiBaseTest { @Mock IOnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener; @Mock IWifiConnectedNetworkScorer mWifiConnectedNetworkScorer; @Mock WifiSettingsConfigStore mWifiSettingsConfigStore; + @Mock WifiScanAlwaysAvailableSettingsCompatibility mScanAlwaysAvailableSettingsCompatibility; WifiLog mLog = new LogcatLog(TAG); @@ -380,6 +381,8 @@ public class WifiServiceImplTest extends WifiBaseTest { when(mWifiInjector.getWifiThreadRunner()) .thenReturn(new WifiThreadRunner(new Handler(mLooper.getLooper()))); when(mWifiInjector.getSettingsConfigStore()).thenReturn(mWifiSettingsConfigStore); + when(mWifiInjector.getWifiScanAlwaysAvailableSettingsCompatibility()) + .thenReturn(mScanAlwaysAvailableSettingsCompatibility); when(mClientModeImpl.syncStartSubscriptionProvisioning(anyInt(), any(OsuProvider.class), any(IProvisioningCallback.class), any())).thenReturn(true); // Create an OSU provider that can be provisioned via an open OSU AP diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java index 7f6e1e1cd..799df547b 100644 --- a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java @@ -1224,17 +1224,17 @@ public class WifiPermissionsUtilTest extends WifiBaseTest { doThrow(new RuntimeException()).when(mLocationManager).isLocationEnabledForUser(any()); when(mMockFrameworkFacade.getIntegerSetting( - any(), eq(Settings.Secure.LOCATION_MODE), anyInt())) + any(Context.class), eq(Settings.Secure.LOCATION_MODE), anyInt())) .thenReturn(Settings.Secure.LOCATION_MODE_OFF); assertFalse(codeUnderTest.isLocationModeEnabled()); when(mMockFrameworkFacade.getIntegerSetting( - any(), eq(Settings.Secure.LOCATION_MODE), anyInt())) + any(Context.class), eq(Settings.Secure.LOCATION_MODE), anyInt())) .thenReturn(Settings.Secure.LOCATION_MODE_ON); assertTrue(codeUnderTest.isLocationModeEnabled()); verify(mMockFrameworkFacade, times(2)).getIntegerSetting( - any(), eq(Settings.Secure.LOCATION_MODE), anyInt()); + any(Context.class), eq(Settings.Secure.LOCATION_MODE), anyInt()); } private Answer<Integer> createPermissionAnswer() { |