diff --git a/res/values/strings.xml b/res/values/strings.xml index a78e74a2828..6ce7bdf22cc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10542,8 +10542,6 @@ Default disabled changes Enabled for targetSdkVersion >= %d - - No apps available App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again. diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index b26005a65bb..32acac66ddb 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -258,7 +258,7 @@ android:key="platform_compat_dashboard" android:title="@string/platform_compat_dashboard_title" android:summary="@string/platform_compat_dashboard_summary" - android:fragment="com.android.settings.development.compat.PlatformCompatDashboard" + settings:controller="com.android.settings.spa.development.compat.PlatformCompatPreferenceController" /> finish()) - .setOnDismissListener(dialog -> finish()) - .setCancelable(false) - .show(); - return; - } - try { - final ApplicationInfo applicationInfo = getApplicationInfo(); - addPreferences(applicationInfo); - return; - } catch (PackageManager.NameNotFoundException e) { - mShouldStartAppPickerOnResume = true; - mSelectedApp = null; - } + Bundle arguments = getArguments(); + if (arguments == null) { + finish(); + return; + } + mSelectedApp = arguments.getString(COMPAT_APP); + try { + final ApplicationInfo applicationInfo = getApplicationInfo(); + addPreferences(applicationInfo); + } catch (PackageManager.NameNotFoundException ignored) { + finish(); } - startAppPicker(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(COMPAT_APP, mSelectedApp); } private void addPreferences(ApplicationInfo applicationInfo) { @@ -266,12 +213,6 @@ public class PlatformCompatDashboard extends DashboardFragment { appPreference.setIcon(icon); appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary, mSelectedApp, applicationInfo.targetSdkVersion)); - appPreference.setKey(mSelectedApp); - appPreference.setOnPreferenceClickListener( - preference -> { - startAppPicker(); - return true; - }); return appPreference; } @@ -294,17 +235,6 @@ public class PlatformCompatDashboard extends DashboardFragment { } } - private void startAppPicker() { - final Intent intent = new Intent(getContext(), AppPicker.class) - .putExtra(AppPicker.EXTRA_INCLUDE_NOTHING, false); - // If build is neither userdebug nor eng, only include debuggable apps - final boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild(); - if (!debuggableBuild) { - intent.putExtra(AppPicker.EXTRA_DEBUGGABLE, true /* value */); - } - startActivityForResult(intent, REQUEST_COMPAT_CHANGE_APP); - } - private class CompatChangePreferenceChangeListener implements OnPreferenceChangeListener { private final long changeId; diff --git a/src/com/android/settings/slices/RestrictedSliceUtils.java b/src/com/android/settings/slices/RestrictedSliceUtils.java new file mode 100644 index 00000000000..a5b5a146458 --- /dev/null +++ b/src/com/android/settings/slices/RestrictedSliceUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.settings.slices; + +import android.content.ContentResolver; +import android.net.Uri; +import android.provider.SettingsSlicesContract; + +/** + * A utility class to check slice Uris for restriction. + */ +public class RestrictedSliceUtils { + + /** + * Uri for the notifying open networks Slice. + */ + private static final Uri NOTIFY_OPEN_NETWORKS_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("notify_open_networks") + .build(); + + /** + * Uri for the auto turning on Wi-Fi Slice. + */ + private static final Uri AUTO_TURN_ON_WIFI_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("enable_wifi_wakeup") + .build(); + + /** + * Uri for the usb tethering Slice. + */ + private static final Uri USB_TETHERING_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("enable_usb_tethering") + .build(); + + /** + * Uri for the bluetooth tethering Slice. + */ + private static final Uri BLUETOOTH_TETHERING_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("enable_bluetooth_tethering_2") + .build(); + + /** + * Returns true if the slice Uri restricts access to guest user. + */ + public static boolean isGuestRestricted(Uri sliceUri) { + if (AUTO_TURN_ON_WIFI_SLICE_URI.equals(sliceUri) + || NOTIFY_OPEN_NETWORKS_SLICE_URI.equals(sliceUri) + || BLUETOOTH_TETHERING_SLICE_URI.equals(sliceUri) + || USB_TETHERING_SLICE_URI.equals(sliceUri) + || CustomSliceRegistry.MOBILE_DATA_SLICE_URI.equals(sliceUri)) { + return true; + } + return false; + } +} diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 12272a7523a..5d2bde31da0 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Binder; import android.os.StrictMode; +import android.os.UserManager; import android.provider.Settings; import android.provider.SettingsSlicesContract; import android.text.TextUtils; @@ -233,6 +234,14 @@ public class SettingsSliceProvider extends SliceProvider { getContext().getTheme().rebase(); } + // Checking if some semi-sensitive slices are requested by a guest user. If so, will + // return an empty slice. + final UserManager userManager = getContext().getSystemService(UserManager.class); + if (userManager.isGuestUser() && RestrictedSliceUtils.isGuestRestricted(sliceUri)) { + Log.i(TAG, "Guest user access denied."); + return null; + } + // Before adding a slice to {@link CustomSliceManager}, please get approval // from the Settings team. if (CustomSliceRegistry.isValidUri(sliceUri)) { diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt index caf5b1532ce..b506005edba 100644 --- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt +++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt @@ -36,6 +36,7 @@ import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider import com.android.settings.spa.core.instrumentation.SpaLogProvider import com.android.settings.spa.development.UsageStatsPageProvider +import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider import com.android.settings.spa.home.HomePageProvider import com.android.settings.spa.network.NetworkAndInternetPageProvider import com.android.settings.spa.notification.AppListNotificationsPageProvider @@ -83,6 +84,7 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { LanguageAndInputPageProvider, AppLanguagesPageProvider, UsageStatsPageProvider, + PlatformCompatAppListPageProvider, BackgroundInstalledAppsPageProvider, CloneAppInfoSettingsProvider, NetworkAndInternetPageProvider, @@ -95,5 +97,5 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) { override val logger = if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_ENABLE_SPA_METRICS)) SpaLogProvider - else object: SpaLogger {} + else object : SpaLogger {} } diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt new file mode 100644 index 00000000000..5f3b4e700b3 --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 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.settings.spa.development.compat + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.rememberContext +import com.android.settingslib.spaprivileged.template.app.AppListPage + +object PlatformCompatAppListPageProvider : SettingsPageProvider { + override val name = "PlatformCompatAppList" + + @Composable + override fun Page(arguments: Bundle?) { + AppListPage( + title = stringResource(R.string.platform_compat_dashboard_title), + listModel = rememberContext(::PlatformCompatAppListModel), + noItemMessage = stringResource(R.string.platform_compat_dialog_text_no_apps), + ) + } +} \ No newline at end of file diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt new file mode 100644 index 00000000000..c6752b9c6d8 --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 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.settings.spa.development.compat + +import android.app.settings.SettingsEnums +import android.content.Context +import android.content.pm.ApplicationInfo +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.core.os.bundleOf +import com.android.settings.core.SubSettingLauncher +import com.android.settings.development.compat.PlatformCompatDashboard +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.framework.util.filterItem +import com.android.settingslib.spa.framework.util.mapItem +import com.android.settingslib.spaprivileged.model.app.AppListModel +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.hasFlag +import com.android.settingslib.spaprivileged.model.app.userHandle +import com.android.settingslib.spaprivileged.template.app.AppListItem +import com.android.settingslib.spaprivileged.template.app.AppListItemModel +import kotlinx.coroutines.flow.Flow + +data class PlatformCompatAppRecord( + override val app: ApplicationInfo, +) : AppRecord + +class PlatformCompatAppListModel( + private val context: Context, +) : AppListModel { + + override fun transform(userIdFlow: Flow, appListFlow: Flow>) = + appListFlow.mapItem(::PlatformCompatAppRecord) + + override fun filter( + userIdFlow: Flow, option: Int, recordListFlow: Flow>, + ) = recordListFlow.filterItem { record -> + Build.IS_DEBUGGABLE || record.app.hasFlag(ApplicationInfo.FLAG_DEBUGGABLE) + } + + @Composable + override fun getSummary(option: Int, record: PlatformCompatAppRecord) = + stateOf(record.app.packageName) + + @Composable + override fun AppListItemModel.AppItem() { + AppListItem { navigateToAppCompat(app = record.app) } + } + + private fun navigateToAppCompat(app: ApplicationInfo) { + SubSettingLauncher(context) + .setDestination(PlatformCompatDashboard::class.qualifiedName) + .setSourceMetricsCategory(SettingsEnums.DEVELOPMENT) + .setArguments(bundleOf(PlatformCompatDashboard.COMPAT_APP to app.packageName)) + .setUserHandle(app.userHandle) + .launch() + } +} diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt new file mode 100644 index 00000000000..c0a421cf31e --- /dev/null +++ b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.settings.spa.development.compat + +import android.content.Context +import androidx.preference.Preference +import com.android.settings.core.BasePreferenceController +import com.android.settings.spa.SpaActivity.Companion.startSpaActivity + +class PlatformCompatPreferenceController(context: Context, preferenceKey: String) : + BasePreferenceController(context, preferenceKey) { + override fun getAvailabilityStatus() = AVAILABLE + + override fun handlePreferenceTreeClick(preference: Preference): Boolean { + if (preference.key == mPreferenceKey) { + mContext.startSpaActivity(PlatformCompatAppListPageProvider.name) + return true + } + return false + } +} \ No newline at end of file diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 2f9031e029d..402d4b18dd4 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -79,6 +79,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment /** Whether to enable the app_copying fragment. */ private static final boolean SHOW_APP_COPYING_PREF = false; + private static final int MESSAGE_PADDING = 20; private UserManager mUserManager; private UserCapabilities mUserCaps; @@ -274,6 +275,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings)); dialogHelper.setTitle(R.string.user_revoke_admin_confirm_title); dialogHelper.setMessage(R.string.user_revoke_admin_confirm_message); + dialogHelper.setMessagePadding(MESSAGE_PADDING); dialogHelper.setPositiveButton(R.string.remove, view -> { updateUserAdminStatus(false); dialogHelper.getDialog().dismiss(); @@ -294,6 +296,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings)); dialogHelper.setTitle(com.android.settingslib.R.string.user_grant_admin_title); dialogHelper.setMessage(com.android.settingslib.R.string.user_grant_admin_message); + dialogHelper.setMessagePadding(MESSAGE_PADDING); dialogHelper.setPositiveButton(com.android.settingslib.R.string.user_grant_admin_button, view -> { updateUserAdminStatus(true); diff --git a/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceController.java index 5dc5758dc90..0b6d533bb90 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceController.java @@ -20,19 +20,29 @@ import android.content.Context; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiManager; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.SwitchPreference; import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.wifi.repository.WifiHotspotRepository; public class WifiTetherAutoOffPreferenceController extends BasePreferenceController implements Preference.OnPreferenceChangeListener { private final WifiManager mWifiManager; private boolean mSettingsOn; + @VisibleForTesting + boolean mNeedShutdownSecondarySap; public WifiTetherAutoOffPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); + WifiHotspotRepository wifiHotspotRepository = FeatureFactory.getFactory(context) + .getWifiFeatureProvider().getWifiHotspotRepository(); + if (wifiHotspotRepository.isSpeedFeatureAvailable() && wifiHotspotRepository.isDualBand()) { + mNeedShutdownSecondarySap = true; + } mWifiManager = context.getSystemService(WifiManager.class); } @@ -51,14 +61,15 @@ public class WifiTetherAutoOffPreferenceController extends BasePreferenceControl @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - final boolean settingsOn = (Boolean) newValue; - SoftApConfiguration softApConfiguration = mWifiManager.getSoftApConfiguration(); - SoftApConfiguration newSoftApConfiguration = - new SoftApConfiguration.Builder(softApConfiguration) - .setAutoShutdownEnabled(settingsOn) - .build(); + boolean settingsOn = (Boolean) newValue; + SoftApConfiguration.Builder configBuilder = + new SoftApConfiguration.Builder(mWifiManager.getSoftApConfiguration()); + configBuilder.setAutoShutdownEnabled(settingsOn); + if (mNeedShutdownSecondarySap) { + configBuilder.setBridgedModeOpportunisticShutdownEnabled(settingsOn); + } mSettingsOn = settingsOn; - return mWifiManager.setSoftApConfiguration(newSoftApConfiguration); + return mWifiManager.setSoftApConfiguration(configBuilder.build()); } public boolean isEnabled() { diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index b7d249d36fe..4903a28ad53 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -119,6 +119,7 @@ public class SettingsSliceProviderTest { private Context mContext; private SettingsSliceProvider mProvider; private ShadowPackageManager mPackageManager; + private ShadowUserManager mShadowUserManager; @Mock private SliceManager mManager; @@ -157,6 +158,7 @@ public class SettingsSliceProviderTest { when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList()); mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mShadowUserManager = ShadowUserManager.getShadow(); SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); } @@ -292,6 +294,37 @@ public class SettingsSliceProviderTest { assertThat(ShadowTheme.isThemeRebased()).isFalse(); } + @Test + public void onBindSlice_guestRestricted_returnsNull() { + final String key = "enable_usb_tethering"; + mShadowUserManager.setGuestUser(true); + final Uri testUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + + final Slice slice = mProvider.onBindSlice(testUri); + + assertThat(slice).isNull(); + } + + @Test + public void onBindSlice_notGuestRestricted_returnsNotNull() { + final String key = "enable_usb_tethering"; + final Uri testUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + + final Slice slice = mProvider.onBindSlice(testUri); + + assertThat(slice).isNotNull(); + } + @Test public void getDescendantUris_fullActionUri_returnsSelf() { final Collection descendants = mProvider.onGetSliceDescendants(ACTION_SLICE_URI); diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index df38420f961..324a82964ae 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -55,6 +55,7 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private int[] profileIdsForUser = new int[0]; private boolean mUserSwitchEnabled; private Bundle mDefaultGuestUserRestriction = new Bundle(); + private boolean mIsGuestUser = false; private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus = UserManager.SWITCHABILITY_STATUS_OK; @@ -270,4 +271,13 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager mUserProfileInfos.get(i).flags |= UserInfo.FLAG_ADMIN; } } + + @Implementation + protected boolean isGuestUser() { + return mIsGuestUser; + } + + public void setGuestUser(boolean isGuestUser) { + mIsGuestUser = isGuestUser; + } } diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceControllerTest.java index fbc4aaa3b90..535e4ab250c 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherAutoOffPreferenceControllerTest.java @@ -18,6 +18,7 @@ package com.android.settings.wifi.tether; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,6 +29,10 @@ import android.net.wifi.WifiManager; import androidx.preference.SwitchPreference; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.wifi.factory.WifiFeatureProvider; +import com.android.settings.wifi.repository.WifiHotspotRepository; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,6 +59,8 @@ public class WifiTetherAutoOffPreferenceControllerTest { mContext = spy(RuntimeEnvironment.application); + WifiFeatureProvider provider = FakeFeatureFactory.setupForTest().getWifiFeatureProvider(); + when(provider.getWifiHotspotRepository()).thenReturn(mock(WifiHotspotRepository.class)); when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager); mSoftApConfiguration = new SoftApConfiguration.Builder().build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration); @@ -101,6 +108,32 @@ public class WifiTetherAutoOffPreferenceControllerTest { assertThat(mSwitchPreference.isChecked()).isTrue(); } + @Test + public void onPreferenceChange_needShutdownSecondarySap_setSecondarySap() { + mController.mNeedShutdownSecondarySap = true; + setConfigShutdownSecondarySap(false); + + mController.onPreferenceChange(mSwitchPreference, true); + + ArgumentCaptor config = + ArgumentCaptor.forClass(SoftApConfiguration.class); + verify(mWifiManager).setSoftApConfiguration(config.capture()); + assertThat(config.getValue().isBridgedModeOpportunisticShutdownEnabled()).isTrue(); + } + + @Test + public void onPreferenceChange_noNeedShutdownSecondarySap_doNotSetSecondarySap() { + mController.mNeedShutdownSecondarySap = false; + setConfigShutdownSecondarySap(false); + + mController.onPreferenceChange(mSwitchPreference, true); + + ArgumentCaptor config = + ArgumentCaptor.forClass(SoftApConfiguration.class); + verify(mWifiManager).setSoftApConfiguration(config.capture()); + assertThat(config.getValue().isBridgedModeOpportunisticShutdownEnabled()).isFalse(); + } + private boolean getAutoOffSetting() { ArgumentCaptor softApConfigCaptor = ArgumentCaptor.forClass(SoftApConfiguration.class); @@ -115,4 +148,12 @@ public class WifiTetherAutoOffPreferenceControllerTest { .build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration); } + + private void setConfigShutdownSecondarySap(boolean enabled) { + mSoftApConfiguration = + new SoftApConfiguration.Builder(mSoftApConfiguration) + .setBridgedModeOpportunisticShutdownEnabled(enabled) + .build(); + when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration); + } } diff --git a/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt new file mode 100644 index 00000000000..78aca852492 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 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.settings.spa.development.compat + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PackageInfoFlags +import androidx.compose.runtime.State +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class PlatformCompatAppListModelTest { + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var packageManager: PackageManager + + private lateinit var listModel: PlatformCompatAppListModel + + @Before + fun setUp() { + whenever(context.packageManager).thenReturn(packageManager) + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenReturn(emptyList()) + listModel = PlatformCompatAppListModel(context) + } + + @Test + fun transform() = runTest { + val recordListFlow = listModel.transform( + userIdFlow = flowOf(USER_ID), + appListFlow = flowOf(listOf(APP)), + ) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + val record = recordList[0] + assertThat(record.app).isSameInstanceAs(APP) + } + + @Test + fun getSummary() = runTest { + val summaryState = getSummaryState(APP) + + assertThat(summaryState.value).isEqualTo(PACKAGE_NAME) + } + + private fun getSummaryState(app: ApplicationInfo): State { + lateinit var summary: State + composeTestRule.setContent { + summary = listModel.getSummary( + option = 0, + record = PlatformCompatAppRecord(app), + ) + } + return summary + } + + private companion object { + const val USER_ID = 0 + const val PACKAGE_NAME = "package.name" + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + } + } +} \ No newline at end of file