Snap for 12185670 from b22a9d03af to 24Q4-release

Change-Id: Id9cd31c17443a3785cff9e8c89f9262676d283ef
This commit is contained in:
Android Build Coastguard Worker
2024-08-05 23:21:24 +00:00
21 changed files with 416 additions and 236 deletions

View File

@@ -53,8 +53,8 @@
<CheckBox <CheckBox
android:id="@+id/audio_sharing_stream_password_checkbox" android:id="@+id/audio_sharing_stream_password_checkbox"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginEnd="20dp" /> android:layout_marginEnd="20dp" />
</LinearLayout> </LinearLayout>

View File

@@ -35,6 +35,9 @@
<action <action
android:id="@+id/action_set_lock_fragment" android:id="@+id/action_set_lock_fragment"
app:destination="@id/ps_profile_lock_fragment"/> app:destination="@id/ps_profile_lock_fragment"/>
<action
android:id="@+id/action_create_profile_error_restrict"
app:destination="@id/ps_profile_error_restricted_fragment"/>
</fragment> </fragment>
<fragment android:id="@+id/ps_profile_error_fragment" <fragment android:id="@+id/ps_profile_error_fragment"
android:name="com.android.settings.privatespace.PrivateProfileCreationError" android:name="com.android.settings.privatespace.PrivateProfileCreationError"
@@ -67,6 +70,9 @@
android:id="@+id/action_lock_success_fragment" android:id="@+id/action_lock_success_fragment"
app:destination="@id/ps_pre_finish_delay_fragment"/> app:destination="@id/ps_pre_finish_delay_fragment"/>
</fragment> </fragment>
<fragment android:id="@+id/ps_profile_error_restricted_fragment"
android:name="com.android.settings.privatespace.PrivateProfileCreationRestrictedError"
android:label="fragment_ps_error_exit"/>
<action android:id="@+id/action_pre_finish_delay_fragment" <action android:id="@+id/action_pre_finish_delay_fragment"
app:destination="@id/ps_pre_finish_delay_fragment"/> app:destination="@id/ps_pre_finish_delay_fragment"/>
<action android:id="@+id/action_advance_login_error" <action android:id="@+id/action_advance_login_error"

View File

@@ -1374,6 +1374,12 @@
<string name="private_space_error_screen_title">Couldn\u2019t set up a private space</string> <string name="private_space_error_screen_title">Couldn\u2019t set up a private space</string>
<!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] --> <!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] -->
<string name="private_space_tryagain_label">Try Again</string> <string name="private_space_tryagain_label">Try Again</string>
<!-- Label for button to exit private space setup on creation error. [CHAR LIMIT=30] -->
<string name="private_space_exit_label">Exit</string>
<!-- Description in Private space error page with a link to the Help Center page[CHAR LIMIT=NONE] -->
<string name="private_space_error_description">Private space isn\u2019t available.\nView possible causes</string>
<!-- Text in Private space error page that points to view possible error causes [CHAR LIMIT=40] -->
<string name="private_space_error_causes_text">View possible causes</string>
<!-- Title for private space lock setup screen. [CHAR LIMIT=90] --> <!-- Title for private space lock setup screen. [CHAR LIMIT=90] -->
<string name="private_space_lockscreen_title">Choose a new lock for private space?</string> <string name="private_space_lockscreen_title">Choose a new lock for private space?</string>
<!-- Summary for the private space lock setup screen. [CHAR LIMIT=NONE] --> <!-- Summary for the private space lock setup screen. [CHAR LIMIT=NONE] -->

View File

@@ -85,9 +85,10 @@
android:summary="@string/auto_data_switch_summary" android:summary="@string/auto_data_switch_summary"
settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/> settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>
<!-- Settings search is handled by RoamingSearchItem. -->
<com.android.settings.spa.preference.ComposePreference <com.android.settings.spa.preference.ComposePreference
android:key="button_roaming_key" android:key="button_roaming_key"
android:title="@string/roaming" settings:searchable="false"
settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/> settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>
<Preference <Preference

View File

@@ -50,7 +50,6 @@ public class BluetoothDetailsPairOtherController extends BluetoothDetailsControl
CachedBluetoothDevice device, CachedBluetoothDevice device,
Lifecycle lifecycle) { Lifecycle lifecycle) {
super(context, fragment, device, lifecycle); super(context, fragment, device, lifecycle);
lifecycle.addObserver(this);
} }
@Override @Override

View File

@@ -106,7 +106,6 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
mProfileManager = mManager.getProfileManager(); mProfileManager = mManager.getProfileManager();
mCachedDevice = device; mCachedDevice = device;
mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
lifecycle.addObserver(this);
} }
@Override @Override

View File

@@ -66,7 +66,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** PreferenceController to control the dialog to choose the active device for calls and alarms */ /** PreferenceController to control the dialog to choose the active device for calls and alarms */
public class AudioSharingCallAudioPreferenceController extends AudioSharingBasePreferenceController public class AudioSharingCallAudioPreferenceController extends AudioSharingBasePreferenceController
implements BluetoothCallback { implements BluetoothCallback {
private static final String TAG = "CallsAndAlarmsPreferenceController"; private static final String TAG = "CallAudioPrefController";
private static final String PREF_KEY = "calls_and_alarms"; private static final String PREF_KEY = "calls_and_alarms";
@VisibleForTesting @VisibleForTesting
@@ -85,7 +85,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
private final ContentObserver mSettingsObserver; private final ContentObserver mSettingsObserver;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
@Nullable private Fragment mFragment; @Nullable private Fragment mFragment;
Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>();
private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
@@ -210,17 +210,18 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
"Skip set fallback active device: unchanged"); "Skip set fallback active device: unchanged");
return; return;
} }
List<CachedBluetoothDevice> devices = List<BluetoothDevice> devices =
mGroupedConnectedDevices.getOrDefault( mGroupedConnectedDevices.getOrDefault(
item.getGroupId(), ImmutableList.of()); item.getGroupId(), ImmutableList.of());
CachedBluetoothDevice lead = CachedBluetoothDevice lead =
AudioSharingUtils.getLeadDevice(devices); AudioSharingUtils.getLeadDevice(
mCacheManager, devices);
if (lead != null) { if (lead != null) {
Log.d( Log.d(
TAG, TAG,
"Set fallback active device: " "Set fallback active device: "
+ lead.getDevice() + lead.getDevice()
.getAnonymizedAddress()); .getAnonymizedAddress());
lead.setActive(); lead.setActive();
logCallAudioDeviceChange(currentGroupId, lead); logCallAudioDeviceChange(currentGroupId, lead);
} else { } else {
@@ -347,8 +348,8 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
*/ */
private void updateSummary() { private void updateSummary() {
updateDeviceItemsInSharingSession(); updateDeviceItemsInSharingSession();
int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( int fallbackActiveGroupId =
mContext.getContentResolver()); BluetoothUtils.getPrimaryGroupIdForBroadcast(mContext.getContentResolver());
if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
for (AudioSharingDeviceItem item : mDeviceItemsInSharingSession) { for (AudioSharingDeviceItem item : mDeviceItemsInSharingSession) {
if (item.getGroupId() == fallbackActiveGroupId) { if (item.getGroupId() == fallbackActiveGroupId) {

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing; package com.android.settings.connecteddevice.audiosharing;
import static java.util.stream.Collectors.toList;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
@@ -38,6 +40,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -49,14 +52,14 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
public class AudioSharingDialogHandler { public class AudioSharingDialogHandler {
private static final String TAG = "AudioSharingDialogHandler"; private static final String TAG = "AudioSharingDlgHandler";
private final Context mContext; private final Context mContext;
private final Fragment mHostFragment; private final Fragment mHostFragment;
@Nullable private final LocalBluetoothManager mLocalBtManager; @Nullable private final LocalBluetoothManager mLocalBtManager;
@Nullable private final CachedBluetoothDeviceManager mDeviceManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast; @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -163,6 +166,7 @@ public class AudioSharingDialogHandler {
mContext = context; mContext = context;
mHostFragment = fragment; mHostFragment = fragment;
mLocalBtManager = Utils.getLocalBluetoothManager(context); mLocalBtManager = Utils.getLocalBluetoothManager(context);
mDeviceManager = mLocalBtManager != null ? mLocalBtManager.getCachedDeviceManager() : null;
mBroadcast = mBroadcast =
mLocalBtManager != null mLocalBtManager != null
? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile() ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile()
@@ -212,7 +216,7 @@ public class AudioSharingDialogHandler {
if (isBroadcasting) { if (isBroadcasting) {
// Show stop audio sharing dialog when an ineligible (non LE audio) remote device // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
// connected during a sharing session. // connected during a sharing session.
Map<Integer, List<CachedBluetoothDevice>> groupedDevices = Map<Integer, List<BluetoothDevice>> groupedDevices =
AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
List<AudioSharingDeviceItem> deviceItemsInSharingSession = List<AudioSharingDeviceItem> deviceItemsInSharingSession =
AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
@@ -256,19 +260,19 @@ public class AudioSharingDialogHandler {
@NonNull CachedBluetoothDevice cachedDevice, @NonNull CachedBluetoothDevice cachedDevice,
boolean isBroadcasting, boolean isBroadcasting,
boolean userTriggered) { boolean userTriggered) {
Map<Integer, List<CachedBluetoothDevice>> groupedDevices = Map<Integer, List<BluetoothDevice>> groupedDevices =
AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
BluetoothDevice btDevice = cachedDevice.getDevice(); BluetoothDevice btDevice = cachedDevice.getDevice();
String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress(); String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress();
int groupId = BluetoothUtils.getGroupId(cachedDevice);
if (isBroadcasting) { if (isBroadcasting) {
// If another device within the same is already in the sharing session, add source to // If another device within the same is already in the sharing session, add source to
// the device automatically. // the device automatically.
int groupId = BluetoothUtils.getGroupId(cachedDevice);
if (groupedDevices.containsKey(groupId) if (groupedDevices.containsKey(groupId)
&& groupedDevices.get(groupId).stream() && groupedDevices.get(groupId).stream()
.anyMatch( .anyMatch(
device -> device ->
BluetoothUtils.hasConnectedBroadcastSource( BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
device, mLocalBtManager))) { device, mLocalBtManager))) {
Log.d( Log.d(
TAG, TAG,
@@ -352,14 +356,17 @@ public class AudioSharingDialogHandler {
} else { } else {
// Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice. // Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice.
List<AudioSharingDeviceItem> deviceItems = new ArrayList<>(); List<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
for (List<CachedBluetoothDevice> devices : groupedDevices.values()) { for (Map.Entry<Integer, List<BluetoothDevice>> entry : groupedDevices.entrySet()) {
if (entry.getKey() == groupId) continue;
// Use random device in the group within the sharing session to represent the group. // Use random device in the group within the sharing session to represent the group.
CachedBluetoothDevice device = devices.get(0); for (BluetoothDevice device : entry.getValue()) {
if (BluetoothUtils.getGroupId(device) CachedBluetoothDevice cDevice =
== BluetoothUtils.getGroupId(cachedDevice)) { mDeviceManager != null ? mDeviceManager.findDevice(device) : null;
continue; if (cDevice != null) {
deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(cDevice));
break;
}
} }
deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
} }
// Show audio sharing join dialog when the second eligible (LE audio) remote // Show audio sharing join dialog when the second eligible (LE audio) remote
// device connect and no sharing session. // device connect and no sharing session.
@@ -368,13 +375,10 @@ public class AudioSharingDialogHandler {
new AudioSharingJoinDialogFragment.DialogEventListener() { new AudioSharingJoinDialogFragment.DialogEventListener() {
@Override @Override
public void onShareClick() { public void onShareClick() {
mTargetSinks = new ArrayList<>(); mTargetSinks =
for (List<CachedBluetoothDevice> devices : groupedDevices.values().stream()
groupedDevices.values()) { .flatMap(items -> items.stream())
for (CachedBluetoothDevice device : devices) { .collect(toList());
mTargetSinks.add(device.getDevice());
}
}
Log.d(TAG, "Start broadcast with sinks = " + mTargetSinks.size()); Log.d(TAG, "Start broadcast with sinks = " + mTargetSinks.size());
if (mBroadcast != null) { if (mBroadcast != null) {
mBroadcast.startPrivateBroadcast(); mBroadcast.startPrivateBroadcast();
@@ -493,7 +497,7 @@ public class AudioSharingDialogHandler {
} }
private void removeSourceForGroup( private void removeSourceForGroup(
int groupId, Map<Integer, List<CachedBluetoothDevice>> groupedDevices) { int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices) {
if (mAssistant == null) { if (mAssistant == null) {
Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId);
return; return;
@@ -503,8 +507,6 @@ public class AudioSharingDialogHandler {
return; return;
} }
groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream()
.map(CachedBluetoothDevice::getDevice)
.filter(Objects::nonNull)
.forEach( .forEach(
device -> { device -> {
for (BluetoothLeBroadcastReceiveState source : for (BluetoothLeBroadcastReceiveState source :
@@ -515,7 +517,7 @@ public class AudioSharingDialogHandler {
} }
private void addSourceForGroup( private void addSourceForGroup(
int groupId, Map<Integer, List<CachedBluetoothDevice>> groupedDevices) { int groupId, Map<Integer, List<BluetoothDevice>> groupedDevices) {
if (mBroadcast == null || mAssistant == null) { if (mBroadcast == null || mAssistant == null) {
Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId);
return; return;
@@ -525,8 +527,6 @@ public class AudioSharingDialogHandler {
return; return;
} }
groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream()
.map(CachedBluetoothDevice::getDevice)
.filter(Objects::nonNull)
.forEach( .forEach(
device -> device ->
mAssistant.addSource( mAssistant.addSource(

View File

@@ -48,7 +48,6 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -63,17 +62,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public class AudioSharingSwitchBarController extends BasePreferenceController public class AudioSharingSwitchBarController extends BasePreferenceController
implements DefaultLifecycleObserver, implements DefaultLifecycleObserver,
OnCheckedChangeListener, OnCheckedChangeListener,
LocalBluetoothProfileManager.ServiceListener { LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "AudioSharingSwitchBarCtl"; private static final String TAG = "AudioSharingSwitchCtlr";
private static final String PREF_KEY = "audio_sharing_main_switch"; private static final String PREF_KEY = "audio_sharing_main_switch";
interface OnAudioSharingStateChangedListener { interface OnAudioSharingStateChangedListener {
@@ -103,7 +100,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
private final Executor mExecutor; private final Executor mExecutor;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final OnAudioSharingStateChangedListener mListener; private final OnAudioSharingStateChangedListener mListener;
private Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>(); private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>();
private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
@VisibleForTesting IntentFilter mIntentFilter; @VisibleForTesting IntentFilter mIntentFilter;
@@ -341,8 +338,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
// FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in
// prod. We can turn off the flag for debug purpose. // prod. We can turn off the flag for debug purpose.
if (FeatureFlagUtils.isEnabled( if (FeatureFlagUtils.isEnabled(
mContext, mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)
&& mAssistant.getAllConnectedDevices().isEmpty()) { && mAssistant.getAllConnectedDevices().isEmpty()) {
// Pop up dialog to ask users to connect at least one lea buds before audio sharing. // Pop up dialog to ask users to connect at least one lea buds before audio sharing.
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
@@ -454,13 +451,11 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
mDeviceItemsForSharing = new ArrayList<>(deviceItems); mDeviceItemsForSharing = new ArrayList<>(deviceItems);
mTargetActiveSinks = new ArrayList<>(); mTargetActiveSinks = new ArrayList<>();
if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
for (CachedBluetoothDevice device : // If active device exists for audio sharing, share to it
// automatically once the broadcast is started.
mTargetActiveSinks =
mGroupedConnectedDevices.getOrDefault( mGroupedConnectedDevices.getOrDefault(
deviceItems.get(0).getGroupId(), ImmutableList.of())) { deviceItems.get(0).getGroupId(), ImmutableList.of());
// If active device exists for audio sharing, share to it
// automatically once the broadcast is started.
mTargetActiveSinks.add(device.getDevice());
}
mDeviceItemsForSharing.remove(0); mDeviceItemsForSharing.remove(0);
} }
if (mBroadcast != null) { if (mBroadcast != null) {
@@ -488,7 +483,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
boolean isStateReady = boolean isStateReady =
isBluetoothOn() isBluetoothOn()
&& AudioSharingUtils.isAudioSharingProfileReady( && AudioSharingUtils.isAudioSharingProfileReady(
mProfileManager); mProfileManager);
AudioSharingUtils.postOnMainThread( AudioSharingUtils.postOnMainThread(
mContext, mContext,
() -> { () -> {
@@ -541,12 +536,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
@Override @Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) { public void onItemClick(@NonNull AudioSharingDeviceItem item) {
AudioSharingUtils.addSourceToTargetSinks( AudioSharingUtils.addSourceToTargetSinks(
mGroupedConnectedDevices mGroupedConnectedDevices.getOrDefault(
.getOrDefault(item.getGroupId(), ImmutableList.of()) item.getGroupId(), ImmutableList.of()),
.stream()
.map(CachedBluetoothDevice::getDevice)
.filter(Objects::nonNull)
.collect(Collectors.toList()),
mBtManager); mBtManager);
mGroupedConnectedDevices.clear(); mGroupedConnectedDevices.clear();
mDeviceItemsForSharing.clear(); mDeviceItemsForSharing.clear();
@@ -575,8 +566,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
@NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) { @NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& (event.getContentChangeTypes() && (event.getContentChangeTypes()
& AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED) & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
!= 0) { != 0) {
Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED"); Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED");
return false; return false;
} }

View File

@@ -22,6 +22,8 @@ import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtil
import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID;
import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED;
import static java.util.stream.Collectors.toList;
import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
@@ -44,10 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.Objects;
public class AudioSharingUtils { public class AudioSharingUtils {
private static final String TAG = "AudioSharingUtils"; private static final String TAG = "AudioSharingUtils";
@@ -62,15 +65,15 @@ public class AudioSharingUtils {
} }
/** /**
* Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are * Fetch {@link BluetoothDevice}s connected to the broadcast assistant. The devices are grouped
* grouped by CSIP group id. * by CSIP group id.
* *
* @param localBtManager The BT manager to provide BT functions. * @param localBtManager The BT manager to provide BT functions.
* @return A map of connected devices grouped by CSIP group id. * @return A map of connected devices grouped by CSIP group id.
*/ */
public static Map<Integer, List<CachedBluetoothDevice>> fetchConnectedDevicesByGroupId( public static Map<Integer, List<BluetoothDevice>> fetchConnectedDevicesByGroupId(
@Nullable LocalBluetoothManager localBtManager) { @Nullable LocalBluetoothManager localBtManager) {
Map<Integer, List<CachedBluetoothDevice>> groupedDevices = new HashMap<>(); Map<Integer, List<BluetoothDevice>> groupedDevices = new HashMap<>();
if (localBtManager == null) { if (localBtManager == null) {
Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to bt manager is null"); Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to bt manager is null");
return groupedDevices; return groupedDevices;
@@ -99,7 +102,7 @@ public class AudioSharingUtils {
if (!groupedDevices.containsKey(groupId)) { if (!groupedDevices.containsKey(groupId)) {
groupedDevices.put(groupId, new ArrayList<>()); groupedDevices.put(groupId, new ArrayList<>());
} }
groupedDevices.get(groupId).add(cachedDevice); groupedDevices.get(groupId).add(device);
} }
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "fetchConnectedDevicesByGroupId: " + groupedDevices); Log.d(TAG, "fetchConnectedDevicesByGroupId: " + groupedDevices);
@@ -122,11 +125,16 @@ public class AudioSharingUtils {
*/ */
public static List<CachedBluetoothDevice> buildOrderedConnectedLeadDevices( public static List<CachedBluetoothDevice> buildOrderedConnectedLeadDevices(
@Nullable LocalBluetoothManager localBtManager, @Nullable LocalBluetoothManager localBtManager,
Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices, Map<Integer, List<BluetoothDevice>> groupedConnectedDevices,
boolean filterByInSharing) { boolean filterByInSharing) {
List<CachedBluetoothDevice> orderedDevices = new ArrayList<>(); List<CachedBluetoothDevice> orderedDevices = new ArrayList<>();
for (List<CachedBluetoothDevice> devices : groupedConnectedDevices.values()) { if (localBtManager == null) {
CachedBluetoothDevice leadDevice = getLeadDevice(devices); Log.d(TAG, "Skip buildOrderedConnectedLeadDevices due to bt manager is null");
return orderedDevices;
}
CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager();
for (List<BluetoothDevice> devices : groupedConnectedDevices.values()) {
CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices);
if (leadDevice == null) { if (leadDevice == null) {
Log.d(TAG, "Skip due to no lead device"); Log.d(TAG, "Skip due to no lead device");
continue; continue;
@@ -141,52 +149,39 @@ public class AudioSharingUtils {
} }
orderedDevices.add(leadDevice); orderedDevices.add(leadDevice);
} }
orderedDevices.sort( orderedDevices.sort(sCachedDeviceComparator);
(CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> {
// Active above not inactive
int comparison =
(isActiveLeAudioDevice(d2) ? 1 : 0)
- (isActiveLeAudioDevice(d1) ? 1 : 0);
if (comparison != 0) return comparison;
// Bonded above not bonded
comparison =
(d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0)
- (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
if (comparison != 0) return comparison;
// Bond timestamp available above unavailable
comparison =
(d2.getBondTimestamp() != null ? 1 : 0)
- (d1.getBondTimestamp() != null ? 1 : 0);
if (comparison != 0) return comparison;
// Order by bond timestamp if it is available
// Otherwise order by device name
return d1.getBondTimestamp() != null
? d1.getBondTimestamp().compareTo(d2.getBondTimestamp())
: d1.getName().compareTo(d2.getName());
});
return orderedDevices; return orderedDevices;
} }
/** /**
* Get the lead device from a list of devices with same group id. * Get the lead device from a list of devices with same group id.
* *
* @param deviceManager CachedBluetoothDeviceManager
* @param devices A list of devices with same group id. * @param devices A list of devices with same group id.
* @return The lead device * @return The lead device
*/ */
@Nullable @Nullable
public static CachedBluetoothDevice getLeadDevice( public static CachedBluetoothDevice getLeadDevice(
@NonNull List<CachedBluetoothDevice> devices) { @Nullable CachedBluetoothDeviceManager deviceManager,
if (devices.isEmpty()) return null; @NonNull List<BluetoothDevice> devices) {
for (CachedBluetoothDevice device : devices) { if (deviceManager == null || devices.isEmpty()) return null;
if (!device.getMemberDevice().isEmpty()) { List<CachedBluetoothDevice> cachedDevices =
return device; devices.stream()
.map(device -> deviceManager.findDevice(device))
.filter(Objects::nonNull)
.collect(toList());
for (CachedBluetoothDevice cachedDevice : cachedDevices) {
if (!cachedDevice.getMemberDevice().isEmpty()) {
return cachedDevice;
} }
} }
CachedBluetoothDevice leadDevice = devices.get(0); CachedBluetoothDevice leadDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0);
Log.d( Log.d(
TAG, TAG,
"No lead device in the group, pick arbitrary device as the lead: " "No lead device in the group, pick arbitrary device as the lead: "
+ leadDevice.getDevice().getAnonymizedAddress()); + (leadDevice == null
? "null"
: leadDevice.getDevice().getAnonymizedAddress()));
return leadDevice; return leadDevice;
} }
@@ -206,13 +201,13 @@ public class AudioSharingUtils {
@NonNull @NonNull
public static List<AudioSharingDeviceItem> buildOrderedConnectedLeadAudioSharingDeviceItem( public static List<AudioSharingDeviceItem> buildOrderedConnectedLeadAudioSharingDeviceItem(
@Nullable LocalBluetoothManager localBtManager, @Nullable LocalBluetoothManager localBtManager,
Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices, Map<Integer, List<BluetoothDevice>> groupedConnectedDevices,
boolean filterByInSharing) { boolean filterByInSharing) {
return buildOrderedConnectedLeadDevices( return buildOrderedConnectedLeadDevices(
localBtManager, groupedConnectedDevices, filterByInSharing) localBtManager, groupedConnectedDevices, filterByInSharing)
.stream() .stream()
.map(AudioSharingUtils::buildAudioSharingDeviceItem) .map(AudioSharingUtils::buildAudioSharingDeviceItem)
.collect(Collectors.toList()); .collect(toList());
} }
/** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
@@ -361,4 +356,27 @@ public class AudioSharingUtils {
Pair.create(METRIC_KEY_CANDIDATE_DEVICE_COUNT.ordinal(), candidateDeviceCount) Pair.create(METRIC_KEY_CANDIDATE_DEVICE_COUNT.ordinal(), candidateDeviceCount)
}; };
} }
private static final Comparator<CachedBluetoothDevice> sCachedDeviceComparator =
(CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> {
// Active above not inactive
int comparison =
(isActiveLeAudioDevice(d2) ? 1 : 0) - (isActiveLeAudioDevice(d1) ? 1 : 0);
if (comparison != 0) return comparison;
// Bonded above not bonded
comparison =
(d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0)
- (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
if (comparison != 0) return comparison;
// Bond timestamp available above unavailable
comparison =
(d2.getBondTimestamp() != null ? 1 : 0)
- (d1.getBondTimestamp() != null ? 1 : 0);
if (comparison != 0) return comparison;
// Order by bond timestamp if it is available
// Otherwise order by device name
return d1.getBondTimestamp() != null
? d1.getBondTimestamp().compareTo(d2.getBondTimestamp())
: d1.getName().compareTo(d2.getName());
};
} }

View File

@@ -23,6 +23,7 @@ import com.android.settings.R
import com.android.settings.network.SubscriptionUtil import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem
import com.android.settings.network.telephony.NrAdvancedCallingPreferenceController.Companion.NrAdvancedCallingSearchItem import com.android.settings.network.telephony.NrAdvancedCallingPreferenceController.Companion.NrAdvancedCallingSearchItem
import com.android.settings.network.telephony.RoamingPreferenceController.Companion.RoamingSearchItem
import com.android.settings.spa.SpaSearchLanding.BundleValue import com.android.settings.spa.SpaSearchLanding.BundleValue
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
@@ -111,6 +112,7 @@ class MobileNetworkSettingsSearchIndex(
fun createSearchItems(context: Context): List<MobileNetworkSettingsSearchItem> = fun createSearchItems(context: Context): List<MobileNetworkSettingsSearchItem> =
listOf( listOf(
RoamingSearchItem(context),
MmsMessageSearchItem(context), MmsMessageSearchItem(context),
NrAdvancedCallingSearchItem(context), NrAdvancedCallingSearchItem(context),
) )

View File

@@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
@@ -47,6 +48,7 @@ constructor(
private var telephonyManager = context.getSystemService(TelephonyManager::class.java)!! private var telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
private val carrierConfigRepository = CarrierConfigRepository(context) private val carrierConfigRepository = CarrierConfigRepository(context)
private val roamingSearchItem = RoamingSearchItem(context)
fun init(fragmentManager: FragmentManager, subId: Int) { fun init(fragmentManager: FragmentManager, subId: Int) {
this.fragmentManager = fragmentManager this.fragmentManager = fragmentManager
@@ -54,14 +56,8 @@ constructor(
telephonyManager = telephonyManager.createForSubscriptionId(subId) telephonyManager = telephonyManager.createForSubscriptionId(subId)
} }
override fun getAvailabilityStatus(): Int { override fun getAvailabilityStatus() =
if (!SubscriptionManager.isValidSubscriptionId(subId)) return CONDITIONALLY_UNAVAILABLE if (roamingSearchItem.isAvailable(subId)) AVAILABLE else CONDITIONALLY_UNAVAILABLE
val isForceHomeNetwork =
carrierConfigRepository.getBoolean(
subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)
return if (isForceHomeNetwork) CONDITIONALLY_UNAVAILABLE else AVAILABLE
}
@Composable @Composable
override fun Content() { override fun Content() {
@@ -101,5 +97,17 @@ constructor(
companion object { companion object {
private const val DIALOG_TAG = "MobileDataDialog" private const val DIALOG_TAG = "MobileDataDialog"
class RoamingSearchItem(context: Context) : MobileNetworkSettingsSearchItem {
override val key = "button_roaming_key"
override val title: String = context.getString(R.string.roaming)
private val carrierConfigRepository = CarrierConfigRepository(context)
override fun isAvailable(subId: Int): Boolean =
SubscriptionManager.isValidSubscriptionId(subId) &&
!carrierConfigRepository.getBoolean(
subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)
}
} }
} }

View File

@@ -116,7 +116,7 @@ public final class PasswordUtils extends com.android.settingslib.Utils {
final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams( final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
lp.leftMargin = layoutTitleParams.leftMargin; lp.setMarginStart(layoutTitleParams.leftMargin);
lp.topMargin = (int) context.getResources().getDimensionPixelSize( lp.topMargin = (int) context.getResources().getDimensionPixelSize(
R.dimen.screen_lock_options_button_margin_top); R.dimen.screen_lock_options_button_margin_top);
optButton.setPadding(0, 0, 0, 0); optButton.setPadding(0, 0, 0, 0);

View File

@@ -0,0 +1,99 @@
/*
* 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.privatespace;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.text.util.Linkify;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import java.util.regex.Pattern;
public class PrivateProfileCreationRestrictedError extends InstrumentedFragment {
private static final String TAG = "PrivateSpaceCreationErr";
@NonNull
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
GlifLayout rootView =
(GlifLayout)
inflater.inflate(R.layout.privatespace_creation_error, container, false);
final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getContext())
.setText(R.string.private_space_exit_label)
.setListener(onExit())
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build());
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@Override
public void handleOnBackPressed() {
// Handle the back button event. We intentionally don't want to allow back
// button to work in this screen during the setup flow.
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
rootView.setDescriptionText(R.string.private_space_error_description);
TextView textView = rootView.getDescriptionTextView();
Pattern pattern = Pattern.compile(getString(R.string.private_space_error_causes_text));
Linkify.addLinks(
textView,
pattern,
getString(R.string.private_space_learn_more_url));
return rootView;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.PRIVATE_SPACE_SETUP_SPACE_CREATION_ERROR;
}
private View.OnClickListener onExit() {
return v -> {
Activity activity = getActivity();
if (activity != null) {
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_CANCEL_CREATE_SPACE);
Log.i(TAG, "private space setup exited");
activity.finish();
}
};
}
}

View File

@@ -29,6 +29,7 @@ import android.net.NetworkInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.UserManager;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -49,6 +50,7 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment {
private static final String TAG = "PrivateSpaceCreateFrag"; private static final String TAG = "PrivateSpaceCreateFrag";
private static final int PRIVATE_SPACE_CREATE_POST_DELAY_MS = 1000; private static final int PRIVATE_SPACE_CREATE_POST_DELAY_MS = 1000;
private static final int PRIVATE_SPACE_ACCOUNT_LOGIN_POST_DELAY_MS = 5000; private static final int PRIVATE_SPACE_ACCOUNT_LOGIN_POST_DELAY_MS = 5000;
private static final int PRIVATE_SPACE_SETUP_NO_ERROR = 0;
private static final Handler sHandler = new Handler(Looper.getMainLooper()); private static final Handler sHandler = new Handler(Looper.getMainLooper());
private Runnable mRunnable = private Runnable mRunnable =
() -> { () -> {
@@ -122,6 +124,11 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment {
Log.i(TAG, "Private Space created"); Log.i(TAG, "Private Space created");
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, true); getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, true);
if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) {
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_ERRORS,
PRIVATE_SPACE_SETUP_NO_ERROR);
}
if (isConnectedToInternet()) { if (isConnectedToInternet()) {
registerReceiver(); registerReceiver();
sHandler.postDelayed( sHandler.postDelayed(
@@ -132,8 +139,18 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment {
} }
} else { } else {
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, false); getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED,
showPrivateSpaceErrorScreen(); false);
if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) {
int errorCode = PrivateSpaceMaintainer.getInstance(
getActivity()).getPrivateSpaceCreateError();
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_ERRORS,
errorCode);
showPrivateSpaceErrorScreen(errorCode);
} else {
showPrivateSpaceErrorScreen();
}
} }
} }
@@ -147,6 +164,16 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment {
.navigate(R.id.action_create_profile_error); .navigate(R.id.action_create_profile_error);
} }
private void showPrivateSpaceErrorScreen(int errorCode) {
if (errorCode == UserManager.USER_OPERATION_ERROR_USER_RESTRICTED
|| errorCode == UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE) {
NavHostFragment.findNavController(PrivateSpaceCreationFragment.this)
.navigate(R.id.action_create_profile_error_restrict);
} else {
showPrivateSpaceErrorScreen();
}
}
/** Returns true if device has an active internet connection, false otherwise. */ /** Returns true if device has an active internet connection, false otherwise. */
private boolean isConnectedToInternet() { private boolean isConnectedToInternet() {
ConnectivityManager cm = ConnectivityManager cm =

View File

@@ -60,6 +60,7 @@ public class PrivateSpaceMaintainer {
private final Context mContext; private final Context mContext;
private final UserManager mUserManager; private final UserManager mUserManager;
private final ActivityManager mActivityManager; private final ActivityManager mActivityManager;
private int mErrorCode;
@GuardedBy("this") @GuardedBy("this")
private UserHandle mUserHandle; private UserHandle mUserHandle;
private final KeyguardManager mKeyguardManager; private final KeyguardManager mKeyguardManager;
@@ -111,6 +112,9 @@ public class PrivateSpaceMaintainer {
userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>()); userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>());
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error creating private space", e); Log.e(TAG, "Error creating private space", e);
if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) {
mErrorCode = ((UserManager.UserOperationException) e).getUserOperationResult();
}
return false; return false;
} }
@@ -312,6 +316,11 @@ public class PrivateSpaceMaintainer {
return mUserManager.canAddPrivateProfile() || doesPrivateSpaceExist(); return mUserManager.canAddPrivateProfile() || doesPrivateSpaceExist();
} }
/** Returns the error code for private space creation failure*/
public int getPrivateSpaceCreateError() {
return mErrorCode;
}
/** Returns true if private space exists and is running, otherwise returns false */ /** Returns true if private space exists and is running, otherwise returns false */
@VisibleForTesting @VisibleForTesting
synchronized boolean isPrivateProfileRunning() { synchronized boolean isPrivateProfileRunning() {

View File

@@ -42,6 +42,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
/** Controller that controls whether the WEP network can be connected. */ /** Controller that controls whether the WEP network can be connected. */
class WepNetworksPreferenceController(context: Context, preferenceKey: String) : class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
@@ -49,68 +51,74 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
var wifiManager = context.getSystemService(WifiManager::class.java)!! var wifiManager = context.getSystemService(WifiManager::class.java)!!
override fun getAvailabilityStatus() = if (Flags.androidVWifiApi()) AVAILABLE override fun getAvailabilityStatus() =
else UNSUPPORTED_ON_DEVICE if (Flags.androidVWifiApi()) AVAILABLE else UNSUPPORTED_ON_DEVICE
@Composable @Composable
override fun Content() { override fun Content() {
val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null) val isWepSupported: Boolean? =
isWepSupportedFlow.collectAsStateWithLifecycle(initialValue = null).value
val isWepAllowed: Boolean? =
wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null).value
var openDialog by rememberSaveable { mutableStateOf(false) } var openDialog by rememberSaveable { mutableStateOf(false) }
val wifiInfo = wifiManager.connectionInfo SwitchPreference(
SwitchPreference(object : SwitchPreferenceModel { object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks) override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary() } override val summary = { getSummary(isWepSupported) }
override val checked = { checked } override val checked = {
override val changeable: () -> Boolean if (isWepSupported == true) isWepAllowed else isWepSupported
get() = { carrierAllowed }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
openDialog = true
} else {
wifiManager.setWepAllowed(newChecked)
wepAllowedFlow.override(newChecked)
} }
} override val changeable: () -> Boolean
}) get() = { isWepSupported == true }
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)
}
}
})
if (openDialog) { if (openDialog) {
SettingsAlertDialogWithIcon( SettingsAlertDialogWithIcon(
onDismissRequest = { openDialog = false }, onDismissRequest = { openDialog = false },
confirmButton = AlertDialogButton( confirmButton =
stringResource(R.string.sim_action_yes) AlertDialogButton(stringResource(R.string.sim_action_yes)) {
) { wifiManager.setWepAllowed(false)
wifiManager.setWepAllowed(false) wepAllowedFlow.override(false)
wepAllowedFlow.override(false) openDialog = false
openDialog = false },
},
dismissButton = dismissButton =
AlertDialogButton( AlertDialogButton(stringResource(R.string.wifi_cancel)) { openDialog = false },
stringResource(R.string.wifi_cancel)
) { openDialog = false },
title = stringResource(R.string.wifi_settings_wep_networks_disconnect_title), title = stringResource(R.string.wifi_settings_wep_networks_disconnect_title),
text = { text = {
Text( Text(
stringResource(R.string.wifi_settings_wep_networks_disconnect_summary), stringResource(R.string.wifi_settings_wep_networks_disconnect_summary),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center textAlign = TextAlign.Center,
) )
}) })
} }
} }
override fun getSummary(): String = mContext.getString( private fun getSummary(isWepSupported: Boolean?): String =
if (carrierAllowed) { mContext.getString(
R.string.wifi_allow_wep_networks_summary when (isWepSupported) {
} else { true -> R.string.wifi_allow_wep_networks_summary
R.string.wifi_allow_wep_networks_summary_carrier_not_allow false -> R.string.wifi_allow_wep_networks_summary_carrier_not_allow
} null -> R.string.summary_placeholder
) })
private val carrierAllowed: Boolean private val isWepSupportedFlow =
get() = wifiManager.isWepSupported flow { emit(wifiManager.isWepSupported) }.flowOn(Dispatchers.Default)
val wepAllowedFlow = OverridableFlow(callbackFlow { val wepAllowedFlow =
wifiManager.queryWepAllowed(Dispatchers.Default.asExecutor(), ::trySend) OverridableFlow(
callbackFlow {
wifiManager.queryWepAllowed(Dispatchers.Default.asExecutor(), ::trySend)
awaitClose { } awaitClose {}
}) })
} }

View File

@@ -23,7 +23,6 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
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.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;

View File

@@ -37,45 +37,32 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule 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.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class BackgroundInstalledAppsPageProviderTest { class BackgroundInstalledAppsPageProviderTest {
@get:Rule @get:Rule val composeTestRule = createComposeRule()
val composeTestRule = createComposeRule()
@get:Rule private val mockPackageManager = mock<PackageManager>()
val mockito: MockitoRule = MockitoJUnit.rule()
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { packageManager } doReturn mockPackageManager
}
@Mock private val mockBackgroundInstallControlService = mock<IBackgroundInstallControlService>()
private lateinit var mockContext: Context
@Mock
private lateinit var mockPackageManager: PackageManager
@Mock
private lateinit var mockBackgroundInstallControlService: IBackgroundInstallControlService
private var packageInfoFlagsCaptor = argumentCaptor<PackageManager.PackageInfoFlags>()
private val fakeNavControllerWrapper = FakeNavControllerWrapper() private val fakeNavControllerWrapper = FakeNavControllerWrapper()
@Before
fun setup() {
whenever(mockContext.packageManager).thenReturn(mockPackageManager)
}
@Test @Test
fun allAppListPageProvider_name() { fun allAppListPageProvider_name() {
assertThat(BackgroundInstalledAppsPageProvider.name) assertThat(BackgroundInstalledAppsPageProvider.name)
@@ -108,7 +95,7 @@ class BackgroundInstalledAppsPageProviderTest {
setInjectEntry(false) setInjectEntry(false)
composeTestRule.onNodeWithText("0 apps").assertIsDisplayed() composeTestRule.waitUntilExists(hasText("0 apps"))
} }
@Test @Test
@@ -200,7 +187,8 @@ class BackgroundInstalledAppsPageProviderTest {
@Test @Test
fun backgroundInstalledAppsWithGroupingListModel_transform() = runTest { fun backgroundInstalledAppsWithGroupingListModel_transform() = runTest {
val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext) val packageInfoFlagsCaptor = argumentCaptor<PackageManager.PackageInfoFlags>()
val listModel = BackgroundInstalledAppsWithGroupingListModel(context)
whenever(mockPackageManager.getPackageInfoAsUser( whenever(mockPackageManager.getPackageInfoAsUser(
eq(TEST_PACKAGE_NAME), eq(TEST_PACKAGE_NAME),
packageInfoFlagsCaptor.capture(), packageInfoFlagsCaptor.capture(),
@@ -218,7 +206,7 @@ class BackgroundInstalledAppsPageProviderTest {
@Test @Test
fun backgroundInstalledAppsWithGroupingListModel_filter() = runTest { fun backgroundInstalledAppsWithGroupingListModel_filter() = runTest {
val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext) val listModel = BackgroundInstalledAppsWithGroupingListModel(context)
listModel.setBackgroundInstallControlService(mockBackgroundInstallControlService) listModel.setBackgroundInstallControlService(mockBackgroundInstallControlService)
whenever(mockBackgroundInstallControlService.getBackgroundInstalledPackages( whenever(mockBackgroundInstallControlService.getBackgroundInstalledPackages(
PackageManager.MATCH_ALL.toLong(), PackageManager.MATCH_ALL.toLong(),

View File

@@ -17,6 +17,7 @@
package com.android.settings.wifi package com.android.settings.wifi
import android.content.Context import android.content.Context
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import androidx.compose.ui.test.assertIsOff import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.assertIsOn
@@ -30,7 +31,6 @@ import androidx.preference.PreferenceManager
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.settings.R import com.android.settings.R
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.spa.preference.ComposePreference import com.android.settings.spa.preference.ComposePreference
import com.android.settingslib.spa.testutils.onDialogText import com.android.settingslib.spa.testutils.onDialogText
import com.android.wifitrackerlib.WifiEntry import com.android.wifitrackerlib.WifiEntry
@@ -45,29 +45,31 @@ import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.spy import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class WepNetworksPreferenceControllerTest { class WepNetworksPreferenceControllerTest {
@get:Rule @get:Rule val composeTestRule = createComposeRule()
val composeTestRule = createComposeRule()
private var wepAllowed = true private var wepAllowed = true
private var mockWifiInfo = mock<android.net.wifi.WifiInfo> { private var mockWifiInfo =
on { it.currentSecurityType } doReturn WifiEntry.SECURITY_EAP mock<WifiInfo> {
on { it.ssid } doReturn SSID on { currentSecurityType } doReturn WifiEntry.SECURITY_EAP
} on { ssid } doReturn SSID
}
private var mockWifiManager = mock<WifiManager> {
on { queryWepAllowed(any(), any()) } doAnswer { private var mockWifiManager =
@Suppress("UNCHECKED_CAST") mock<WifiManager> {
val consumer = it.arguments[1] as Consumer<Boolean> on { queryWepAllowed(any(), any()) } doAnswer
consumer.accept(wepAllowed) {
@Suppress("UNCHECKED_CAST") val consumer = it.arguments[1] as Consumer<Boolean>
consumer.accept(wepAllowed)
}
on { isWepSupported } doReturn true
on { connectionInfo } doReturn mockWifiInfo
} }
on { it.isWepSupported } doReturn true
on { it.connectionInfo } doReturn mockWifiInfo
}
private var context: Context = private var context: Context =
spy(ApplicationProvider.getApplicationContext()) { spy(ApplicationProvider.getApplicationContext()) {
@@ -85,74 +87,101 @@ class WepNetworksPreferenceControllerTest {
} }
@Test @Test
fun wepAllowedTrue_turnOn() { fun isChecked_wepSupportedAndAllowed_isOn() {
mockWifiManager.stub { on { isWepSupported } doReturn true }
wepAllowed = true wepAllowed = true
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) composeTestRule.setContent { controller.Content() }
composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn() .assertIsOn()
} }
@Test @Test
fun wepAllowedFalse_turnOff() { fun isChecked_wepSupportedAndNotAllowed_isOff() {
mockWifiManager.stub { on { isWepSupported } doReturn true }
wepAllowed = false wepAllowed = false
composeTestRule.setContent {
controller.Content()
}
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) composeTestRule.setContent { controller.Content() }
composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
@Test
fun isChecked_wepNotSupportedAndAllowed_isOff() {
mockWifiManager.stub { on { isWepSupported } doReturn false }
wepAllowed = true
composeTestRule.setContent { controller.Content() }
composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
@Test
fun isChecked_wepNotSupportedAndNotAllowed_isOff() {
mockWifiManager.stub { on { isWepSupported } doReturn false }
wepAllowed = false
composeTestRule.setContent { controller.Content() }
composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff() .assertIsOff()
} }
@Test @Test
fun onClick_turnOn() { fun onClick_turnOn() {
wepAllowed = false wepAllowed = false
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn() .assertIsOn()
} }
@Test @Test
fun onClick_turnOff() { fun onClick_turnOff() {
wepAllowed = true wepAllowed = true
composeTestRule.setContent { composeTestRule.setContent { controller.Content() }
controller.Content()
}
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) composeTestRule
.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff() .assertIsOff()
} }
@Test @Test
fun whenClick_wepAllowed_openDialog() { fun whenClick_wepAllowed_openDialog() {
wepAllowed = true wepAllowed = true
Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP) mockWifiInfo.stub {
composeTestRule.setContent { on { currentSecurityType } doReturn WifiEntry.SECURITY_WEP
controller.Content()
} }
composeTestRule.setContent { controller.Content() }
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text)) composeTestRule
.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
.isDisplayed() .isDisplayed()
} }
@Test @Test
fun whenClick_wepDisallowed_openDialog() { fun whenClick_wepDisallowed_openDialog() {
wepAllowed = false wepAllowed = false
Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP) mockWifiInfo.stub {
composeTestRule.setContent { on { currentSecurityType } doReturn WifiEntry.SECURITY_WEP
controller.Content()
} }
composeTestRule.setContent { controller.Content() }
composeTestRule.onRoot().performClick() composeTestRule.onRoot().performClick()
composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text)) composeTestRule
.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
.isNotDisplayed() .isNotDisplayed()
} }
@@ -160,4 +189,4 @@ class WepNetworksPreferenceControllerTest {
const val TEST_KEY = "test_key" const val TEST_KEY = "test_key"
const val SSID = "ssid" const val SSID = "ssid"
} }
} }

View File

@@ -17,13 +17,11 @@
package com.android.settings.localepicker; package com.android.settings.localepicker;
import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.os.Looper; import android.os.Looper;
@@ -66,14 +64,6 @@ public class TermsOfAddressCategoryControllerTest {
Locale.setDefault(mCacheLocale); Locale.setDefault(mCacheLocale);
} }
@Test
public void getAvailabilityStatus_returnUnavailable() {
Locale.setDefault(Locale.forLanguageTag("fr-CA"));
assertThat(mTermsOfAddressCategoryController.getAvailabilityStatus()).isEqualTo(
CONDITIONALLY_UNAVAILABLE);
}
@Test @Test
public void getAvailabilityStatus_returnAvailable() { public void getAvailabilityStatus_returnAvailable() {
Locale.setDefault(Locale.forLanguageTag("fr-FR")); Locale.setDefault(Locale.forLanguageTag("fr-FR"));