Snap for 12784745 from 7bd52367bb to 25Q2-release

Change-Id: I23df0ad9eb63e376abfa449639e863bcf2e81465
This commit is contained in:
Android Build Coastguard Worker
2024-12-11 16:17:40 -08:00
13 changed files with 210 additions and 112 deletions

View File

@@ -17,7 +17,6 @@
package com.android.settings;
import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.settingslib.media.PhoneMediaDevice.isDesktop;
import android.app.Activity;
import android.app.Dialog;
@@ -187,13 +186,6 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
/** Returns if catalyst is enabled on current screen. */
public final boolean isCatalystEnabled() {
// TODO(b/379130874): make Catalyst compatible with desktop device, such as user restriction
// check.
Context context = getContext();
if (context != null && isDesktop(context)) {
return false;
}
return getPreferenceScreenCreator() != null;
}

View File

@@ -144,7 +144,7 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling {
@Override
protected void startEnrollmentInternal() {
super.startEnrollment();
super.startEnrollmentInternal();
mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_FACE_PREVIEW);
if (mPreviewFragment == null) {

View File

@@ -54,7 +54,7 @@ class AccessibilityInteractorImpl(private val accessibilityManager: Accessibilit
.stateIn(
scope,
SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
false,
accessibilityManager.isEnabled,
)
override val isEnabled: Boolean

View File

@@ -48,6 +48,8 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
import com.android.settingslib.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -58,8 +60,11 @@ import java.util.concurrent.TimeUnit;
* device pairing detail page.
*/
public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15);
private static final int AUTO_DISMISS_MESSAGE_ID = 1001;
private static final ImmutableList<Integer> AUDIO_SHARING_PROFILES = ImmutableList.of(
BluetoothProfile.LE_AUDIO,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, BluetoothProfile.VOLUME_CONTROL);
protected boolean mInitialScanStarted;
@VisibleForTesting
@@ -229,12 +234,13 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
if (device != null
&& mSelectedList.contains(device)) {
if (BluetoothUtils.isAudioSharingUIAvailable(getContext())) {
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
if (mShouldTriggerAudioSharingShareThenPairFlow
&& state == BluetoothAdapter.STATE_CONNECTED
&& device.equals(mJustBonded)
&& mShouldTriggerAudioSharingShareThenPairFlow) {
&& AUDIO_SHARING_PROFILES.contains(bluetoothProfile)
&& isReadyForAudioSharing(cachedDevice, bluetoothProfile)) {
Log.d(getLogTag(),
"onProfileConnectionStateChanged, assistant profile connected");
"onProfileConnectionStateChanged, ready for audio sharing");
dismissConnectingDialog();
mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
finishFragmentWithResultForAudioSharing(device);
@@ -322,6 +328,35 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
return false;
}
private boolean isReadyForAudioSharing(@NonNull CachedBluetoothDevice cachedDevice,
int justConnectedProfile) {
for (int profile : AUDIO_SHARING_PROFILES) {
// Skip checking connection state for just connected profile
if (profile == justConnectedProfile) continue;
switch (profile) {
case BluetoothProfile.LE_AUDIO -> {
if (!cachedDevice.isConnectedLeAudioDevice()) {
Log.d(getLogTag(), "isReadyForAudioSharing, LE_AUDIO not ready");
return false;
}
}
case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT -> {
if (!cachedDevice.isConnectedLeAudioBroadcastAssistantDevice()) {
Log.d(getLogTag(), "isReadyForAudioSharing, ASSISTANT not ready");
return false;
}
}
case BluetoothProfile.VOLUME_CONTROL -> {
if (!cachedDevice.isConnectedVolumeControlDevice()) {
Log.d(getLogTag(), "isReadyForAudioSharing, VC not ready");
return false;
}
}
}
}
return true;
}
private void addOnMetadataChangedListener(@Nullable BluetoothDevice device) {
var unused = ThreadUtils.postOnBackgroundThread(() -> {
if (mBluetoothAdapter != null && device != null

View File

@@ -72,7 +72,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
// LINT.IfChange
/**
* Displays preferences for Tethering.
*/
@@ -209,19 +208,16 @@ public class TetherSettings extends RestrictedDashboardFragment
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
}
if (!isCatalystEnabled()) {
if (!bluetoothAvailable) {
mBluetoothTether.setVisible(false);
if (!bluetoothAvailable) {
getPreferenceScreen().removePreference(mBluetoothTether);
} else {
BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
} else {
BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
} else {
mBluetoothTether.setChecked(false);
}
mBluetoothTether.setChecked(false);
}
}
if (!ethernetAvailable) getPreferenceScreen().removePreference(mEthernetTether);
// Set initial state based on Data Saver mode.
onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled());
@@ -274,9 +270,7 @@ public class TetherSettings extends RestrictedDashboardFragment
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled);
}
mUsbTether.setEnabled(!mDataSaverEnabled);
if (!isCatalystEnabled()) {
mBluetoothTether.setEnabled(!mDataSaverEnabled);
}
mBluetoothTether.setEnabled(!mDataSaverEnabled);
mEthernetTether.setEnabled(!mDataSaverEnabled);
mDataSaverFooter.setVisible(mDataSaverEnabled);
}
@@ -526,8 +520,6 @@ public class TetherSettings extends RestrictedDashboardFragment
}
private void updateBluetoothState() {
if (isCatalystEnabled()) return;
final int btState = getBluetoothState();
if (DEBUG) {
Log.d(TAG, "updateBluetoothState() btState : " + btState);
@@ -583,7 +575,7 @@ public class TetherSettings extends RestrictedDashboardFragment
}
private void startTethering(int choice) {
if (choice == TETHERING_BLUETOOTH && !isCatalystEnabled()) {
if (choice == TETHERING_BLUETOOTH) {
// Turn on Bluetooth first.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.getState() == BluetoothAdapter.STATE_OFF) {
@@ -605,7 +597,7 @@ public class TetherSettings extends RestrictedDashboardFragment
} else {
mCm.stopTethering(TETHERING_USB);
}
} else if (preference == mBluetoothTether && !isCatalystEnabled()) {
} else if (preference == mBluetoothTether) {
if (mBluetoothTether.isChecked()) {
startTethering(TETHERING_BLUETOOTH);
} else {
@@ -753,4 +745,3 @@ public class TetherSettings extends RestrictedDashboardFragment
return TetherScreen.KEY;
}
}
// LINT.ThenChange(BluetoothTetherSwitchPreference.kt)

View File

@@ -691,7 +691,8 @@ public class ChooseLockPassword extends SettingsActivity {
return;
}
final TextView sucTitleView = view.findViewById(R.id.suc_layout_title);
final TextView sucTitleView = view.findViewById(
com.google.android.setupdesign.R.id.suc_layout_title);
final ViewGroup.MarginLayoutParams titleLayoutParams =
(ViewGroup.MarginLayoutParams) sucTitleView.getLayoutParams();
mPasswordRestrictionView = new RecyclerView(getActivity());

View File

@@ -109,7 +109,8 @@ public final class PasswordUtils extends com.android.settingslib.Utils {
public static void setupScreenLockOptionsButton(Context context, View view, Button optButton) {
final LinearLayout headerLayout = view.findViewById(
com.google.android.setupdesign.R.id.sud_layout_header);
final TextView sucTitleView = headerLayout.findViewById(R.id.suc_layout_title);
final TextView sucTitleView = headerLayout.findViewById(
com.google.android.setupdesign.R.id.suc_layout_title);
if (headerLayout != null && sucTitleView != null) {
final ViewGroup.MarginLayoutParams layoutTitleParams =
(ViewGroup.MarginLayoutParams) sucTitleView.getLayoutParams();

View File

@@ -25,9 +25,9 @@ import com.android.settingslib.preference.PreferenceScreenBindingHelper.Companio
/** Helper to rebind preference immediately when user restriction is changed. */
class UserRestrictionBindingHelper(
context: Context,
private val context: Context,
private val screenBindingHelper: PreferenceScreenBindingHelper,
) : AutoCloseable {
) : KeyedObserver<String>, AutoCloseable {
private val restrictionKeysToPreferenceKeys: Map<String, MutableSet<String>> =
mutableMapOf<String, MutableSet<String>>()
.apply {
@@ -42,27 +42,29 @@ class UserRestrictionBindingHelper(
}
.toMap()
private val userRestrictionObserver: KeyedObserver<String?>?
init {
if (restrictionKeysToPreferenceKeys.isEmpty()) {
userRestrictionObserver = null
} else {
val observer =
KeyedObserver<String?> { restrictionKey, _ ->
restrictionKey?.let { notifyRestrictionChanged(it) }
}
UserRestrictions.addObserver(context, observer, HandlerExecutor.main)
userRestrictionObserver = observer
val restrictionKeys = restrictionKeysToPreferenceKeys.keys
if (restrictionKeys.isNotEmpty()) {
val userRestrictions = UserRestrictions.get(context)
val executor = HandlerExecutor.main
for (restrictionKey in restrictionKeys) {
userRestrictions.addObserver(restrictionKey, this, executor)
}
}
}
private fun notifyRestrictionChanged(restrictionKey: String) {
override fun onKeyChanged(restrictionKey: String, reason: Int) {
val keys = restrictionKeysToPreferenceKeys[restrictionKey] ?: return
for (key in keys) screenBindingHelper.notifyChange(key, CHANGE_REASON_STATE)
}
override fun close() {
userRestrictionObserver?.let { UserRestrictions.removeObserver(it) }
val restrictionKeys = restrictionKeysToPreferenceKeys.keys
if (restrictionKeys.isNotEmpty()) {
val userRestrictions = UserRestrictions.get(context)
for (restrictionKey in restrictionKeys) {
userRestrictions.removeObserver(restrictionKey, this)
}
}
}
}

View File

@@ -16,68 +16,58 @@
package com.android.settings.restriction
import android.content.BroadcastReceiver
import android.content.Context
import android.os.Bundle
import android.os.IUserRestrictionsListener
import android.content.Intent
import android.content.IntentFilter
import android.os.UserManager
import com.android.settingslib.datastore.KeyedDataObservable
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason
import com.android.settingslib.datastore.KeyedObserver
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
/** Helper class to monitor user restriction changes. */
object UserRestrictions {
private val observable = KeyedDataObservable<String>()
class UserRestrictions private constructor(private val applicationContext: Context) {
private val userRestrictionsListener =
object : IUserRestrictionsListener.Stub() {
override fun onUserRestrictionsChanged(
userId: Int,
newRestrictions: Bundle,
prevRestrictions: Bundle,
) {
// there is no API to remove listener, do a quick check to avoid unnecessary work
if (!observable.hasAnyObserver()) return
private val observable =
object : AbstractKeyedDataObservable<String>() {
override fun onFirstObserverAdded() {
val intentFilter = IntentFilter()
intentFilter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)
applicationContext.registerReceiver(broadcastReceiver, intentFilter)
}
val changedKeys = mutableSetOf<String>()
val keys = newRestrictions.keySet() + prevRestrictions.keySet()
for (key in keys) {
if (newRestrictions.getBoolean(key) != prevRestrictions.getBoolean(key)) {
changedKeys.add(key)
}
}
for (key in changedKeys) observable.notifyChange(key, 0)
override fun onLastObserverRemoved() {
applicationContext.unregisterReceiver(broadcastReceiver)
}
}
private val listenerAdded = AtomicBoolean()
private val broadcastReceiver: BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// there is no way to get the changed keys, just notify all observers
observable.notifyChange(DataChangeReason.UPDATE)
}
}
fun addObserver(context: Context, observer: KeyedObserver<String?>, executor: Executor) {
context.addUserRestrictionsListener()
fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
observable.addObserver(observer, executor)
}
fun addObserver(
context: Context,
key: String,
observer: KeyedObserver<String>,
executor: Executor,
) {
context.addUserRestrictionsListener()
fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
observable.addObserver(key, observer, executor)
}
private fun Context.addUserRestrictionsListener() {
if (listenerAdded.getAndSet(true)) return
// surprisingly, there is no way to remove the listener
applicationContext
.getSystemService(UserManager::class.java)
.addUserRestrictionsListener(userRestrictionsListener)
}
fun removeObserver(observer: KeyedObserver<String?>) = observable.removeObserver(observer)
fun removeObserver(key: String, observer: KeyedObserver<String>) =
observable.removeObserver(key, observer)
companion object {
@Volatile private var instance: UserRestrictions? = null
fun get(context: Context) =
instance
?: synchronized(this) {
instance ?: UserRestrictions(context.applicationContext).also { instance = it }
}
}
}

View File

@@ -18,6 +18,9 @@ package com.android.settings.wifi
import android.content.Context
import android.net.wifi.WifiManager
import android.security.advancedprotection.AdvancedProtectionManager
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -27,6 +30,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextAlign
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
@@ -50,6 +54,9 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
var wifiManager = context.getSystemService(WifiManager::class.java)!!
var aapmManager = if (android.security.Flags.aapmApi() && Flags.wepDisabledInApm())
context.getSystemService(AdvancedProtectionManager::class.java)!!
else null
override fun getAvailabilityStatus() =
if (Flags.androidVWifiApi()) AVAILABLE else UNSUPPORTED_ON_DEVICE
@@ -60,27 +67,49 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
isWepSupportedFlow.collectAsStateWithLifecycle(initialValue = null).value
val isWepAllowed: Boolean? =
wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null).value
var openDialog by rememberSaveable { mutableStateOf(false) }
SwitchPreference(
object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary(isWepSupported) }
override val checked = {
if (isWepSupported == true) isWepAllowed else isWepSupported
}
override val changeable: () -> Boolean
get() = { isWepSupported == true }
val isAapmEnabled: Boolean? = if (android.security.Flags.aapmApi()
&& Flags.wepDisabledInApm())
isAapmEnabledFlow.collectAsStateWithLifecycle(initialValue = null).value
else false
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
val wifiInfo = wifiManager.connectionInfo
if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
openDialog = true
} else {
wifiManager.setWepAllowed(newChecked)
wepAllowedFlow.override(newChecked)
var openDialog by rememberSaveable { mutableStateOf(false) }
RestrictionWrapper(
restricted = isAapmEnabled == true
) {
SwitchPreference(
object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary(isWepSupported) }
override val checked = {
when {
isWepSupported == false -> false
isAapmEnabled == true -> false
else -> isWepAllowed
}
}
override val changeable: () -> Boolean
get() = { isWepSupported == true && isAapmEnabled == false }
override val onCheckedChange: ((Boolean) -> Unit)? =
if (isAapmEnabled == true) {
null
} else {
{ newChecked ->
val wifiInfo = wifiManager.connectionInfo
if (!newChecked &&
wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP
) {
openDialog = true
} else {
wifiManager.setWepAllowed(newChecked)
wepAllowedFlow.override(newChecked)
}
}
}
}
})
)
}
if (openDialog) {
SettingsAlertDialogWithIcon(
onDismissRequest = { openDialog = false },
@@ -103,6 +132,21 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
}
}
@Composable
private fun RestrictionWrapper(restricted: Boolean, content: @Composable () -> Unit) {
if (restricted) {
Box(
Modifier.clickable(
enabled = true,
role = Role.Switch,
onClick = ::startSupportIntent
)
) { content() }
} else {
content()
}
}
private fun getSummary(isWepSupported: Boolean?): String =
mContext.getString(
when (isWepSupported) {
@@ -114,6 +158,16 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
private val isWepSupportedFlow =
flow { emit(wifiManager.isWepSupported) }.flowOn(Dispatchers.Default)
private val isAapmEnabledFlow = flow {
emit(aapmManager?.isAdvancedProtectionEnabled ?: false) }.flowOn(Dispatchers.Default)
private fun startSupportIntent() {
aapmManager?.createSupportIntent(
AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING
)?.let { mContext.startActivity(it) }
}
val wepAllowedFlow =
OverridableFlow(
callbackFlow {

View File

@@ -308,6 +308,9 @@ public class BluetoothDevicePairingDetailBaseTest {
shadowOf(Looper.getMainLooper()).idle();
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioBroadcastAssistantDevice()).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedVolumeControlDevice()).thenReturn(true);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

View File

@@ -17,8 +17,10 @@
package com.android.settings.wifi
import android.content.Context
import android.content.Intent
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.security.advancedprotection.AdvancedProtectionManager
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isDisplayed
@@ -43,9 +45,12 @@ import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class WepNetworksPreferenceControllerTest {
@@ -71,9 +76,15 @@ class WepNetworksPreferenceControllerTest {
on { connectionInfo } doReturn mockWifiInfo
}
private var mockAapmManager =
mock<AdvancedProtectionManager> {
on { isAdvancedProtectionEnabled } doReturn false
}
private var context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(WifiManager::class.java) } doReturn mockWifiManager
on { getSystemService(AdvancedProtectionManager::class.java) } doReturn mockAapmManager
}
private var controller = WepNetworksPreferenceController(context, TEST_KEY)
@@ -185,6 +196,23 @@ class WepNetworksPreferenceControllerTest {
.isNotDisplayed()
}
@Test
fun whenClick_aapmEnabled_openDialog() {
mockAapmManager.stub {
on { isAdvancedProtectionEnabled } doReturn true
on { createSupportIntent(any(), any()) } doReturn Intent()
}
doNothing().whenever(context).startActivity(any())
composeTestRule.setContent { controller.Content() }
composeTestRule.onRoot().performClick()
composeTestRule
.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
.isNotDisplayed()
verify(context).startActivity(any())
}
private companion object {
const val TEST_KEY = "test_key"
const val SSID = "ssid"

View File

@@ -116,6 +116,7 @@ public class SimStatusDialogControllerTest {
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(
anyInt());
doReturn(2).when(mTelephonyManager).getCardIdForDefaultEuicc();
doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mTelephonyManager).getVoiceNetworkType();
doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mTelephonyManager).getDataNetworkType();
mUpdatePhoneNumberCount = new AtomicInteger();