Merge changes from topic "MobileNetworkSettingsSearchIndex" into main

* changes:
  Fix search for MMS Message
  Show category when search "Mobile data"
This commit is contained in:
Chaohui Wang
2024-07-31 09:07:20 +00:00
committed by Android (Google) Code Review
18 changed files with 658 additions and 175 deletions

View File

@@ -82,12 +82,10 @@ android_library {
"android.hardware.dumpstate-V1-java",
"android.hardware.dumpstate-V1.0-java",
"android.hardware.dumpstate-V1.1-java",
"android.view.accessibility.flags-aconfig-java",
"com_android_server_accessibility_flags_lib",
"net-utils-framework-common",
"notification_flags_lib",
"securebox",
"android.os.flags-aconfig-java",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"WindowManager-Shell-shared-desktopMode",
@@ -103,12 +101,9 @@ android_library {
"contextualcards",
"development_settings_flag_lib",
"factory_reset_flags_lib",
"fuelgauge-log-protos-lite",
"settings-protos-lite",
"fuelgauge-protos-lite",
"settings-contextual-card-protos-lite",
"settings-log-bridge-protos-lite",
"settings-logtags",
"settings-telephony-protos-lite",
"statslog-settings",
"telephony_flags_core_java_lib",
"setupdesign-lottie-loading-layout",

View File

@@ -1,44 +1,12 @@
package {
default_team: "trendy_team_android_settings_app",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "packages_apps_Settings_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["packages_apps_Settings_license"],
}
java_library_static {
name: "settings-contextual-card-protos-lite",
host_supported: true,
proto: {
type: "lite",
},
srcs: ["contextual_card_list.proto"],
}
java_library_static {
name: "settings-log-bridge-protos-lite",
host_supported: true,
proto: {
type: "lite",
},
srcs: ["settings_log_bridge.proto"],
}
java_library_static {
name: "settings-telephony-protos-lite",
host_supported: true,
proto: {
type: "lite",
},
srcs: ["network_mode_choices.proto"],
}
java_library {
name: "fuelgauge-log-protos-lite",
name: "settings-protos-lite",
proto: {
type: "lite",
},
srcs: ["fuelgauge_log.proto"],
srcs: ["*.proto"],
}

View File

@@ -0,0 +1,34 @@
syntax = "proto2";
package com.android.settings.spa;
message SpaSearchLandingKey {
oneof page {
SpaSearchLandingSpaPage spa_page = 1;
SpaSearchLandingFragment fragment = 2;
}
}
message SpaSearchLandingSpaPage {
/** The destination of SPA page. */
optional string destination = 1;
}
message SpaSearchLandingFragment {
/** The fragment class name. */
optional string fragment_name = 1;
/** The key of the preference to highlight the item. */
optional string preference_key = 2;
/** The arguments passed to the page. */
map<string, BundleValue> arguments = 3;
}
/** A value in an Android Bundle. */
message BundleValue {
oneof value {
/** A 32-bit signed integer value. */
int32 int_value = 1;
}
}

View File

@@ -112,10 +112,12 @@
android:selectable="false"
settings:searchable="false"/>
<!-- Settings search is handled by MmsMessageSearchItem. -->
<SwitchPreferenceCompat
android:key="mms_message"
android:title="@string/mms_message_title"
android:summary="@string/mms_message_summary"
settings:searchable="false"
settings:controller="com.android.settings.network.telephony.MmsMessagePreferenceController"/>
<SwitchPreferenceCompat

View File

@@ -34,7 +34,7 @@ object EmbeddedDeepLinkUtils {
private const val TAG = "EmbeddedDeepLinkUtils"
@JvmStatic
fun Activity.tryStartMultiPaneDeepLink(
fun Context.tryStartMultiPaneDeepLink(
intent: Intent,
highlightMenuKey: String? = null,
): Boolean {

View File

@@ -22,46 +22,38 @@ import android.telephony.TelephonyManager
import android.telephony.data.ApnSetting
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.Settings.MobileNetworkActivity.EXTRA_MMS_MESSAGE
import com.android.settings.core.TogglePreferenceController
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.flow.combine
/**
* Preference controller for "MMS messages"
*/
class MmsMessagePreferenceController @JvmOverloads constructor(
/** Preference controller for "MMS messages" */
class MmsMessagePreferenceController
@JvmOverloads
constructor(
context: Context,
key: String,
private val getDefaultDataSubId: () -> Int = {
SubscriptionManager.getDefaultDataSubscriptionId()
},
) : TelephonyTogglePreferenceController(context, key) {
) : TogglePreferenceController(context, key) {
private lateinit var telephonyManager: TelephonyManager
private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var telephonyManager: TelephonyManager =
context.getSystemService(TelephonyManager::class.java)!!
private var preferenceScreen: PreferenceScreen? = null
fun init(subId: Int) {
mSubId = subId
telephonyManager = mContext.getSystemService(TelephonyManager::class.java)!!
.createForSubscriptionId(subId)
this.subId = subId
telephonyManager = telephonyManager.createForSubscriptionId(subId)
}
override fun getAvailabilityStatus(subId: Int) =
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
this::telephonyManager.isInitialized &&
!telephonyManager.isDataEnabled &&
telephonyManager.isApnMetered(ApnSetting.TYPE_MMS) &&
!isFallbackDataEnabled()
) AVAILABLE else CONDITIONALLY_UNAVAILABLE
private fun isFallbackDataEnabled(): Boolean {
val defaultDataSubId = getDefaultDataSubId()
return defaultDataSubId != mSubId &&
telephonyManager.createForSubscriptionId(defaultDataSubId).isDataEnabled &&
telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH
)
}
override fun getAvailabilityStatus() =
if (getAvailabilityStatus(telephonyManager, subId, getDefaultDataSubId)) AVAILABLE
else CONDITIONALLY_UNAVAILABLE
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
@@ -70,16 +62,20 @@ class MmsMessagePreferenceController @JvmOverloads constructor(
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
combine(
MobileDataRepository(mContext).mobileDataEnabledChangedFlow(mSubId),
mContext.subscriptionsChangedFlow(), // Capture isMobileDataPolicyEnabled() changes
) { _, _ -> }.collectLatestWithLifecycle(viewLifecycleOwner) {
preferenceScreen?.let { super.displayPreference(it) }
}
MobileDataRepository(mContext).mobileDataEnabledChangedFlow(subId),
mContext.subscriptionsChangedFlow(), // Capture isMobileDataPolicyEnabled() changes
) { _, _ ->
}
.collectLatestWithLifecycle(viewLifecycleOwner) {
preferenceScreen?.let { super.displayPreference(it) }
}
}
override fun isChecked(): Boolean = telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED
)
override fun getSliceHighlightMenuRes() = NO_RES
override fun isChecked(): Boolean =
telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)
override fun setChecked(isChecked: Boolean): Boolean {
telephonyManager.setMobileDataPolicyEnabled(
@@ -88,4 +84,45 @@ class MmsMessagePreferenceController @JvmOverloads constructor(
)
return true
}
companion object {
private fun getAvailabilityStatus(
telephonyManager: TelephonyManager,
subId: Int,
getDefaultDataSubId: () -> Int,
): Boolean {
return SubscriptionManager.isValidSubscriptionId(subId) &&
!telephonyManager.isDataEnabled &&
telephonyManager.isApnMetered(ApnSetting.TYPE_MMS) &&
!isFallbackDataEnabled(telephonyManager, subId, getDefaultDataSubId())
}
private fun isFallbackDataEnabled(
telephonyManager: TelephonyManager,
subId: Int,
defaultDataSubId: Int,
): Boolean {
return defaultDataSubId != subId &&
telephonyManager.createForSubscriptionId(defaultDataSubId).isDataEnabled &&
telephonyManager.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
}
class MmsMessageSearchItem(
context: Context,
private val getDefaultDataSubId: () -> Int = {
SubscriptionManager.getDefaultDataSubscriptionId()
},
) : MobileNetworkSettingsSearchItem {
private var telephonyManager: TelephonyManager =
context.getSystemService(TelephonyManager::class.java)!!
override val key: String = EXTRA_MMS_MESSAGE
override val title: String = context.getString(R.string.mms_message_title)
override fun isAvailable(subId: Int): Boolean =
getAvailabilityStatus(
telephonyManager.createForSubscriptionId(subId), subId, getDefaultDataSubId)
}
}
}

View File

@@ -467,14 +467,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.mobile_network_settings) {
/** suppress full page if user is not admin */
@Override
protected boolean isPageSearchEnabled(Context context) {
boolean isAirplaneOff = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 0;
return isAirplaneOff && SubscriptionUtil.isSimHardwareVisible(context)
&& context.getSystemService(UserManager.class).isAdminUser();
return MobileNetworkSettingsSearchIndex
.isMobileNetworkSettingsSearchable(context);
}
};

View File

@@ -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.network.telephony
import android.content.Context
import android.provider.Settings
import android.telephony.SubscriptionInfo
import com.android.settings.R
import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem
import com.android.settings.spa.SpaSearchLanding.BundleValue
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.android.settings.spa.search.SpaSearchRepository.Companion.createSearchIndexableRaw
import com.android.settings.spa.search.SpaSearchRepository.Companion.searchIndexProviderOf
import com.android.settingslib.search.SearchIndexableData
import com.android.settingslib.search.SearchIndexableRaw
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
class MobileNetworkSettingsSearchIndex(
private val searchItemsFactory: (context: Context) -> List<MobileNetworkSettingsSearchItem> =
::createSearchItems,
) {
interface MobileNetworkSettingsSearchItem {
val key: String
val title: String
fun isAvailable(subId: Int): Boolean
}
fun createSearchIndexableData(): SearchIndexableData {
val searchIndexProvider = searchIndexProviderOf { context ->
if (!isMobileNetworkSettingsSearchable(context)) {
return@searchIndexProviderOf emptyList()
}
val subInfos = context.requireSubscriptionManager().activeSubscriptionInfoList
if (subInfos.isNullOrEmpty()) {
return@searchIndexProviderOf emptyList()
}
searchItemsFactory(context).flatMap { searchItem ->
searchIndexableRawList(context, searchItem, subInfos)
}
}
return SearchIndexableData(MobileNetworkSettings::class.java, searchIndexProvider)
}
private fun searchIndexableRawList(
context: Context,
searchItem: MobileNetworkSettingsSearchItem,
subInfos: List<SubscriptionInfo>
): List<SearchIndexableRaw> =
subInfos
.filter { searchItem.isAvailable(it.subscriptionId) }
.map { subInfo -> searchIndexableRaw(context, searchItem, subInfo) }
private fun searchIndexableRaw(
context: Context,
searchItem: MobileNetworkSettingsSearchItem,
subInfo: SubscriptionInfo,
): SearchIndexableRaw {
val key =
SpaSearchLandingKey.newBuilder()
.setFragment(
SpaSearchLandingFragment.newBuilder()
.setFragmentName(MobileNetworkSettings::class.java.name)
.setPreferenceKey(searchItem.key)
.putArguments(
Settings.EXTRA_SUB_ID,
BundleValue.newBuilder().setIntValue(subInfo.subscriptionId).build()))
.build()
val simsTitle = context.getString(R.string.provider_network_settings_title)
return createSearchIndexableRaw(
context = context,
spaSearchLandingKey = key,
itemTitle = searchItem.title,
indexableClass = MobileNetworkSettings::class.java,
pageTitle = "$simsTitle > ${subInfo.displayName}",
)
}
companion object {
/** suppress full page if user is not admin */
@JvmStatic
fun isMobileNetworkSettingsSearchable(context: Context): Boolean {
val isAirplaneMode by context.settingsGlobalBoolean(Settings.Global.AIRPLANE_MODE_ON)
return SubscriptionUtil.isSimHardwareVisible(context) &&
!isAirplaneMode &&
context.userManager.isAdminUser
}
fun createSearchItems(context: Context): List<MobileNetworkSettingsSearchItem> =
listOf(
MmsMessageSearchItem(context),
)
}
}

View File

@@ -16,7 +16,7 @@
package com.android.settings.spa
import android.app.Activity
import android.content.Context
import android.content.Intent
import com.android.settings.activityembedding.ActivityEmbeddingUtils
import com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink
@@ -27,16 +27,16 @@ data class SpaDestination(
val destination: String,
val highlightMenuKey: String?,
) {
fun startFromExportedActivity(activity: Activity) {
val intent = Intent(activity, SpaActivity::class.java)
fun startFromExportedActivity(context: Context) {
val intent = Intent(context, SpaActivity::class.java)
.appendSpaParams(
destination = destination,
sessionName = SESSION_EXTERNAL,
)
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(activity) ||
!activity.tryStartMultiPaneDeepLink(intent, highlightMenuKey)
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context) ||
!context.tryStartMultiPaneDeepLink(intent, highlightMenuKey)
) {
activity.startActivity(intent)
context.startActivity(intent)
}
}
}

View File

@@ -197,6 +197,9 @@ open class NetworkCellularGroupProvider : SettingsPageProvider, SearchablePage {
// Do nothing
}
override fun getPageTitleForSearch(context: Context): String =
context.getString(R.string.provider_network_settings_title)
override fun getSearchableTitles(context: Context): List<String> {
if (!isPageSearchable(context)) return emptyList()
return buildList {

View File

@@ -20,6 +20,9 @@ import android.content.Context
interface SearchablePage {
/** Gets the searchable titles at the current moment. */
/** Gets the title of the page. */
fun getPageTitleForSearch(context: Context): String = ""
/** Gets the titles of the searchable items at the current moment. */
fun getSearchableTitles(context: Context): List<String>
}

View File

@@ -17,26 +17,68 @@
package com.android.settings.spa.search
import android.app.Activity
import android.app.settings.SettingsEnums
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY
import com.android.settings.core.SubSettingLauncher
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settings.password.PasswordUtils
import com.android.settings.spa.SpaDestination
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
class SpaSearchLandingActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!isValidCall()) return
val destination = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY)
if (destination.isNullOrBlank()) return
SpaDestination(destination = destination, highlightMenuKey = null)
.startFromExportedActivity(this)
val keyString = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY)
if (!keyString.isNullOrEmpty() && isValidCall()) {
tryLaunch(this, keyString)
}
finish()
}
private fun isValidCall() =
PasswordUtils.getCallingAppPackageName(activityToken) ==
featureFactory.searchFeatureProvider.getSettingsIntelligencePkgName(this)
companion object {
@VisibleForTesting
fun tryLaunch(context: Context, keyString: String) {
val key =
try {
SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(keyString))
} catch (e: InvalidProtocolBufferException) {
Log.w(TAG, "arg key ($keyString) invalid", e)
return
}
if (key.hasSpaPage()) {
val destination = key.spaPage.destination
if (destination.isNotEmpty()) {
SpaDestination(destination = destination, highlightMenuKey = null)
.startFromExportedActivity(context)
}
}
if (key.hasFragment()) {
val arguments =
Bundle().apply {
key.fragment.argumentsMap.forEach { (k, v) ->
if (v.hasIntValue()) putInt(k, v.intValue)
}
putString(EXTRA_FRAGMENT_ARG_KEY, key.fragment.preferenceKey)
}
SubSettingLauncher(context)
.setDestination(key.fragment.fragmentName)
.setArguments(arguments)
.setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
.launch()
}
}
private const val TAG = "SpaSearchLandingActivity"
}
}

View File

@@ -20,6 +20,9 @@ import android.content.Context
import android.provider.SearchIndexableResource
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingSpaPage
import com.android.settingslib.search.Indexable
import com.android.settingslib.search.SearchIndexableData
import com.android.settingslib.search.SearchIndexableRaw
@@ -34,9 +37,10 @@ class SpaSearchRepository(
Log.d(TAG, "getSearchIndexableDataList")
return spaEnvironment.pageProviderRepository.value.getAllProviders().mapNotNull { page ->
if (page is SearchablePage) {
page.createSearchIndexableData(page::getSearchableTitles)
page.createSearchIndexableData(
page::getPageTitleForSearch, page::getSearchableTitles)
} else null
}
} + MobileNetworkSettingsSearchIndex().createSearchIndexableData()
}
companion object {
@@ -44,40 +48,60 @@ class SpaSearchRepository(
@VisibleForTesting
fun SettingsPageProvider.createSearchIndexableData(
getPageTitleForSearch: (context: Context) -> String,
titlesProvider: (context: Context) -> List<String>,
): SearchIndexableData {
val searchIndexProvider =
object : Indexable.SearchIndexProvider {
override fun getXmlResourcesToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableResource> = emptyList()
override fun getRawDataToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableRaw> = emptyList()
override fun getDynamicRawDataToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableRaw> =
titlesProvider(context).map { title ->
createSearchIndexableRaw(context, title)
}
override fun getNonIndexableKeys(context: Context): List<String> = emptyList()
val key =
SpaSearchLandingKey.newBuilder()
.setSpaPage(SpaSearchLandingSpaPage.newBuilder().setDestination(name))
.build()
val indexableClass = this::class.java
val searchIndexProvider = searchIndexProviderOf { context ->
val pageTitle = getPageTitleForSearch(context)
titlesProvider(context).map { itemTitle ->
createSearchIndexableRaw(context, key, itemTitle, indexableClass, pageTitle)
}
return SearchIndexableData(this::class.java, searchIndexProvider)
}
return SearchIndexableData(indexableClass, searchIndexProvider)
}
private fun SettingsPageProvider.createSearchIndexableRaw(context: Context, title: String) =
fun searchIndexProviderOf(
getDynamicRawDataToIndex: (context: Context) -> List<SearchIndexableRaw>,
) =
object : Indexable.SearchIndexProvider {
override fun getXmlResourcesToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableResource> = emptyList()
override fun getRawDataToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableRaw> = emptyList()
override fun getDynamicRawDataToIndex(
context: Context,
enabled: Boolean,
): List<SearchIndexableRaw> = getDynamicRawDataToIndex(context)
override fun getNonIndexableKeys(context: Context): List<String> = emptyList()
}
fun createSearchIndexableRaw(
context: Context,
spaSearchLandingKey: SpaSearchLandingKey,
itemTitle: String,
indexableClass: Class<*>,
pageTitle: String,
) =
SearchIndexableRaw(context).apply {
key = name
this.title = title
key = spaSearchLandingKey.toByteString().toStringUtf8()
title = itemTitle
intentAction = SEARCH_LANDING_ACTION
intentTargetClass = SpaSearchLandingActivity::class.qualifiedName
packageName = context.packageName
className = SpaSearchLandingActivity::class.qualifiedName
className = indexableClass.name
screenTitle = pageTitle
}
private const val SEARCH_LANDING_ACTION = "android.settings.SPA_SEARCH_LANDING"

View File

@@ -29,18 +29,14 @@ import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.res.Resources;
import android.net.NetworkPolicyManager;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.datausage.DataUsageSummaryPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -53,7 +49,6 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@@ -73,7 +68,6 @@ public class MobileNetworkSettingsTest {
private FragmentActivity mActivity;
private Context mContext;
private Resources mResources;
private MobileNetworkSettings mFragment;
@Before
@@ -81,10 +75,6 @@ public class MobileNetworkSettingsTest {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mResources = spy(mContext.getResources());
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
when(mActivity.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
when(mContext.getSystemService(NetworkStatsManager.class)).thenReturn(mNetworkStatsManager);
@@ -123,34 +113,4 @@ public class MobileNetworkSettingsTest {
mFragment.onActivityResult(REQUEST_CODE_DELETE_SUBSCRIPTION, Activity.RESULT_OK, null);
verify(mActivity).finish();
}
@Test
public void isPageSearchEnabled_adminUser_shouldReturnTrue() {
final UserManager userManager = mock(UserManager.class);
when(mContext.getSystemService(UserManager.class)).thenReturn(userManager);
when(userManager.isAdminUser()).thenReturn(true);
final BaseSearchIndexProvider provider =
(BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
ReflectionHelpers.ClassParameter.from(Context.class, mContext));
final boolean isEnabled = (Boolean) obj;
assertThat(isEnabled).isTrue();
}
@Test
public void isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
final UserManager userManager = mock(UserManager.class);
when(mContext.getSystemService(UserManager.class)).thenReturn(userManager);
when(userManager.isAdminUser()).thenReturn(false);
final BaseSearchIndexProvider provider =
(BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
ReflectionHelpers.ClassParameter.from(Context.class, mContext));
final boolean isEnabled = (Boolean) obj;
assertThat(isEnabled).isFalse();
}
}

View File

@@ -24,6 +24,7 @@ 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.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,13 +61,13 @@ class MmsMessagePreferenceControllerTest {
context = context,
key = KEY,
getDefaultDataSubId = { defaultDataSubId },
).apply { init(SUB_2_ID) }
)
@Test
fun getAvailabilityStatus_invalidSubscription_unavailable() {
controller.init(INVALID_SUBSCRIPTION_ID)
val availabilityStatus = controller.getAvailabilityStatus(INVALID_SUBSCRIPTION_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@@ -76,8 +77,9 @@ class MmsMessagePreferenceControllerTest {
mockTelephonyManager2.stub {
on { isDataEnabled } doReturn true
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@@ -87,8 +89,9 @@ class MmsMessagePreferenceControllerTest {
mockTelephonyManager2.stub {
on { isApnMetered(ApnSetting.TYPE_MMS) } doReturn false
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@@ -102,8 +105,9 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@@ -117,14 +121,16 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(AVAILABLE)
}
@Test
fun getAvailabilityStatus_defaultDataOnAndAutoDataSwitchOn_unavailable() {
fun getAvailabilityStatus_notDefaultDataAndDataOnAndAutoDataSwitchOn_unavailable() {
defaultDataSubId = SUB_1_ID
mockTelephonyManager1.stub {
on { isDataEnabled } doReturn true
}
@@ -133,14 +139,16 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE)
}
@Test
fun getAvailabilityStatus_defaultDataOffAndAutoDataSwitchOn_available() {
fun getAvailabilityStatus_notDefaultDataAndDataOffAndAutoDataSwitchOn_available() {
defaultDataSubId = SUB_1_ID
mockTelephonyManager1.stub {
on { isDataEnabled } doReturn false
}
@@ -149,12 +157,49 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
controller.init(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus(SUB_2_ID)
val availabilityStatus = controller.getAvailabilityStatus()
assertThat(availabilityStatus).isEqualTo(AVAILABLE)
}
@Test
fun searchIsAvailable_notDefaultDataAndDataOnAndAutoDataSwitchOn_unavailable() {
mockTelephonyManager1.stub {
on { isDataEnabled } doReturn true
}
mockTelephonyManager2.stub {
on { isApnMetered(ApnSetting.TYPE_MMS) } doReturn true
on {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
val mmsMessageSearchItem = MmsMessageSearchItem(context) { SUB_1_ID }
val isAvailable = mmsMessageSearchItem.isAvailable(SUB_2_ID)
assertThat(isAvailable).isFalse()
}
@Test
fun searchIsAvailable_notDefaultDataAndDataOffAndAutoDataSwitchOn_available() {
mockTelephonyManager1.stub {
on { isDataEnabled } doReturn false
}
mockTelephonyManager2.stub {
on { isApnMetered(ApnSetting.TYPE_MMS) } doReturn true
on {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
} doReturn true
}
val mmsMessageSearchItem = MmsMessageSearchItem(context) { SUB_1_ID }
val isAvailable = mmsMessageSearchItem.isAvailable(SUB_2_ID)
assertThat(isAvailable).isTrue()
}
@Test
fun isChecked_whenMmsNotAlwaysAllowed_returnFalse() {
mockTelephonyManager2.stub {
@@ -162,6 +207,7 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)
} doReturn false
}
controller.init(SUB_2_ID)
val isChecked = controller.isChecked()
@@ -175,6 +221,7 @@ class MmsMessagePreferenceControllerTest {
isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)
} doReturn true
}
controller.init(SUB_2_ID)
val isChecked = controller.isChecked()
@@ -183,6 +230,8 @@ class MmsMessagePreferenceControllerTest {
@Test
fun setChecked_setTrue_setDataIntoSubscriptionManager() {
controller.init(SUB_2_ID)
controller.setChecked(true)
verify(mockTelephonyManager2).setMobileDataPolicyEnabled(
@@ -192,6 +241,8 @@ class MmsMessagePreferenceControllerTest {
@Test
fun setChecked_setFalse_setDataIntoSubscriptionManager() {
controller.init(SUB_2_ID)
controller.setChecked(false)
verify(mockTelephonyManager2).setMobileDataPolicyEnabled(

View File

@@ -0,0 +1,148 @@
/*
* 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.os.UserManager
import android.provider.Settings
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.R
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.Companion.isMobileNetworkSettingsSearchable
import com.android.settings.spa.SpaSearchLanding.BundleValue
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.android.settings.spa.search.SpaSearchLandingActivity
import com.google.common.truth.Truth.assertThat
import com.google.protobuf.ByteString
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class MobileNetworkSettingsSearchIndexTest {
private val mockUserManager = mock<UserManager> { on { isAdminUser } doReturn true }
private val mockSubscriptionManager =
mock<SubscriptionManager> {
on { activeSubscriptionInfoList } doReturn listOf(SUB_INFO_1, SUB_INFO_2)
}
private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(UserManager::class.java) } doReturn mockUserManager
on { getSystemService(SubscriptionManager::class.java) } doReturn
mockSubscriptionManager
}
private val resources =
spy(context.resources) { on { getBoolean(R.bool.config_show_sim_info) } doReturn true }
private val mobileNetworkSettingsSearchIndex = MobileNetworkSettingsSearchIndex {
listOf(
object : MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem {
override val key = KEY
override val title = TITLE
override fun isAvailable(subId: Int) = subId == SUB_ID_1
})
}
@Before
fun setUp() {
context.stub { on { resources } doReturn resources }
}
@Test
fun isMobileNetworkSettingsSearchable_adminUser_returnTrue() {
mockUserManager.stub { on { isAdminUser } doReturn true }
val isSearchable = isMobileNetworkSettingsSearchable(context)
assertThat(isSearchable).isTrue()
}
@Test
fun isMobileNetworkSettingsSearchable_nonAdminUser_returnFalse() {
mockUserManager.stub { on { isAdminUser } doReturn false }
val isSearchable = isMobileNetworkSettingsSearchable(context)
assertThat(isSearchable).isFalse()
}
@Test
fun createSearchIndexableData() {
val searchIndexableData = mobileNetworkSettingsSearchIndex.createSearchIndexableData()
assertThat(searchIndexableData.targetClass).isEqualTo(MobileNetworkSettings::class.java)
val dynamicRawDataToIndex =
searchIndexableData.searchIndexProvider.getDynamicRawDataToIndex(context, true)
assertThat(dynamicRawDataToIndex).hasSize(1)
val rawData = dynamicRawDataToIndex[0]
val key = SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(rawData.key))
assertThat(key)
.isEqualTo(
SpaSearchLandingKey.newBuilder()
.setFragment(
SpaSearchLandingFragment.newBuilder()
.setFragmentName(MobileNetworkSettings::class.java.name)
.setPreferenceKey(KEY)
.putArguments(
Settings.EXTRA_SUB_ID,
BundleValue.newBuilder().setIntValue(SUB_ID_1).build()))
.build())
assertThat(rawData.title).isEqualTo(TITLE)
assertThat(rawData.intentAction).isEqualTo("android.settings.SPA_SEARCH_LANDING")
assertThat(rawData.intentTargetClass)
.isEqualTo(SpaSearchLandingActivity::class.qualifiedName)
assertThat(rawData.className).isEqualTo(MobileNetworkSettings::class.java.name)
assertThat(rawData.screenTitle).isEqualTo("SIMs > $SUB_DISPLAY_NAME_1")
}
private companion object {
const val KEY = "key"
const val TITLE = "Title"
const val SUB_ID_1 = 1
const val SUB_ID_2 = 2
const val SUB_DISPLAY_NAME_1 = "Sub 1"
const val SUB_DISPLAY_NAME_2 = "Sub 2"
val SUB_INFO_1: SubscriptionInfo =
SubscriptionInfo.Builder()
.apply {
setId(SUB_ID_1)
setDisplayName(SUB_DISPLAY_NAME_1)
}
.build()
val SUB_INFO_2: SubscriptionInfo =
SubscriptionInfo.Builder()
.apply {
setId(SUB_ID_2)
setDisplayName(SUB_DISPLAY_NAME_2)
}
.build()
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.spa.search
import android.content.Context
import android.content.Intent
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.SettingsActivity
import com.android.settings.spa.SpaSearchLanding.BundleValue
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingSpaPage
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SpaSearchLandingActivityTest {
private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any())
}
@Test
fun tryLaunch_spaPage() {
val key =
SpaSearchLandingKey.newBuilder()
.setSpaPage(SpaSearchLandingSpaPage.newBuilder().setDestination(DESTINATION))
.build()
SpaSearchLandingActivity.tryLaunch(context, key.toByteString().toStringUtf8())
verify(context).startActivity(argThat { getStringExtra(KEY_DESTINATION) == DESTINATION })
}
@Test
fun tryLaunch_fragment() {
val key =
SpaSearchLandingKey.newBuilder()
.setFragment(
SpaSearchLandingFragment.newBuilder()
.setFragmentName(DESTINATION)
.setPreferenceKey(PREFERENCE_KEY)
.putArguments(
ARGUMENT_KEY,
BundleValue.newBuilder().setIntValue(ARGUMENT_VALUE).build()))
.build()
SpaSearchLandingActivity.tryLaunch(context, key.toByteString().toStringUtf8())
val intent = argumentCaptor<Intent> { verify(context).startActivity(capture()) }.firstValue
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DESTINATION)
val fragmentArguments =
intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!!
assertThat(fragmentArguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY))
.isEqualTo(PREFERENCE_KEY)
assertThat(fragmentArguments.getInt(ARGUMENT_KEY)).isEqualTo(ARGUMENT_VALUE)
}
private companion object {
const val DESTINATION = "Destination"
const val PREFERENCE_KEY = "preference_key"
const val ARGUMENT_KEY = "argument_key"
const val ARGUMENT_VALUE = 123
}
}

View File

@@ -18,9 +18,12 @@ package com.android.settings.spa.search
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingSpaPage
import com.android.settings.spa.search.SpaSearchRepository.Companion.createSearchIndexableData
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.google.common.truth.Truth.assertThat
import com.google.protobuf.ByteString
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -35,17 +38,31 @@ class SpaSearchRepositoryTest {
override val name = PAGE_NAME
}
val searchIndexableData = pageProvider.createSearchIndexableData { listOf(TITLE) }
val searchIndexableData =
pageProvider.createSearchIndexableData({ PAGE_TITLE }) { listOf(ITEM_TITLE) }
val dynamicRawDataToIndex =
searchIndexableData.searchIndexProvider.getDynamicRawDataToIndex(mock<Context>(), true)
assertThat(searchIndexableData.targetClass).isEqualTo(pageProvider::class.java)
assertThat(dynamicRawDataToIndex).hasSize(1)
assertThat(dynamicRawDataToIndex[0].title).isEqualTo(TITLE)
val rawData = dynamicRawDataToIndex[0]
val key = SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(rawData.key))
assertThat(key)
.isEqualTo(
SpaSearchLandingKey.newBuilder()
.setSpaPage(SpaSearchLandingSpaPage.newBuilder().setDestination(PAGE_NAME))
.build())
assertThat(rawData.title).isEqualTo(ITEM_TITLE)
assertThat(rawData.intentAction).isEqualTo("android.settings.SPA_SEARCH_LANDING")
assertThat(rawData.intentTargetClass)
.isEqualTo(SpaSearchLandingActivity::class.qualifiedName)
assertThat(rawData.className).isEqualTo(pageProvider::class.java.name)
assertThat(rawData.screenTitle).isEqualTo(PAGE_TITLE)
}
private companion object {
const val PAGE_NAME = "PageName"
const val TITLE = "Title"
const val PAGE_TITLE = "Page Title"
const val ITEM_TITLE = "Item Title"
}
}