Merge "[Catalyst] Migrate Airplane Mode preference" into main

This commit is contained in:
Treehugger Robot
2024-12-05 10:47:21 +00:00
committed by Android (Google) Code Review
6 changed files with 268 additions and 22 deletions

View File

@@ -147,9 +147,24 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
* @return any subscription within device is under ECM mode * @return any subscription within device is under ECM mode
*/ */
public boolean isInEcmMode() { public boolean isInEcmMode() {
return isInEcmMode(mContext, mTelephonyManager);
}
/**
* Check the status of ECM mode
*
* @param context Caller's {@link Context}
* @param telephonyManager The default {@link TelephonyManager}
*
* @return any subscription within device is under ECM mode
*/
public static boolean isInEcmMode(Context context, TelephonyManager telephonyManager) {
if (context == null || telephonyManager == null) {
return false;
}
if (Flags.enforceTelephonyFeatureMappingForPublicApis()) { if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
try { try {
if (mTelephonyManager.getEmergencyCallbackMode()) { if (telephonyManager.getEmergencyCallbackMode()) {
return true; return true;
} }
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
@@ -157,26 +172,26 @@ public class AirplaneModeEnabler extends GlobalSettingsChangeListener {
// Ignore exception, device is not in ECM mode. // Ignore exception, device is not in ECM mode.
} }
} else { } else {
if (mTelephonyManager.getEmergencyCallbackMode()) { if (telephonyManager.getEmergencyCallbackMode()) {
return true; return true;
} }
} }
final List<SubscriptionInfo> subInfoList = final List<SubscriptionInfo> subInfoList =
ProxySubscriptionManager.getInstance(mContext).getActiveSubscriptionsInfo(); ProxySubscriptionManager.getInstance(context).getActiveSubscriptionsInfo();
if (subInfoList == null) { if (subInfoList == null) {
return false; return false;
} }
for (SubscriptionInfo subInfo : subInfoList) { for (SubscriptionInfo subInfo : subInfoList) {
final TelephonyManager telephonyManager = final TelephonyManager telephonyManagerForSubId =
mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); telephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
if (telephonyManager != null) { if (telephonyManagerForSubId != null) {
if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) { if (!Flags.enforceTelephonyFeatureMappingForPublicApis()) {
if (telephonyManager.getEmergencyCallbackMode()) { if (telephonyManagerForSubId.getEmergencyCallbackMode()) {
return true; return true;
} }
} else { } else {
try { try {
if (telephonyManager.getEmergencyCallbackMode()) { if (telephonyManagerForSubId.getEmergencyCallbackMode()) {
return true; return true;
} }
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {

View File

@@ -16,34 +16,188 @@
package com.android.settings.network package com.android.settings.network
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Looper
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings import android.provider.Settings
import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import android.util.Log
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.preference.Preference
import com.android.settings.AirplaneModeEnabler
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R import com.android.settings.R
import com.android.settings.Utils
import com.android.settingslib.RestrictedSwitchPreference
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.SettingsGlobalStore import com.android.settingslib.datastore.SettingsGlobalStore
import com.android.settingslib.datastore.SettingsStore
import com.android.settingslib.metadata.PreferenceAvailabilityProvider import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.metadata.SwitchPreference
import com.android.settingslib.preference.SwitchPreferenceBinding
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
// LINT.IfChange // LINT.IfChange
class AirplaneModePreference : open class AirplaneModePreference :
SwitchPreference(KEY, R.string.airplane_mode), PreferenceAvailabilityProvider { SwitchPreference(KEY, R.string.airplane_mode),
SwitchPreferenceBinding,
PreferenceAvailabilityProvider,
PreferenceLifecycleProvider,
PreferenceRestrictionMixin {
override val icon: Int override val icon: Int
@DrawableRes get() = R.drawable.ic_airplanemode_active @DrawableRes get() = R.drawable.ic_airplanemode_active
override fun storage(context: Context) = SettingsGlobalStore.get(context)
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
override fun isAvailable(context: Context) = override fun isAvailable(context: Context) =
(context.resources.getBoolean(R.bool.config_show_toggle_airplane) && (context.resources.getBoolean(R.bool.config_show_toggle_airplane) &&
!context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
override fun isEnabled(context: Context) = super<PreferenceRestrictionMixin>.isEnabled(context)
override val restrictionKeys
get() = arrayOf(UserManager.DISALLOW_AIRPLANE_MODE)
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
when {
isSatelliteOn(context) || isInEcmMode(context) -> ReadWritePermit.DISALLOW
else -> ReadWritePermit.ALLOW
}
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
override fun storage(context: Context): KeyValueStore =
AirplaneModeStorage(context, SettingsGlobalStore.get(context))
@Suppress("DEPRECATION", "MissingPermission", "UNCHECKED_CAST")
private class AirplaneModeStorage(
private val context: Context,
private val settingsStore: SettingsStore,
) : AbstractKeyedDataObservable<String>(), KeyValueStore {
private var phoneStateListener: PhoneStateListener? = null
override fun contains(key: String) =
settingsStore.contains(KEY) &&
context.getSystemService(TelephonyManager::class.java) != null
override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>) =
DEFAULT_VALUE as T
override fun <T : Any> getValue(key: String, valueType: Class<T>): T =
(settingsStore.getBoolean(key) ?: DEFAULT_VALUE) as T
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value is Boolean) {
settingsStore.setBoolean(key, value)
val intent = Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
intent.putExtra("state", value)
context.sendBroadcastAsUser(intent, UserHandle.ALL)
}
}
override fun onFirstObserverAdded() {
context.getSystemService(TelephonyManager::class.java)?.let {
phoneStateListener =
object : PhoneStateListener(Looper.getMainLooper()) {
@Deprecated("Deprecated in Java")
override fun onRadioPowerStateChanged(state: Int) {
Log.d(TAG, "onRadioPowerStateChanged(), state=$state")
notifyChange(KEY, DataChangeReason.UPDATE)
}
}
it.listen(phoneStateListener, PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED)
}
}
override fun onLastObserverRemoved() {
context
.getSystemService(TelephonyManager::class.java)
?.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE)
}
}
override fun onCreate(context: PreferenceLifecycleContext) {
context.requirePreference<RestrictedSwitchPreference>(KEY).onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference, _: Any ->
if (isInEcmMode(context)) {
showEcmDialog(context)
return@OnPreferenceChangeListener false
}
if (isSatelliteOn(context)) {
showSatelliteDialog(context)
return@OnPreferenceChangeListener false
}
return@OnPreferenceChangeListener true
}
}
override fun onActivityResult(
context: PreferenceLifecycleContext,
requestCode: Int,
resultCode: Int,
data: Intent?,
): Boolean {
if (requestCode == REQUEST_CODE_EXIT_ECM && resultCode == Activity.RESULT_OK) {
storage(context).setValue(KEY, Boolean::class.javaObjectType, true)
}
return true
}
private fun isInEcmMode(context: Context) =
AirplaneModeEnabler.isInEcmMode(
context,
context.getSystemService(TelephonyManager::class.java),
)
private fun isSatelliteOn(context: Context): Boolean {
try {
return SatelliteRepository(context)
.requestIsSessionStarted(Executors.newSingleThreadExecutor())
.get(2000, TimeUnit.MILLISECONDS)
} catch (e: Exception) {
Log.e(TAG, "Error to get satellite status : $e")
}
return false
}
private fun showEcmDialog(context: PreferenceLifecycleContext) {
val intent =
Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null)
.setPackage(Utils.PHONE_PACKAGE_NAME)
context.startActivityForResult(intent, REQUEST_CODE_EXIT_ECM, null)
}
private fun showSatelliteDialog(context: PreferenceLifecycleContext) {
val intent =
Intent(context, SatelliteWarningDialogActivity::class.java)
.putExtra(
SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG,
SatelliteWarningDialogActivity.TYPE_IS_AIRPLANE_MODE,
)
context.startActivity(intent)
}
companion object { companion object {
const val TAG = "AirplaneModePreference"
const val KEY = Settings.Global.AIRPLANE_MODE_ON const val KEY = Settings.Global.AIRPLANE_MODE_ON
const val DEFAULT_VALUE = false
const val REQUEST_CODE_EXIT_ECM = 1
fun Context.isAirplaneModeOn() = SettingsGlobalStore.get(this).getBoolean(KEY) == true fun Context.isAirplaneModeOn() = SettingsGlobalStore.get(this).getBoolean(KEY) == true
} }

View File

@@ -59,7 +59,9 @@ public class NetworkDashboardFragment extends DashboardFragment implements
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (isCatalystEnabled()) {
use(AirplaneModePreferenceController.class).setFragment(this); use(AirplaneModePreferenceController.class).setFragment(this);
}
use(NetworkProviderCallsSmsController.class).init(this); use(NetworkProviderCallsSmsController.class).init(this);
} }
@@ -102,8 +104,10 @@ public class NetworkDashboardFragment extends DashboardFragment implements
switch (requestCode) { switch (requestCode) {
case AirplaneModePreferenceController.REQUEST_CODE_EXIT_ECM: case AirplaneModePreferenceController.REQUEST_CODE_EXIT_ECM:
if (isCatalystEnabled()) {
use(AirplaneModePreferenceController.class) use(AirplaneModePreferenceController.class)
.onActivityResult(requestCode, resultCode, data); .onActivityResult(requestCode, resultCode, data);
}
break; break;
} }
} }

View File

@@ -47,6 +47,7 @@ class NetworkDashboardScreen : PreferenceScreenCreator, PreferenceIconProvider {
override fun getPreferenceHierarchy(context: Context) = override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(this) { preferenceHierarchy(this) {
+MobileNetworkListScreen.KEY order -15 +MobileNetworkListScreen.KEY order -15
+AirplaneModePreference() order -5
+DataSaverScreen.KEY order 10 +DataSaverScreen.KEY order 10
} }

View File

@@ -16,6 +16,10 @@
package com.android.settings.network; package com.android.settings.network;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static com.android.settings.flags.Flags.FLAG_CATALYST_NETWORK_PROVIDER_AND_INTERNET_SCREEN;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +32,7 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Looper; import android.os.Looper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings; import android.provider.Settings;
import android.provider.SettingsSlicesContract; import android.provider.SettingsSlicesContract;
import android.util.AndroidRuntimeException; import android.util.AndroidRuntimeException;
@@ -42,6 +47,7 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.RestrictedSwitchPreference;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -49,6 +55,8 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AirplaneModePreferenceControllerTest { public class AirplaneModePreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int ON = 1; private static final int ON = 1;
private static final int OFF = 0; private static final int OFF = 0;
@@ -66,6 +74,7 @@ public class AirplaneModePreferenceControllerTest {
@Before @Before
public void setUp() { public void setUp() {
mSetFlagsRule.disableFlags(FLAG_CATALYST_NETWORK_PROVIDER_AND_INTERNET_SCREEN);
if (Looper.myLooper() == null) { if (Looper.myLooper() == null) {
Looper.prepare(); Looper.prepare();
} }

View File

@@ -20,8 +20,14 @@ import android.content.ContextWrapper
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_LEANBACK import android.content.pm.PackageManager.FEATURE_LEANBACK
import android.content.res.Resources import android.content.res.Resources
import android.provider.Settings
import android.telephony.TelephonyManager
import androidx.annotation.DrawableRes
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.datastore.SettingsGlobalStore
import com.android.settingslib.preference.createAndBindWidget
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -31,19 +37,31 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.stub import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AirplaneModePreferenceTest { open class AirplaneModePreferenceTest {
private val mockPackageManager = mock<PackageManager>()
private val mockResources = mock<Resources>() private val mockResources = mock<Resources>()
private val mockPackageManager = mock<PackageManager>()
private var mockTelephonyManager = mock<TelephonyManager>()
private val context = private val context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) { object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getResources(): Resources = mockResources
override fun getPackageManager(): PackageManager = mockPackageManager override fun getPackageManager(): PackageManager = mockPackageManager
override fun getResources(): Resources = mockResources override fun getSystemService(name: String): Any? =
when (name) {
getSystemServiceName(TelephonyManager::class.java) -> mockTelephonyManager
else -> super.getSystemService(name)
}
} }
private val airplaneModePreference = AirplaneModePreference() private var airplaneModePreference =
object : AirplaneModePreference() {
// TODO: Remove override
override val icon: Int
@DrawableRes get() = 0
}
@Test @Test
fun isAvailable_hasConfigAndNoFeatureLeanback_shouldReturnTrue() { fun isAvailable_hasConfigAndNoFeatureLeanback_shouldReturnTrue() {
@@ -68,4 +86,49 @@ class AirplaneModePreferenceTest {
assertThat(airplaneModePreference.isAvailable(context)).isFalse() assertThat(airplaneModePreference.isAvailable(context)).isFalse()
} }
@Test
fun getValue_defaultOn_returnOn() {
SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1)
val getValue =
airplaneModePreference
.storage(context)
.getValue(AirplaneModePreference.KEY, Boolean::class.javaObjectType)
assertThat(getValue).isTrue()
}
@Test
fun getValue_defaultOff_returnOff() {
SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0)
val getValue =
airplaneModePreference
.storage(context)
.getValue(AirplaneModePreference.KEY, Boolean::class.javaObjectType)
assertThat(getValue).isFalse()
}
@Test
fun performClick_defaultOn_checkedIsFalse() {
SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 1)
val preference = getSwitchPreference().apply { performClick() }
assertThat(preference.isChecked).isFalse()
}
@Test
fun performClick_defaultOff_checkedIsTrue() {
SettingsGlobalStore.get(context).setInt(Settings.Global.AIRPLANE_MODE_ON, 0)
val preference = getSwitchPreference().apply { performClick() }
assertThat(preference.isChecked).isTrue()
}
private fun getSwitchPreference(): SwitchPreferenceCompat =
airplaneModePreference.createAndBindWidget(context)
} }