diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ad815516c61..fd9f2e50a74 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2551,9 +2551,9 @@ + android:value="com.android.settings.display.ColorContrastFragment" /> + android:value="@string/menu_key_display"/> @@ -3343,6 +3343,7 @@ + @@ -5138,8 +5139,6 @@ - diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto index 4bee75ca058..b16958d8e2b 100644 --- a/protos/fuelgauge_log.proto +++ b/protos/fuelgauge_log.proto @@ -45,6 +45,7 @@ message BatteryUsageHistoricalLogEntry { FETCH_USAGE_DATA = 4; INSERT_USAGE_DATA = 5; TIME_UPDATED = 6; + TIMEZONE_UPDATED = 7; } optional int64 timestamp = 1; diff --git a/res/drawable-sw600dp/ic_settings_about_device_filled.xml b/res/drawable-sw600dp/ic_settings_about_device_filled.xml new file mode 100644 index 00000000000..33ec5fe3f79 --- /dev/null +++ b/res/drawable-sw600dp/ic_settings_about_device_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/homepage_highlighted_item_background_v2.xml b/res/drawable/homepage_highlighted_item_background_v2.xml new file mode 100644 index 00000000000..7aa489527a0 --- /dev/null +++ b/res/drawable/homepage_highlighted_item_background_v2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/homepage_selectable_item_background_v2.xml b/res/drawable/homepage_selectable_item_background_v2.xml new file mode 100644 index 00000000000..d2f79ff9bf6 --- /dev/null +++ b/res/drawable/homepage_selectable_item_background_v2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/res/drawable/ic_apps_filled.xml b/res/drawable/ic_apps_filled.xml new file mode 100644 index 00000000000..5f86a92e013 --- /dev/null +++ b/res/drawable/ic_apps_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_color_contrast.xml b/res/drawable/ic_color_contrast.xml deleted file mode 100644 index 9d56ada2f0c..00000000000 --- a/res/drawable/ic_color_contrast.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/ic_devices_other_filled.xml b/res/drawable/ic_devices_other_filled.xml new file mode 100644 index 00000000000..a2ded48c57f --- /dev/null +++ b/res/drawable/ic_devices_other_filled.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_help_filled.xml b/res/drawable/ic_help_filled.xml new file mode 100644 index 00000000000..79cbb0b131f --- /dev/null +++ b/res/drawable/ic_help_filled.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/drawable/ic_notifications_filled.xml b/res/drawable/ic_notifications_filled.xml new file mode 100644 index 00000000000..3f539132f22 --- /dev/null +++ b/res/drawable/ic_notifications_filled.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/drawable/ic_settings_about_device_filled.xml b/res/drawable/ic_settings_about_device_filled.xml new file mode 100644 index 00000000000..fb6b2be5884 --- /dev/null +++ b/res/drawable/ic_settings_about_device_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_accessibility_filled.xml b/res/drawable/ic_settings_accessibility_filled.xml new file mode 100644 index 00000000000..24a5304be8f --- /dev/null +++ b/res/drawable/ic_settings_accessibility_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_battery_filled.xml b/res/drawable/ic_settings_battery_filled.xml new file mode 100644 index 00000000000..122fb0a40f8 --- /dev/null +++ b/res/drawable/ic_settings_battery_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_display_filled.xml b/res/drawable/ic_settings_display_filled.xml new file mode 100644 index 00000000000..ef61cbbb3d2 --- /dev/null +++ b/res/drawable/ic_settings_display_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_emergency_filled.xml b/res/drawable/ic_settings_emergency_filled.xml new file mode 100644 index 00000000000..af58127fac3 --- /dev/null +++ b/res/drawable/ic_settings_emergency_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_location_filled.xml b/res/drawable/ic_settings_location_filled.xml new file mode 100644 index 00000000000..264952122ad --- /dev/null +++ b/res/drawable/ic_settings_location_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_passwords_filled.xml b/res/drawable/ic_settings_passwords_filled.xml new file mode 100644 index 00000000000..eee77afbf21 --- /dev/null +++ b/res/drawable/ic_settings_passwords_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_privacy_filled.xml b/res/drawable/ic_settings_privacy_filled.xml new file mode 100644 index 00000000000..95127351a72 --- /dev/null +++ b/res/drawable/ic_settings_privacy_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_safety_center_filled.xml b/res/drawable/ic_settings_safety_center_filled.xml new file mode 100644 index 00000000000..8b6bb6c314a --- /dev/null +++ b/res/drawable/ic_settings_safety_center_filled.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/res/drawable/ic_settings_security_filled.xml b/res/drawable/ic_settings_security_filled.xml new file mode 100644 index 00000000000..fa2a42b6192 --- /dev/null +++ b/res/drawable/ic_settings_security_filled.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/res/drawable/ic_settings_system_dashboard_filled.xml b/res/drawable/ic_settings_system_dashboard_filled.xml new file mode 100644 index 00000000000..aa2756ebaa0 --- /dev/null +++ b/res/drawable/ic_settings_system_dashboard_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_wallpaper_filled.xml b/res/drawable/ic_settings_wallpaper_filled.xml new file mode 100644 index 00000000000..cbcc3b2d5f6 --- /dev/null +++ b/res/drawable/ic_settings_wallpaper_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_wireless_filled.xml b/res/drawable/ic_settings_wireless_filled.xml new file mode 100644 index 00000000000..ec85a8b8146 --- /dev/null +++ b/res/drawable/ic_settings_wireless_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_storage_filled.xml b/res/drawable/ic_storage_filled.xml new file mode 100644 index 00000000000..2fa3c74a2a9 --- /dev/null +++ b/res/drawable/ic_storage_filled.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_volume_up_filled.xml b/res/drawable/ic_volume_up_filled.xml new file mode 100644 index 00000000000..da3a867c501 --- /dev/null +++ b/res/drawable/ic_volume_up_filled.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/layout/fingerprint_rename_dialog.xml b/res/layout/fingerprint_rename_dialog.xml index 1e1ef112a5c..070d9249d51 100644 --- a/res/layout/fingerprint_rename_dialog.xml +++ b/res/layout/fingerprint_rename_dialog.xml @@ -39,6 +39,7 @@ android:id="@+id/fingerprint_rename_field" android:layout_width="match_parent" android:layout_height="wrap_content" - android:inputType="textCapWords"/> + android:inputType="textCapWords" + android:minHeight = "48dp"/> diff --git a/res/layout/homepage_preference_v2.xml b/res/layout/homepage_preference_v2.xml new file mode 100644 index 00000000000..4d441d36842 --- /dev/null +++ b/res/layout/homepage_preference_v2.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/layout/search_bar_unified_version.xml b/res/layout/search_bar_unified_version.xml new file mode 100644 index 00000000000..eec8406af7d --- /dev/null +++ b/res/layout/search_bar_unified_version.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/res/layout/settings_homepage_app_bar_unified_layout.xml b/res/layout/settings_homepage_app_bar_unified_layout.xml new file mode 100644 index 00000000000..3e254186822 --- /dev/null +++ b/res/layout/settings_homepage_app_bar_unified_layout.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/res/layout/settings_homepage_container_v2.xml b/res/layout/settings_homepage_container_v2.xml new file mode 100644 index 00000000000..73b8f21cebf --- /dev/null +++ b/res/layout/settings_homepage_container_v2.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/config.xml b/res/values/config.xml index 6d9d784cb84..4d3a23348e4 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -257,7 +257,7 @@ true - true + false false diff --git a/res/values/strings.xml b/res/values/strings.xml index e14a1073451..855866d8747 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -672,8 +672,10 @@ Learn more about Location settings - - To change location access, go to Settings > Security and Privacy > Privacy controls + + + To change go to ChromeOS Settings > Privacy and security > Privacy controls > Location access + Accounts @@ -4688,8 +4690,6 @@ Higher contrast makes text, buttons, and icons stand out more. Choose the contrast that looks best to you. Some apps may not support all color and text contrast settings - - Adjust how colors and text look against your screen\'s background color Preview @@ -12579,11 +12579,17 @@ Thread - - Connect to compatible devices using Thread for a seamless smart home experience + + Use Thread - - Turn off airplane mode to use Thread + + Thread helps connect your smart home devices, boosting efficiency, and performance.\n\nWhen enabled, this device is eligible to join a Thread network, allowing control of Matter supported devices through this phone. + + + Learn more about Thread + + + https://developers.home.google.com Camera access diff --git a/res/xml/accessibility_color_and_motion.xml b/res/xml/accessibility_color_and_motion.xml index 35222347a29..a500b72d958 100644 --- a/res/xml/accessibility_color_and_motion.xml +++ b/res/xml/accessibility_color_and_motion.xml @@ -21,16 +21,6 @@ android:persistent="false" android:title="@string/accessibility_color_and_motion_title"> - - + settings:controller="com.android.settings.display.ContrastSelectorPreferenceController" /> + settings:controller="com.android.settings.display.ColorContrastFooterPreferenceController" /> diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index 87db61921c3..68b4c04572e 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -46,6 +46,17 @@ settings:controller="com.android.settings.wfd.WifiDisplayPreferenceController" settings:keywords="@string/keywords_wifi_display_settings" /> + + - - diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml index 0c6d673fb20..abf0cc6d3d9 100644 --- a/res/xml/display_settings.xml +++ b/res/xml/display_settings.xml @@ -111,6 +111,14 @@ android:fragment="com.android.settings.display.ColorModePreferenceFragment" settings:controller="com.android.settings.display.ColorModePreferenceController" settings:keywords="@string/keywords_color_mode"/> + + + + + + + + + diff --git a/res/xml/top_level_settings_v2.xml b/res/xml/top_level_settings_v2.xml new file mode 100644 index 00000000000..9cd8dbe1d70 --- /dev/null +++ b/res/xml/top_level_settings_v2.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/accessibility/ContrastPreferenceController.java b/src/com/android/settings/accessibility/ContrastPreferenceController.java deleted file mode 100644 index 33d3087bcc8..00000000000 --- a/src/com/android/settings/accessibility/ContrastPreferenceController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 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.accessibility; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.android.settings.core.BasePreferenceController; - -/** - * Controller for {@link ColorContrastFragment}. - */ -public class ContrastPreferenceController extends BasePreferenceController { - - public ContrastPreferenceController(@NonNull Context context, @NonNull String preferenceKey) { - super(context, preferenceKey); - } - - @Override - public int getAvailabilityStatus() { - // Hide color contrast entry point inside Accessibility settings. - return CONDITIONALLY_UNAVAILABLE; - } -} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/BaseThreadNetworkController.kt b/src/com/android/settings/connecteddevice/threadnetwork/BaseThreadNetworkController.kt new file mode 100644 index 00000000000..583706a63ac --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/BaseThreadNetworkController.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.net.thread.ThreadNetworkController +import android.net.thread.ThreadNetworkController.StateCallback +import android.net.thread.ThreadNetworkException +import android.os.OutcomeReceiver +import androidx.annotation.VisibleForTesting +import java.util.concurrent.Executor + +/** + * A testable interface for [ThreadNetworkController] which is `final`. + * + * We are in a awkward situation that Android API guideline suggest `final` for API classes + * while Robolectric test is being deprecated for platform testing (See + * tests/robotests/new_tests_hook.sh). This force us to use "mockito-target-extended" but it's + * conflicting with the default "mockito-target" which is somehow indirectly depended by the + * `SettingsUnitTests` target. + */ +@VisibleForTesting +interface BaseThreadNetworkController { + fun setEnabled( + enabled: Boolean, + executor: Executor, + receiver: OutcomeReceiver + ) + + fun registerStateCallback(executor: Executor, callback: StateCallback) + + fun unregisterStateCallback(callback: StateCallback) +} \ No newline at end of file diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFooterController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFooterController.kt new file mode 100644 index 00000000000..1e3b62484de --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFooterController.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.util.Log +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settingslib.HelpUtils +import com.android.settingslib.widget.FooterPreference + +/** + * The footer preference controller for Thread settings in + * "Connected devices > Connection preferences > Thread". + */ +class ThreadNetworkFooterController( + context: Context, + preferenceKey: String +) : BasePreferenceController(context, preferenceKey) { + override fun getAvailabilityStatus(): Int { + // The thread_network_settings screen won't be displayed and it doesn't matter if this + // controller always return AVAILABLE + return AVAILABLE + } + + override fun displayPreference(screen: PreferenceScreen) { + val footer: FooterPreference? = screen.findPreference(KEY_PREFERENCE_FOOTER) + if (footer != null) { + footer.setLearnMoreAction { _ -> openLocaleLearnMoreLink() } + footer.setLearnMoreText(mContext.getString(R.string.thread_network_settings_learn_more)) + } + } + + private fun openLocaleLearnMoreLink() { + val intent = HelpUtils.getHelpIntent( + mContext, + mContext.getString(R.string.thread_network_settings_learn_more_link), + mContext::class.java.name + ) + if (intent != null) { + mContext.startActivity(intent) + } else { + Log.w(TAG, "HelpIntent is null") + } + } + + companion object { + private const val TAG = "ThreadNetworkSettings" + private const val KEY_PREFERENCE_FOOTER = "thread_network_settings_footer" + } +} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragment.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragment.kt new file mode 100644 index 00000000000..fd385d7ee1d --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragment.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.app.settings.SettingsEnums +import com.android.settings.R +import com.android.settings.dashboard.DashboardFragment +import com.android.settings.search.BaseSearchIndexProvider +import com.android.settingslib.search.SearchIndexable + +/** The fragment for Thread settings in "Connected devices > Connection preferences > Thread". */ +@SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv()) +class ThreadNetworkFragment : DashboardFragment() { + override fun getPreferenceScreenResId() = R.xml.thread_network_settings + + override fun getLogTag() = "ThreadNetworkFragment" + + override fun getMetricsCategory() = SettingsEnums.CONNECTED_DEVICE_PREFERENCES_THREAD + + companion object { + /** For Search. */ + @JvmField + val SEARCH_INDEX_DATA_PROVIDER = BaseSearchIndexProvider(R.xml.thread_network_settings) + } +} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentController.kt new file mode 100644 index 00000000000..beb824a36a0 --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentController.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.net.thread.ThreadNetworkController +import android.net.thread.ThreadNetworkController.StateCallback +import androidx.annotation.VisibleForTesting +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.core.BasePreferenceController +import com.android.settings.flags.Flags +import java.util.concurrent.Executor + +/** + * The fragment controller for Thread settings in + * "Connected devices > Connection preferences > Thread". + */ +class ThreadNetworkFragmentController @VisibleForTesting constructor( + context: Context, + preferenceKey: String, + private val executor: Executor, + private val threadController: BaseThreadNetworkController? +) : BasePreferenceController(context, preferenceKey), LifecycleEventObserver { + private val stateCallback: StateCallback + private var threadEnabled = false + private var preference: Preference? = null + + constructor(context: Context, preferenceKey: String) : this( + context, + preferenceKey, + ContextCompat.getMainExecutor(context), + ThreadNetworkUtils.getThreadNetworkController(context) + ) + + init { + stateCallback = newStateCallback() + } + + override fun getAvailabilityStatus(): Int { + return if (!Flags.threadSettingsEnabled()) { + CONDITIONALLY_UNAVAILABLE + } else if (threadController == null) { + UNSUPPORTED_ON_DEVICE + } else { + AVAILABLE + } + } + + override fun getSummary(): CharSequence { + return if (threadEnabled) { + mContext.getText(R.string.switch_on_text) + } else { + mContext.getText(R.string.switch_off_text) + } + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (threadController == null) { + return + } + + when (event) { + Lifecycle.Event.ON_START -> + threadController.registerStateCallback(executor, stateCallback) + + Lifecycle.Event.ON_STOP -> + threadController.unregisterStateCallback(stateCallback) + + else -> {} + } + } + + private fun newStateCallback(): StateCallback { + return object : StateCallback { + override fun onThreadEnableStateChanged(enabledState: Int) { + threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED + preference?.let { preference -> refreshSummary(preference) } + } + + override fun onDeviceRoleChanged(role: Int) {} + } + } +} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt deleted file mode 100644 index 1c0175036d5..00000000000 --- a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2024 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.connecteddevice.threadnetwork - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.content.pm.PackageManager -import android.net.thread.ThreadNetworkController -import android.net.thread.ThreadNetworkController.StateCallback -import android.net.thread.ThreadNetworkException -import android.net.thread.ThreadNetworkManager -import android.os.OutcomeReceiver -import android.provider.Settings -import android.util.Log -import androidx.annotation.VisibleForTesting -import androidx.core.content.ContextCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner -import androidx.preference.Preference -import androidx.preference.PreferenceScreen -import com.android.settings.R -import com.android.settings.core.TogglePreferenceController -import com.android.settings.flags.Flags -import java.util.concurrent.Executor - -/** Controller for the "Thread" toggle in "Connected devices > Connection preferences". */ -class ThreadNetworkPreferenceController @VisibleForTesting constructor( - context: Context, - key: String, - private val executor: Executor, - private val threadController: BaseThreadNetworkController? -) : TogglePreferenceController(context, key), LifecycleEventObserver { - private val stateCallback: StateCallback - private val airplaneModeReceiver: BroadcastReceiver - private var threadEnabled = false - private var airplaneModeOn = false - private var preference: Preference? = null - - /** - * A testable interface for [ThreadNetworkController] which is `final`. - * - * We are in a awkward situation that Android API guideline suggest `final` for API classes - * while Robolectric test is being deprecated for platform testing (See - * tests/robotests/new_tests_hook.sh). This force us to use "mockito-target-extended" but it's - * conflicting with the default "mockito-target" which is somehow indirectly depended by the - * `SettingsUnitTests` target. - */ - @VisibleForTesting - interface BaseThreadNetworkController { - fun setEnabled( - enabled: Boolean, - executor: Executor, - receiver: OutcomeReceiver - ) - - fun registerStateCallback(executor: Executor, callback: StateCallback) - - fun unregisterStateCallback(callback: StateCallback) - } - - constructor(context: Context, key: String) : this( - context, - key, - ContextCompat.getMainExecutor(context), - getThreadNetworkController(context) - ) - - init { - stateCallback = newStateCallback() - airplaneModeReceiver = newAirPlaneModeReceiver() - } - - val isThreadSupportedOnDevice: Boolean - get() = threadController != null - - private fun newStateCallback(): StateCallback { - return object : StateCallback { - override fun onThreadEnableStateChanged(enabledState: Int) { - threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED - } - - override fun onDeviceRoleChanged(role: Int) {} - } - } - - private fun newAirPlaneModeReceiver(): BroadcastReceiver { - return object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - airplaneModeOn = isAirplaneModeOn(context) - Log.i(TAG, "Airplane mode is " + if (airplaneModeOn) "ON" else "OFF") - preference?.let { preference -> updateState(preference) } - } - } - } - - override fun getAvailabilityStatus(): Int { - return if (!Flags.threadSettingsEnabled()) { - CONDITIONALLY_UNAVAILABLE - } else if (!isThreadSupportedOnDevice) { - UNSUPPORTED_ON_DEVICE - } else if (airplaneModeOn) { - DISABLED_DEPENDENT_SETTING - } else { - AVAILABLE - } - } - - override fun displayPreference(screen: PreferenceScreen) { - super.displayPreference(screen) - preference = screen.findPreference(preferenceKey) - } - - override fun isChecked(): Boolean { - // TODO (b/322742298): - // Check airplane mode here because it's planned to disable Thread state in airplane mode - // (code in the mainline module). But it's currently not implemented yet (b/322742298). - // By design, the toggle should be unchecked in airplane mode, so explicitly check the - // airplane mode here to acchieve the same UX. - return !airplaneModeOn && threadEnabled - } - - override fun setChecked(isChecked: Boolean): Boolean { - if (threadController == null) { - return false - } - val action = if (isChecked) "enable" else "disable" - threadController.setEnabled( - isChecked, - executor, - object : OutcomeReceiver { - override fun onError(e: ThreadNetworkException) { - // TODO(b/327549838): gracefully handle the failure by resetting the UI state - Log.e(TAG, "Failed to $action Thread", e) - } - - override fun onResult(unused: Void?) { - Log.d(TAG, "Successfully $action Thread") - } - }) - return true - } - - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if (threadController == null) { - return - } - - when (event) { - Lifecycle.Event.ON_START -> { - threadController.registerStateCallback(executor, stateCallback) - airplaneModeOn = isAirplaneModeOn(mContext) - mContext.registerReceiver( - airplaneModeReceiver, - IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED) - ) - preference?.let { preference -> updateState(preference) } - } - Lifecycle.Event.ON_STOP -> { - threadController.unregisterStateCallback(stateCallback) - mContext.unregisterReceiver(airplaneModeReceiver) - } - else -> {} - } - } - - override fun updateState(preference: Preference) { - super.updateState(preference) - preference.isEnabled = !airplaneModeOn - refreshSummary(preference) - } - - override fun getSummary(): CharSequence { - val resId: Int = if (airplaneModeOn) { - R.string.thread_network_settings_summary_airplane_mode - } else { - R.string.thread_network_settings_summary - } - return mContext.getResources().getString(resId) - } - - override fun getSliceHighlightMenuRes(): Int { - return R.string.menu_key_connected_devices - } - - companion object { - private const val TAG = "ThreadNetworkSettings" - private fun getThreadNetworkController(context: Context): BaseThreadNetworkController? { - if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_THREAD_NETWORK)) { - return null - } - val manager = context.getSystemService(ThreadNetworkManager::class.java) ?: return null - val controller = manager.allThreadNetworkControllers[0] - return object : BaseThreadNetworkController { - override fun setEnabled( - enabled: Boolean, - executor: Executor, - receiver: OutcomeReceiver - ) { - controller.setEnabled(enabled, executor, receiver) - } - - override fun registerStateCallback(executor: Executor, callback: StateCallback) { - controller.registerStateCallback(executor, callback) - } - - override fun unregisterStateCallback(callback: StateCallback) { - controller.unregisterStateCallback(callback) - } - } - } - - private fun isAirplaneModeOn(context: Context): Boolean { - return Settings.Global.getInt( - context.contentResolver, - Settings.Global.AIRPLANE_MODE_ON, - 0 - ) == 1 - } - } -} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleController.kt new file mode 100644 index 00000000000..2af46759dd7 --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleController.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.net.thread.ThreadNetworkController +import android.net.thread.ThreadNetworkController.StateCallback +import android.net.thread.ThreadNetworkException +import android.os.OutcomeReceiver +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.android.settings.R +import com.android.settings.core.TogglePreferenceController +import com.android.settings.flags.Flags +import java.util.concurrent.Executor + +/** + * Controller for the "Use Thread" toggle in "Connected devices > Connection preferences > Thread". + */ +class ThreadNetworkToggleController @VisibleForTesting constructor( + context: Context, + key: String, + private val executor: Executor, + private val threadController: BaseThreadNetworkController? +) : TogglePreferenceController(context, key), LifecycleEventObserver { + private val stateCallback: StateCallback + private var threadEnabled = false + private var preference: Preference? = null + + constructor(context: Context, key: String) : this( + context, + key, + ContextCompat.getMainExecutor(context), + ThreadNetworkUtils.getThreadNetworkController(context) + ) + + init { + stateCallback = newStateCallback() + } + + val isThreadSupportedOnDevice: Boolean + get() = threadController != null + + private fun newStateCallback(): StateCallback { + return object : StateCallback { + override fun onThreadEnableStateChanged(enabledState: Int) { + threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED + preference?.let { preference -> updateState(preference) } + } + + override fun onDeviceRoleChanged(role: Int) {} + } + } + + override fun getAvailabilityStatus(): Int { + return if (!Flags.threadSettingsEnabled()) { + CONDITIONALLY_UNAVAILABLE + } else if (!isThreadSupportedOnDevice) { + UNSUPPORTED_ON_DEVICE + } else { + AVAILABLE + } + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun isChecked(): Boolean { + return threadEnabled + } + + override fun setChecked(isChecked: Boolean): Boolean { + if (threadController == null) { + return false + } + + // Avoids dead loop of setChecked -> threadController.setEnabled() -> + // StateCallback.onThreadEnableStateChanged -> updateState -> setChecked + if (isChecked == isChecked()) { + return true + } + + val action = if (isChecked) "enable" else "disable" + threadController.setEnabled( + isChecked, + executor, + object : OutcomeReceiver { + override fun onError(e: ThreadNetworkException) { + // TODO(b/327549838): gracefully handle the failure by resetting the UI state + Log.e(TAG, "Failed to $action Thread", e) + } + + override fun onResult(unused: Void?) { + Log.d(TAG, "Successfully $action Thread") + } + }) + return true + } + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (threadController == null) { + return + } + + when (event) { + Lifecycle.Event.ON_START -> { + threadController.registerStateCallback(executor, stateCallback) + } + + Lifecycle.Event.ON_STOP -> { + threadController.unregisterStateCallback(stateCallback) + } + + else -> {} + } + } + + override fun getSliceHighlightMenuRes(): Int { + return R.string.menu_key_connected_devices + } + + companion object { + private const val TAG = "ThreadNetworkSettings" + } +} diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkUtils.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkUtils.kt new file mode 100644 index 00000000000..70830ed3803 --- /dev/null +++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkUtils.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.content.pm.PackageManager +import android.net.thread.ThreadNetworkController +import android.net.thread.ThreadNetworkController.StateCallback +import android.net.thread.ThreadNetworkException +import android.net.thread.ThreadNetworkManager +import android.os.OutcomeReceiver +import androidx.annotation.VisibleForTesting +import java.util.concurrent.Executor + +/** Common utilities for Thread settings classes. */ +object ThreadNetworkUtils { + /** + * Retrieves the [BaseThreadNetworkController] instance that is backed by the Android + * [ThreadNetworkController]. + */ + fun getThreadNetworkController(context: Context): BaseThreadNetworkController? { + if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_THREAD_NETWORK)) { + return null + } + val manager = context.getSystemService(ThreadNetworkManager::class.java) ?: return null + val controller = manager.allThreadNetworkControllers[0] + return object : BaseThreadNetworkController { + override fun setEnabled( + enabled: Boolean, + executor: Executor, + receiver: OutcomeReceiver + ) { + controller.setEnabled(enabled, executor, receiver) + } + + override fun registerStateCallback(executor: Executor, callback: StateCallback) { + controller.registerStateCallback(executor, callback) + } + + override fun unregisterStateCallback(callback: StateCallback) { + controller.unregisterStateCallback(callback) + } + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/core/RoundCornerPreferenceAdapter.java b/src/com/android/settings/core/RoundCornerPreferenceAdapter.java new file mode 100644 index 00000000000..e5f3763a641 --- /dev/null +++ b/src/com/android/settings/core/RoundCornerPreferenceAdapter.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 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.core; + +import android.os.Handler; +import android.os.Looper; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceGroupAdapter; +import androidx.preference.PreferenceViewHolder; + +import com.android.settingslib.widget.theme.R; + +import java.util.ArrayList; +import java.util.List; + +public class RoundCornerPreferenceAdapter extends PreferenceGroupAdapter { + + private static final int ROUND_CORNER_CENTER = 1; + private static final int ROUND_CORNER_TOP = 1 << 1; + private static final int ROUND_CORNER_BOTTOM = 1 << 2; + + private final PreferenceGroup mPreferenceGroup; + + private List mRoundCornerMappingList; + + private final Handler mHandler; + + private final Runnable mSyncRunnable = new Runnable() { + @Override + public void run() { + updatePreferences(); + } + }; + + public RoundCornerPreferenceAdapter(@NonNull PreferenceGroup preferenceGroup) { + super(preferenceGroup); + mPreferenceGroup = preferenceGroup; + mHandler = new Handler(Looper.getMainLooper()); + updatePreferences(); + } + + @Override + public void onPreferenceHierarchyChange(@NonNull Preference preference) { + super.onPreferenceHierarchyChange(preference); + mHandler.removeCallbacks(mSyncRunnable); + mHandler.post(mSyncRunnable); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + updateBackground(holder, position); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + private void updatePreferences() { + mRoundCornerMappingList = new ArrayList<>(); + mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup); + } + private void mappingPreferenceGroup(List visibleList, PreferenceGroup group) { + int groupSize = group.getPreferenceCount(); + int firstVisible = 0; + int lastVisible = 0; + for (int i = 0; i < groupSize; i++) { + Preference pref = group.getPreference(i); + if (!pref.isVisible()) { + continue; + } + + //the first visible preference. + Preference firstVisiblePref = group.getPreference(firstVisible); + if (!firstVisiblePref.isVisible()) { + firstVisible = i; + } + + int value = 0; + if (group instanceof PreferenceCategory) { + if (pref instanceof PreferenceCategory) { + visibleList.add(value); + mappingPreferenceGroup(visibleList, (PreferenceCategory) pref); + } else { + if (i == firstVisible) { + value |= ROUND_CORNER_TOP; + } + + value |= ROUND_CORNER_BOTTOM; + if (i > lastVisible) { + // the last + int lastIndex = visibleList.size() - 1; + int newValue = visibleList.get(lastIndex) & ~ROUND_CORNER_BOTTOM; + visibleList.set(lastIndex, newValue); + lastVisible = i; + } + + value |= ROUND_CORNER_CENTER; + visibleList.add(value); + } + } else { + visibleList.add(value); + if (pref instanceof PreferenceCategory) { + mappingPreferenceGroup(visibleList, (PreferenceCategory) pref); + } + } + } + } + + /** handle roundCorner background */ + private void updateBackground(PreferenceViewHolder holder, int position) { + int CornerType = mRoundCornerMappingList.get(position); + + if ((CornerType & ROUND_CORNER_CENTER) == 0) { + return; + } + + View v = holder.itemView; + if (((CornerType & ROUND_CORNER_TOP) != 0) && ((CornerType & ROUND_CORNER_BOTTOM) == 0)) { + // the first + v.setBackgroundResource(R.drawable.settingslib_round_background_top); + } else if (((CornerType & ROUND_CORNER_BOTTOM) != 0) + && ((CornerType & ROUND_CORNER_TOP) == 0)) { + // the last + v.setBackgroundResource(R.drawable.settingslib_round_background_bottom); + } else if (((CornerType & ROUND_CORNER_TOP) != 0) + && ((CornerType & ROUND_CORNER_BOTTOM) != 0)) { + // the only one preference + v.setBackgroundResource(R.drawable.settingslib_round_background); + } else { + // in the center + v.setBackgroundResource(R.drawable.settingslib_round_background_center); + } + } +} diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index bee1da7af80..4d43d7964d5 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -30,7 +30,6 @@ import com.android.settings.accessibility.AccessibilitySettings; import com.android.settings.accessibility.AccessibilitySettingsForSetupWizard; import com.android.settings.accessibility.CaptioningPropertiesFragment; import com.android.settings.accessibility.ColorAndMotionFragment; -import com.android.settings.accessibility.ColorContrastFragment; import com.android.settings.accessibility.HearingDevicePairingFragment; import com.android.settings.accessibility.TextReadingPreferenceFragment; import com.android.settings.accessibility.TextReadingPreferenceFragmentForSetupWizard; @@ -105,6 +104,7 @@ import com.android.settings.deviceinfo.batteryinfo.BatteryInfoFragment; import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings; import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard; import com.android.settings.display.AutoBrightnessSettings; +import com.android.settings.display.ColorContrastFragment; import com.android.settings.display.NightDisplaySettings; import com.android.settings.display.ScreenTimeoutSettings; import com.android.settings.display.SmartAutoRotatePreferenceFragment; diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index b95d927414a..ffc97dc722c 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -444,7 +444,9 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } if (TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) { iconDrawable.setTint(Utils.getHomepageIconColor(preference.getContext())); - } else if (forceRoundedIcon && !TextUtils.equals(mContext.getPackageName(), iconPackage)) { + } + + if (forceRoundedIcon && !TextUtils.equals(mContext.getPackageName(), iconPackage)) { iconDrawable = new AdaptiveIcon(mContext, iconDrawable, R.dimen.dashboard_tile_foreground_image_inset); ((AdaptiveIcon) iconDrawable).setBackgroundColor(mContext, tile); diff --git a/src/com/android/settings/datausage/BillingCyclePreference.kt b/src/com/android/settings/datausage/BillingCyclePreference.kt index a6904bc4eb6..8dd7d0f7030 100644 --- a/src/com/android/settings/datausage/BillingCyclePreference.kt +++ b/src/com/android/settings/datausage/BillingCyclePreference.kt @@ -26,11 +26,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R import com.android.settings.core.SubSettingLauncher import com.android.settings.datausage.lib.BillingCycleRepository -import com.android.settings.network.mobileDataEnabledFlow import com.android.settings.spa.preference.ComposePreference import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import kotlinx.coroutines.flow.map /** * Preference which displays billing cycle of subscription @@ -46,8 +44,8 @@ class BillingCyclePreference @JvmOverloads constructor( override fun setTemplate(template: NetworkTemplate, subId: Int) { setContent { - val isModifiable by remember { - context.mobileDataEnabledFlow(subId).map { repository.isModifiable(subId) } + val isModifiable by remember(subId) { + repository.isModifiableFlow(subId) }.collectAsStateWithLifecycle(initialValue = false) Preference(object : PreferenceModel { diff --git a/src/com/android/settings/datausage/DataUsageList.kt b/src/com/android/settings/datausage/DataUsageList.kt index 1995097d6f1..a8f5460a18c 100644 --- a/src/com/android/settings/datausage/DataUsageList.kt +++ b/src/com/android/settings/datausage/DataUsageList.kt @@ -35,7 +35,7 @@ import com.android.settings.datausage.lib.BillingCycleRepository import com.android.settings.datausage.lib.NetworkUsageData import com.android.settings.network.MobileNetworkRepository import com.android.settings.network.SubscriptionUtil -import com.android.settings.network.mobileDataEnabledFlow +import com.android.settings.network.telephony.requireSubscriptionManager import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import com.android.settingslib.spaprivileged.framework.common.userManager @@ -113,8 +113,8 @@ open class DataUsageList : DashboardFragment() { override fun onViewCreated(v: View, savedInstanceState: Bundle?) { super.onViewCreated(v, savedInstanceState) - requireContext().mobileDataEnabledFlow(subId) - .collectLatestWithLifecycle(viewLifecycleOwner) { updatePolicy() } + billingCycleRepository.isModifiableFlow(subId) + .collectLatestWithLifecycle(viewLifecycleOwner, action = ::updatePolicy) val template = template ?: return viewModel.templateFlow.value = template @@ -163,16 +163,14 @@ open class DataUsageList : DashboardFragment() { } /** Update chart sweeps and cycle list to reflect [NetworkPolicy] for current [template]. */ - private fun updatePolicy() { - val isBillingCycleModifiable = isBillingCycleModifiable() + private fun updatePolicy(isModifiable: Boolean) { + val isBillingCycleModifiable = isModifiable && isActiveSubscription() dataUsageListHeaderController?.setConfigButtonVisible(isBillingCycleModifiable) chartDataUsagePreferenceController?.setBillingCycleModifiable(isBillingCycleModifiable) } - private fun isBillingCycleModifiable(): Boolean = - billingCycleRepository.isModifiable(subId) && - requireContext().getSystemService(SubscriptionManager::class.java)!! - .getActiveSubscriptionInfo(subId) != null + private fun isActiveSubscription(): Boolean = + requireContext().requireSubscriptionManager().getActiveSubscriptionInfo(subId) != null /** * Updates the chart and detail data when initial loaded or selected cycle changed. diff --git a/src/com/android/settings/datausage/lib/BillingCycleRepository.kt b/src/com/android/settings/datausage/lib/BillingCycleRepository.kt index bd6aa273ad2..d324c75d6f9 100644 --- a/src/com/android/settings/datausage/lib/BillingCycleRepository.kt +++ b/src/com/android/settings/datausage/lib/BillingCycleRepository.kt @@ -19,10 +19,15 @@ package com.android.settings.datausage.lib import android.content.Context import android.os.INetworkManagementService import android.os.ServiceManager -import android.telephony.TelephonyManager import android.util.Log import androidx.annotation.OpenForTesting +import com.android.settings.network.telephony.TelephonyRepository import com.android.settingslib.spaprivileged.framework.common.userManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map @OpenForTesting open class BillingCycleRepository @JvmOverloads constructor( @@ -31,12 +36,14 @@ open class BillingCycleRepository @JvmOverloads constructor( INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE) ), + private val telephonyRepository: TelephonyRepository = TelephonyRepository(context), ) { private val userManager = context.userManager - private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!! - fun isModifiable(subId: Int): Boolean = - isBandwidthControlEnabled() && userManager.isAdminUser && isDataEnabled(subId) + fun isModifiableFlow(subId: Int): Flow = + telephonyRepository.isDataEnabledFlow(subId).map { isDataEnabled -> + isDataEnabled && isBandwidthControlEnabled() && userManager.isAdminUser + }.conflate().flowOn(Dispatchers.Default) open fun isBandwidthControlEnabled(): Boolean = try { networkService.isBandwidthControlEnabled @@ -45,10 +52,6 @@ open class BillingCycleRepository @JvmOverloads constructor( false } - private fun isDataEnabled(subId: Int): Boolean = - telephonyManager.createForSubscriptionId(subId) - .isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) - companion object { private const val TAG = "BillingCycleRepository" } diff --git a/src/com/android/settings/development/GrammaticalGenderPreferenceController.java b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java index 054097487e0..347894d7ee6 100644 --- a/src/com/android/settings/development/GrammaticalGenderPreferenceController.java +++ b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java @@ -69,12 +69,19 @@ public class GrammaticalGenderPreferenceController extends DeveloperOptionsPrefe @Override public boolean onPreferenceChange(Preference preference, Object newValue) { + final var oldValue = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY, + Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED); SystemProperties.set(GRAMMATICAL_GENDER_PROPERTY, newValue.toString()); updateState(mPreference); try { Configuration config = mActivityManager.getConfiguration(); - config.setGrammaticalGender(Integer.parseInt(newValue.toString())); - mActivityManager.updatePersistentConfiguration(config); + // Only apply the developer settings value if it is the one currently used, + // otherwise it means there's some kind of override that we don't want to + // touch here. + if (config.getGrammaticalGender() == oldValue) { + config.setGrammaticalGender(Integer.parseInt(newValue.toString())); + mActivityManager.updatePersistentConfiguration(config); + } } catch (RemoteException ex) { // intentional no-op } diff --git a/src/com/android/settings/accessibility/ColorContrastFooterPreferenceController.java b/src/com/android/settings/display/ColorContrastFooterPreferenceController.java similarity index 90% rename from src/com/android/settings/accessibility/ColorContrastFooterPreferenceController.java rename to src/com/android/settings/display/ColorContrastFooterPreferenceController.java index 99f74183e69..6f1e9c0acf8 100644 --- a/src/com/android/settings/accessibility/ColorContrastFooterPreferenceController.java +++ b/src/com/android/settings/display/ColorContrastFooterPreferenceController.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import android.content.Context; import androidx.annotation.NonNull; import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityFooterPreferenceController; /** Preference controller for footer in color contrast page. */ public class ColorContrastFooterPreferenceController extends diff --git a/src/com/android/settings/accessibility/ColorContrastFragment.java b/src/com/android/settings/display/ColorContrastFragment.java similarity index 97% rename from src/com/android/settings/accessibility/ColorContrastFragment.java rename to src/com/android/settings/display/ColorContrastFragment.java index 904826e47ec..f61430d6b6b 100644 --- a/src/com/android/settings/accessibility/ColorContrastFragment.java +++ b/src/com/android/settings/display/ColorContrastFragment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import android.app.settings.SettingsEnums; @@ -29,7 +29,6 @@ public class ColorContrastFragment extends DashboardFragment { private static final String TAG = "ColorContrastFragment"; - @Override protected int getPreferenceScreenResId() { return R.xml.accessibility_color_contrast; diff --git a/src/com/android/settings/display/ContrastPreferenceController.java b/src/com/android/settings/display/ContrastPreferenceController.java new file mode 100644 index 00000000000..f330d8b4702 --- /dev/null +++ b/src/com/android/settings/display/ContrastPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.display; + +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH; +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM; +import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD; +import static android.app.UiModeManager.ContrastUtils.toContrastLevel; + +import android.app.UiModeManager; +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.android.settings.R; +import com.android.settings.accessibility.Flags; +import com.android.settings.core.BasePreferenceController; + +import java.util.Map; + +/** + * Controller for {@link ColorContrastFragment}. + */ +public class ContrastPreferenceController extends BasePreferenceController { + + public ContrastPreferenceController(@NonNull Context context, @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return Flags.enableColorContrastControl() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public CharSequence getSummary() { + Map mContrastLevelToResId = Map.ofEntries( + Map.entry(CONTRAST_LEVEL_STANDARD, R.string.contrast_default), + Map.entry(CONTRAST_LEVEL_MEDIUM, R.string.contrast_medium), + Map.entry(CONTRAST_LEVEL_HIGH, R.string.contrast_high) + ); + + float contrastLevel = mContext.getSystemService(UiModeManager.class).getContrast(); + return mContext.getString(mContrastLevelToResId.get(toContrastLevel(contrastLevel))); + } +} diff --git a/src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java b/src/com/android/settings/display/ContrastSelectorPreferenceController.java similarity index 99% rename from src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java rename to src/com/android/settings/display/ContrastSelectorPreferenceController.java index 5b746cdd705..ba98601d603 100644 --- a/src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java +++ b/src/com/android/settings/display/ContrastSelectorPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH; import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index b938c72e4b2..5e17f4b4871 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -649,9 +649,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private final class HourlyChartLabelTextGenerator extends BaseLabelTextGenerator implements BatteryChartViewModel.LabelTextGenerator { - private static final int FULL_CHARGE_BATTERY_LEVEL = 100; - - private boolean mIsFromFullCharge; + private boolean mIsStartTimestamp; private long mFistTimestamp; private long mLatestTimestamp; @@ -664,7 +662,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll long timestamp = timestamps.get(index); boolean showMinute = false; if (Objects.equal(timestamp, mFistTimestamp)) { - if (mIsFromFullCharge) { + if (mIsStartTimestamp) { showMinute = true; } else { // starts from 7 days ago @@ -699,8 +697,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll @NonNull final BatteryLevelData batteryLevelData) { BatteryLevelData.PeriodBatteryLevelData firstDayLevelData = batteryLevelData.getHourlyBatteryLevelsPerDay().get(0); - this.mIsFromFullCharge = - firstDayLevelData.getLevels().get(0) == FULL_CHARGE_BATTERY_LEVEL; + this.mIsStartTimestamp = firstDayLevelData.isStartTimestamp(); this.mFistTimestamp = firstDayLevelData.getTimestamps().get(0); this.mLatestTimestamp = getLast( diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java index 231c730a176..d1bf49b6cc5 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java @@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.util.Preconditions; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -39,17 +40,24 @@ public final class BatteryLevelData { private static final long MIN_SIZE = 2; private static final long TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2; + // For testing only. + @VisibleForTesting @Nullable static Calendar sTestCalendar; + /** A container for the battery timestamp and level data. */ public static final class PeriodBatteryLevelData { // The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when // there is no level data for the corresponding timestamp. private final List mTimestamps; private final List mLevels; + private final boolean mIsStartTimestamp; public PeriodBatteryLevelData( - @NonNull Map batteryLevelMap, @NonNull List timestamps) { + @NonNull Map batteryLevelMap, + @NonNull List timestamps, + boolean isStartTimestamp) { mTimestamps = timestamps; mLevels = new ArrayList<>(timestamps.size()); + mIsStartTimestamp = isStartTimestamp; for (Long timestamp : timestamps) { mLevels.add( batteryLevelMap.containsKey(timestamp) @@ -66,6 +74,10 @@ public final class BatteryLevelData { return mLevels; } + public boolean isStartTimestamp() { + return mIsStartTimestamp; + } + @Override public String toString() { return String.format( @@ -105,14 +117,21 @@ public final class BatteryLevelData { final List timestampList = new ArrayList<>(batteryLevelMap.keySet()); Collections.sort(timestampList); + final long minTimestamp = timestampList.get(0); + final long sixDaysAgoTimestamp = + DatabaseUtils.getTimestampSixDaysAgo(sTestCalendar != null ? sTestCalendar : null); + final boolean isStartTimestamp = minTimestamp > sixDaysAgoTimestamp; final List dailyTimestamps = getDailyTimestamps(timestampList); final List> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps); - mDailyBatteryLevels = new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps); + mDailyBatteryLevels = + new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps, isStartTimestamp); mHourlyBatteryLevelsPerDay = new ArrayList<>(hourlyTimestamps.size()); - for (List hourlyTimestampsPerDay : hourlyTimestamps) { + for (int i = 0; i < hourlyTimestamps.size(); i++) { + final List hourlyTimestampsPerDay = hourlyTimestamps.get(i); mHourlyBatteryLevelsPerDay.add( - new PeriodBatteryLevelData(batteryLevelMap, hourlyTimestampsPerDay)); + new PeriodBatteryLevelData( + batteryLevelMap, hourlyTimestampsPerDay, isStartTimestamp && i == 0)); } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java index e407c636ddf..b758df4bd6b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java @@ -67,9 +67,13 @@ public final class BootBroadcastReceiver extends BroadcastReceiver { refreshJobs(context); break; case Intent.ACTION_TIME_CHANGED: - Log.d(TAG, "refresh job and clear all data from action=" + action); + Log.d(TAG, "refresh job and clear data from action=" + action); DatabaseUtils.clearDataAfterTimeChangedIfNeeded(context, intent); break; + case Intent.ACTION_TIMEZONE_CHANGED: + Log.d(TAG, "refresh job and clear all data from action=" + action); + DatabaseUtils.clearDataAfterTimeZoneChangedIfNeeded(context); + break; default: Log.w(TAG, "receive unsupported action=" + action); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index a41e9bd0388..5b28abb422f 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -16,8 +16,6 @@ package com.android.settings.fuelgauge.batteryusage; -import static android.content.Intent.FLAG_RECEIVER_REPLACE_PENDING; - import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging; import android.app.usage.IUsageStatsManager; @@ -59,6 +57,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TimeZone; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -436,6 +435,23 @@ public final class DatabaseUtils { }); } + /** Clears data after a specific startTimestamp in the battery usage database. */ + public static void clearAllAfter(Context context, long startTimestamp) { + AsyncTask.execute( + () -> { + try { + final BatteryStateDatabase database = + BatteryStateDatabase.getInstance(context.getApplicationContext()); + database.appUsageEventDao().clearAllAfter(startTimestamp); + database.batteryEventDao().clearAllAfter(startTimestamp); + database.batteryStateDao().clearAllAfter(startTimestamp); + database.batteryUsageSlotDao().clearAllAfter(startTimestamp); + } catch (RuntimeException e) { + Log.e(TAG, "clearAllAfter() failed", e); + } + }); + } + /** Clears all out-of-date data in the battery usage database. */ public static void clearExpiredDataIfNeeded(Context context) { AsyncTask.execute( @@ -456,14 +472,14 @@ public final class DatabaseUtils { }); } - /** Clears all data and jobs if current timestamp is out of the range of last recorded job. */ + /** Clears data after new updated time and refresh periodic job. */ public static void clearDataAfterTimeChangedIfNeeded(Context context, Intent intent) { - if ((intent.getFlags() & FLAG_RECEIVER_REPLACE_PENDING) != 0) { + if ((intent.hasExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT))) { BatteryUsageLogUtils.writeLog( context, Action.TIME_UPDATED, - "Database is not cleared because the time change intent is only" - + " for the existing pending receiver."); + "Database is not cleared because the time change intent is" + + " for time format change"); return; } AsyncTask.execute( @@ -480,6 +496,22 @@ public final class DatabaseUtils { }); } + /** Clears all data and reset jobs if timezone changed. */ + public static void clearDataAfterTimeZoneChangedIfNeeded(Context context) { + AsyncTask.execute( + () -> { + try { + clearDataAfterTimeZoneChangedIfNeededInternal(context); + } catch (RuntimeException e) { + Log.e(TAG, "clearDataAfterTimeZoneChangedIfNeeded() failed", e); + BatteryUsageLogUtils.writeLog( + context, + Action.TIMEZONE_UPDATED, + "clearDataAfterTimeZoneChangedIfNeeded() failed" + e); + } + }); + } + /** Returns the timestamp for 00:00 6 days before the calendar date. */ public static long getTimestampSixDaysAgo(Calendar calendar) { Calendar startCalendar = @@ -861,37 +893,38 @@ public final class DatabaseUtils { } private static void clearDataAfterTimeChangedIfNeededInternal(Context context) { + final long currentTime = System.currentTimeMillis(); + final String logInfo = + String.format(Locale.ENGLISH, "clear data after current time = %d", currentTime); + Log.d(TAG, logInfo); + BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo); + DatabaseUtils.clearAllAfter(context, currentTime); + PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false); + final List batteryLevelRecordEvents = DatabaseUtils.getBatteryEvents( context, Calendar.getInstance(), getLastFullChargeTime(context), BATTERY_LEVEL_RECORD_EVENTS); - final long lastRecordTimestamp = - batteryLevelRecordEvents.isEmpty() - ? INVALID_TIMESTAMP - : batteryLevelRecordEvents.get(0).getTimestamp(); - final long nextRecordTimestamp = - TimestampUtils.getNextEvenHourTimestamp(lastRecordTimestamp); - final long currentTime = System.currentTimeMillis(); - final boolean isOutOfTimeRange = - lastRecordTimestamp == INVALID_TIMESTAMP - || currentTime < lastRecordTimestamp - || currentTime > nextRecordTimestamp; + if (batteryLevelRecordEvents.isEmpty()) { + // Take a snapshot of battery usage data immediately if there's no battery events. + BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true); + } + } + + private static void clearDataAfterTimeZoneChangedIfNeededInternal(Context context) { final String logInfo = String.format( Locale.ENGLISH, - "clear database = %b, current time = %d, last record time = %d", - isOutOfTimeRange, - currentTime, - lastRecordTimestamp); + "clear database for new time zone = %s", + TimeZone.getDefault().toString()); + BatteryUsageLogUtils.writeLog(context, Action.TIMEZONE_UPDATED, logInfo); Log.d(TAG, logInfo); - BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo); - if (isOutOfTimeRange) { - DatabaseUtils.clearAll(context); - PeriodicJobManager.getInstance(context) - .refreshJob(/* fromBoot= */ false); - } + DatabaseUtils.clearAll(context); + PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false); + // Take a snapshot of battery usage data immediately + BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true); } private static long loadLongFromContentProvider( diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java index d220b15968f..249780125f2 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java @@ -55,6 +55,10 @@ public interface AppUsageEventDao { @Query("DELETE FROM AppUsageEventEntity WHERE timestamp <= :timestamp") void clearAllBefore(long timestamp); + /** Deletes all recorded data after a specific timestamp. */ + @Query("DELETE FROM AppUsageEventEntity WHERE timestamp >= :timestamp") + void clearAllAfter(long timestamp); + /** Clears all recorded data in the database. */ @Query("DELETE FROM AppUsageEventEntity") void clearAll(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java index 8b696fe96c0..19d20438afa 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java @@ -65,6 +65,10 @@ public interface BatteryEventDao { @Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp") void clearAllBefore(long timestamp); + /** Deletes all recorded data after a specific timestamp. */ + @Query("DELETE FROM BatteryEventEntity WHERE timestamp >= :timestamp") + void clearAllAfter(long timestamp); + /** Clears all recorded data in the database. */ @Query("DELETE FROM BatteryEventEntity") void clearAll(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java index 520c6bed484..049251eb718 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java @@ -61,6 +61,10 @@ public interface BatteryStateDao { @Query("DELETE FROM BatteryState WHERE timestamp <= :timestamp") void clearAllBefore(long timestamp); + /** Deletes all recorded data after a specific timestamp. */ + @Query("DELETE FROM BatteryState WHERE timestamp >= :timestamp") + void clearAllAfter(long timestamp); + /** Clears all recorded data in the database. */ @Query("DELETE FROM BatteryState") void clearAll(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java index d8cf41d0ce8..d53b0cf2532 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java @@ -52,6 +52,10 @@ public interface BatteryUsageSlotDao { @Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp <= :timestamp") void clearAllBefore(long timestamp); + /** Deletes all recorded data after a specific timestamp. */ + @Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp >= :timestamp") + void clearAllAfter(long timestamp); + /** Clears all recorded data in the database. */ @Query("DELETE FROM BatteryUsageSlotEntity") void clearAll(); diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 566136d887f..e2557ed54ce 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -73,6 +73,7 @@ import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.activityembedding.EmbeddedDeepLinkUtils; import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; +import com.android.settings.flags.Flags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.safetycenter.SafetyCenterManagerWrapper; @@ -160,8 +161,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements if (mAllowUpdateSuggestion) { Log.i(TAG, "showHomepageWithSuggestion: " + showSuggestion); mAllowUpdateSuggestion = false; - mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); - mTwoPaneSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); + if (Flags.homepageRevamp()) { + mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); + } else { + mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); + mTwoPaneSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); + } } if (mHomepageView == null) { @@ -245,7 +250,10 @@ public class SettingsHomepageActivity extends FragmentActivity implements } setupEdgeToEdge(); - setContentView(R.layout.settings_homepage_container); + setContentView( + Flags.homepageRevamp() + ? R.layout.settings_homepage_container_v2 + : R.layout.settings_homepage_container); mIsTwoPane = ActivityEmbeddingUtils.isAlreadyEmbedded(this); @@ -397,19 +405,31 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private void initSearchBarView() { - final Toolbar toolbar = findViewById(R.id.search_action_bar); - FeatureFactory.getFeatureFactory().getSearchFeatureProvider() - .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE); - - if (mIsEmbeddingActivityEnabled) { - final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane); + if (Flags.homepageRevamp()) { + Toolbar toolbar = findViewById(R.id.search_action_bar_unified); FeatureFactory.getFeatureFactory().getSearchFeatureProvider() - .initSearchToolbar(this /* activity */, toolbarTwoPaneVersion, + .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE); + } else { + final Toolbar toolbar = findViewById(R.id.search_action_bar); + FeatureFactory.getFeatureFactory().getSearchFeatureProvider() + .initSearchToolbar(this /* activity */, toolbar, + SettingsEnums.SETTINGS_HOMEPAGE); + + if (mIsEmbeddingActivityEnabled) { + final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane); + FeatureFactory.getFeatureFactory().getSearchFeatureProvider() + .initSearchToolbar(this /* activity */, toolbarTwoPaneVersion, + SettingsEnums.SETTINGS_HOMEPAGE); + } } } private void initAvatarView() { + if (Flags.homepageRevamp()) { + return; + } + final ImageView avatarView = findViewById(R.id.account_avatar); final ImageView avatarTwoPaneView = findViewById(R.id.account_avatar_two_pane_version); if (AvatarViewMixin.isAvatarSupported(this)) { @@ -458,8 +478,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements return; } - mSuggestionView = findViewById(R.id.suggestion_content); - mTwoPaneSuggestionView = findViewById(R.id.two_pane_suggestion_content); + if (Flags.homepageRevamp()) { + mSuggestionView = findViewById(R.id.unified_suggestion_content); + } else { + mSuggestionView = findViewById(R.id.suggestion_content); + mTwoPaneSuggestionView = findViewById(R.id.two_pane_suggestion_content); + } mHomepageView = findViewById(R.id.settings_homepage_container); // Hide the homepage for preparing the suggestion. If scrolling is needed, the list views // should be initialized in the invisible homepage view to prevent a scroll flicker. @@ -467,11 +491,16 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Schedule a timer to show the homepage and hide the suggestion on timeout. mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false), HOMEPAGE_LOADING_TIMEOUT_MS); - showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ false), - R.id.suggestion_content); - if (mIsEmbeddingActivityEnabled) { - showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ true), - R.id.two_pane_suggestion_content); + if (Flags.homepageRevamp()) { + showFragment(new SuggestionFragCreator(fragmentClass, true), + R.id.unified_suggestion_content); + } else { + showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ false), + R.id.suggestion_content); + if (mIsEmbeddingActivityEnabled) { + showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ true), + R.id.two_pane_suggestion_content); + } } } @@ -736,7 +765,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private void updateHomepageAppBar() { - if (!mIsEmbeddingActivityEnabled) { + if (Flags.homepageRevamp() || !mIsEmbeddingActivityEnabled) { return; } updateAppBarMinHeight(); @@ -752,7 +781,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private void updateHomepagePaddings() { - if (!mIsEmbeddingActivityEnabled) { + if (Flags.homepageRevamp() || !mIsEmbeddingActivityEnabled) { return; } if (mIsTwoPane) { @@ -766,6 +795,9 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private void updateAppBarMinHeight() { + if (Flags.homepageRevamp()) { + return; + } final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height); final int margin = getResources().getDimensionPixelSize( mIsEmbeddingActivityEnabled && mIsTwoPane diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index d1fa7601322..66428611a5b 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -42,8 +42,10 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.activityembedding.ActivityEmbeddingRulesController; import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.core.RoundCornerPreferenceAdapter; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.flags.Flags; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.support.SupportPreferenceController; @@ -84,7 +86,7 @@ public class TopLevelSettings extends DashboardFragment implements SplitLayoutLi @Override protected int getPreferenceScreenResId() { - return R.xml.top_level_settings; + return Flags.homepageRevamp() ? R.xml.top_level_settings_v2 : R.xml.top_level_settings; } @Override @@ -331,10 +333,14 @@ public class TopLevelSettings extends DashboardFragment implements SplitLayoutLi @Override protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { - if (!mIsEmbeddingActivityEnabled || !(getActivity() instanceof SettingsHomepageActivity)) { - return super.onCreateAdapter(preferenceScreen); + if (mIsEmbeddingActivityEnabled && (getActivity() instanceof SettingsHomepageActivity)) { + return mHighlightMixin.onCreateAdapter(this, preferenceScreen, mScrollNeeded); } - return mHighlightMixin.onCreateAdapter(this, preferenceScreen, mScrollNeeded); + + if (Flags.homepageRevamp()) { + return new RoundCornerPreferenceAdapter(preferenceScreen); + } + return super.onCreateAdapter(preferenceScreen); } @Override @@ -376,7 +382,10 @@ public class TopLevelSettings extends DashboardFragment implements SplitLayoutLi } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.top_level_settings) { + new BaseSearchIndexProvider( + Flags.homepageRevamp() + ? R.xml.top_level_settings_v2 + : R.xml.top_level_settings) { @Override protected boolean isPageSearchEnabled(Context context) { diff --git a/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java deleted file mode 100644 index d14c8d09e44..00000000000 --- a/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.settings.network.telephony; - -import android.content.Context; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; - -import com.android.settings.network.SubscriptionUtil; - -/** - * Shows information about disable a physical SIM. - */ -public class DisableSimFooterPreferenceController extends TelephonyBasePreferenceController { - - /** - * Constructor - */ - public DisableSimFooterPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - } - - /** - * re-init for SIM based on given subscription ID. - * @param subId is the given subscription ID - */ - public void init(int subId) { - mSubId = subId; - } - - @Override - public int getAvailabilityStatus(int subId) { - if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - return CONDITIONALLY_UNAVAILABLE; - } - - SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); - for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) { - if (info.getSubscriptionId() == subId) { - if (info.isEmbedded() || SubscriptionUtil.showToggleForPhysicalSim(subManager)) { - return CONDITIONALLY_UNAVAILABLE; - } - break; - } - } - return AVAILABLE; - } -} diff --git a/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.kt b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.kt new file mode 100644 index 00000000000..8e3e398db8e --- /dev/null +++ b/src/com/android/settings/network/telephony/DisableSimFooterPreferenceController.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 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.network.telephony + +import android.content.Context +import android.telephony.SubscriptionManager + +/** + * Shows information about disable a physical SIM. + */ +class DisableSimFooterPreferenceController @JvmOverloads constructor( + context: Context, + preferenceKey: String, + private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context), +) : TelephonyBasePreferenceController(context, preferenceKey) { + + /** + * Re-init for SIM based on given subscription ID. + * + * @param subId is the given subscription ID + */ + fun init(subId: Int) { + mSubId = subId + } + + override fun getAvailabilityStatus(subId: Int): Int { + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID || + subscriptionRepository.canDisablePhysicalSubscription() + ) { + return CONDITIONALLY_UNAVAILABLE + } + + val isAvailable = + subscriptionRepository.getSelectableSubscriptionInfoList().any { subInfo -> + subInfo.subscriptionId == subId && !subInfo.isEmbedded + } + + return if (isAvailable) AVAILABLE else CONDITIONALLY_UNAVAILABLE + } +} diff --git a/src/com/android/settings/network/telephony/NetworkSelectSettings.java b/src/com/android/settings/network/telephony/NetworkSelectSettings.java index f29a5efd2d6..9455b70d091 100644 --- a/src/com/android/settings/network/telephony/NetworkSelectSettings.java +++ b/src/com/android/settings/network/telephony/NetworkSelectSettings.java @@ -48,13 +48,12 @@ import com.android.internal.telephony.flags.Flags; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.network.telephony.scan.NetworkScanRepository; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete; -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; +import com.google.common.collect.ImmutableList; + import kotlin.Unit; import java.util.ArrayList; @@ -83,7 +82,8 @@ public class NetworkSelectSettings extends DashboardFragment { private View mProgressHeader; private Preference mStatusMessagePreference; @VisibleForTesting - List mCellInfoList; + @NonNull + List mCellInfoList = ImmutableList.of(); private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private TelephonyManager mTelephonyManager; private SatelliteManager mSatelliteManager; @@ -96,7 +96,6 @@ public class NetworkSelectSettings extends DashboardFragment { private AtomicBoolean mShouldFilterOutSatellitePlmn = new AtomicBoolean(); private NetworkScanRepository mNetworkScanRepository; - private boolean mUpdateScanResult = false; private NetworkSelectRepository mNetworkSelectRepository; @@ -213,38 +212,16 @@ public class NetworkSelectSettings extends DashboardFragment { } private void launchNetworkScan() { + setProgressBarVisible(true); mNetworkScanRepository.launchNetworkScan(getViewLifecycleOwner(), (networkScanResult) -> { - if (!mUpdateScanResult) { - // Not update UI if not in scan mode. - return Unit.INSTANCE; - } - if (networkScanResult instanceof NetworkScanCellInfos networkScanCellInfos) { - scanResultHandler(networkScanCellInfos.getCellInfos()); - return Unit.INSTANCE; - } - if (!isPreferenceScreenEnabled()) { - clearPreferenceSummary(); - enablePreferenceScreen(true); - } else if (networkScanResult instanceof NetworkScanComplete - && mCellInfoList == null) { - // In case the scan timeout before getting any results - addMessagePreference(R.string.empty_networks_list); - } else if (networkScanResult instanceof NetworkScanError) { - addMessagePreference(R.string.network_query_error); + if (isPreferenceScreenEnabled()) { + scanResultHandler(networkScanResult); } return Unit.INSTANCE; }); } - @Override - public void onStart() { - super.onStart(); - - setProgressBarVisible(true); - mUpdateScanResult = true; - } - /** * Update forbidden PLMNs from the USIM App */ @@ -268,8 +245,6 @@ public class NetworkSelectSettings extends DashboardFragment { return false; } - mUpdateScanResult = false; - // Refresh the last selected item in case users reselect network. clearPreferenceSummary(); if (mSelectedPreference != null) { @@ -380,27 +355,19 @@ public class NetworkSelectSettings extends DashboardFragment { } } - @Keep @VisibleForTesting - protected void scanResultHandler(List results) { - mCellInfoList = filterOutSatellitePlmn(results); + protected void scanResultHandler(NetworkScanRepository.NetworkScanResult results) { + mCellInfoList = filterOutSatellitePlmn(results.getCellInfos()); Log.d(TAG, "CellInfoList: " + CellInfoUtil.cellInfoListToString(mCellInfoList)); - if (mCellInfoList != null && mCellInfoList.size() != 0) { - final NetworkOperatorPreference connectedPref = updateAllPreferenceCategory(); - if (connectedPref != null) { - // update selected preference instance into connected preference - if (mSelectedPreference != null) { - mSelectedPreference = connectedPref; - } - } else if (!isPreferenceScreenEnabled()) { - mSelectedPreference.setSummary(R.string.network_connecting); - } - enablePreferenceScreen(true); - } else if (isPreferenceScreenEnabled()) { + updateAllPreferenceCategory(); + NetworkScanRepository.NetworkScanState state = results.getState(); + if (state == NetworkScanRepository.NetworkScanState.ERROR) { + addMessagePreference(R.string.network_query_error); + } else if (mCellInfoList.isEmpty()) { addMessagePreference(R.string.empty_networks_list); - // keep showing progress bar, it will be stopped when error or completed - setProgressBarVisible(true); } + // keep showing progress bar, it will be stopped when error or completed + setProgressBarVisible(state == NetworkScanRepository.NetworkScanState.ACTIVE); } @Keep @@ -417,11 +384,8 @@ public class NetworkSelectSettings extends DashboardFragment { /** * Update the content of network operators list. - * - * @return preference which shows connected */ - @Nullable - private NetworkOperatorPreference updateAllPreferenceCategory() { + private void updateAllPreferenceCategory() { int numberOfPreferences = mPreferenceCategory.getPreferenceCount(); // remove unused preferences @@ -432,7 +396,6 @@ public class NetworkSelectSettings extends DashboardFragment { } // update the content of preference - NetworkOperatorPreference connectedPref = null; for (int index = 0; index < mCellInfoList.size(); index++) { final CellInfo cellInfo = mCellInfoList.get(index); @@ -457,23 +420,10 @@ public class NetworkSelectSettings extends DashboardFragment { if (mCellInfoList.get(index).isRegistered()) { pref.setSummary(R.string.network_connected); - connectedPref = pref; } else { pref.setSummary(null); } } - - // update selected preference instance by index - for (int index = 0; index < mCellInfoList.size(); index++) { - final CellInfo cellInfo = mCellInfoList.get(index); - - if ((mSelectedPreference != null) && mSelectedPreference.isSameCell(cellInfo)) { - mSelectedPreference = (NetworkOperatorPreference) - (mPreferenceCategory.getPreference(index)); - } - } - - return connectedPref; } /** @@ -524,13 +474,6 @@ public class NetworkSelectSettings extends DashboardFragment { } } - private boolean isProgressBarVisible() { - if (mProgressHeader == null) { - return false; - } - return (mProgressHeader.getVisibility() == View.VISIBLE); - } - protected void setProgressBarVisible(boolean visible) { if (mProgressHeader != null) { mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); @@ -538,7 +481,6 @@ public class NetworkSelectSettings extends DashboardFragment { } private void addMessagePreference(int messageId) { - setProgressBarVisible(false); mStatusMessagePreference.setTitle(messageId); mPreferenceCategory.removeAll(); mPreferenceCategory.addPreference(mStatusMessagePreference); diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index f4bbc763fb9..05cfad884b3 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -36,6 +36,8 @@ import kotlinx.coroutines.flow.onEach private const val TAG = "SubscriptionRepository" class SubscriptionRepository(private val context: Context) { + private val subscriptionManager = context.requireSubscriptionManager() + /** * Return a list of subscriptions that are available and visible to the user. * @@ -55,6 +57,7 @@ class SubscriptionRepository(private val context: Context) { isSubscriptionEnabledFlow(subId).collectLatestWithLifecycle(lifecycleOwner, action = action) } + fun canDisablePhysicalSubscription() = subscriptionManager.canDisablePhysicalSubscription() } val Context.subscriptionManager: SubscriptionManager? diff --git a/src/com/android/settings/network/telephony/TelephonyRepository.kt b/src/com/android/settings/network/telephony/TelephonyRepository.kt index cc9b53dba82..d0d53b7be95 100644 --- a/src/com/android/settings/network/telephony/TelephonyRepository.kt +++ b/src/com/android/settings/network/telephony/TelephonyRepository.kt @@ -29,10 +29,12 @@ import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach class TelephonyRepository( private val context: Context, @@ -64,19 +66,21 @@ class TelephonyRepository( telephonyManager.setMobileDataPolicyEnabled(policy, enabled) } - fun isDataEnabled( - subId: Int, - ): Flow { + fun isDataEnabledFlow(subId: Int): Flow { if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) - Log.d(TAG, "register mobileDataEnabledFlow: [$subId]") return context.mobileDataEnabledFlow(subId) .map { - Log.d(TAG, "mobileDataEnabledFlow: receive mobile data [$subId] start") val telephonyManager = context.telephonyManager(subId) telephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) - .also { Log.d(TAG, "mobileDataEnabledFlow: [$subId] isDataEnabled(): $it") } } + .catch { + Log.w(TAG, "[$subId] isDataEnabledFlow: exception", it) + emit(false) + } + .onEach { Log.d(TAG, "[$subId] isDataEnabledFlow: isDataEnabled() = $it") } + .conflate() + .flowOn(Dispatchers.Default) } fun setMobileData( @@ -100,6 +104,7 @@ class TelephonyRepository( wifiPickerTrackerHelper.setCarrierNetworkEnabled(enabled) } } + private companion object { private const val TAG = "TelephonyRepository" } diff --git a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt index dfa79cbc869..4ae5842c513 100644 --- a/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt +++ b/src/com/android/settings/network/telephony/scan/NetworkScanRepository.kt @@ -27,25 +27,29 @@ import android.telephony.TelephonyScanManager import android.util.Log import androidx.lifecycle.LifecycleOwner import com.android.settings.R -import com.android.settings.network.telephony.CellInfoUtil import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle +import com.android.settings.network.telephony.telephonyManager import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach class NetworkScanRepository(private val context: Context, subId: Int) { - sealed interface NetworkScanResult + enum class NetworkScanState { + ACTIVE, COMPLETE, ERROR + } - data class NetworkScanCellInfos(val cellInfos: List) : NetworkScanResult - data object NetworkScanComplete : NetworkScanResult - data class NetworkScanError(val error: Int) : NetworkScanResult + data class NetworkScanResult( + val state: NetworkScanState, + val cellInfos: List, + ) - private val telephonyManager = - context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId) + private val telephonyManager = context.telephonyManager(subId) /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ fun launchNetworkScan(lifecycleOwner: LifecycleOwner, onResult: (NetworkScanResult) -> Unit) { @@ -65,23 +69,29 @@ class NetworkScanRepository(private val context: Context, subId: Int) { } fun networkScanFlow(): Flow = callbackFlow { + var state = NetworkScanState.ACTIVE + var cellInfos: List = emptyList() + val callback = object : TelephonyScanManager.NetworkScanCallback() { override fun onResults(results: List) { - val cellInfos = results.distinctBy { CellInfoScanKey(it) } - trySend(NetworkScanCellInfos(cellInfos)) - Log.d(TAG, "CellInfoList: ${CellInfoUtil.cellInfoListToString(cellInfos)}") + cellInfos = results.distinctBy { CellInfoScanKey(it) } + sendResult() } override fun onComplete() { - trySend(NetworkScanComplete) - close() - Log.d(TAG, "onComplete") + state = NetworkScanState.COMPLETE + sendResult() + // Don't call close() here since onComplete() could happens before onResults() } override fun onError(error: Int) { - trySend(NetworkScanError(error)) + state = NetworkScanState.ERROR + sendResult() close() - Log.d(TAG, "onError: $error") + } + + private fun sendResult() { + trySend(NetworkScanResult(state, cellInfos)) } } @@ -92,7 +102,7 @@ class NetworkScanRepository(private val context: Context, subId: Int) { ) awaitClose { networkScan.stopScan() } - }.flowOn(Dispatchers.Default) + }.conflate().onEach { Log.d(TAG, "networkScanFlow: $it") }.flowOn(Dispatchers.Default) /** Create network scan for allowed network types. */ private fun createNetworkScan(): NetworkScanRequest { diff --git a/src/com/android/settings/panel/PanelContent.java b/src/com/android/settings/panel/PanelContent.java index 6b582288457..1bbe2dba83a 100644 --- a/src/com/android/settings/panel/PanelContent.java +++ b/src/com/android/settings/panel/PanelContent.java @@ -28,7 +28,10 @@ import java.util.List; /** * Represents the data class needed to create a Settings Panel. See {@link PanelFragment}. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public interface PanelContent extends Instrumentable { int VIEW_TYPE_SLIDER = 1; diff --git a/src/com/android/settings/panel/PanelContentCallback.java b/src/com/android/settings/panel/PanelContentCallback.java index e59d69913db..cceecd1e4ed 100644 --- a/src/com/android/settings/panel/PanelContentCallback.java +++ b/src/com/android/settings/panel/PanelContentCallback.java @@ -18,7 +18,10 @@ package com.android.settings.panel; /** * PanelContentCallback provides a callback interface for {@link PanelFragment} to receive * events from {@link PanelContent}. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public interface PanelContentCallback { /** diff --git a/src/com/android/settings/panel/PanelFeatureProvider.java b/src/com/android/settings/panel/PanelFeatureProvider.java index 402a562d53d..943c37def46 100644 --- a/src/com/android/settings/panel/PanelFeatureProvider.java +++ b/src/com/android/settings/panel/PanelFeatureProvider.java @@ -19,6 +19,7 @@ package com.android.settings.panel; import android.content.Context; import android.os.Bundle; +@Deprecated(forRemoval = true) public interface PanelFeatureProvider { /** diff --git a/src/com/android/settings/panel/PanelFeatureProviderImpl.java b/src/com/android/settings/panel/PanelFeatureProviderImpl.java index ddfce652c4b..a0aeec60856 100644 --- a/src/com/android/settings/panel/PanelFeatureProviderImpl.java +++ b/src/com/android/settings/panel/PanelFeatureProviderImpl.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import com.android.settings.Utils; import com.android.settings.flags.Flags; +@Deprecated(forRemoval = true) public class PanelFeatureProviderImpl implements PanelFeatureProvider { @Override diff --git a/src/com/android/settings/panel/PanelFragment.java b/src/com/android/settings/panel/PanelFragment.java index 159028369aa..b3a28844be5 100644 --- a/src/com/android/settings/panel/PanelFragment.java +++ b/src/com/android/settings/panel/PanelFragment.java @@ -66,6 +66,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +@Deprecated(forRemoval = true) public class PanelFragment extends Fragment { private static final String TAG = "PanelFragment"; @@ -519,6 +520,7 @@ public class PanelFragment extends Fragment { return mPanel.getViewType(); } + @Deprecated(forRemoval = true) class LocalPanelCallback implements PanelContentCallback { @Override diff --git a/src/com/android/settings/panel/PanelLoggingContract.java b/src/com/android/settings/panel/PanelLoggingContract.java index e6e3012abef..fd145f865fb 100644 --- a/src/com/android/settings/panel/PanelLoggingContract.java +++ b/src/com/android/settings/panel/PanelLoggingContract.java @@ -21,7 +21,10 @@ package com.android.settings.panel; *

* Constants should only be removed if underlying panel, or use case is removed. *

+ * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public class PanelLoggingContract { /** diff --git a/src/com/android/settings/panel/PanelSlicesAdapter.java b/src/com/android/settings/panel/PanelSlicesAdapter.java index a2360d8367b..2223cbb61ab 100644 --- a/src/com/android/settings/panel/PanelSlicesAdapter.java +++ b/src/com/android/settings/panel/PanelSlicesAdapter.java @@ -48,7 +48,10 @@ import java.util.Map; /** * RecyclerView adapter for Slices in Settings Panels. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public class PanelSlicesAdapter extends RecyclerView.Adapter { @@ -112,7 +115,10 @@ public class PanelSlicesAdapter /** * ViewHolder for binding Slices to SliceViews. + * + * @deprecated this is no longer used after V and will be removed. */ + @Deprecated(forRemoval = true) public class SliceRowViewHolder extends RecyclerView.ViewHolder implements DividerItemDecoration.DividedViewHolder { diff --git a/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatch.java b/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatch.java index 6137d6c564e..49fd8619941 100644 --- a/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatch.java +++ b/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatch.java @@ -36,7 +36,10 @@ import java.util.concurrent.CountDownLatch; * {@link Uri}. Then check if all of the Slices have loaded with * {@link #isPanelReadyToLoad()}, which will return {@code true} the first time after all * Slices have loaded. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public class PanelSlicesLoaderCountdownLatch { private final Set mLoadedSlices; private final CountDownLatch mCountDownLatch; diff --git a/src/com/android/settings/panel/SettingsPanelActivity.java b/src/com/android/settings/panel/SettingsPanelActivity.java index 60b8f887936..d539c433c58 100644 --- a/src/com/android/settings/panel/SettingsPanelActivity.java +++ b/src/com/android/settings/panel/SettingsPanelActivity.java @@ -42,7 +42,10 @@ import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; /** * Dialog Activity to host Settings Slices. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public class SettingsPanelActivity extends FragmentActivity { private static final String TAG = "SettingsPanelActivity"; diff --git a/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java b/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java index 623816a01b4..0b35bd3fbc2 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java +++ b/src/com/android/settings/privatespace/PrivateSpaceAuthenticationActivity.java @@ -19,6 +19,7 @@ package com.android.settings.privatespace; import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD; import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS; +import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink; import android.app.ActivityOptions; import android.app.AlertDialog; @@ -36,11 +37,12 @@ import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.SetScreenLockDialogActivity; import com.android.settings.R; -import com.android.settings.SettingsActivity; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.transition.SettingsTransitionHelper; @@ -52,7 +54,7 @@ import com.google.android.setupdesign.util.ThemeHelper; * user to set a device lock if not set with an alert dialog. This can be launched using the intent * com.android.settings.action.OPEN_PRIVATE_SPACE_SETTINGS. */ -public class PrivateSpaceAuthenticationActivity extends SettingsActivity { +public class PrivateSpaceAuthenticationActivity extends FragmentActivity { private static final String TAG = "PrivateSpaceAuthCheck"; public static final String EXTRA_SHOW_PRIVATE_SPACE_UNLOCKED = "extra_show_private_space_unlocked"; @@ -76,31 +78,55 @@ public class PrivateSpaceAuthenticationActivity extends SettingsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (isFinishing()) { + if (!(Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures())) { + finish(); return; } - if (Flags.allowPrivateProfile() - && android.multiuser.Flags.enablePrivateSpaceFeatures()) { - ThemeHelper.trySetDynamicColor(this); - mPrivateSpaceMaintainer = - new Injector().injectPrivateSpaceMaintainer(getApplicationContext()); - if (getKeyguardManager().isDeviceSecure()) { - if (savedInstanceState == null) { - if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { - unlockAndLaunchPrivateSpaceSettings(this); - } else { - authenticatePrivateSpaceEntry(); - } + Intent intent = getIntent(); + String highlightMenuKey = getString(R.string.menu_key_security); + if (shouldShowMultiPaneDeepLink(intent) + && tryStartMultiPaneDeepLink(this, intent, highlightMenuKey)) { + finish(); + return; + } + + ThemeHelper.trySetDynamicColor(this); + mPrivateSpaceMaintainer = + new Injector().injectPrivateSpaceMaintainer(getApplicationContext()); + if (getKeyguardManager().isDeviceSecure()) { + if (savedInstanceState == null) { + if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { + unlockAndLaunchPrivateSpaceSettings(this); + } else { + authenticatePrivateSpaceEntry(); } - } else { - promptToSetDeviceLock(); } } else { - finish(); + promptToSetDeviceLock(); } } + private boolean shouldShowMultiPaneDeepLink(Intent intent) { + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { + return false; + } + + // If the activity is task root, starting trampoline is needed in order to show two-pane UI. + // If FLAG_ACTIVITY_NEW_TASK is set, the activity will become the start of a new task on + // this history stack, so starting trampoline is needed in order to notify the homepage that + // the highlight key is changed. + if (!isTaskRoot() && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + return false; + } + + // Only starts trampoline for deep links. Should return false for all the cases that + // Settings app starts SettingsActivity or SubSetting by itself. + // Other apps should send deep link intent which matches intent filter of the Activity. + return intent.getAction() != null; + } + /** Starts private space setup flow or the PS settings page on device lock authentication */ @VisibleForTesting public void onLockAuthentication(Context context) { diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt index 28b7a9e1cab..98d83402339 100644 --- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt +++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt @@ -36,11 +36,11 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.android.settings.R @@ -62,7 +62,6 @@ import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBool import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOf @@ -207,7 +206,7 @@ fun MobileDataSectionImpl( }.collectAsStateWithLifecycle(initialValue = null) val mobileDataStateChanged by remember(mobileDataSelectedId.intValue) { - TelephonyRepository(context).isDataEnabled(mobileDataSelectedId.intValue) + TelephonyRepository(context).isDataEnabledFlow(mobileDataSelectedId.intValue) }.collectAsStateWithLifecycle(initialValue = false) val coroutineScope = rememberCoroutineScope() diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java index 0060fa0c7b7..b1dfd1454f9 100644 --- a/src/com/android/settings/webview/WebViewAppPicker.java +++ b/src/com/android/settings/webview/WebViewAppPicker.java @@ -33,14 +33,12 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.applications.defaultapps.DefaultAppPickerFragment; -import com.android.settings.development.DeveloperOptionAwareMixin; import com.android.settingslib.applications.DefaultAppInfo; import java.util.ArrayList; import java.util.List; -public class WebViewAppPicker extends DefaultAppPickerFragment implements - DeveloperOptionAwareMixin { +public class WebViewAppPicker extends DefaultAppPickerFragment { private WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper; private WebViewUpdateServiceWrapper getWebViewUpdateServiceWrapper() { diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java index 8084a4811d6..4ba12056630 100644 --- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -34,6 +34,7 @@ import androidx.window.embedding.ActivityEmbeddingController; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.flags.Flags; import com.android.settings.homepage.SettingsHomepageActivity; /** @@ -46,9 +47,13 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L; private static final int RES_NORMAL_BACKGROUND = - R.drawable.homepage_selectable_item_background; + Flags.homepageRevamp() + ? R.drawable.homepage_selectable_item_background_v2 + : R.drawable.homepage_selectable_item_background; private static final int RES_HIGHLIGHTED_BACKGROUND = - R.drawable.homepage_highlighted_item_background; + Flags.homepageRevamp() + ? R.drawable.homepage_highlighted_item_background_v2 + : R.drawable.homepage_highlighted_item_background; private final int mTitleColorNormal; private final int mTitleColorHighlight; diff --git a/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java b/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java index 6242e23000c..2251180e5f6 100644 --- a/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java +++ b/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java @@ -22,6 +22,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; +import com.android.settings.flags.Flags; /** Helper for homepage preference to manage layout. */ public class HomepagePreferenceLayoutHelper { @@ -39,7 +40,10 @@ public class HomepagePreferenceLayoutHelper { } public HomepagePreferenceLayoutHelper(Preference preference) { - preference.setLayoutResource(R.layout.homepage_preference); + preference.setLayoutResource( + Flags.homepageRevamp() + ? R.layout.homepage_preference_v2 + : R.layout.homepage_preference); } /** Sets whether the icon should be visible */ diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index ee000686b2a..b2449dab39e 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -30,7 +30,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -44,7 +44,6 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.shadow.ShadowAudioManager; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowCachedBluetoothDeviceManager; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.flags.Flags; @@ -68,7 +67,7 @@ import java.util.Collection; public class ConnectedBluetoothDeviceUpdaterTest { private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; - private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"; + private static final String TEST_EXCLUSIVE_MANAGER = "com.test.manager"; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -355,13 +354,16 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_notAllowedExclusiveManagedDevice_addDevice() { + public void update_exclusivelyManagedDevice_packageNotInstalled_addDevice() + throws Exception { mAudioManager.setMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - FAKE_EXCLUSIVE_MANAGER_NAME.getBytes()); + TEST_EXCLUSIVE_MANAGER.getBytes()); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); @@ -370,64 +372,39 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_existingExclusivelyManagedDeviceWithPackageInstalled_removePreference() + public void update_exclusivelyManagedDevice_packageNotEnabled_addDevice() throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; mAudioManager.setMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater - .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); - - mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); - - verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); - verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_newExclusivelyManagedDeviceWithPackageInstalled_doNotAddPreference() - throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - when(mBluetoothDeviceUpdater - .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); - when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); - when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); - - mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); - - verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); - verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_exclusivelyManagedDeviceWithoutPackageInstalled_addDevice() - throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - when(mBluetoothDeviceUpdater - .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); - when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); - when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); - doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo( - exclusiveManagerName, 0); + TEST_EXCLUSIVE_MANAGER.getBytes()); + doReturn(appInfo).when(mPackageManager).getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) + public void update_exclusivelyManagedDevice_packageInstalledAndEnabled_removePreference() + throws Exception { + mAudioManager.setMode(AudioManager.MODE_NORMAL); + when(mBluetoothDeviceUpdater + .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); + when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER.getBytes()); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER, 0); + + mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); + + verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); + verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java index 796120d43f5..e2cf14810c4 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java @@ -29,7 +29,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.platform.test.annotations.RequiresFlagsDisabled; @@ -41,7 +41,6 @@ import android.util.Pair; import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -66,7 +65,7 @@ import java.util.List; public class SavedBluetoothDeviceUpdaterTest { private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; - private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"; + private static final String TEST_EXCLUSIVE_MANAGER = "com.test.manager"; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @@ -339,42 +338,18 @@ public class SavedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_notAllowedExclusivelyManagedDevice_addDevice() { - final Collection cachedDevices = new ArrayList<>(); - cachedDevices.add(mCachedBluetoothDevice); - - when(mBluetoothAdapter.isEnabled()).thenReturn(true); - when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); - when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); - when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - when(mBluetoothDevice.isConnected()).thenReturn(false); - when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - FAKE_EXCLUSIVE_MANAGER_NAME.getBytes()); - - mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); - - verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice, - BluetoothDevicePreference.SortType.TYPE_NO_SORT); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_existingExclusivelyManagedDeviceWithPackageInstalled_removePreference() + public void update_existingExclusivelyManagedDevice_packageEnabled_removePreference() throws Exception { final Collection cachedDevices = new ArrayList<>(); - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); - when(mBluetoothAdapter.isEnabled()).thenReturn(true); when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mBluetoothDevice.isConnected()).thenReturn(false); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); - - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); + TEST_EXCLUSIVE_MANAGER.getBytes()); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER, 0); mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); @@ -386,23 +361,19 @@ public class SavedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_newExclusivelyManagedDeviceWithPackageInstalled_doNotAddPreference() + public void update_newExclusivelyManagedDevice_packageEnabled_doNotAddPreference() throws Exception { final Collection cachedDevices = new ArrayList<>(); - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); cachedDevices.add(mCachedBluetoothDevice); - when(mBluetoothAdapter.isEnabled()).thenReturn(true); when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mBluetoothDevice.isConnected()).thenReturn(false); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); - - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); + TEST_EXCLUSIVE_MANAGER.getBytes()); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER, 0); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); @@ -413,24 +384,42 @@ public class SavedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - public void update_exclusivelyManagedDeviceWithoutPackageInstalled_addDevice() + public void update_exclusivelyManagedDevice_packageNotInstalled_addDevice() throws Exception { final Collection cachedDevices = new ArrayList<>(); - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); cachedDevices.add(mCachedBluetoothDevice); - when(mBluetoothAdapter.isEnabled()).thenReturn(true); when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); when(mBluetoothDevice.isConnected()).thenReturn(false); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); + TEST_EXCLUSIVE_MANAGER.getBytes()); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0); - doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo( - exclusiveManagerName, 0); + mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); + + verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice, + BluetoothDevicePreference.SortType.TYPE_NO_SORT); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) + public void update_exclusivelyManagedDevice_packageNotEnabled_addDevice() + throws Exception { + final Collection cachedDevices = new ArrayList<>(); + cachedDevices.add(mCachedBluetoothDevice); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; + when(mBluetoothAdapter.isEnabled()).thenReturn(true); + when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices); + when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mBluetoothDevice.isConnected()).thenReturn(false); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER.getBytes()); + doReturn(appInfo).when(mPackageManager).getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0); mBluetoothDeviceUpdater.update(mCachedBluetoothDevice); diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java b/tests/robotests/src/com/android/settings/display/ColorContrastFragmentTest.java similarity index 96% rename from tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java rename to tests/robotests/src/com/android/settings/display/ColorContrastFragmentTest.java index 3077637a8e4..47a7363b531 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/ColorContrastFragmentTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import static com.google.common.truth.Truth.assertThat; @@ -28,6 +28,7 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.accessibility.ShortcutsSettingsFragment; import com.android.settings.testutils.XmlTestUtils; import org.junit.Before; diff --git a/tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ContrastPreferenceControllerTest.java similarity index 55% rename from tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/display/ContrastPreferenceControllerTest.java index 07c3b54f946..1ddc81960b6 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/ContrastPreferenceControllerTest.java @@ -14,15 +14,22 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import static com.google.common.truth.Truth.assertThat; +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + import androidx.test.core.app.ApplicationProvider; +import com.android.settings.accessibility.Flags; import com.android.settings.core.BasePreferenceController; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -31,19 +38,30 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class ContrastPreferenceControllerTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String PREFERENCE_KEY = "preference_key"; + private Context mContext; private ContrastPreferenceController mController; @Before public void setUp() { - mController = new ContrastPreferenceController(ApplicationProvider.getApplicationContext(), - PREFERENCE_KEY); + mContext = ApplicationProvider.getApplicationContext(); + mController = new ContrastPreferenceController(mContext, PREFERENCE_KEY); } @Test - public void getAvailabilityStatus_shouldReturnUnavailable() { + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CONTRAST_CONTROL) + public void getAvailabilityStatus_flagsEnabled_shouldReturnAvailable() { assertThat(mController.getAvailabilityStatus()) - .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_COLOR_CONTRAST_CONTROL) + public void getAvailabilityStatus_flagsDisabled_shouldReturnUnsupported() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ContrastSelectorPreferenceControllerTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/display/ContrastSelectorPreferenceControllerTest.java index 83d9cb957b4..0d490a88347 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/ContrastSelectorPreferenceControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.accessibility; +package com.android.settings.display; import static com.google.common.truth.Truth.assertThat; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java index f62fdb8ce6f..44a16f19cca 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -50,6 +50,7 @@ import android.widget.TextView; import com.android.settings.SettingsActivity; import com.android.settings.testutils.FakeFeatureFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +59,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.Map; @@ -84,10 +86,13 @@ public final class BatteryChartPreferenceControllerTest { MockitoAnnotations.initMocks(this); Locale.setDefault(new Locale("en_US")); org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false); - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + final TimeZone timeZone = TimeZone.getTimeZone("UTC"); + TimeZone.setDefault(timeZone); DataProcessor.sTestSystemAppsPackageNames = Set.of(); mFeatureFactory = FakeFeatureFactory.setupForTest(); mContext = spy(RuntimeEnvironment.application); + BatteryLevelData.sTestCalendar = Calendar.getInstance(); + BatteryLevelData.sTestCalendar.setTimeZone(timeZone); doReturn(mContext).when(mContext).getApplicationContext(); doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); doReturn(true).when(mUserManager).isUserUnlocked(anyInt()); @@ -115,6 +120,11 @@ public final class BatteryChartPreferenceControllerTest { new BatteryEntry.NameAndIcon("fakeName", /* icon= */ null, /* iconId= */ 1)); } + @After + public void tearDown() { + BatteryLevelData.sTestCalendar = null; + } + @Test public void onDestroy_activityIsChanging_clearBatteryEntryCache() { doReturn(true).when(mSettingsActivity).isChangingConfigurations(); @@ -141,7 +151,8 @@ public final class BatteryChartPreferenceControllerTest { reset(mHourlyChartView); setupHourlyChartViewAnimationMock(); - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE); // Ignore fast refresh ui from the data processor callback. @@ -178,7 +189,8 @@ public final class BatteryChartPreferenceControllerTest { BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS, mBatteryChartPreferenceController.mDailyChartLabelTextGenerator); - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE); verify(mViewPropertyAnimator, atLeastOnce()).alpha(0f); @@ -283,7 +295,8 @@ public final class BatteryChartPreferenceControllerTest { public void onBatteryLevelDataUpdate_oneDay_showHourlyChartOnly() { doReturn(View.GONE).when(mHourlyChartView).getVisibility(); - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); verify(mChartSummaryTextView).setVisibility(View.VISIBLE); verify(mDailyChartView).setVisibility(View.GONE); @@ -295,7 +308,8 @@ public final class BatteryChartPreferenceControllerTest { doReturn(View.GONE).when(mHourlyChartView).getVisibility(); mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL; - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); verify(mChartSummaryTextView).setVisibility(View.VISIBLE); verify(mDailyChartView).setVisibility(View.VISIBLE); @@ -307,7 +321,8 @@ public final class BatteryChartPreferenceControllerTest { doReturn(View.GONE).when(mHourlyChartView).getVisibility(); mBatteryChartPreferenceController.mDailyChartIndex = 0; - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); verify(mChartSummaryTextView).setVisibility(View.VISIBLE); verify(mDailyChartView).setVisibility(View.VISIBLE); @@ -379,7 +394,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectAllDaysAllHours_returnNull() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL; mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; @@ -390,7 +406,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; @@ -401,7 +418,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectADayAllHours_onlyDayText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 1; mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; @@ -412,7 +430,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 2; @@ -426,7 +445,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_SelectADayAnHour_dayAndHourText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 1; mBatteryChartPreferenceController.mHourlyChartIndex = 8; @@ -439,8 +459,9 @@ public final class BatteryChartPreferenceControllerTest { } @Test - public void selectedSlotText_selectFirstSlot_withMinuteText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + public void selectedSlotText_selectFirstSlotAfterFullCharged_withMinuteText() { + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 0; @@ -452,9 +473,29 @@ public final class BatteryChartPreferenceControllerTest { .isEqualTo("Battery level percentage from 100% to 99%"); } + @Test + public void selectedSlotText_selectFirstSlotAfterTimeUpdated_withMinuteText() { + BatteryLevelData batteryLevelData = + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 10); + assertThat(batteryLevelData.getHourlyBatteryLevelsPerDay().get(0).isStartTimestamp()) + .isTrue(); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 10)); + mBatteryChartPreferenceController.mDailyChartIndex = 0; + mBatteryChartPreferenceController.mHourlyChartIndex = 0; + + assertThat(mBatteryChartPreferenceController.getSlotInformation(false)) + .isEqualTo("7:01 AM - 8 AM"); + assertThat(mBatteryChartPreferenceController.getSlotInformation(true)) + .isEqualTo("7:01 AM to 8 AM"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 90% to 89%"); + } + @Test public void selectedSlotText_selectLastSlot_withNowText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 6, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 3; @@ -468,7 +509,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void selectedSlotText_selectOnlySlot_withMinuteAndNowText() { - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(1)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 1, /* levelOffset= */ 0)); mBatteryChartPreferenceController.mDailyChartIndex = 0; mBatteryChartPreferenceController.mHourlyChartIndex = 0; @@ -493,7 +535,8 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = -1; mBatteryChartPreferenceController.onCreate(bundle); - mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(25)); + mBatteryChartPreferenceController.onBatteryLevelDataUpdate( + createBatteryLevelData(/* numOfHours= */ 25, /* levelOffset= */ 0)); assertThat(mBatteryChartPreferenceController.mDailyChartIndex) .isEqualTo(expectedDailyIndex); @@ -503,7 +546,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void getTotalHours_getExpectedResult() { - BatteryLevelData batteryLevelData = createBatteryLevelData(60); + BatteryLevelData batteryLevelData = + createBatteryLevelData(/* numOfHours= */ 60, /* levelOffset= */ 0); final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData); @@ -516,10 +560,10 @@ public final class BatteryChartPreferenceControllerTest { return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS; } - private static BatteryLevelData createBatteryLevelData(int numOfHours) { + private static BatteryLevelData createBatteryLevelData(int numOfHours, int levelOffset) { Map batteryLevelMap = new ArrayMap<>(); for (int index = 0; index < numOfHours; index += 2) { - final Integer level = 100 - index; + final Integer level = 100 - index - levelOffset; Long timestamp = generateTimestamp(index); if (index == 0) { timestamp += DateUtils.MINUTE_IN_MILLIS; @@ -529,6 +573,8 @@ public final class BatteryChartPreferenceControllerTest { } long current = generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2; batteryLevelMap.put(current, 66); + + BatteryLevelData.sTestCalendar.setTimeInMillis(current); DataProcessor.sTestCurrentTimeMillis = current; return new BatteryLevelData(batteryLevelMap); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java index df330a36ac5..545f7733f41 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java @@ -35,7 +35,6 @@ import com.android.settings.testutils.BatteryTestUtils; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -64,9 +63,8 @@ public final class BootBroadcastReceiverTest { // Inserts fake data into database for testing. final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext); - BatteryTestUtils.insertDataToBatteryStateTable( - mContext, Clock.systemUTC().millis(), "com.android.systemui"); mDao = database.batteryStateDao(); + mDao.clearAll(); clearSharedPreferences(); } @@ -129,10 +127,13 @@ public final class BootBroadcastReceiverTest { assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); } - @Ignore("b/314921894") @Test - public void onReceive_withTimeChangedIntent_clearsAllDataAndRefreshesJob() + public void onReceive_withTimeChangedIntentSetEarlierTime_refreshesJob() throws InterruptedException { + BatteryTestUtils.insertDataToBatteryStateTable( + mContext, Clock.systemUTC().millis() + 60000, "com.android.systemui"); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED)); TimeUnit.MILLISECONDS.sleep(100); @@ -140,6 +141,52 @@ public final class BootBroadcastReceiverTest { assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull(); } + @Test + public void onReceive_withTimeChangedIntentSetLaterTime_clearNoDataAndRefreshesJob() + throws InterruptedException { + BatteryTestUtils.insertDataToBatteryStateTable( + mContext, Clock.systemUTC().millis() - 60000, "com.android.systemui"); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED)); + + TimeUnit.MILLISECONDS.sleep(100); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull(); + } + + @Test + public void onReceive_withTimeFormatChangedIntent_skipRefreshJob() throws InterruptedException { + BatteryTestUtils.insertDataToBatteryStateTable( + mContext, Clock.systemUTC().millis() + 60000, "com.android.systemui"); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + + mReceiver.onReceive( + mContext, + new Intent(Intent.EXTRA_INTENT) + .putExtra( + Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, + Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR)); + + TimeUnit.MILLISECONDS.sleep(100); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull(); + } + + @Test + public void onReceive_withTimeZoneChangedIntent_clearAllDataAndRefreshesJob() + throws InterruptedException { + BatteryTestUtils.insertDataToBatteryStateTable( + mContext, Clock.systemUTC().millis(), "com.android.systemui"); + assertThat(mDao.getAllAfter(0).size()).isEqualTo(1); + + mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIMEZONE_CHANGED)); + + TimeUnit.MILLISECONDS.sleep(100); + assertThat(mDao.getAllAfter(0)).isEmpty(); + assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull(); + } + @Test public void invokeJobRecheck_broadcastsIntent() { BootBroadcastReceiver.invokeJobRecheck(mContext); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index 7faca0d0960..60428014048 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -170,7 +170,8 @@ public final class DataProcessManagerTest { final Map batteryLevelMap1 = Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1)); + new BatteryLevelData.PeriodBatteryLevelData( + batteryLevelMap1, timestamps1, /* isStartTimestamp= */ false)); // Adds the day 2 data. hourlyBatteryLevelsPerDay.add(null); // Adds the day 3 data. @@ -178,7 +179,8 @@ public final class DataProcessManagerTest { final Map batteryLevelMap2 = Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2)); + new BatteryLevelData.PeriodBatteryLevelData( + batteryLevelMap2, timestamps2, /* isStartTimestamp= */ false)); // Fake current usage data. final UsageEvents.Event event1 = getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /* timestamp= */ 1, packageName); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index 28973430ce9..ae4c56d035a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -209,7 +209,8 @@ public final class DataProcessorTest { final Map batteryLevelMap1 = Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1)); + new BatteryLevelData.PeriodBatteryLevelData( + batteryLevelMap1, timestamps1, /* isStartTimestamp= */ false)); // Adds the day 2 data. hourlyBatteryLevelsPerDay.add(null); // Adds the day 3 data. @@ -217,7 +218,8 @@ public final class DataProcessorTest { final Map batteryLevelMap2 = Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2)); + new BatteryLevelData.PeriodBatteryLevelData( + batteryLevelMap2, timestamps2, /* isStartTimestamp= */ false)); final List appUsageEventList = new ArrayList<>(); // Adds some events before the start timestamp. appUsageEventList.add( @@ -365,7 +367,8 @@ public final class DataProcessorTest { final List hourlyBatteryLevelsPerDay = new ArrayList<>(); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>())); + new BatteryLevelData.PeriodBatteryLevelData( + new ArrayMap<>(), new ArrayList<>(), /* isStartTimestamp= */ false)); assertThat( DataProcessor.generateAppUsagePeriodMap( mContext, @@ -858,7 +861,8 @@ public final class DataProcessorTest { new ArrayList<>(); hourlyBatteryLevelsPerDay.add( - new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>())); + new BatteryLevelData.PeriodBatteryLevelData( + new ArrayMap<>(), new ArrayList<>(), /* isStartTimestamp= */ false)); assertThat( DataProcessor.getBatteryDiffDataMap( diff --git a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java index 44f44aa8589..36c48e3cdd7 100644 --- a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java +++ b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java @@ -16,8 +16,6 @@ package com.android.settings.homepage; -import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; @@ -59,11 +57,6 @@ public class TopLevelSettingsTest { mSettings.onAttach(mContext); } - @Test - public void shouldForceRoundedIcon_true() { - assertThat(mSettings.shouldForceRoundedIcon()).isTrue(); - } - @Test public void onCreatePreferences_shouldTintPreferenceIcon() { final Preference preference = new Preference(mContext); diff --git a/tests/robotests/src/com/android/settings/panel/FakePanelContent.java b/tests/robotests/src/com/android/settings/panel/FakePanelContent.java index 06beb3ecf51..17787cd01ae 100644 --- a/tests/robotests/src/com/android/settings/panel/FakePanelContent.java +++ b/tests/robotests/src/com/android/settings/panel/FakePanelContent.java @@ -29,7 +29,10 @@ import java.util.List; /** * Fake PanelContent for testing. + * + * @deprecated this is no longer used after V and will be removed. */ +@Deprecated(forRemoval = true) public class FakePanelContent implements PanelContent { public static final String FAKE_ACTION = "fake_action"; diff --git a/tests/robotests/src/com/android/settings/panel/FakeSettingsPanelActivity.java b/tests/robotests/src/com/android/settings/panel/FakeSettingsPanelActivity.java index ba763ce3a06..fe19f287794 100644 --- a/tests/robotests/src/com/android/settings/panel/FakeSettingsPanelActivity.java +++ b/tests/robotests/src/com/android/settings/panel/FakeSettingsPanelActivity.java @@ -19,6 +19,7 @@ package com.android.settings.panel; import android.content.ComponentName; import android.content.Intent; +@Deprecated(forRemoval = true) public class FakeSettingsPanelActivity extends SettingsPanelActivity { @Override public ComponentName getCallingActivity() { diff --git a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java index 42f3977a227..e77eeab569b 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelFragmentTest.java @@ -54,6 +54,7 @@ import org.robolectric.annotation.Config; import java.util.Objects; +@Deprecated(forRemoval = true) @Ignore("b/313576125") @RunWith(RobolectricTestRunner.class) @Config(shadows = { diff --git a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java index 87a798a2197..e778cb8cec8 100644 --- a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java +++ b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java @@ -67,6 +67,7 @@ import org.robolectric.annotation.Implements; import java.util.LinkedHashMap; import java.util.Map; +@Deprecated(forRemoval = true) @RunWith(RobolectricTestRunner.class) @Config(shadows = PanelSlicesAdapterTest.ShadowLayoutInflater.class) public class PanelSlicesAdapterTest { diff --git a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java index e550284b028..4f03abb664b 100644 --- a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java +++ b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java @@ -59,6 +59,7 @@ import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; +@Deprecated(forRemoval = true) @RunWith(RobolectricTestRunner.class) @Config(shadows = { com.android.settings.testutils.shadow.ShadowFragment.class, diff --git a/tests/spa_unit/src/com/android/settings/datausage/BillingCyclePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/datausage/BillingCyclePreferenceTest.kt index 4bf385169d3..1db0d48f211 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/BillingCyclePreferenceTest.kt +++ b/tests/spa_unit/src/com/android/settings/datausage/BillingCyclePreferenceTest.kt @@ -27,6 +27,8 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R import com.android.settings.datausage.lib.BillingCycleRepository +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -39,7 +41,9 @@ class BillingCyclePreferenceTest { @get:Rule val composeTestRule = createComposeRule() - private val mockBillingCycleRepository = mock() + private val mockBillingCycleRepository = mock { + on { isModifiableFlow(SUB_ID) } doReturn emptyFlow() + } private val context: Context = ApplicationProvider.getApplicationContext() @@ -56,7 +60,7 @@ class BillingCyclePreferenceTest { @Test fun setTemplate_modifiable_enabled() { mockBillingCycleRepository.stub { - on { isModifiable(SUB_ID) } doReturn true + on { isModifiableFlow(SUB_ID) } doReturn flowOf(true) } setTemplate() @@ -67,7 +71,7 @@ class BillingCyclePreferenceTest { @Test fun setTemplate_notModifiable_notEnabled() { mockBillingCycleRepository.stub { - on { isModifiable(SUB_ID) } doReturn false + on { isModifiableFlow(SUB_ID) } doReturn flowOf(false) } setTemplate() diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/BillingCycleRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/BillingCycleRepositoryTest.kt index deaaf2dd1f0..22e5dfe14b4 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/lib/BillingCycleRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/datausage/lib/BillingCycleRepositoryTest.kt @@ -22,8 +22,10 @@ import android.os.UserManager import android.telephony.TelephonyManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn @@ -55,43 +57,43 @@ class BillingCycleRepositoryTest { private val repository = BillingCycleRepository(context, mockNetworkManagementService) @Test - fun isModifiable_bandwidthControlDisabled_returnFalse() { + fun isModifiable_bandwidthControlDisabled_returnFalse() = runBlocking { whenever(mockNetworkManagementService.isBandwidthControlEnabled).thenReturn(false) - val modifiable = repository.isModifiable(SUB_ID) + val modifiable = repository.isModifiableFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(modifiable).isFalse() } @Test - fun isModifiable_notAdminUser_returnFalse() { + fun isModifiable_notAdminUser_returnFalse() = runBlocking { whenever(mockUserManager.isAdminUser).thenReturn(false) - val modifiable = repository.isModifiable(SUB_ID) + val modifiable = repository.isModifiableFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(modifiable).isFalse() } @Test - fun isModifiable_dataDisabled_returnFalse() { + fun isModifiable_dataDisabled_returnFalse() = runBlocking { whenever( mockTelephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) ).thenReturn(false) - val modifiable = repository.isModifiable(SUB_ID) + val modifiable = repository.isModifiableFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(modifiable).isFalse() } @Test - fun isModifiable_meetAllRequirements_returnTrue() { + fun isModifiable_meetAllRequirements_returnTrue() = runBlocking { whenever(mockNetworkManagementService.isBandwidthControlEnabled).thenReturn(true) whenever(mockUserManager.isAdminUser).thenReturn(true) whenever( mockTelephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) ).thenReturn(true) - val modifiable = repository.isModifiable(SUB_ID) + val modifiable = repository.isModifiableFlow(SUB_ID).firstWithTimeoutOrNull() assertThat(modifiable).isTrue() } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.kt new file mode 100644 index 00000000000..0ddaa520baf --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 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.network.telephony + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.core.BasePreferenceController.AVAILABLE +import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class DisableSimFooterPreferenceControllerTest { + + private val subscriptionInfo = mock { + on { subscriptionId } doReturn SUB_ID + } + + private var context: Context = ApplicationProvider.getApplicationContext() + + private val mockSubscriptionRepository = mock { + on { getSelectableSubscriptionInfoList() } doReturn listOf(subscriptionInfo) + } + + private var controller = DisableSimFooterPreferenceController( + context = context, + preferenceKey = PREFERENCE_KEY, + subscriptionRepository = mockSubscriptionRepository, + ).apply { init(SUB_ID) } + + @Test + fun getAvailabilityStatus_invalidId_notAvailable() { + val availabilityStatus = controller.getAvailabilityStatus(INVALID_SUBSCRIPTION_ID) + + assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + @Test + fun getAvailabilityStatus_eSim_notAvailable() { + subscriptionInfo.stub { + on { isEmbedded } doReturn true + } + + val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) + + assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + @Test + fun getAvailabilityStatus_pSimAndCannotDisable_available() { + mockSubscriptionRepository.stub { + on { canDisablePhysicalSubscription() } doReturn false + } + subscriptionInfo.stub { + on { isEmbedded } doReturn false + } + + val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) + + assertThat(availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun getAvailabilityStatus_pSimAndCanDisable_notAvailable() { + mockSubscriptionRepository.stub { + on { canDisablePhysicalSubscription() } doReturn true + } + subscriptionInfo.stub { + on { isEmbedded } doReturn false + } + + val availabilityStatus = controller.getAvailabilityStatus(SUB_ID) + + assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + private companion object { + const val PREFERENCE_KEY = "preference_key" + const val SUB_ID = 111 + } +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt index 60589358e3a..65e8c47023d 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyRepositoryTest.kt @@ -93,7 +93,7 @@ class TelephonyRepositoryTest { @Test fun isDataEnabled_invalidSub_returnFalse() = runBlocking { - val state = repository.isDataEnabled( + val state = repository.isDataEnabledFlow( subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID, ) @@ -108,9 +108,7 @@ class TelephonyRepositoryTest { } doReturn true } - val state = repository.isDataEnabled( - subId = SUB_ID, - ) + val state = repository.isDataEnabledFlow(subId = SUB_ID) assertThat(state.firstWithTimeoutOrNull()).isTrue() } diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt index 070c779b5ea..c0b918fcf5d 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/scan/NetworkScanRepositoryTest.kt @@ -32,9 +32,6 @@ import android.telephony.TelephonyManager.NETWORK_CLASS_BITMASK_5G import android.telephony.TelephonyScanManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanCellInfos -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanComplete -import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanError import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.toListWithTimeout import com.google.common.truth.Truth.assertThat @@ -88,7 +85,12 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) - assertThat(listDeferred.await()).containsExactly(NetworkScanCellInfos(cellInfos)) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = cellInfos, + ) + ) } @Test @@ -100,7 +102,12 @@ class NetworkScanRepositoryTest { callback?.onComplete() - assertThat(listDeferred.await()).containsExactly(NetworkScanComplete) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.COMPLETE, + cellInfos = emptyList(), + ) + ) } @Test @@ -112,7 +119,12 @@ class NetworkScanRepositoryTest { callback?.onError(1) - assertThat(listDeferred.await()).containsExactly(NetworkScanError(1)) + assertThat(listDeferred.await()).containsExactly( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ERROR, + cellInfos = emptyList(), + ) + ) } @Test @@ -133,12 +145,13 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) assertThat(listDeferred.await()).containsExactly( - NetworkScanCellInfos( - listOf( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = listOf( createCellInfoLte("123", false), createCellInfoLte("124", true), createCellInfoGsm("123", false), - ) + ), ) ) } @@ -162,8 +175,9 @@ class NetworkScanRepositoryTest { callback?.onResults(cellInfos) assertThat(listDeferred.await()).containsExactly( - NetworkScanCellInfos( - listOf( + NetworkScanRepository.NetworkScanResult( + state = NetworkScanRepository.NetworkScanState.ACTIVE, + cellInfos = listOf( createCellInfoLte("123", false), createCellInfoLte("123", true), createCellInfoLte("124", false), diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt deleted file mode 100644 index 976096c7cc4..00000000000 --- a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2024 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.connecteddevice.threadnetwork - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.net.thread.ThreadNetworkController.STATE_DISABLED -import android.net.thread.ThreadNetworkController.STATE_DISABLING -import android.net.thread.ThreadNetworkController.STATE_ENABLED -import android.net.thread.ThreadNetworkController.StateCallback -import android.net.thread.ThreadNetworkException -import android.os.OutcomeReceiver -import android.platform.test.flag.junit.SetFlagsRule -import android.provider.Settings -import androidx.core.content.ContextCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.preference.PreferenceManager -import androidx.preference.SwitchPreference -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.R -import com.android.settings.core.BasePreferenceController.AVAILABLE -import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE -import com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING -import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE -import com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController.BaseThreadNetworkController -import com.android.settings.flags.Flags -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.mock -import org.mockito.Mockito.spy -import org.mockito.Mockito.verify -import java.util.concurrent.Executor - -/** Unit tests for [ThreadNetworkPreferenceController]. */ -@RunWith(AndroidJUnit4::class) -class ThreadNetworkPreferenceControllerTest { - @get:Rule - val mSetFlagsRule = SetFlagsRule() - private lateinit var context: Context - private lateinit var executor: Executor - private lateinit var controller: ThreadNetworkPreferenceController - private lateinit var fakeThreadNetworkController: FakeThreadNetworkController - private lateinit var preference: SwitchPreference - private val broadcastReceiverArgumentCaptor = ArgumentCaptor.forClass( - BroadcastReceiver::class.java - ) - - @Before - fun setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) - context = spy(ApplicationProvider.getApplicationContext()) - executor = ContextCompat.getMainExecutor(context) - fakeThreadNetworkController = FakeThreadNetworkController(executor) - controller = newControllerWithThreadFeatureSupported(true) - val preferenceManager = PreferenceManager(context) - val preferenceScreen = preferenceManager.createPreferenceScreen(context) - preference = SwitchPreference(context) - preference.key = "thread_network_settings" - preferenceScreen.addPreference(preference) - controller.displayPreference(preferenceScreen) - - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) - } - - private fun newControllerWithThreadFeatureSupported( - present: Boolean - ): ThreadNetworkPreferenceController { - return ThreadNetworkPreferenceController( - context, - "thread_network_settings" /* key */, - executor, - if (present) fakeThreadNetworkController else null - ) - } - - @Test - fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() { - mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) - assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE) - } - - @Test - fun availabilityStatus_airPlaneModeOn_returnsDisabledDependentSetting() { - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1) - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(controller.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING) - } - - @Test - fun availabilityStatus_airPlaneModeOff_returnsAvailable() { - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE) - } - - @Test - fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() { - controller = newControllerWithThreadFeatureSupported(false) - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(fakeThreadNetworkController.registeredStateCallback).isNull() - assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE) - } - - @Test - fun isChecked_threadSetEnabled_returnsTrue() { - fakeThreadNetworkController.setEnabled(true, executor) { } - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(controller.isChecked).isTrue() - } - - @Test - fun isChecked_threadSetDisabled_returnsFalse() { - fakeThreadNetworkController.setEnabled(false, executor) { } - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(controller.isChecked).isFalse() - } - - @Test - fun setChecked_setChecked_threadIsEnabled() { - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - controller.setChecked(true) - - assertThat(fakeThreadNetworkController.isEnabled).isTrue() - } - - @Test - fun setChecked_setUnchecked_threadIsDisabled() { - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - controller.setChecked(false) - - assertThat(fakeThreadNetworkController.isEnabled).isFalse() - } - - @Test - fun updatePreference_airPlaneModeOff_preferenceEnabled() { - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(preference.isEnabled).isTrue() - assertThat(preference.summary).isEqualTo( - context.resources.getString(R.string.thread_network_settings_summary) - ) - } - - @Test - fun updatePreference_airPlaneModeOn_preferenceDisabled() { - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1) - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - - assertThat(preference.isEnabled).isFalse() - assertThat(preference.summary).isEqualTo( - context.resources.getString(R.string.thread_network_settings_summary_airplane_mode) - ) - } - - @Test - fun updatePreference_airPlaneModeTurnedOn_preferenceDisabled() { - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) - startControllerAndCaptureCallbacks() - - Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1) - broadcastReceiverArgumentCaptor.value.onReceive(context, Intent()) - - assertThat(preference.isEnabled).isFalse() - assertThat(preference.summary).isEqualTo( - context.resources.getString(R.string.thread_network_settings_summary_airplane_mode) - ) - } - - private fun startControllerAndCaptureCallbacks() { - controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) - verify(context)!!.registerReceiver(broadcastReceiverArgumentCaptor.capture(), any()) - } - - private class FakeThreadNetworkController(private val executor: Executor) : - BaseThreadNetworkController { - var isEnabled = true - private set - var registeredStateCallback: StateCallback? = null - private set - - override fun setEnabled( - enabled: Boolean, - executor: Executor, - receiver: OutcomeReceiver - ) { - isEnabled = enabled - if (registeredStateCallback != null) { - if (!isEnabled) { - executor.execute { - registeredStateCallback!!.onThreadEnableStateChanged( - STATE_DISABLING - ) - } - executor.execute { - registeredStateCallback!!.onThreadEnableStateChanged( - STATE_DISABLED - ) - } - } else { - executor.execute { - registeredStateCallback!!.onThreadEnableStateChanged( - STATE_ENABLED - ) - } - } - } - executor.execute { receiver.onResult(null) } - } - - override fun registerStateCallback( - executor: Executor, - callback: StateCallback - ) { - require(callback !== registeredStateCallback) { "callback is already registered" } - registeredStateCallback = callback - val enabledState = - if (isEnabled) STATE_ENABLED else STATE_DISABLED - executor.execute { registeredStateCallback!!.onThreadEnableStateChanged(enabledState) } - } - - override fun unregisterStateCallback(callback: StateCallback) { - requireNotNull(registeredStateCallback) { "callback is already unregistered" } - registeredStateCallback = null - } - } -} diff --git a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/FakeThreadNetworkController.kt b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/FakeThreadNetworkController.kt new file mode 100644 index 00000000000..e30226e25fb --- /dev/null +++ b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/FakeThreadNetworkController.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.net.thread.ThreadNetworkController +import android.net.thread.ThreadNetworkException +import android.os.OutcomeReceiver +import java.util.concurrent.Executor + +/** A fake implementation of [BaseThreadNetworkController] for unit tests. */ +class FakeThreadNetworkController : BaseThreadNetworkController { + var isEnabled = false + private set + var registeredStateCallback: ThreadNetworkController.StateCallback? = null + private set + + override fun setEnabled( + enabled: Boolean, + executor: Executor, + receiver: OutcomeReceiver + ) { + isEnabled = enabled + if (registeredStateCallback != null) { + if (!isEnabled) { + executor.execute { + registeredStateCallback!!.onThreadEnableStateChanged( + ThreadNetworkController.STATE_DISABLING + ) + } + executor.execute { + registeredStateCallback!!.onThreadEnableStateChanged( + ThreadNetworkController.STATE_DISABLED + ) + } + } else { + executor.execute { + registeredStateCallback!!.onThreadEnableStateChanged( + ThreadNetworkController.STATE_ENABLED + ) + } + } + } + executor.execute { receiver.onResult(null) } + } + + override fun registerStateCallback( + executor: Executor, + callback: ThreadNetworkController.StateCallback + ) { + require(callback !== registeredStateCallback) { "callback is already registered" } + registeredStateCallback = callback + val enabledState = + if (isEnabled) ThreadNetworkController.STATE_ENABLED else ThreadNetworkController.STATE_DISABLED + executor.execute { registeredStateCallback!!.onThreadEnableStateChanged(enabledState) } + } + + override fun unregisterStateCallback(callback: ThreadNetworkController.StateCallback) { + requireNotNull(registeredStateCallback) { "callback is already unregistered" } + registeredStateCallback = null + } +} diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/OWNERS similarity index 100% rename from tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS rename to tests/unit/src/com/android/settings/connecteddevice/threadnetwork/OWNERS diff --git a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt new file mode 100644 index 00000000000..0d57dafc144 --- /dev/null +++ b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.platform.test.flag.junit.SetFlagsRule +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.core.BasePreferenceController.AVAILABLE +import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE +import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE +import com.android.settings.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import java.util.concurrent.Executor + +/** Unit tests for [ThreadNetworkFragmentController]. */ +@RunWith(AndroidJUnit4::class) +class ThreadNetworkFragmentControllerTest { + @get:Rule + val mSetFlagsRule = SetFlagsRule() + private lateinit var context: Context + private lateinit var executor: Executor + private lateinit var controller: ThreadNetworkFragmentController + private lateinit var fakeThreadNetworkController: FakeThreadNetworkController + + @Before + fun setUp() { + mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) + context = spy(ApplicationProvider.getApplicationContext()) + executor = ContextCompat.getMainExecutor(context) + fakeThreadNetworkController = FakeThreadNetworkController() + controller = newControllerWithThreadFeatureSupported(true) + } + + private fun newControllerWithThreadFeatureSupported( + present: Boolean + ): ThreadNetworkFragmentController { + return ThreadNetworkFragmentController( + context, + "thread_network_settings" /* key */, + executor, + if (present) fakeThreadNetworkController else null + ) + } + + @Test + fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() { + mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) + startController(controller) + + assertThat(controller.availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + @Test + fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() { + controller = newControllerWithThreadFeatureSupported(false) + startController(controller) + + assertThat(fakeThreadNetworkController.registeredStateCallback).isNull() + assertThat(controller.availabilityStatus).isEqualTo(UNSUPPORTED_ON_DEVICE) + } + + @Test + fun availabilityStatus_threadFeatureSupported_returnsAvailable() { + controller = newControllerWithThreadFeatureSupported(true) + startController(controller) + + assertThat(controller.availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun getSummary_ThreadIsEnabled_returnsOn() { + startController(controller) + fakeThreadNetworkController.setEnabled(true, executor) {} + + assertThat(controller.summary).isEqualTo("On") + } + + @Test + fun getSummary_ThreadIsDisabled_returnsOff() { + startController(controller) + fakeThreadNetworkController.setEnabled(false, executor) {} + + assertThat(controller.summary).isEqualTo("Off") + } + + private fun startController(controller: ThreadNetworkFragmentController) { + controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) + } +} diff --git a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt new file mode 100644 index 00000000000..04ebc9252ef --- /dev/null +++ b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 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.connecteddevice.threadnetwork + +import android.content.Context +import android.platform.test.flag.junit.SetFlagsRule +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.preference.PreferenceManager +import androidx.preference.SwitchPreference +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE +import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE +import com.android.settings.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import java.util.concurrent.Executor + +/** Unit tests for [ThreadNetworkToggleController]. */ +@RunWith(AndroidJUnit4::class) +class ThreadNetworkToggleControllerTest { + @get:Rule + val mSetFlagsRule = SetFlagsRule() + private lateinit var context: Context + private lateinit var executor: Executor + private lateinit var controller: ThreadNetworkToggleController + private lateinit var fakeThreadNetworkController: FakeThreadNetworkController + private lateinit var preference: SwitchPreference + + @Before + fun setUp() { + mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) + context = spy(ApplicationProvider.getApplicationContext()) + executor = Executor { runnable: Runnable -> runnable.run() } + fakeThreadNetworkController = FakeThreadNetworkController() + controller = newControllerWithThreadFeatureSupported(true) + val preferenceManager = PreferenceManager(context) + val preferenceScreen = preferenceManager.createPreferenceScreen(context) + preference = SwitchPreference(context) + preference.key = "toggle_thread_network" + preferenceScreen.addPreference(preference) + controller.displayPreference(preferenceScreen) + } + + private fun newControllerWithThreadFeatureSupported( + present: Boolean + ): ThreadNetworkToggleController { + return ThreadNetworkToggleController( + context, + "toggle_thread_network" /* key */, + executor, + if (present) fakeThreadNetworkController else null + ) + } + + @Test + fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() { + mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) + assertThat(controller.availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + @Test + fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() { + controller = newControllerWithThreadFeatureSupported(false) + startController(controller) + + assertThat(fakeThreadNetworkController.registeredStateCallback).isNull() + assertThat(controller.availabilityStatus).isEqualTo(UNSUPPORTED_ON_DEVICE) + } + + @Test + fun isChecked_threadSetEnabled_returnsTrue() { + fakeThreadNetworkController.setEnabled(true, executor) { } + startController(controller) + + assertThat(controller.isChecked).isTrue() + } + + @Test + fun isChecked_threadSetDisabled_returnsFalse() { + fakeThreadNetworkController.setEnabled(false, executor) { } + startController(controller) + + assertThat(controller.isChecked).isFalse() + } + + @Test + fun setChecked_setChecked_threadIsEnabled() { + startController(controller) + + controller.setChecked(true) + + assertThat(fakeThreadNetworkController.isEnabled).isTrue() + } + + @Test + fun setChecked_setUnchecked_threadIsDisabled() { + startController(controller) + + controller.setChecked(false) + + assertThat(fakeThreadNetworkController.isEnabled).isFalse() + } + + private fun startController(controller: ThreadNetworkToggleController) { + controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START) + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java deleted file mode 100644 index bbbee216994..00000000000 --- a/tests/unit/src/com/android/settings/network/telephony/DisableSimFooterPreferenceControllerTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.settings.network.telephony; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.network.SubscriptionUtil; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -public class DisableSimFooterPreferenceControllerTest { - private static final String PREF_KEY = "pref_key"; - private static final int SUB_ID = 111; - - @Mock - private SubscriptionInfo mInfo; - - private Context mContext; - @Mock - private SubscriptionManager mSubscriptionManager; - private DisableSimFooterPreferenceController mController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager); - when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager); - - when(mInfo.getSubscriptionId()).thenReturn(SUB_ID); - SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(mInfo)); - mController = new DisableSimFooterPreferenceController(mContext, PREF_KEY); - } - - @Test - public void isAvailable_noInit_notAvailable() { - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_eSIM_notAvailable() { - when(mInfo.isEmbedded()).thenReturn(true); - mController.init(SUB_ID); - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_pSIM_available_cannot_disable_pSIM() { - when(mInfo.isEmbedded()).thenReturn(false); - mController.init(SUB_ID); - doReturn(false).when(mSubscriptionManager).canDisablePhysicalSubscription(); - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void isAvailable_pSIM_available_can_disable_pSIM() { - when(mInfo.isEmbedded()).thenReturn(false); - mController.init(SUB_ID); - doReturn(true).when(mSubscriptionManager).canDisablePhysicalSubscription(); - assertThat(mController.isAvailable()).isFalse(); - } -} diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java index a4657cee8a0..d71af84dcf8 100644 --- a/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/NetworkSelectSettingsTest.java @@ -44,8 +44,12 @@ import androidx.preference.PreferenceManager; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.network.telephony.scan.NetworkScanRepository; +import com.android.settings.network.telephony.scan.NetworkScanRepository.NetworkScanResult; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -163,8 +167,7 @@ public class NetworkSelectSettingsTest { } @Override - protected NetworkOperatorPreference - createNetworkOperatorPreference(CellInfo cellInfo) { + protected NetworkOperatorPreference createNetworkOperatorPreference(CellInfo cellInfo) { NetworkOperatorPreference pref = super.createNetworkOperatorPreference(cellInfo); if (cellInfo == mTestEnv.mCellInfo1) { pref.updateCell(cellInfo, mTestEnv.mCellId1); @@ -183,9 +186,14 @@ public class NetworkSelectSettingsTest { @Test @UiThreadTest public void updateAllPreferenceCategory_correctOrderingPreference() { + NetworkScanResult result = new NetworkScanResult( + NetworkScanRepository.NetworkScanState.COMPLETE, + ImmutableList.of(mCellInfo1, mCellInfo2)); mNetworkSelectSettings.onCreateInitialization(); mNetworkSelectSettings.enablePreferenceScreen(true); - mNetworkSelectSettings.scanResultHandler(Arrays.asList(mCellInfo1, mCellInfo2)); + + mNetworkSelectSettings.scanResultHandler(result); + assertThat(mPreferenceCategory.getPreferenceCount()).isEqualTo(2); final NetworkOperatorPreference preference = (NetworkOperatorPreference) mPreferenceCategory.getPreference(1); diff --git a/tests/unit/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatchTest.java b/tests/unit/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatchTest.java index 3794e00ea10..e201f42e4b3 100644 --- a/tests/unit/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatchTest.java +++ b/tests/unit/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatchTest.java @@ -29,6 +29,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +@Deprecated(forRemoval = true) @RunWith(AndroidJUnit4.class) public class PanelSlicesLoaderCountdownLatchTest { diff --git a/tests/unit/src/com/android/settings/privatespace/delete/PrivateSpaceDeletionProgressFragmentTest.java b/tests/unit/src/com/android/settings/privatespace/delete/PrivateSpaceDeletionProgressFragmentTest.java index 9806540d540..62505400c8d 100644 --- a/tests/unit/src/com/android/settings/privatespace/delete/PrivateSpaceDeletionProgressFragmentTest.java +++ b/tests/unit/src/com/android/settings/privatespace/delete/PrivateSpaceDeletionProgressFragmentTest.java @@ -64,6 +64,11 @@ public class PrivateSpaceDeletionProgressFragmentTest { public void setup() { MockitoAnnotations.initMocks(this); mContext = ApplicationProvider.getApplicationContext(); + final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + .thenReturn(mLockPatternUtils); + doReturn(true).when(mLockPatternUtils).isSecure(anyInt()); + mFragment = new PrivateSpaceDeletionProgressFragment(); PrivateSpaceDeletionProgressFragment.Injector injector = new PrivateSpaceDeletionProgressFragment.Injector() { @@ -74,10 +79,6 @@ public class PrivateSpaceDeletionProgressFragmentTest { }; mPrivateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext); mFragment.setPrivateSpaceMaintainer(injector); - final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); - when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) - .thenReturn(mLockPatternUtils); - doReturn(true).when(mLockPatternUtils).isSecure(anyInt()); } @After