Snap for 12391343 from 6c584d7f42 to 24Q4-release
Change-Id: I7adbc4e226402d8fe76ca61f1ef6e6b84e9c5e66
This commit is contained in:
@@ -5362,6 +5362,14 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".shortcut.ShortcutsUpdateReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- This is the longest AndroidManifest.xml ever. -->
|
<!-- This is the longest AndroidManifest.xml ever. -->
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="960"
|
||||||
android:tint="?attr/colorControlNormal">
|
android:tint="?android:attr/colorControlNormal">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M487,600L392,505L346,551L376,580Q399,603 399,637Q399,671 376,694L333,737Q310,760 276.5,760Q243,760 220,737L103,620Q80,597 80,563.5Q80,530 103,507L146,464Q169,441 203,441Q237,441 260,464L289,494L335,448L160,273L273,160L448,335L493,290L464,260Q441,237 441,203Q441,169 464,146L507,103Q530,80 563.5,80Q597,80 620,103L737,220Q760,243 760,276.5Q760,310 737,333L694,376Q671,399 637,399Q603,399 580,376L550,347L505,392L600,487L487,600ZM520,880L520,800Q637,800 718.5,718.5Q800,637 800,520L880,520Q880,595 851.5,660.5Q823,726 774.5,774.5Q726,823 660.5,851.5Q595,880 520,880ZM520,720L520,640Q570,640 605,605Q640,570 640,520L720,520Q720,603 661.5,661.5Q603,720 520,720ZM520,203L550,233L593,190L563,160Q563,160 563,160Q563,160 563,160L520,203Q520,203 520,203Q520,203 520,203ZM160,563L190,593L233,550L203,520Q203,520 203,520Q203,520 203,520L160,563Q160,563 160,563Q160,563 160,563ZM637,320L680,277Q680,277 680,277Q680,277 680,277L650,247L607,290L637,320Q637,320 637,320Q637,320 637,320ZM277,680L320,637Q320,637 320,637Q320,637 320,637L290,607L247,650L277,680Q277,680 277,680Q277,680 277,680Z"/>
|
android:pathData="M487,600L392,505L346,551L376,580Q399,603 399,637Q399,671 376,694L333,737Q310,760 276.5,760Q243,760 220,737L103,620Q80,597 80,563.5Q80,530 103,507L146,464Q169,441 203,441Q237,441 260,464L289,494L335,448L160,273L273,160L448,335L493,290L464,260Q441,237 441,203Q441,169 464,146L507,103Q530,80 563.5,80Q597,80 620,103L737,220Q760,243 760,276.5Q760,310 737,333L694,376Q671,399 637,399Q603,399 580,376L550,347L505,392L600,487L487,600ZM520,880L520,800Q637,800 718.5,718.5Q800,637 800,520L880,520Q880,595 851.5,660.5Q823,726 774.5,774.5Q726,823 660.5,851.5Q595,880 520,880ZM520,720L520,640Q570,640 605,605Q640,570 640,520L720,520Q720,603 661.5,661.5Q603,720 520,720ZM520,203L550,233L593,190L563,160Q563,160 563,160Q563,160 563,160L520,203Q520,203 520,203Q520,203 520,203ZM160,563L190,593L233,550L203,520Q203,520 203,520Q203,520 203,520L160,563Q160,563 160,563Q160,563 160,563ZM637,320L680,277Q680,277 680,277Q680,277 680,277L650,247L607,290L637,320Q637,320 637,320Q637,320 637,320ZM277,680L320,637Q320,637 320,637Q320,637 320,637L290,607L247,650L277,680Q277,680 277,680Q277,680 277,680Z"/>
|
||||||
|
|||||||
@@ -8030,10 +8030,10 @@
|
|||||||
<string name="spatial_audio_multi_toggle_off">Off</string>
|
<string name="spatial_audio_multi_toggle_off">Off</string>
|
||||||
|
|
||||||
<!-- Bluetooth device details: spatial audio is on. [CHAR LIMIT=20]-->
|
<!-- Bluetooth device details: spatial audio is on. [CHAR LIMIT=20]-->
|
||||||
<string name="spatial_audio_multi_toggle_on">Off</string>
|
<string name="spatial_audio_multi_toggle_on">Fixed</string>
|
||||||
|
|
||||||
<!-- Bluetooth device details: head tracking is on. [CHAR LIMIT=20]-->
|
<!-- Bluetooth device details: head tracking is on. [CHAR LIMIT=20]-->
|
||||||
<string name="spatial_audio_multi_toggle_head_tracking_on">Off</string>
|
<string name="spatial_audio_multi_toggle_head_tracking_on">Head Tracking</string>
|
||||||
|
|
||||||
<!-- Zen Modes: Summary for the Do not Disturb option that describes how many automatic rules (schedules) are enabled [CHAR LIMIT=NONE]-->
|
<!-- Zen Modes: Summary for the Do not Disturb option that describes how many automatic rules (schedules) are enabled [CHAR LIMIT=NONE]-->
|
||||||
<string name="zen_mode_settings_schedules_summary">
|
<string name="zen_mode_settings_schedules_summary">
|
||||||
@@ -8249,8 +8249,7 @@
|
|||||||
<!-- Do not disturb: device effect option, not first in list [CHAR LIMIT=80] -->
|
<!-- Do not disturb: device effect option, not first in list [CHAR LIMIT=80] -->
|
||||||
<string name="mode_dark_theme_title_secondary_list">enable dark theme</string>
|
<string name="mode_dark_theme_title_secondary_list">enable dark theme</string>
|
||||||
<!-- Do not disturb: device effect summary [CHAR LIMIT=NONE] -->
|
<!-- Do not disturb: device effect summary [CHAR LIMIT=NONE] -->
|
||||||
<string name="mode_dark_theme_summary">Switch the OS and apps to prefer light text on a dark
|
<string name="mode_dark_theme_summary">Switch the device theme to use a dark background, which is easier on the eyes</string>
|
||||||
background, which may be easier on the eyes and confers significant battery savings on some devices</string>
|
|
||||||
<!-- [CHAR LIMIT=NONE] Zen mode settings: Summary for sound interruption settings -->
|
<!-- [CHAR LIMIT=NONE] Zen mode settings: Summary for sound interruption settings -->
|
||||||
<string name="mode_display_settings_summary">
|
<string name="mode_display_settings_summary">
|
||||||
{count, plural, offset:2
|
{count, plural, offset:2
|
||||||
|
|||||||
@@ -52,9 +52,8 @@
|
|||||||
android:order="-15"
|
android:order="-15"
|
||||||
settings:keywords="@string/keywords_more_mobile_networks"
|
settings:keywords="@string/keywords_more_mobile_networks"
|
||||||
settings:userRestriction="no_config_mobile_networks"
|
settings:userRestriction="no_config_mobile_networks"
|
||||||
settings:isPreferenceVisible="@bool/config_show_sim_info"
|
|
||||||
settings:useAdminDisabledSummary="true"
|
settings:useAdminDisabledSummary="true"
|
||||||
settings:searchable="@bool/config_show_sim_info"/>
|
settings:controller="com.android.settings.network.MobileNetworkSummaryController" />
|
||||||
|
|
||||||
<com.android.settingslib.RestrictedSwitchPreference
|
<com.android.settingslib.RestrictedSwitchPreference
|
||||||
android:key="airplane_mode"
|
android:key="airplane_mode"
|
||||||
|
|||||||
@@ -16,17 +16,19 @@
|
|||||||
|
|
||||||
package com.android.settings.backup;
|
package com.android.settings.backup;
|
||||||
|
|
||||||
|
|
||||||
import android.app.backup.BackupAgentHelper;
|
import android.app.backup.BackupAgentHelper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.settings.flags.Flags;
|
import com.android.settings.flags.Flags;
|
||||||
import com.android.settings.onboarding.OnboardingFeatureProvider;
|
import com.android.settings.onboarding.OnboardingFeatureProvider;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.shortcut.CreateShortcutPreferenceController;
|
import com.android.settings.shortcut.ShortcutsUpdater;
|
||||||
import com.android.settingslib.datastore.BackupRestoreStorageManager;
|
import com.android.settingslib.datastore.BackupRestoreStorageManager;
|
||||||
|
|
||||||
/** Backup agent for Settings APK */
|
/** Backup agent for Settings APK */
|
||||||
public class SettingsBackupHelper extends BackupAgentHelper {
|
public class SettingsBackupHelper extends BackupAgentHelper {
|
||||||
|
private static final String TAG = "SettingsBackupHelper";
|
||||||
|
|
||||||
public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
|
public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
|
||||||
public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER =
|
public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER =
|
||||||
"AccessibilityAppearanceSettingsBackup";
|
"AccessibilityAppearanceSettingsBackup";
|
||||||
@@ -58,6 +60,10 @@ public class SettingsBackupHelper extends BackupAgentHelper {
|
|||||||
public void onRestoreFinished() {
|
public void onRestoreFinished() {
|
||||||
super.onRestoreFinished();
|
super.onRestoreFinished();
|
||||||
BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
|
BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
|
||||||
CreateShortcutPreferenceController.updateRestoredShortcuts(this);
|
try {
|
||||||
|
ShortcutsUpdater.updatePinnedShortcuts(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error updating shortcuts after restoring backup", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
|
|||||||
} else if (mShouldUseReverseLandscape) {
|
} else if (mShouldUseReverseLandscape) {
|
||||||
swapHeaderAndContent();
|
swapHeaderAndContent();
|
||||||
}
|
}
|
||||||
|
mUdfpsEnrollView.setVisibility(View.VISIBLE);
|
||||||
setOnHoverListener();
|
setOnHoverListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,32 +18,94 @@ package com.android.settings.bluetooth;
|
|||||||
|
|
||||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||||
|
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.SettingsActivity;
|
||||||
import com.android.settings.accessibility.AccessibilityStatsLogUtils;
|
import com.android.settings.accessibility.AccessibilityStatsLogUtils;
|
||||||
|
import com.android.settings.connecteddevice.audiosharing.AudioSharingIncompatibleDialogFragment;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
|
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth
|
* Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth
|
||||||
* device pairing detail page.
|
* device pairing detail page.
|
||||||
*/
|
*/
|
||||||
public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
|
public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
|
||||||
|
private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
|
||||||
|
private static final int AUTO_DISMISS_MESSAGE_ID = 1001;
|
||||||
|
|
||||||
protected boolean mInitialScanStarted;
|
protected boolean mInitialScanStarted;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected BluetoothProgressCategory mAvailableDevicesCategory;
|
protected BluetoothProgressCategory mAvailableDevicesCategory;
|
||||||
|
@Nullable
|
||||||
|
private volatile BluetoothDevice mJustBonded = null;
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
@Nullable
|
||||||
|
private AlertDialog mLoadingDialog = null;
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean mShouldTriggerAudioSharingShareThenPairFlow = false;
|
||||||
|
private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener =
|
||||||
|
new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
// BluetoothDevicePreference updates the summary based on several callbacks, including
|
||||||
|
// BluetoothAdapter.OnMetadataChangedListener and BluetoothCallback. In most cases,
|
||||||
|
// metadata changes callback will be triggered before onDeviceBondStateChanged(BOND_BONDED).
|
||||||
|
// And before we hear onDeviceBondStateChanged(BOND_BONDED), the BluetoothDevice.getState() has
|
||||||
|
// already been BOND_BONDED. These event sequence will lead to: before we hear
|
||||||
|
// onDeviceBondStateChanged(BOND_BONDED), BluetoothDevicePreference's summary has already
|
||||||
|
// change from "Pairing..." to empty since it listens to metadata changes happens earlier.
|
||||||
|
//
|
||||||
|
// In share then pair flow, we have to wait on this page till the device is connected.
|
||||||
|
// The BluetoothDevicePreference summary will be blank for seconds between "Pairing..." and
|
||||||
|
// "Connecting..." To help users better understand the process, we listen to metadata change
|
||||||
|
// as well and show a loading dialog with "Connecting to ...." once BluetoothDevice.getState()
|
||||||
|
// gets to BOND_BONDED.
|
||||||
|
final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
|
||||||
|
new BluetoothAdapter.OnMetadataChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onMetadataChanged(@NonNull BluetoothDevice device, int key,
|
||||||
|
@Nullable byte[] value) {
|
||||||
|
Log.d(getLogTag(), "onMetadataChanged device = " + device + ", key = " + key);
|
||||||
|
if (mShouldTriggerAudioSharingShareThenPairFlow && mLoadingDialog == null
|
||||||
|
&& device.getBondState() == BluetoothDevice.BOND_BONDED
|
||||||
|
&& mSelectedList.contains(device)) {
|
||||||
|
triggerAudioSharingShareThenPairFlow(device);
|
||||||
|
// Once device is bonded, remove the listener
|
||||||
|
removeOnMetadataChangedListener(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public BluetoothDevicePairingDetailBase() {
|
public BluetoothDevicePairingDetailBase() {
|
||||||
super(DISALLOW_CONFIG_BLUETOOTH);
|
super(DISALLOW_CONFIG_BLUETOOTH);
|
||||||
@@ -68,6 +130,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateBluetooth();
|
updateBluetooth();
|
||||||
|
mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,6 +143,26 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
disableScanning();
|
disableScanning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
var unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
mDevicesWithMetadataChangedListener.forEach(
|
||||||
|
device -> {
|
||||||
|
try {
|
||||||
|
if (mBluetoothAdapter != null) {
|
||||||
|
mBluetoothAdapter.removeOnMetadataChangedListener(device,
|
||||||
|
mMetadataListener);
|
||||||
|
mDevicesWithMetadataChangedListener.remove(device);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(getLogTag(), "Fail to remove listener: " + e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mDevicesWithMetadataChangedListener.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBluetoothStateChanged(int bluetoothState) {
|
public void onBluetoothStateChanged(int bluetoothState) {
|
||||||
super.onBluetoothStateChanged(bluetoothState);
|
super.onBluetoothStateChanged(bluetoothState);
|
||||||
@@ -92,16 +175,37 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
@Override
|
@Override
|
||||||
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
|
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
|
||||||
if (bondState == BluetoothDevice.BOND_BONDED) {
|
if (bondState == BluetoothDevice.BOND_BONDED) {
|
||||||
|
if (cachedDevice != null && mShouldTriggerAudioSharingShareThenPairFlow) {
|
||||||
|
BluetoothDevice device = cachedDevice.getDevice();
|
||||||
|
if (device != null && mSelectedList.contains(device)) {
|
||||||
|
triggerAudioSharingShareThenPairFlow(device);
|
||||||
|
removeOnMetadataChangedListener(device);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
// If one device is connected(bonded), then close this fragment.
|
// If one device is connected(bonded), then close this fragment.
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else if (bondState == BluetoothDevice.BOND_BONDING) {
|
} else if (bondState == BluetoothDevice.BOND_BONDING) {
|
||||||
|
if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) {
|
||||||
|
BluetoothDevice device = cachedDevice.getDevice();
|
||||||
|
if (device != null && mSelectedList.contains(device)) {
|
||||||
|
addOnMetadataChangedListener(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Set the bond entry where binding process starts for logging hearing aid device info
|
// Set the bond entry where binding process starts for logging hearing aid device info
|
||||||
final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
|
final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
|
||||||
.getAttribution(getActivity());
|
.getAttribution(getActivity());
|
||||||
final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
|
final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
|
||||||
pageId);
|
pageId);
|
||||||
HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
|
HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
|
||||||
|
} else if (bondState == BluetoothDevice.BOND_NONE) {
|
||||||
|
if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) {
|
||||||
|
BluetoothDevice device = cachedDevice.getDevice();
|
||||||
|
if (device != null && mSelectedList.contains(device)) {
|
||||||
|
removeOnMetadataChangedListener(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (mSelectedDevice != null && cachedDevice != null) {
|
if (mSelectedDevice != null && cachedDevice != null) {
|
||||||
BluetoothDevice device = cachedDevice.getDevice();
|
BluetoothDevice device = cachedDevice.getDevice();
|
||||||
@@ -114,7 +218,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
|
public void onProfileConnectionStateChanged(
|
||||||
|
@NonNull CachedBluetoothDevice cachedDevice, @ConnectionState int state,
|
||||||
int bluetoothProfile) {
|
int bluetoothProfile) {
|
||||||
// This callback is used to handle the case that bonded device is connected in pairing list.
|
// This callback is used to handle the case that bonded device is connected in pairing list.
|
||||||
// 1. If user selected multiple bonded devices in pairing list, after connected
|
// 1. If user selected multiple bonded devices in pairing list, after connected
|
||||||
@@ -123,8 +228,22 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
// removed from paring list.
|
// removed from paring list.
|
||||||
if (cachedDevice != null && cachedDevice.isConnected()) {
|
if (cachedDevice != null && cachedDevice.isConnected()) {
|
||||||
final BluetoothDevice device = cachedDevice.getDevice();
|
final BluetoothDevice device = cachedDevice.getDevice();
|
||||||
if (device != null && mSelectedList.contains(device)) {
|
if (device != null
|
||||||
finish();
|
&& mSelectedList.contains(device)) {
|
||||||
|
if (!BluetoothUtils.isAudioSharingEnabled()) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
|
||||||
|
&& state == BluetoothAdapter.STATE_CONNECTED
|
||||||
|
&& device.equals(mJustBonded)
|
||||||
|
&& mShouldTriggerAudioSharingShareThenPairFlow) {
|
||||||
|
Log.d(getLogTag(),
|
||||||
|
"onProfileConnectionStateChanged, assistant profile connected");
|
||||||
|
dismissConnectingDialog();
|
||||||
|
mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
|
||||||
|
finishFragmentWithResultForAudioSharing(device);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onDeviceDeleted(cachedDevice);
|
onDeviceDeleted(cachedDevice);
|
||||||
}
|
}
|
||||||
@@ -148,6 +267,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
|
public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
|
||||||
disableScanning();
|
disableScanning();
|
||||||
super.onDevicePreferenceClick(btPreference);
|
super.onDevicePreferenceClick(btPreference);
|
||||||
|
// Clean up the previous bond value
|
||||||
|
mJustBonded = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -165,8 +286,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
* {@code bluetoothState} is off.
|
* {@code bluetoothState} is off.
|
||||||
*
|
*
|
||||||
* @param bluetoothState the current Bluetooth state, the possible values that will handle here:
|
* @param bluetoothState the current Bluetooth state, the possible values that will handle here:
|
||||||
* {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
|
* {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
|
||||||
* {@link android.bluetooth.BluetoothAdapter#STATE_ON},
|
* {@link android.bluetooth.BluetoothAdapter#STATE_ON},
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void updateContent(int bluetoothState) {
|
public void updateContent(int bluetoothState) {
|
||||||
@@ -187,4 +308,122 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
|
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean shouldTriggerAudioSharingShareThenPairFlow() {
|
||||||
|
if (!BluetoothUtils.isAudioSharingEnabled()) return false;
|
||||||
|
Activity activity = getActivity();
|
||||||
|
Intent intent = activity == null ? null : activity.getIntent();
|
||||||
|
Bundle args =
|
||||||
|
intent == null ? null :
|
||||||
|
intent.getBundleExtra(
|
||||||
|
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||||
|
return args != null
|
||||||
|
&& args.getBoolean(EXTRA_PAIR_AND_JOIN_SHARING, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addOnMetadataChangedListener(@Nullable BluetoothDevice device) {
|
||||||
|
var unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
if (mBluetoothAdapter != null && device != null
|
||||||
|
&& !mDevicesWithMetadataChangedListener.contains(device)) {
|
||||||
|
mBluetoothAdapter.addOnMetadataChangedListener(device, mExecutor,
|
||||||
|
mMetadataListener);
|
||||||
|
mDevicesWithMetadataChangedListener.add(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeOnMetadataChangedListener(@Nullable BluetoothDevice device) {
|
||||||
|
var unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
if (mBluetoothAdapter != null && device != null
|
||||||
|
&& mDevicesWithMetadataChangedListener.contains(device)) {
|
||||||
|
try {
|
||||||
|
mBluetoothAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
|
||||||
|
mDevicesWithMetadataChangedListener.remove(device);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(getLogTag(), "Fail to remove listener: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void triggerAudioSharingShareThenPairFlow(
|
||||||
|
@NonNull BluetoothDevice device) {
|
||||||
|
var unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
if (mJustBonded != null) {
|
||||||
|
Log.d(getLogTag(), "Skip triggerAudioSharingShareThenPairFlow, already done");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mJustBonded = device;
|
||||||
|
// Show connecting device loading state
|
||||||
|
String aliasName = device.getAlias();
|
||||||
|
String deviceName = TextUtils.isEmpty(aliasName) ? device.getAddress()
|
||||||
|
: aliasName;
|
||||||
|
showConnectingDialog("Connecting to " + deviceName + "...");
|
||||||
|
// Wait for AUTO_DISMISS_TIME_THRESHOLD_MS and check if the paired device supports audio
|
||||||
|
// sharing.
|
||||||
|
if (!mHandler.hasMessages(AUTO_DISMISS_MESSAGE_ID)) {
|
||||||
|
mHandler.postDelayed(() ->
|
||||||
|
postOnMainThread(
|
||||||
|
() -> {
|
||||||
|
Log.d(getLogTag(), "Show incompatible dialog when timeout");
|
||||||
|
dismissConnectingDialog();
|
||||||
|
AudioSharingIncompatibleDialogFragment.show(this, deviceName,
|
||||||
|
() -> finish());
|
||||||
|
}), AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishFragmentWithResultForAudioSharing(@Nullable BluetoothDevice device) {
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().setResult(Activity.RESULT_OK, resultIntent);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use DialogFragment
|
||||||
|
private void showConnectingDialog(@NonNull String message) {
|
||||||
|
postOnMainThread(() -> {
|
||||||
|
if (mLoadingDialog != null) {
|
||||||
|
Log.d(getLogTag(), "showConnectingDialog, is already showing");
|
||||||
|
TextView textView = mLoadingDialog.findViewById(R.id.message);
|
||||||
|
if (textView != null && !message.equals(textView.getText().toString())) {
|
||||||
|
Log.d(getLogTag(), "showConnectingDialog, update message");
|
||||||
|
// TODO: use string res once finalized
|
||||||
|
textView.setText(message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(getLogTag(), "showConnectingDialog, show dialog");
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(builder.getContext());
|
||||||
|
View customView = inflater.inflate(
|
||||||
|
R.layout.dialog_audio_sharing_loading_state, /* root= */
|
||||||
|
null);
|
||||||
|
TextView textView = customView.findViewById(R.id.message);
|
||||||
|
if (textView != null) {
|
||||||
|
// TODO: use string res once finalized
|
||||||
|
textView.setText(message);
|
||||||
|
}
|
||||||
|
AlertDialog dialog = builder.setView(customView).setCancelable(false).create();
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
mLoadingDialog = dialog;
|
||||||
|
dialog.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismissConnectingDialog() {
|
||||||
|
postOnMainThread(() -> {
|
||||||
|
if (mLoadingDialog != null) {
|
||||||
|
mLoadingDialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postOnMainThread(@NonNull Runnable runnable) {
|
||||||
|
getContext().getMainExecutor().execute(runnable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -62,6 +63,7 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.semantics.toggleableState
|
import androidx.compose.ui.semantics.toggleableState
|
||||||
import androidx.compose.ui.state.ToggleableState
|
import androidx.compose.ui.state.ToggleableState
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
@@ -78,7 +80,11 @@ fun MultiTogglePreferenceGroup(
|
|||||||
var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
|
var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
settingIdForPopUp?.let { id ->
|
settingIdForPopUp?.let { id ->
|
||||||
preferenceModels.find { it.id == id }?.let { dialog(it) { settingIdForPopUp = null } }
|
preferenceModels.find { it.id == id && it.isAllowedChangingState }?.let {
|
||||||
|
dialog(it) { settingIdForPopUp = null }
|
||||||
|
} ?: run {
|
||||||
|
settingIdForPopUp = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -102,7 +108,9 @@ fun MultiTogglePreferenceGroup(
|
|||||||
Modifier.fillMaxSize().padding(8.dp).semantics {
|
Modifier.fillMaxSize().padding(8.dp).semantics {
|
||||||
role = Role.Switch
|
role = Role.Switch
|
||||||
toggleableState =
|
toggleableState =
|
||||||
if (preferenceModel.isActive) {
|
if (!preferenceModel.isAllowedChangingState) {
|
||||||
|
ToggleableState.Indeterminate
|
||||||
|
} else if (preferenceModel.isActive) {
|
||||||
ToggleableState.On
|
ToggleableState.On
|
||||||
} else {
|
} else {
|
||||||
ToggleableState.Off
|
ToggleableState.Off
|
||||||
@@ -110,6 +118,7 @@ fun MultiTogglePreferenceGroup(
|
|||||||
contentDescription = preferenceModel.title
|
contentDescription = preferenceModel.title
|
||||||
},
|
},
|
||||||
onClick = { settingIdForPopUp = preferenceModel.id },
|
onClick = { settingIdForPopUp = preferenceModel.id },
|
||||||
|
enabled = preferenceModel.isAllowedChangingState,
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = getButtonColors(preferenceModel.isActive),
|
colors = getButtonColors(preferenceModel.isActive),
|
||||||
contentPadding = PaddingValues(0.dp)) {
|
contentPadding = PaddingValues(0.dp)) {
|
||||||
@@ -254,7 +263,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.Mu
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().height(32.dp),
|
modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
) {
|
) {
|
||||||
@@ -263,6 +272,7 @@ private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.Mu
|
|||||||
text = toggle.label,
|
text = toggle.label,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
|
modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,18 @@
|
|||||||
|
|
||||||
package com.android.settings.connecteddevice.audiosharing;
|
package com.android.settings.connecteddevice.audiosharing;
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
@@ -27,16 +35,21 @@ import com.android.settings.SettingsActivity;
|
|||||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
|
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
import com.android.settings.widget.SettingsMainSwitchBar;
|
import com.android.settings.widget.SettingsMainSwitchBar;
|
||||||
|
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
public class AudioSharingDashboardFragment extends DashboardFragment
|
public class AudioSharingDashboardFragment extends DashboardFragment
|
||||||
implements AudioSharingSwitchBarController.OnAudioSharingStateChangedListener {
|
implements AudioSharingSwitchBarController.OnAudioSharingStateChangedListener {
|
||||||
private static final String TAG = "AudioSharingDashboardFrag";
|
private static final String TAG = "AudioSharingDashboardFrag";
|
||||||
|
|
||||||
|
public static final int SHARE_THEN_PAIR_REQUEST_CODE = 1002;
|
||||||
|
|
||||||
SettingsMainSwitchBar mMainSwitchBar;
|
SettingsMainSwitchBar mMainSwitchBar;
|
||||||
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
|
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
|
||||||
private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
|
private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
|
||||||
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
|
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
|
||||||
private AudioStreamsCategoryController mAudioStreamsCategoryController;
|
private AudioStreamsCategoryController mAudioStreamsCategoryController;
|
||||||
|
private AudioSharingSwitchBarController mAudioSharingSwitchBarController;
|
||||||
|
|
||||||
public AudioSharingDashboardFragment() {
|
public AudioSharingDashboardFragment() {
|
||||||
super();
|
super();
|
||||||
@@ -84,13 +97,38 @@ public class AudioSharingDashboardFragment extends DashboardFragment
|
|||||||
final SettingsActivity activity = (SettingsActivity) getActivity();
|
final SettingsActivity activity = (SettingsActivity) getActivity();
|
||||||
mMainSwitchBar = activity.getSwitchBar();
|
mMainSwitchBar = activity.getSwitchBar();
|
||||||
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
|
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
|
||||||
AudioSharingSwitchBarController switchBarController =
|
mAudioSharingSwitchBarController =
|
||||||
new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
|
new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
|
||||||
switchBarController.init(this);
|
mAudioSharingSwitchBarController.init(this);
|
||||||
getSettingsLifecycle().addObserver(switchBarController);
|
getSettingsLifecycle().addObserver(mAudioSharingSwitchBarController);
|
||||||
mMainSwitchBar.show();
|
mMainSwitchBar.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (!BluetoothUtils.isAudioSharingEnabled()) return;
|
||||||
|
// In share then pair flow, after users be routed to pair new device page and successfully
|
||||||
|
// pair and connect an LEA headset, the pair fragment will be finished with RESULT_OK
|
||||||
|
// and EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, pass the BT device to switch bar controller,
|
||||||
|
// which is responsible for adding source to the device with loading indicator.
|
||||||
|
if (requestCode == SHARE_THEN_PAIR_REQUEST_CODE) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
BluetoothDevice btDevice =
|
||||||
|
data != null
|
||||||
|
? data.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
|
||||||
|
BluetoothDevice.class)
|
||||||
|
: null;
|
||||||
|
Log.d(TAG, "onActivityResult: RESULT_OK with device = " + btDevice);
|
||||||
|
if (btDevice != null) {
|
||||||
|
var unused = ThreadUtils.postOnBackgroundThread(
|
||||||
|
() -> mAudioSharingSwitchBarController.handleAutoAddSourceAfterPair(
|
||||||
|
btDevice));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioSharingStateChanged() {
|
public void onAudioSharingStateChanged() {
|
||||||
updateVisibilityForAttachedPreferences();
|
updateVisibilityForAttachedPreferences();
|
||||||
@@ -107,11 +145,13 @@ public class AudioSharingDashboardFragment extends DashboardFragment
|
|||||||
AudioSharingDeviceVolumeGroupController volumeGroupController,
|
AudioSharingDeviceVolumeGroupController volumeGroupController,
|
||||||
AudioSharingCallAudioPreferenceController callAudioController,
|
AudioSharingCallAudioPreferenceController callAudioController,
|
||||||
AudioSharingPlaySoundPreferenceController playSoundController,
|
AudioSharingPlaySoundPreferenceController playSoundController,
|
||||||
AudioStreamsCategoryController streamsCategoryController) {
|
AudioStreamsCategoryController streamsCategoryController,
|
||||||
|
AudioSharingSwitchBarController switchBarController) {
|
||||||
mAudioSharingDeviceVolumeGroupController = volumeGroupController;
|
mAudioSharingDeviceVolumeGroupController = volumeGroupController;
|
||||||
mAudioSharingCallAudioPreferenceController = callAudioController;
|
mAudioSharingCallAudioPreferenceController = callAudioController;
|
||||||
mAudioSharingPlaySoundPreferenceController = playSoundController;
|
mAudioSharingPlaySoundPreferenceController = playSoundController;
|
||||||
mAudioStreamsCategoryController = streamsCategoryController;
|
mAudioStreamsCategoryController = streamsCategoryController;
|
||||||
|
mAudioSharingSwitchBarController = switchBarController;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVisibilityForAttachedPreferences() {
|
private void updateVisibilityForAttachedPreferences() {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.android.settings.connecteddevice.audiosharing;
|
package com.android.settings.connecteddevice.audiosharing;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -48,19 +51,23 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
// The host creates an instance of this dialog fragment must implement this interface to receive
|
// The host creates an instance of this dialog fragment must implement this interface to receive
|
||||||
// event callbacks.
|
// event callbacks.
|
||||||
public interface DialogEventListener {
|
public interface DialogEventListener {
|
||||||
|
/** Called when users click the positive button in the dialog. */
|
||||||
|
default void onPositiveClick() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when users click the device item for sharing in the dialog.
|
* Called when users click the device item for sharing in the dialog.
|
||||||
*
|
*
|
||||||
* @param item The device item clicked.
|
* @param item The device item clicked.
|
||||||
*/
|
*/
|
||||||
void onItemClick(AudioSharingDeviceItem item);
|
default void onItemClick(@NonNull AudioSharingDeviceItem item) {}
|
||||||
|
|
||||||
/** Called when users click the cancel button in the dialog. */
|
/** Called when users click the cancel button in the dialog. */
|
||||||
void onCancelClick();
|
default void onCancelClick() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable private static DialogEventListener sListener;
|
@Nullable private static DialogEventListener sListener;
|
||||||
private static Pair<Integer, Object>[] sEventData = new Pair[0];
|
private static Pair<Integer, Object>[] sEventData = new Pair[0];
|
||||||
|
@Nullable private static Fragment sHost;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getMetricsCategory() {
|
public int getMetricsCategory() {
|
||||||
@@ -70,10 +77,10 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
/**
|
/**
|
||||||
* Display the {@link AudioSharingDialogFragment} dialog.
|
* Display the {@link AudioSharingDialogFragment} dialog.
|
||||||
*
|
*
|
||||||
* @param host The Fragment this dialog will be hosted.
|
* @param host The Fragment this dialog will be hosted.
|
||||||
* @param deviceItems The connected device items eligible for audio sharing.
|
* @param deviceItems The connected device items eligible for audio sharing.
|
||||||
* @param listener The callback to handle the user action on this dialog.
|
* @param listener The callback to handle the user action on this dialog.
|
||||||
* @param eventData The eventData to log with for dialog onClick events.
|
* @param eventData The eventData to log with for dialog onClick events.
|
||||||
*/
|
*/
|
||||||
public static void show(
|
public static void show(
|
||||||
@NonNull Fragment host,
|
@NonNull Fragment host,
|
||||||
@@ -88,6 +95,7 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
Log.d(TAG, "Fail to show dialog: " + e.getMessage());
|
Log.d(TAG, "Fail to show dialog: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
sHost = host;
|
||||||
sListener = listener;
|
sListener = listener;
|
||||||
sEventData = eventData;
|
sEventData = eventData;
|
||||||
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
|
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
|
||||||
@@ -136,23 +144,33 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
.setCustomPositiveButton(
|
.setCustomPositiveButton(
|
||||||
R.string.audio_sharing_pair_button_label,
|
R.string.audio_sharing_pair_button_label,
|
||||||
v -> {
|
v -> {
|
||||||
dismiss();
|
if (sListener != null) {
|
||||||
new SubSettingLauncher(getContext())
|
sListener.onPositiveClick();
|
||||||
.setDestination(BluetoothPairingDetail.class.getName())
|
}
|
||||||
.setSourceMetricsCategory(getMetricsCategory())
|
|
||||||
.launch();
|
|
||||||
logDialogPositiveBtnClick();
|
logDialogPositiveBtnClick();
|
||||||
|
dismiss();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, true);
|
||||||
|
SubSettingLauncher launcher =
|
||||||
|
new SubSettingLauncher(getContext())
|
||||||
|
.setDestination(
|
||||||
|
BluetoothPairingDetail.class.getName())
|
||||||
|
.setSourceMetricsCategory(getMetricsCategory())
|
||||||
|
.setArguments(args);
|
||||||
|
if (sHost != null) {
|
||||||
|
launcher.setResultListener(sHost, SHARE_THEN_PAIR_REQUEST_CODE);
|
||||||
|
}
|
||||||
|
launcher.launch();
|
||||||
})
|
})
|
||||||
.setCustomNegativeButton(
|
.setCustomNegativeButton(
|
||||||
R.string.audio_sharing_qrcode_button_label,
|
R.string.audio_sharing_qrcode_button_label,
|
||||||
v -> {
|
v -> {
|
||||||
dismiss();
|
onCancelClick();
|
||||||
new SubSettingLauncher(getContext())
|
new SubSettingLauncher(getContext())
|
||||||
.setTitleRes(R.string.audio_streams_qr_code_page_title)
|
.setTitleRes(R.string.audio_streams_qr_code_page_title)
|
||||||
.setDestination(AudioStreamsQrCodeFragment.class.getName())
|
.setDestination(AudioStreamsQrCodeFragment.class.getName())
|
||||||
.setSourceMetricsCategory(getMetricsCategory())
|
.setSourceMetricsCategory(getMetricsCategory())
|
||||||
.launch();
|
.launch();
|
||||||
logDialogNegativeBtnClick();
|
|
||||||
});
|
});
|
||||||
} else if (deviceItems.size() == 1) {
|
} else if (deviceItems.size() == 1) {
|
||||||
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
|
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
|
||||||
@@ -166,8 +184,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
v -> {
|
v -> {
|
||||||
if (sListener != null) {
|
if (sListener != null) {
|
||||||
sListener.onItemClick(deviceItem);
|
sListener.onItemClick(deviceItem);
|
||||||
logDialogPositiveBtnClick();
|
|
||||||
}
|
}
|
||||||
|
logDialogPositiveBtnClick();
|
||||||
dismiss();
|
dismiss();
|
||||||
})
|
})
|
||||||
.setCustomNegativeButton(
|
.setCustomNegativeButton(
|
||||||
@@ -182,8 +200,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
(AudioSharingDeviceItem item) -> {
|
(AudioSharingDeviceItem item) -> {
|
||||||
if (sListener != null) {
|
if (sListener != null) {
|
||||||
sListener.onItemClick(item);
|
sListener.onItemClick(item);
|
||||||
logDialogPositiveBtnClick();
|
|
||||||
}
|
}
|
||||||
|
logDialogPositiveBtnClick();
|
||||||
dismiss();
|
dismiss();
|
||||||
},
|
},
|
||||||
AudioSharingDeviceAdapter.ActionType.SHARE))
|
AudioSharingDeviceAdapter.ActionType.SHARE))
|
||||||
@@ -196,8 +214,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
|||||||
private void onCancelClick() {
|
private void onCancelClick() {
|
||||||
if (sListener != null) {
|
if (sListener != null) {
|
||||||
sListener.onCancelClick();
|
sListener.onCancelClick();
|
||||||
logDialogNegativeBtnClick();
|
|
||||||
}
|
}
|
||||||
|
logDialogNegativeBtnClick();
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import androidx.fragment.app.FragmentManager;
|
|||||||
|
|
||||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
|
||||||
|
|
||||||
public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
|
public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
|
||||||
private static final String TAG = "AudioSharingIncompatDlg";
|
private static final String TAG = "AudioSharingIncompatDlg";
|
||||||
@@ -59,7 +58,7 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
|
|||||||
*
|
*
|
||||||
* @param host The Fragment this dialog will be hosted.
|
* @param host The Fragment this dialog will be hosted.
|
||||||
*/
|
*/
|
||||||
public static void show(@Nullable Fragment host, @NonNull CachedBluetoothDevice cachedDevice,
|
public static void show(@Nullable Fragment host, @NonNull String deviceName,
|
||||||
@NonNull DialogEventListener listener) {
|
@NonNull DialogEventListener listener) {
|
||||||
if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
|
if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
|
||||||
final FragmentManager manager;
|
final FragmentManager manager;
|
||||||
@@ -77,7 +76,7 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
|
|||||||
}
|
}
|
||||||
Log.d(TAG, "Show up the incompatible device dialog.");
|
Log.d(TAG, "Show up the incompatible device dialog.");
|
||||||
final Bundle bundle = new Bundle();
|
final Bundle bundle = new Bundle();
|
||||||
bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
|
bundle.putString(BUNDLE_KEY_DEVICE_NAME, deviceName);
|
||||||
AudioSharingIncompatibleDialogFragment dialogFrag =
|
AudioSharingIncompatibleDialogFragment dialogFrag =
|
||||||
new AudioSharingIncompatibleDialogFragment();
|
new AudioSharingIncompatibleDialogFragment();
|
||||||
dialogFrag.setArguments(bundle);
|
dialogFrag.setArguments(bundle);
|
||||||
|
|||||||
@@ -115,10 +115,6 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
|
|||||||
@NonNull
|
@NonNull
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
mHandler = new Handler(Looper.getMainLooper());
|
mHandler = new Handler(Looper.getMainLooper());
|
||||||
mHandler.postDelayed(() -> {
|
|
||||||
Log.d(TAG, "Auto dismiss dialog after timeout");
|
|
||||||
dismiss();
|
|
||||||
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
|
|
||||||
Bundle args = requireArguments();
|
Bundle args = requireArguments();
|
||||||
String message = args.getString(BUNDLE_KEY_MESSAGE, "");
|
String message = args.getString(BUNDLE_KEY_MESSAGE, "");
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
@@ -132,6 +128,26 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if (mHandler != null) {
|
||||||
|
Log.d(TAG, "onStart, postTimeOut for auto dismiss");
|
||||||
|
mHandler.postDelayed(() -> {
|
||||||
|
Log.d(TAG, "Try to auto dismiss dialog after timeout");
|
||||||
|
try {
|
||||||
|
Dialog dialog = getDialog();
|
||||||
|
if (dialog != null) {
|
||||||
|
Log.d(TAG, "Dialog is not null, dismiss");
|
||||||
|
dismissAllowingStateLoss();
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.d(TAG, "Fail to dismiss: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import com.android.settingslib.bluetooth.BluetoothCallback;
|
|||||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||||
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;
|
||||||
@@ -78,9 +79,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
public class AudioSharingSwitchBarController extends BasePreferenceController
|
public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||||
implements DefaultLifecycleObserver,
|
implements DefaultLifecycleObserver,
|
||||||
OnCheckedChangeListener,
|
OnCheckedChangeListener,
|
||||||
LocalBluetoothProfileManager.ServiceListener,
|
LocalBluetoothProfileManager.ServiceListener,
|
||||||
BluetoothCallback {
|
BluetoothCallback {
|
||||||
private static final String TAG = "AudioSharingSwitchCtlr";
|
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";
|
||||||
|
|
||||||
@@ -464,6 +465,18 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
|||||||
this.mFragment = fragment;
|
this.mFragment = fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handle auto add source to the just paired device in share then pair flow. */
|
||||||
|
public void handleAutoAddSourceAfterPair(@NonNull BluetoothDevice device) {
|
||||||
|
CachedBluetoothDeviceManager deviceManager =
|
||||||
|
mBtManager == null ? null : mBtManager.getCachedDeviceManager();
|
||||||
|
CachedBluetoothDevice cachedDevice =
|
||||||
|
deviceManager == null ? null : deviceManager.findDevice(device);
|
||||||
|
if (cachedDevice != null) {
|
||||||
|
Log.d(TAG, "handleAutoAddSourceAfterPair, device = " + device.getAnonymizedAddress());
|
||||||
|
addSourceToTargetSinks(ImmutableList.of(device), cachedDevice.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Test only: set callback registration status in tests. */
|
/** Test only: set callback registration status in tests. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setCallbacksRegistered(boolean registered) {
|
void setCallbacksRegistered(boolean registered) {
|
||||||
@@ -610,8 +623,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
|||||||
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
|
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
|
||||||
mTargetActiveItem = null;
|
mTargetActiveItem = null;
|
||||||
if (mIntentHandleStage.compareAndSet(
|
if (mIntentHandleStage.compareAndSet(
|
||||||
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
|
StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
|
||||||
StartIntentHandleStage.HANDLED.ordinal())
|
StartIntentHandleStage.HANDLED.ordinal())
|
||||||
&& mDeviceItemsForSharing.size() == 1) {
|
&& mDeviceItemsForSharing.size() == 1) {
|
||||||
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
|
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
|
||||||
AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
|
AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
|
||||||
@@ -638,6 +651,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
|||||||
private void showDialog(Pair<Integer, Object>[] eventData) {
|
private void showDialog(Pair<Integer, Object>[] eventData) {
|
||||||
AudioSharingDialogFragment.DialogEventListener listener =
|
AudioSharingDialogFragment.DialogEventListener listener =
|
||||||
new AudioSharingDialogFragment.DialogEventListener() {
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPositiveClick() {
|
||||||
|
// Could go to other pages, dismiss the loading dialog.
|
||||||
|
dismissLoadingStateDialogIfNeeded();
|
||||||
|
cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
|
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
|
||||||
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
|
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
|
||||||
@@ -648,6 +668,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelClick() {
|
public void onCancelClick() {
|
||||||
|
// Could go to other pages, dismiss the loading dialog.
|
||||||
dismissLoadingStateDialogIfNeeded();
|
dismissLoadingStateDialogIfNeeded();
|
||||||
cleanUp();
|
cleanUp();
|
||||||
}
|
}
|
||||||
@@ -669,8 +690,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.settings.localepicker;
|
package com.android.settings.localepicker;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
@@ -41,7 +43,8 @@ import com.android.internal.app.LocalePicker;
|
|||||||
import com.android.internal.app.LocaleStore;
|
import com.android.internal.app.LocaleStore;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
import com.android.settings.shortcut.ShortcutsUpdateTask;
|
import com.android.settings.shortcut.ShortcutsUpdater;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -96,7 +99,7 @@ class LocaleDragAndDropAdapter
|
|||||||
LocaleDragAndDropAdapter(LocaleListEditor parent, List<LocaleStore.LocaleInfo> feedItemList) {
|
LocaleDragAndDropAdapter(LocaleListEditor parent, List<LocaleStore.LocaleInfo> feedItemList) {
|
||||||
mFeedItemList = feedItemList;
|
mFeedItemList = feedItemList;
|
||||||
mCacheItemList = new ArrayList<>(feedItemList);
|
mCacheItemList = new ArrayList<>(feedItemList);
|
||||||
mContext = parent.getContext();
|
mContext = checkNotNull(parent.getContext());
|
||||||
|
|
||||||
final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
|
final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
|
||||||
mContext.getResources().getDisplayMetrics());
|
mContext.getResources().getDisplayMetrics());
|
||||||
@@ -350,7 +353,8 @@ class LocaleDragAndDropAdapter
|
|||||||
|
|
||||||
LocalePicker.updateLocales(mLocalesToSetNext);
|
LocalePicker.updateLocales(mLocalesToSetNext);
|
||||||
mLocalesSetLast = mLocalesToSetNext;
|
mLocalesSetLast = mLocalesToSetNext;
|
||||||
new ShortcutsUpdateTask(mContext).execute();
|
ThreadUtils.postOnBackgroundThread(
|
||||||
|
() -> ShortcutsUpdater.updatePinnedShortcuts(mContext));
|
||||||
|
|
||||||
mLocalesToSetNext = null;
|
mLocalesToSetNext = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.network;
|
|
||||||
|
|
||||||
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
|
|
||||||
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.telephony.SubscriptionManager;
|
|
||||||
import android.telephony.euicc.EuiccManager;
|
|
||||||
|
|
||||||
import androidx.lifecycle.Lifecycle;
|
|
||||||
import androidx.lifecycle.LifecycleObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.OnLifecycleEvent;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
|
||||||
import com.android.settings.network.telephony.SimRepository;
|
|
||||||
import com.android.settings.network.telephony.euicc.EuiccRepository;
|
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
|
||||||
import com.android.settingslib.RestrictedPreference;
|
|
||||||
import com.android.settingslib.core.AbstractPreferenceController;
|
|
||||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
|
||||||
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
|
|
||||||
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
|
|
||||||
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class MobileNetworkSummaryController extends AbstractPreferenceController implements
|
|
||||||
LifecycleObserver, PreferenceControllerMixin,
|
|
||||||
MobileNetworkRepository.MobileNetworkCallback {
|
|
||||||
private static final String TAG = "MobileNetSummaryCtlr";
|
|
||||||
|
|
||||||
private static final String KEY = "mobile_network_list";
|
|
||||||
|
|
||||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
|
||||||
private RestrictedPreference mPreference;
|
|
||||||
|
|
||||||
private MobileNetworkRepository mMobileNetworkRepository;
|
|
||||||
private List<SubscriptionInfoEntity> mSubInfoEntityList;
|
|
||||||
private List<UiccInfoEntity> mUiccInfoEntityList;
|
|
||||||
private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList;
|
|
||||||
private boolean mIsAirplaneModeOn;
|
|
||||||
private LifecycleOwner mLifecycleOwner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This controls the summary text and click behavior of the "Mobile network" item on the
|
|
||||||
* Network & internet page. There are 3 separate cases depending on the number of mobile network
|
|
||||||
* subscriptions:
|
|
||||||
* <ul>
|
|
||||||
* <li>No subscription: click action begins a UI flow to add a network subscription, and
|
|
||||||
* the summary text indicates this</li>
|
|
||||||
*
|
|
||||||
* <li>One subscription: click action takes you to details for that one network, and
|
|
||||||
* the summary text is the network name</li>
|
|
||||||
*
|
|
||||||
* <li>More than one subscription: click action takes you to a page listing the subscriptions,
|
|
||||||
* and the summary text gives the count of SIMs</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public MobileNetworkSummaryController(Context context, Lifecycle lifecycle,
|
|
||||||
LifecycleOwner lifecycleOwner) {
|
|
||||||
super(context);
|
|
||||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
|
||||||
mLifecycleOwner = lifecycleOwner;
|
|
||||||
mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
|
|
||||||
mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn();
|
|
||||||
if (lifecycle != null) {
|
|
||||||
lifecycle.addObserver(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnLifecycleEvent(ON_RESUME)
|
|
||||||
public void onResume() {
|
|
||||||
mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
||||||
mMobileNetworkRepository.updateEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnLifecycleEvent(ON_PAUSE)
|
|
||||||
public void onPause() {
|
|
||||||
mMobileNetworkRepository.removeRegister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displayPreference(PreferenceScreen screen) {
|
|
||||||
super.displayPreference(screen);
|
|
||||||
mPreference = screen.findPreference(getPreferenceKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getSummary() {
|
|
||||||
|
|
||||||
if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
|
|
||||||
mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
|
|
||||||
mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
|
|
||||||
if (new EuiccRepository(mContext).showEuiccSettings()) {
|
|
||||||
return mContext.getResources().getString(
|
|
||||||
R.string.mobile_network_summary_add_a_network);
|
|
||||||
}
|
|
||||||
// set empty string to override previous text for carrier when SIM available
|
|
||||||
return "";
|
|
||||||
} else if (mSubInfoEntityList.size() == 1) {
|
|
||||||
SubscriptionInfoEntity info = mSubInfoEntityList.get(0);
|
|
||||||
CharSequence displayName = info.uniqueName;
|
|
||||||
if (info.isEmbedded || mUiccInfoEntityList.get(0).isActive
|
|
||||||
|| mMobileNetworkInfoEntityList.get(0).showToggleForPhysicalSim) {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
return mContext.getString(R.string.mobile_network_tap_to_activate, displayName);
|
|
||||||
} else {
|
|
||||||
return mSubInfoEntityList.stream()
|
|
||||||
.map(SubscriptionInfoEntity::getUniqueDisplayName)
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logPreferenceClick(Preference preference) {
|
|
||||||
mMetricsFeatureProvider.logClickedPreference(preference,
|
|
||||||
preference.getExtras().getInt(DashboardFragment.CATEGORY));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startAddSimFlow() {
|
|
||||||
final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION);
|
|
||||||
intent.setPackage(com.android.settings.Utils.PHONE_PACKAGE_NAME);
|
|
||||||
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true);
|
|
||||||
mContext.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPreference() {
|
|
||||||
refreshSummary(mPreference);
|
|
||||||
mPreference.setOnPreferenceClickListener(null);
|
|
||||||
mPreference.setFragment(null);
|
|
||||||
mPreference.setEnabled(!mIsAirplaneModeOn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update() {
|
|
||||||
if (mPreference == null || mPreference.isDisabledByAdmin()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
initPreference();
|
|
||||||
if (((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty())
|
|
||||||
|| (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
|
|
||||||
|| (mMobileNetworkInfoEntityList == null
|
|
||||||
|| mMobileNetworkInfoEntityList.isEmpty()))) {
|
|
||||||
if (new EuiccRepository(mContext).showEuiccSettings()) {
|
|
||||||
mPreference.setOnPreferenceClickListener((Preference pref) -> {
|
|
||||||
logPreferenceClick(pref);
|
|
||||||
startAddSimFlow();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mPreference.setEnabled(false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return new SimRepository(mContext).showMobileNetworkPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPreferenceKey() {
|
|
||||||
return KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
|
|
||||||
if (mIsAirplaneModeOn != airplaneModeEnabled) {
|
|
||||||
mIsAirplaneModeOn = airplaneModeEnabled;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
|
|
||||||
mSubInfoEntityList = subInfoEntityList;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
|
|
||||||
mUiccInfoEntityList = uiccInfoEntityList;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAllMobileNetworkInfoChanged(
|
|
||||||
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
|
|
||||||
mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.core.BasePreferenceController
|
||||||
|
import com.android.settings.dashboard.DashboardFragment
|
||||||
|
import com.android.settings.network.telephony.SimRepository
|
||||||
|
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||||
|
import com.android.settings.spa.network.startAddSimFlow
|
||||||
|
import com.android.settingslib.RestrictedPreference
|
||||||
|
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
|
||||||
|
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controls the summary text and click behavior of the "Mobile network" item on the Network &
|
||||||
|
* internet page. There are 2 separate cases depending on the number of mobile network
|
||||||
|
* subscriptions:
|
||||||
|
* - No subscription: click action begins a UI flow to add a network subscription, and the summary
|
||||||
|
* text indicates this
|
||||||
|
* - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary
|
||||||
|
* text gives the count of SIMs
|
||||||
|
*/
|
||||||
|
class MobileNetworkSummaryController
|
||||||
|
@JvmOverloads
|
||||||
|
constructor(
|
||||||
|
private val context: Context,
|
||||||
|
preferenceKey: String,
|
||||||
|
private val repository: MobileNetworkSummaryRepository =
|
||||||
|
MobileNetworkSummaryRepository(context),
|
||||||
|
private val airplaneModeOnFlow: Flow<Boolean> =
|
||||||
|
context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
|
||||||
|
) : BasePreferenceController(context, preferenceKey) {
|
||||||
|
private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
|
||||||
|
private var preference: RestrictedPreference? = null
|
||||||
|
|
||||||
|
private var isAirplaneModeOn = false
|
||||||
|
|
||||||
|
override fun getAvailabilityStatus() =
|
||||||
|
if (SimRepository(mContext).showMobileNetworkPage()) AVAILABLE
|
||||||
|
else CONDITIONALLY_UNAVAILABLE
|
||||||
|
|
||||||
|
override fun displayPreference(screen: PreferenceScreen) {
|
||||||
|
super.displayPreference(screen)
|
||||||
|
preference = screen.findPreference(preferenceKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||||
|
repository
|
||||||
|
.subscriptionsStateFlow()
|
||||||
|
.collectLatestWithLifecycle(viewLifecycleOwner, action = ::update)
|
||||||
|
airplaneModeOnFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
|
||||||
|
isAirplaneModeOn = it
|
||||||
|
updateEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) {
|
||||||
|
val preference = preference ?: return
|
||||||
|
preference.onPreferenceClickListener = null
|
||||||
|
preference.fragment = null
|
||||||
|
when (state) {
|
||||||
|
MobileNetworkSummaryRepository.AddNetwork -> {
|
||||||
|
preference.summary =
|
||||||
|
context.getString(R.string.mobile_network_summary_add_a_network)
|
||||||
|
preference.onPreferenceClickListener =
|
||||||
|
Preference.OnPreferenceClickListener {
|
||||||
|
logPreferenceClick()
|
||||||
|
startAddSimFlow(context)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileNetworkSummaryRepository.NoSubscriptions -> {
|
||||||
|
preference.summary = null
|
||||||
|
}
|
||||||
|
|
||||||
|
is MobileNetworkSummaryRepository.HasSubscriptions -> {
|
||||||
|
preference.summary = state.displayNames.joinToString(", ")
|
||||||
|
preference.fragment = MobileNetworkListFragment::class.java.canonicalName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateEnabled() {
|
||||||
|
val preference = preference ?: return
|
||||||
|
if (preference.isDisabledByAdmin) return
|
||||||
|
preference.isEnabled =
|
||||||
|
(preference.onPreferenceClickListener != null || preference.fragment != null) &&
|
||||||
|
!isAirplaneModeOn
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logPreferenceClick() {
|
||||||
|
val preference = preference ?: return
|
||||||
|
metricsFeatureProvider.logClickedPreference(
|
||||||
|
preference,
|
||||||
|
preference.extras.getInt(DashboardFragment.CATEGORY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.telephony.SubscriptionInfo
|
||||||
|
import com.android.settings.network.telephony.SubscriptionRepository
|
||||||
|
import com.android.settings.network.telephony.euicc.EuiccRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.conflate
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class MobileNetworkSummaryRepository(
|
||||||
|
private val context: Context,
|
||||||
|
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
|
||||||
|
private val euiccRepository: EuiccRepository = EuiccRepository(context),
|
||||||
|
private val getDisplayName: (SubscriptionInfo) -> String = { subInfo ->
|
||||||
|
SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context).toString()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
sealed interface SubscriptionsState
|
||||||
|
|
||||||
|
data object AddNetwork : SubscriptionsState
|
||||||
|
|
||||||
|
data object NoSubscriptions : SubscriptionsState
|
||||||
|
|
||||||
|
data class HasSubscriptions(val displayNames: List<String>) : SubscriptionsState
|
||||||
|
|
||||||
|
fun subscriptionsStateFlow(): Flow<SubscriptionsState> =
|
||||||
|
subDisplayNamesFlow()
|
||||||
|
.map { displayNames ->
|
||||||
|
if (displayNames.isEmpty()) {
|
||||||
|
if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions
|
||||||
|
} else {
|
||||||
|
HasSubscriptions(displayNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.conflate()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
|
||||||
|
private fun subDisplayNamesFlow(): Flow<List<String>> =
|
||||||
|
subscriptionRepository
|
||||||
|
.selectableSubscriptionInfoListFlow()
|
||||||
|
.map { subInfos -> subInfos.map(getDisplayName) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.conflate()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import android.app.settings.SettingsEnums;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsDumpService;
|
import com.android.settings.SettingsDumpService;
|
||||||
@@ -69,12 +69,11 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
|
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
|
||||||
return buildPreferenceControllers(context, getSettingsLifecycle(),
|
return buildPreferenceControllers(context, getSettingsLifecycle());
|
||||||
this /* LifecycleOwner */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
|
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
|
||||||
Lifecycle lifecycle, LifecycleOwner lifecycleOwner) {
|
@Nullable Lifecycle lifecycle) {
|
||||||
final VpnPreferenceController vpnPreferenceController =
|
final VpnPreferenceController vpnPreferenceController =
|
||||||
new VpnPreferenceController(context);
|
new VpnPreferenceController(context);
|
||||||
final PrivateDnsPreferenceController privateDnsPreferenceController =
|
final PrivateDnsPreferenceController privateDnsPreferenceController =
|
||||||
@@ -87,7 +86,6 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
|||||||
|
|
||||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
||||||
|
|
||||||
controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
|
|
||||||
controllers.add(vpnPreferenceController);
|
controllers.add(vpnPreferenceController);
|
||||||
controllers.add(privateDnsPreferenceController);
|
controllers.add(privateDnsPreferenceController);
|
||||||
|
|
||||||
@@ -114,8 +112,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public List<AbstractPreferenceController> createPreferenceControllers(Context
|
public List<AbstractPreferenceController> createPreferenceControllers(Context
|
||||||
context) {
|
context) {
|
||||||
return buildPreferenceControllers(context, null /* lifecycle */,
|
return buildPreferenceControllers(context, null /* lifecycle */);
|
||||||
null /* LifecycleOwner */);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.android.settings.network.telephony
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.telephony.CarrierConfigManager
|
import android.telephony.CarrierConfigManager
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.telephony.TelephonyManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -37,12 +38,13 @@ class VoNrRepository(
|
|||||||
fun isVoNrAvailable(subId: Int): Boolean {
|
fun isVoNrAvailable(subId: Int): Boolean {
|
||||||
if (!nrRepository.isNrAvailable(subId)) return false
|
if (!nrRepository.isNrAvailable(subId)) return false
|
||||||
data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
|
data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
|
||||||
|
|
||||||
val carrierConfig =
|
val carrierConfig =
|
||||||
carrierConfigRepository.transformConfig(subId) {
|
carrierConfigRepository.transformConfig(subId) {
|
||||||
Config(
|
Config(
|
||||||
isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
|
isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
|
||||||
isVoNrSettingVisibility =
|
isVoNrSettingVisibility =
|
||||||
getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
|
getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
|
return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
|
||||||
@@ -52,7 +54,14 @@ class VoNrRepository(
|
|||||||
val telephonyManager = context.telephonyManager(subId)
|
val telephonyManager = context.telephonyManager(subId)
|
||||||
return context
|
return context
|
||||||
.subscriptionsChangedFlow()
|
.subscriptionsChangedFlow()
|
||||||
.map { telephonyManager.isVoNrEnabled }
|
.map {
|
||||||
|
try {
|
||||||
|
telephonyManager.isVoNrEnabled
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.e(TAG, "IllegalStateException - isVoNrEnabled : $e")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
.conflate()
|
.conflate()
|
||||||
.onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
|
.onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
|
||||||
.flowOn(Dispatchers.Default)
|
.flowOn(Dispatchers.Default)
|
||||||
@@ -61,11 +70,17 @@ class VoNrRepository(
|
|||||||
suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) =
|
suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) =
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
|
if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
|
||||||
val result = context.telephonyManager(subId).setVoNrEnabled(enabled)
|
var result = TelephonyManager.ENABLE_VONR_RADIO_INVALID_STATE
|
||||||
Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
|
try {
|
||||||
|
result = context.telephonyManager(subId).setVoNrEnabled(enabled)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
Log.e(TAG, "IllegalStateException - setVoNrEnabled : $e")
|
||||||
|
} finally {
|
||||||
|
Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val TAG = "VoNrRepository"
|
private const val TAG = "VoNrRepository"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ package com.android.settings.notification;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.RingtoneManager;
|
import android.media.RingtoneManager;
|
||||||
|
|
||||||
|
import com.android.server.notification.Flags;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
|
|
||||||
public class NotificationRingtonePreferenceController extends RingtonePreferenceControllerBase {
|
public class NotificationRingtonePreferenceController extends RingtonePreferenceControllerBase {
|
||||||
@@ -31,6 +32,9 @@ public class NotificationRingtonePreferenceController extends RingtonePreference
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
|
if (isVibrationInSoundUriEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return mContext.getResources().getBoolean(R.bool.config_show_notification_ringtone);
|
return mContext.getResources().getBoolean(R.bool.config_show_notification_ringtone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,4 +47,9 @@ public class NotificationRingtonePreferenceController extends RingtonePreference
|
|||||||
public int getRingtoneType() {
|
public int getRingtoneType() {
|
||||||
return RingtoneManager.TYPE_NOTIFICATION;
|
return RingtoneManager.TYPE_NOTIFICATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isVibrationInSoundUriEnabled() {
|
||||||
|
return Flags.notificationVibrationInSoundUri() && mContext.getResources().getBoolean(
|
||||||
|
com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,27 +16,19 @@
|
|||||||
|
|
||||||
package com.android.settings.shortcut;
|
package com.android.settings.shortcut;
|
||||||
|
|
||||||
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.pm.ShortcutManager;
|
import android.content.pm.ShortcutManager;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.Icon;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextThemeWrapper;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
@@ -48,7 +40,6 @@ import com.android.settings.Settings;
|
|||||||
import com.android.settings.Settings.DataUsageSummaryActivity;
|
import com.android.settings.Settings.DataUsageSummaryActivity;
|
||||||
import com.android.settings.Settings.TetherSettingsActivity;
|
import com.android.settings.Settings.TetherSettingsActivity;
|
||||||
import com.android.settings.Settings.WifiTetherSettingsActivity;
|
import com.android.settings.Settings.WifiTetherSettingsActivity;
|
||||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
import com.android.settings.gestures.OneHandedSettingsUtils;
|
import com.android.settings.gestures.OneHandedSettingsUtils;
|
||||||
import com.android.settings.network.SubscriptionUtil;
|
import com.android.settings.network.SubscriptionUtil;
|
||||||
@@ -69,11 +60,6 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
|||||||
|
|
||||||
private static final String TAG = "CreateShortcutPrefCtrl";
|
private static final String TAG = "CreateShortcutPrefCtrl";
|
||||||
|
|
||||||
static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
|
|
||||||
static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
|
|
||||||
.addCategory("com.android.settings.SHORTCUT")
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
|
|
||||||
private final ShortcutManager mShortcutManager;
|
private final ShortcutManager mShortcutManager;
|
||||||
private final PackageManager mPackageManager;
|
private final PackageManager mPackageManager;
|
||||||
private final ConnectivityManager mConnectivityManager;
|
private final ConnectivityManager mConnectivityManager;
|
||||||
@@ -132,9 +118,7 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
|||||||
if (mHost == null) {
|
if (mHost == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Intent shortcutIntent = createResultIntent(
|
final Intent shortcutIntent = createResultIntent(info);
|
||||||
buildShortcutIntent(uiContext, info),
|
|
||||||
info, clickTarget.getTitle());
|
|
||||||
mHost.setResult(Activity.RESULT_OK, shortcutIntent);
|
mHost.setResult(Activity.RESULT_OK, shortcutIntent);
|
||||||
logCreateShortcut(info);
|
logCreateShortcut(info);
|
||||||
mHost.finish();
|
mHost.finish();
|
||||||
@@ -149,21 +133,20 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
|||||||
* launcher widget using this intent.
|
* launcher widget using this intent.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo,
|
Intent createResultIntent(ResolveInfo resolveInfo) {
|
||||||
CharSequence label) {
|
ShortcutInfo info = Shortcuts.createShortcutInfo(mContext, resolveInfo);
|
||||||
ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label);
|
|
||||||
Intent intent = mShortcutManager.createShortcutResultIntent(info);
|
Intent intent = mShortcutManager.createShortcutResultIntent(info);
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
intent = new Intent();
|
intent = new Intent();
|
||||||
}
|
}
|
||||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
||||||
Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
|
Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
|
||||||
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
|
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, info.getIntent())
|
||||||
.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
|
.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getShortLabel());
|
||||||
|
|
||||||
final ActivityInfo activityInfo = resolveInfo.activityInfo;
|
final ActivityInfo activityInfo = resolveInfo.activityInfo;
|
||||||
if (activityInfo.icon != 0) {
|
if (activityInfo.icon != 0) {
|
||||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
|
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, Shortcuts.createIcon(
|
||||||
mContext,
|
mContext,
|
||||||
activityInfo.applicationInfo,
|
activityInfo.applicationInfo,
|
||||||
activityInfo.icon,
|
activityInfo.icon,
|
||||||
@@ -239,87 +222,6 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
|||||||
info.activityInfo.name);
|
info.activityInfo.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Intent buildShortcutIntent(Context context, ResolveInfo info) {
|
|
||||||
Intent intent = new Intent(SHORTCUT_PROBE)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
||||||
.setClassName(info.activityInfo.packageName, info.activityInfo.name);
|
|
||||||
if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ShortcutInfo createShortcutInfo(Context context, Intent shortcutIntent,
|
|
||||||
ResolveInfo resolveInfo, CharSequence label) {
|
|
||||||
final ActivityInfo activityInfo = resolveInfo.activityInfo;
|
|
||||||
|
|
||||||
final Icon maskableIcon;
|
|
||||||
if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
|
|
||||||
maskableIcon = Icon.createWithAdaptiveBitmap(createIcon(
|
|
||||||
context,
|
|
||||||
activityInfo.applicationInfo, activityInfo.icon,
|
|
||||||
R.layout.shortcut_badge_maskable,
|
|
||||||
context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
|
|
||||||
} else {
|
|
||||||
maskableIcon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
|
||||||
}
|
|
||||||
final String shortcutId = SHORTCUT_ID_PREFIX +
|
|
||||||
shortcutIntent.getComponent().flattenToShortString();
|
|
||||||
return new ShortcutInfo.Builder(context, shortcutId)
|
|
||||||
.setShortLabel(label)
|
|
||||||
.setIntent(shortcutIntent)
|
|
||||||
.setIcon(maskableIcon)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Bitmap createIcon(Context context, ApplicationInfo app, int resource,
|
|
||||||
int layoutRes, int size) {
|
|
||||||
final Context themedContext = new ContextThemeWrapper(context,
|
|
||||||
android.R.style.Theme_Material);
|
|
||||||
final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null);
|
|
||||||
final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
|
|
||||||
view.measure(spec, spec);
|
|
||||||
final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
|
|
||||||
Bitmap.Config.ARGB_8888);
|
|
||||||
final Canvas canvas = new Canvas(bitmap);
|
|
||||||
|
|
||||||
Drawable iconDrawable;
|
|
||||||
try {
|
|
||||||
iconDrawable = context.getPackageManager().getResourcesForApplication(app)
|
|
||||||
.getDrawable(resource, themedContext.getTheme());
|
|
||||||
if (iconDrawable instanceof LayerDrawable) {
|
|
||||||
iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
|
|
||||||
}
|
|
||||||
((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
|
|
||||||
Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
|
||||||
((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
|
||||||
view.draw(canvas);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateRestoredShortcuts(Context context) {
|
|
||||||
ShortcutManager sm = context.getSystemService(ShortcutManager.class);
|
|
||||||
List<ShortcutInfo> updatedShortcuts = new ArrayList<>();
|
|
||||||
for (ShortcutInfo si : sm.getPinnedShortcuts()) {
|
|
||||||
if (si.getId().startsWith(SHORTCUT_ID_PREFIX)) {
|
|
||||||
ResolveInfo ri = context.getPackageManager().resolveActivity(si.getIntent(), 0);
|
|
||||||
|
|
||||||
if (ri != null) {
|
|
||||||
updatedShortcuts.add(createShortcutInfo(context,
|
|
||||||
buildShortcutIntent(context, ri), ri, si.getShortLabel()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updatedShortcuts.isEmpty()) {
|
|
||||||
sm.updateShortcuts(updatedShortcuts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR =
|
private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR =
|
||||||
(i1, i2) -> i1.priority - i2.priority;
|
(i1, i2) -> i1.priority - i2.priority;
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/com/android/settings/shortcut/Shortcuts.java
Normal file
118
src/com/android/settings/shortcut/Shortcuts.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.shortcut;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||||
|
|
||||||
|
class Shortcuts {
|
||||||
|
|
||||||
|
private static final String TAG = "Shortcuts";
|
||||||
|
|
||||||
|
static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
|
||||||
|
static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
|
||||||
|
.addCategory("com.android.settings.SHORTCUT")
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
|
||||||
|
static ShortcutInfo createShortcutInfo(Context context, ResolveInfo target) {
|
||||||
|
checkArgument(target.activityInfo != null);
|
||||||
|
String shortcutId = SHORTCUT_ID_PREFIX
|
||||||
|
+ target.activityInfo.getComponentName().flattenToShortString();
|
||||||
|
|
||||||
|
return createShortcutInfo(context, shortcutId, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ShortcutInfo createShortcutInfo(Context context, String id, ResolveInfo target) {
|
||||||
|
Intent intent = new Intent(SHORTCUT_PROBE)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
.setClassName(target.activityInfo.packageName, target.activityInfo.name);
|
||||||
|
if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
CharSequence label = target.loadLabel(context.getPackageManager());
|
||||||
|
Icon maskableIcon = getMaskableIcon(context, target.activityInfo);
|
||||||
|
|
||||||
|
return new ShortcutInfo.Builder(context, id)
|
||||||
|
.setIntent(intent)
|
||||||
|
.setShortLabel(label)
|
||||||
|
.setIcon(maskableIcon)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Icon getMaskableIcon(Context context, ActivityInfo activityInfo) {
|
||||||
|
if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
|
||||||
|
return Icon.createWithAdaptiveBitmap(createIcon(
|
||||||
|
context,
|
||||||
|
activityInfo.applicationInfo, activityInfo.icon,
|
||||||
|
R.layout.shortcut_badge_maskable,
|
||||||
|
context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
|
||||||
|
} else {
|
||||||
|
return Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Bitmap createIcon(Context context, ApplicationInfo app, int resource, int layoutRes,
|
||||||
|
int size) {
|
||||||
|
final Context themedContext = new ContextThemeWrapper(context,
|
||||||
|
android.R.style.Theme_Material);
|
||||||
|
final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null);
|
||||||
|
final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
|
||||||
|
view.measure(spec, spec);
|
||||||
|
final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
|
||||||
|
Bitmap.Config.ARGB_8888);
|
||||||
|
final Canvas canvas = new Canvas(bitmap);
|
||||||
|
|
||||||
|
Drawable iconDrawable;
|
||||||
|
try {
|
||||||
|
iconDrawable = context.getPackageManager().getResourcesForApplication(app)
|
||||||
|
.getDrawable(resource, themedContext.getTheme());
|
||||||
|
if (iconDrawable instanceof LayerDrawable) {
|
||||||
|
iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
|
||||||
|
}
|
||||||
|
((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
|
||||||
|
Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
||||||
|
((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||||
|
view.draw(canvas);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.shortcut;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
|
|
||||||
|
public class ShortcutsUpdateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private static final String TAG = "ShortcutsUpdateReceiver";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
||||||
|
if (!Flags.modesApi() || !Flags.modesUi()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||||
|
PendingResult pendingResult = goAsync();
|
||||||
|
|
||||||
|
ThreadUtils.getBackgroundExecutor().execute(() -> {
|
||||||
|
try {
|
||||||
|
ShortcutsUpdater.updatePinnedShortcuts(context);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error trying to update Settings shortcuts", e);
|
||||||
|
} finally {
|
||||||
|
pendingResult.finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 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.shortcut;
|
|
||||||
|
|
||||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
|
|
||||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_PROBE;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.ShortcutInfo;
|
|
||||||
import android.content.pm.ShortcutManager;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ShortcutsUpdateTask extends AsyncTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
public ShortcutsUpdateTask(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void doInBackground(Void... params) {
|
|
||||||
ShortcutManager sm = mContext.getSystemService(ShortcutManager.class);
|
|
||||||
PackageManager pm = mContext.getPackageManager();
|
|
||||||
|
|
||||||
List<ShortcutInfo> updates = new ArrayList<>();
|
|
||||||
for (ShortcutInfo info : sm.getPinnedShortcuts()) {
|
|
||||||
if (!info.getId().startsWith(SHORTCUT_ID_PREFIX)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ComponentName cn = ComponentName.unflattenFromString(
|
|
||||||
info.getId().substring(SHORTCUT_ID_PREFIX.length()));
|
|
||||||
ResolveInfo ri = pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
|
|
||||||
if (ri == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
updates.add(new ShortcutInfo.Builder(mContext, info.getId())
|
|
||||||
.setShortLabel(ri.loadLabel(pm)).build());
|
|
||||||
}
|
|
||||||
if (!updates.isEmpty()) {
|
|
||||||
sm.updateShortcuts(updates);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
src/com/android/settings/shortcut/ShortcutsUpdater.java
Normal file
96
src/com/android/settings/shortcut/ShortcutsUpdater.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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.shortcut;
|
||||||
|
|
||||||
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
|
||||||
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
|
import android.content.pm.ShortcutManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.Settings;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ShortcutsUpdater {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update label, icon, and intent of pinned shortcuts to Settings subpages.
|
||||||
|
*
|
||||||
|
* <p>Should be called whenever any of those could have changed, such as after changing locale,
|
||||||
|
* restoring a backup from a different device, or when flags controlling available features
|
||||||
|
* may have flipped.
|
||||||
|
*/
|
||||||
|
public static void updatePinnedShortcuts(Context context) {
|
||||||
|
ShortcutManager sm = checkNotNull(context.getSystemService(ShortcutManager.class));
|
||||||
|
|
||||||
|
List<ShortcutInfo> updates = new ArrayList<>();
|
||||||
|
for (ShortcutInfo info : sm.getPinnedShortcuts()) {
|
||||||
|
ResolveInfo resolvedActivity = resolveActivity(context, info);
|
||||||
|
if (resolvedActivity != null) {
|
||||||
|
// Id is preserved to update an existing shortcut, but the activity it opens might
|
||||||
|
// be different, according to maybeGetReplacingComponent.
|
||||||
|
updates.add(Shortcuts.createShortcutInfo(context, info.getId(), resolvedActivity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
sm.updateShortcuts(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ResolveInfo resolveActivity(Context context, ShortcutInfo shortcut) {
|
||||||
|
if (!shortcut.getId().startsWith(SHORTCUT_ID_PREFIX)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentName cn = ComponentName.unflattenFromString(
|
||||||
|
shortcut.getId().substring(SHORTCUT_ID_PREFIX.length()));
|
||||||
|
if (cn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the componentName is obsolete and has been replaced by a different one.
|
||||||
|
cn = maybeGetReplacingComponent(context, cn);
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
return pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ComponentName maybeGetReplacingComponent(Context context, ComponentName cn) {
|
||||||
|
// ZenModeSettingsActivity is replaced by ModesSettingsActivity and will be deleted
|
||||||
|
// soon (so we shouldn't use ZenModeSettingsActivity.class).
|
||||||
|
if (Flags.modesApi() && Flags.modesUi()
|
||||||
|
&& cn.getClassName().endsWith("Settings$ZenModeSettingsActivity")) {
|
||||||
|
return new ComponentName(context, Settings.ModesSettingsActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,7 +137,7 @@ private fun AddSim() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startAddSimFlow(context: Context) {
|
fun startAddSimFlow(context: Context) {
|
||||||
val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
|
val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
|
||||||
intent.setPackage(Utils.PHONE_PACKAGE_NAME)
|
intent.setPackage(Utils.PHONE_PACKAGE_NAME)
|
||||||
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
|
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
|
||||||
|
|||||||
@@ -41,19 +41,20 @@ public class WallpaperSuggestionActivity extends StyleSuggestionActivityBase imp
|
|||||||
private static final String WALLPAPER_FOCUS = "focus_wallpaper";
|
private static final String WALLPAPER_FOCUS = "focus_wallpaper";
|
||||||
private static final String WALLPAPER_ONLY = "wallpaper_only";
|
private static final String WALLPAPER_ONLY = "wallpaper_only";
|
||||||
private static final String LAUNCHED_SUW = "app_launched_suw";
|
private static final String LAUNCHED_SUW = "app_launched_suw";
|
||||||
|
private static final String LAUNCH_SOURCE_SETTINGS_SEARCH = "app_launched_settings_search";
|
||||||
private String mWallpaperLaunchExtra;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addExtras(Intent intent) {
|
protected void addExtras(Intent intent) {
|
||||||
|
String wallpaperLaunchExtra =
|
||||||
|
getResources().getString(R.string.config_wallpaper_picker_launch_extra);;
|
||||||
if (WizardManagerHelper.isAnySetupWizard(intent)) {
|
if (WizardManagerHelper.isAnySetupWizard(intent)) {
|
||||||
intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_ONLY);
|
intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_ONLY);
|
||||||
|
intent.putExtra(wallpaperLaunchExtra, LAUNCHED_SUW);
|
||||||
mWallpaperLaunchExtra =
|
|
||||||
getResources().getString(R.string.config_wallpaper_picker_launch_extra);
|
|
||||||
intent.putExtra(mWallpaperLaunchExtra, LAUNCHED_SUW);
|
|
||||||
} else {
|
} else {
|
||||||
|
// This is the case when user enter the wallpaper picker from the search result entry
|
||||||
|
// on the Settings app
|
||||||
intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_FOCUS);
|
intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_FOCUS);
|
||||||
|
intent.putExtra(wallpaperLaunchExtra, LAUNCH_SOURCE_SETTINGS_SEARCH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,52 +16,80 @@
|
|||||||
|
|
||||||
package com.android.settings.bluetooth;
|
package com.android.settings.bluetooth;
|
||||||
|
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
|
||||||
|
|
||||||
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.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.bluetooth.BluetoothStatusCodes;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.SettingsActivity;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||||
|
import com.android.settingslib.flags.Flags;
|
||||||
|
|
||||||
import org.junit.Before;
|
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.Answers;
|
import org.mockito.Answers;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnit;
|
import org.mockito.junit.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.Robolectric;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadow.api.Shadow;
|
import org.robolectric.shadow.api.Shadow;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/** Tests for {@link BluetoothDevicePairingDetailBase}. */
|
/** Tests for {@link BluetoothDevicePairingDetailBase}. */
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {
|
@Config(shadows = {
|
||||||
ShadowBluetoothAdapter.class,
|
ShadowBluetoothAdapter.class,
|
||||||
|
ShadowAlertDialogCompat.class,
|
||||||
com.android.settings.testutils.shadow.ShadowFragment.class,
|
com.android.settings.testutils.shadow.ShadowFragment.class,
|
||||||
})
|
})
|
||||||
public class BluetoothDevicePairingDetailBaseTest {
|
public class BluetoothDevicePairingDetailBaseTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
public static final String KEY_DEVICE_LIST_GROUP = "test_key";
|
public static final String KEY_DEVICE_LIST_GROUP = "test_key";
|
||||||
|
|
||||||
@@ -86,8 +114,12 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
|
mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
|
||||||
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter());
|
||||||
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||||
|
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
|
||||||
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
|
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
||||||
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||||
final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
|
final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
|
||||||
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
|
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
|
||||||
@@ -155,8 +187,88 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
verify(mFragment).showBluetoothTurnedOnToast();
|
verify(mFragment).showBluetoothTurnedOnToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingDisabled_finish() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(false);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
|
||||||
|
|
||||||
|
verify(mFragment).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
|
assertThat(dialog).isNotNull();
|
||||||
|
TextView message = dialog.findViewById(R.id.message);
|
||||||
|
assertThat(message).isNotNull();
|
||||||
|
// TODO: use stringr res once finalized
|
||||||
|
assertThat(message.getText().toString()).isEqualTo(
|
||||||
|
"Connecting to " + TEST_DEVICE_ADDRESS + "...");
|
||||||
|
verify(mFragment, never()).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingDisabled_doNothing() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(false);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||||
|
|
||||||
|
verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(any(BluetoothDevice.class),
|
||||||
|
any(Executor.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingEnabled_addListener() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||||
|
|
||||||
|
verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mBluetoothDevice),
|
||||||
|
any(Executor.class),
|
||||||
|
any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingDisabled_doNothing() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
|
||||||
|
|
||||||
|
verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(
|
||||||
|
any(BluetoothDevice.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingEnabled_removeListener() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
|
||||||
|
|
||||||
|
verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mBluetoothDevice),
|
||||||
|
any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
|
public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
||||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
mFragment.mSelectedList.add(device);
|
mFragment.mSelectedList.add(device);
|
||||||
@@ -165,13 +277,43 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
||||||
|
|
||||||
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
|
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||||
|
|
||||||
verify(mFragment).finish();
|
verify(mFragment).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
onProfileConnectionStateChanged_deviceInSelectedListAndConnected_pairAndJoinSharing() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
|
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||||
|
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
|
||||||
|
|
||||||
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
|
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||||
|
verify(mFragment.getActivity()).setResult(eq(Activity.RESULT_OK), captor.capture());
|
||||||
|
Intent intent = captor.getValue();
|
||||||
|
BluetoothDevice btDevice =
|
||||||
|
intent != null
|
||||||
|
? intent.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
|
||||||
|
BluetoothDevice.class)
|
||||||
|
: null;
|
||||||
|
assertThat(btDevice).isNotNull();
|
||||||
|
assertThat(btDevice).isEqualTo(mBluetoothDevice);
|
||||||
|
verify(mFragment).finish();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
|
public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
||||||
mFragment.mSelectedList.add(device);
|
mFragment.mSelectedList.add(device);
|
||||||
|
|
||||||
@@ -179,13 +321,14 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
|
|
||||||
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
|
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||||
|
|
||||||
// not crash
|
// not crash
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
|
public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
||||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||||
mFragment.mSelectedList.add(device);
|
mFragment.mSelectedList.add(device);
|
||||||
@@ -194,13 +337,14 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
||||||
|
|
||||||
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
|
BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.A2DP);
|
||||||
|
|
||||||
// not crash
|
// not crash
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
|
public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
final BluetoothDevicePreference preference =
|
final BluetoothDevicePreference preference =
|
||||||
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
||||||
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
||||||
@@ -211,13 +355,14 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
||||||
|
|
||||||
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
|
BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||||
|
|
||||||
assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
|
assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
|
public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
|
||||||
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
|
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
|
||||||
final BluetoothDevicePreference preference =
|
final BluetoothDevicePreference preference =
|
||||||
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
||||||
@@ -233,12 +378,26 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
|
when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
|
||||||
when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
|
when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
|
||||||
|
|
||||||
mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
|
mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothAdapter.STATE_CONNECTED,
|
||||||
BluetoothAdapter.STATE_CONNECTED);
|
BluetoothProfile.A2DP);
|
||||||
|
|
||||||
// not crash
|
// not crash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setUpFragmentWithPairAndJoinSharingIntent(boolean enablePairAndJoinSharing) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, enablePairAndJoinSharing);
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
|
||||||
|
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
|
||||||
|
doReturn(intent).when(activity).getIntent();
|
||||||
|
doReturn(activity).when(mFragment).getActivity();
|
||||||
|
FragmentManager fragmentManager = mock(FragmentManager.class);
|
||||||
|
doReturn(fragmentManager).when(mFragment).getFragmentManager();
|
||||||
|
mFragment.mShouldTriggerAudioSharingShareThenPairFlow =
|
||||||
|
mFragment.shouldTriggerAudioSharingShareThenPairFlow();
|
||||||
|
}
|
||||||
|
|
||||||
private static class TestBluetoothDevicePairingDetailBase extends
|
private static class TestBluetoothDevicePairingDetailBase extends
|
||||||
BluetoothDevicePairingDetailBase {
|
BluetoothDevicePairingDetailBase {
|
||||||
|
|
||||||
|
|||||||
@@ -16,16 +16,29 @@
|
|||||||
|
|
||||||
package com.android.settings.connecteddevice.audiosharing;
|
package com.android.settings.connecteddevice.audiosharing;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
|
||||||
|
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
|
||||||
|
|
||||||
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.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothStatusCodes;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
@@ -33,24 +46,29 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.SettingsActivity;
|
import com.android.settings.SettingsActivity;
|
||||||
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
|
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settings.testutils.shadow.ShadowFragment;
|
import com.android.settings.testutils.shadow.ShadowFragment;
|
||||||
import com.android.settings.widget.SettingsMainSwitchBar;
|
import com.android.settings.widget.SettingsMainSwitchBar;
|
||||||
|
import com.android.settingslib.flags.Flags;
|
||||||
|
|
||||||
import org.junit.Before;
|
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.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.MockitoJUnit;
|
import org.mockito.junit.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.shadow.api.Shadow;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {ShadowFragment.class})
|
@Config(shadows = {ShadowFragment.class, ShadowBluetoothAdapter.class})
|
||||||
public class AudioSharingDashboardFragmentTest {
|
public class AudioSharingDashboardFragmentTest {
|
||||||
|
|
||||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||||
|
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
@Mock private SettingsActivity mActivity;
|
@Mock private SettingsActivity mActivity;
|
||||||
@Mock private SettingsMainSwitchBar mSwitchBar;
|
@Mock private SettingsMainSwitchBar mSwitchBar;
|
||||||
@@ -59,11 +77,19 @@ public class AudioSharingDashboardFragmentTest {
|
|||||||
@Mock private AudioSharingCallAudioPreferenceController mCallAudioController;
|
@Mock private AudioSharingCallAudioPreferenceController mCallAudioController;
|
||||||
@Mock private AudioSharingPlaySoundPreferenceController mPlaySoundController;
|
@Mock private AudioSharingPlaySoundPreferenceController mPlaySoundController;
|
||||||
@Mock private AudioStreamsCategoryController mStreamsCategoryController;
|
@Mock private AudioStreamsCategoryController mStreamsCategoryController;
|
||||||
|
@Mock private AudioSharingSwitchBarController mSwitchBarController;
|
||||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||||
private AudioSharingDashboardFragment mFragment;
|
private AudioSharingDashboardFragment mFragment;
|
||||||
|
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
|
||||||
|
mShadowBluetoothAdapter.setEnabled(true);
|
||||||
|
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
|
||||||
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
|
mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
||||||
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
when(mSwitchBar.getRootView()).thenReturn(mView);
|
when(mSwitchBar.getRootView()).thenReturn(mView);
|
||||||
mFragment = new AudioSharingDashboardFragment();
|
mFragment = new AudioSharingDashboardFragment();
|
||||||
}
|
}
|
||||||
@@ -100,13 +126,73 @@ public class AudioSharingDashboardFragmentTest {
|
|||||||
verify(mSwitchBar).show();
|
verify(mSwitchBar).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onActivityResult_shareThenPairWithBadCode_doNothing() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
mFragment.setControllers(
|
||||||
|
mVolumeGroupController,
|
||||||
|
mCallAudioController,
|
||||||
|
mPlaySoundController,
|
||||||
|
mStreamsCategoryController,
|
||||||
|
mSwitchBarController);
|
||||||
|
Intent data = new Intent();
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
|
||||||
|
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
|
||||||
|
data.putExtras(extras);
|
||||||
|
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onActivityResult_shareThenPairWithNoDevice_doNothing() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
mFragment.setControllers(
|
||||||
|
mVolumeGroupController,
|
||||||
|
mCallAudioController,
|
||||||
|
mPlaySoundController,
|
||||||
|
mStreamsCategoryController,
|
||||||
|
mSwitchBarController);
|
||||||
|
Intent data = new Intent();
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, null);
|
||||||
|
data.putExtras(extras);
|
||||||
|
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onActivityResult_shareThenPairWithDevice_handleAutoAddSource() {
|
||||||
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
mFragment.setControllers(
|
||||||
|
mVolumeGroupController,
|
||||||
|
mCallAudioController,
|
||||||
|
mPlaySoundController,
|
||||||
|
mStreamsCategoryController,
|
||||||
|
mSwitchBarController);
|
||||||
|
Intent data = new Intent();
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
|
||||||
|
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
|
||||||
|
data.putExtras(extras);
|
||||||
|
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_OK, data);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mSwitchBarController).handleAutoAddSourceAfterPair(device);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onAudioSharingStateChanged_updateVisibilityForControllers() {
|
public void onAudioSharingStateChanged_updateVisibilityForControllers() {
|
||||||
mFragment.setControllers(
|
mFragment.setControllers(
|
||||||
mVolumeGroupController,
|
mVolumeGroupController,
|
||||||
mCallAudioController,
|
mCallAudioController,
|
||||||
mPlaySoundController,
|
mPlaySoundController,
|
||||||
mStreamsCategoryController);
|
mStreamsCategoryController,
|
||||||
|
mSwitchBarController);
|
||||||
mFragment.onAudioSharingStateChanged();
|
mFragment.onAudioSharingStateChanged();
|
||||||
verify(mVolumeGroupController).updateVisibility();
|
verify(mVolumeGroupController).updateVisibility();
|
||||||
verify(mCallAudioController).updateVisibility();
|
verify(mCallAudioController).updateVisibility();
|
||||||
@@ -120,7 +206,8 @@ public class AudioSharingDashboardFragmentTest {
|
|||||||
mVolumeGroupController,
|
mVolumeGroupController,
|
||||||
mCallAudioController,
|
mCallAudioController,
|
||||||
mPlaySoundController,
|
mPlaySoundController,
|
||||||
mStreamsCategoryController);
|
mStreamsCategoryController,
|
||||||
|
mSwitchBarController);
|
||||||
mFragment.onAudioSharingProfilesConnected();
|
mFragment.onAudioSharingProfilesConnected();
|
||||||
verify(mVolumeGroupController).onAudioSharingProfilesConnected();
|
verify(mVolumeGroupController).onAudioSharingProfilesConnected();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import android.widget.Button;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -82,11 +83,6 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
|
new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
|
||||||
private static final AudioSharingDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
|
private static final AudioSharingDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
|
||||||
new AudioSharingDialogFragment.DialogEventListener() {
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
@Override
|
|
||||||
public void onItemClick(AudioSharingDeviceItem item) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancelClick() {}
|
|
||||||
};
|
};
|
||||||
private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
|
private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
|
||||||
private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
|
private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
|
||||||
@@ -176,8 +172,17 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onCreateDialog_noExtraConnectedDevice_pairNewDevice() {
|
public void onCreateDialog_noExtraConnectedDevice_pairNewDevice() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
AtomicBoolean isPairBtnClicked = new AtomicBoolean(false);
|
||||||
AudioSharingDialogFragment.show(
|
AudioSharingDialogFragment.show(
|
||||||
mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
|
mParent,
|
||||||
|
new ArrayList<>(),
|
||||||
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onPositiveClick() {
|
||||||
|
isPairBtnClicked.set(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TEST_EVENT_DATA_LIST);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
assertThat(dialog).isNotNull();
|
assertThat(dialog).isNotNull();
|
||||||
@@ -191,14 +196,24 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
any(Context.class),
|
any(Context.class),
|
||||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
|
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
|
||||||
eq(TEST_EVENT_DATA));
|
eq(TEST_EVENT_DATA));
|
||||||
|
assertThat(isPairBtnClicked.get()).isTrue();
|
||||||
assertThat(dialog.isShowing()).isFalse();
|
assertThat(dialog.isShowing()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onCreateDialog_noExtraConnectedDevice_showQRCode() {
|
public void onCreateDialog_noExtraConnectedDevice_showQRCode() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
|
AtomicBoolean isQrCodeBtnClicked = new AtomicBoolean(false);
|
||||||
AudioSharingDialogFragment.show(
|
AudioSharingDialogFragment.show(
|
||||||
mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
|
mParent,
|
||||||
|
new ArrayList<>(),
|
||||||
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancelClick() {
|
||||||
|
isQrCodeBtnClicked.set(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TEST_EVENT_DATA_LIST);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
assertThat(dialog).isNotNull();
|
assertThat(dialog).isNotNull();
|
||||||
@@ -212,6 +227,7 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
any(Context.class),
|
any(Context.class),
|
||||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
|
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
|
||||||
eq(TEST_EVENT_DATA));
|
eq(TEST_EVENT_DATA));
|
||||||
|
assertThat(isQrCodeBtnClicked.get()).isTrue();
|
||||||
assertThat(dialog.isShowing()).isFalse();
|
assertThat(dialog.isShowing()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,12 +302,9 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
list,
|
list,
|
||||||
new AudioSharingDialogFragment.DialogEventListener() {
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AudioSharingDeviceItem item) {
|
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
|
||||||
isShareBtnClicked.set(true);
|
isShareBtnClicked.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancelClick() {}
|
|
||||||
},
|
},
|
||||||
TEST_EVENT_DATA_LIST);
|
TEST_EVENT_DATA_LIST);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
@@ -359,9 +372,6 @@ public class AudioSharingDialogFragmentTest {
|
|||||||
mParent,
|
mParent,
|
||||||
list,
|
list,
|
||||||
new AudioSharingDialogFragment.DialogEventListener() {
|
new AudioSharingDialogFragment.DialogEventListener() {
|
||||||
@Override
|
|
||||||
public void onItemClick(AudioSharingDeviceItem item) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelClick() {
|
public void onCancelClick() {
|
||||||
isCancelBtnClicked.set(true);
|
isCancelBtnClicked.set(true);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.android.settings.connecteddevice.audiosharing;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
@@ -34,7 +33,6 @@ import androidx.fragment.app.FragmentActivity;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
|
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
|
||||||
import com.android.settingslib.flags.Flags;
|
import com.android.settingslib.flags.Flags;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@@ -42,7 +40,6 @@ 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.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
@@ -62,7 +59,6 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
|
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
|
||||||
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
|
|
||||||
private Fragment mParent;
|
private Fragment mParent;
|
||||||
private AudioSharingIncompatibleDialogFragment mFragment;
|
private AudioSharingIncompatibleDialogFragment mFragment;
|
||||||
|
|
||||||
@@ -76,7 +72,6 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
|
||||||
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
BluetoothStatusCodes.FEATURE_SUPPORTED);
|
||||||
when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
|
|
||||||
mFragment = new AudioSharingIncompatibleDialogFragment();
|
mFragment = new AudioSharingIncompatibleDialogFragment();
|
||||||
mParent = new Fragment();
|
mParent = new Fragment();
|
||||||
FragmentController.setupFragment(mParent, FragmentActivity.class, /* containerViewId= */
|
FragmentController.setupFragment(mParent, FragmentActivity.class, /* containerViewId= */
|
||||||
@@ -97,7 +92,7 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onCreateDialog_flagOff_dialogNotExist() {
|
public void onCreateDialog_flagOff_dialogNotExist() {
|
||||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
|
AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
|
||||||
EMPTY_EVENT_LISTENER);
|
EMPTY_EVENT_LISTENER);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
@@ -107,7 +102,7 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onCreateDialog_unattachedFragment_dialogNotExist() {
|
public void onCreateDialog_unattachedFragment_dialogNotExist() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
AudioSharingIncompatibleDialogFragment.show(new Fragment(), mCachedBluetoothDevice,
|
AudioSharingIncompatibleDialogFragment.show(new Fragment(), TEST_DEVICE_NAME,
|
||||||
EMPTY_EVENT_LISTENER);
|
EMPTY_EVENT_LISTENER);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
@@ -117,7 +112,7 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
@Test
|
@Test
|
||||||
public void onCreateDialog_flagOn_showDialog() {
|
public void onCreateDialog_flagOn_showDialog() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
|
AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
|
||||||
EMPTY_EVENT_LISTENER);
|
EMPTY_EVENT_LISTENER);
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
@@ -134,7 +129,7 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
|||||||
public void onCreateDialog_clickBtn_callbackTriggered() {
|
public void onCreateDialog_clickBtn_callbackTriggered() {
|
||||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||||
AtomicBoolean isBtnClicked = new AtomicBoolean(false);
|
AtomicBoolean isBtnClicked = new AtomicBoolean(false);
|
||||||
AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
|
AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
|
||||||
() -> isBtnClicked.set(true));
|
() -> isBtnClicked.set(true));
|
||||||
shadowMainLooper().idle();
|
shadowMainLooper().idle();
|
||||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||||
|
|||||||
@@ -934,6 +934,19 @@ public class AudioSharingSwitchBarControllerTest {
|
|||||||
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
|
childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleAutoAddSourceAfterPair() {
|
||||||
|
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1));
|
||||||
|
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
|
||||||
|
mController.handleAutoAddSourceAfterPair(mDevice1);
|
||||||
|
shadowOf(Looper.getMainLooper()).idle();
|
||||||
|
|
||||||
|
verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
|
||||||
|
List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
|
||||||
|
assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
|
||||||
|
AudioSharingLoadingStateDialogFragment.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
private Fragment setUpFragmentWithStartSharingIntent() {
|
private Fragment setUpFragmentWithStartSharingIntent() {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
|
args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.settings.network;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.ArgumentMatchers.notNull;
|
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.telephony.SubscriptionInfo;
|
|
||||||
import android.telephony.SubscriptionManager;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.telephony.euicc.EuiccManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
|
||||||
|
|
||||||
import com.android.settings.Settings.MobileNetworkActivity;
|
|
||||||
import com.android.settings.widget.AddPreference;
|
|
||||||
import com.android.settingslib.RestrictedLockUtils;
|
|
||||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
public class MobileNetworkSummaryControllerTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private TelephonyManager mTelephonyManager;
|
|
||||||
@Mock
|
|
||||||
private SubscriptionManager mSubscriptionManager;
|
|
||||||
@Mock
|
|
||||||
private EuiccManager mEuiccManager;
|
|
||||||
@Mock
|
|
||||||
private PreferenceScreen mPreferenceScreen;
|
|
||||||
@Mock
|
|
||||||
private MobileNetworkRepository mMobileNetworkRepository;
|
|
||||||
@Mock
|
|
||||||
private MobileNetworkRepository.MobileNetworkCallback mMobileNetworkCallback;
|
|
||||||
|
|
||||||
private AddPreference mPreference;
|
|
||||||
private Context mContext;
|
|
||||||
private MobileNetworkSummaryController mController;
|
|
||||||
private LifecycleOwner mLifecycleOwner;
|
|
||||||
private Lifecycle mLifecycle;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
mContext = spy(RuntimeEnvironment.application);
|
|
||||||
doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
|
|
||||||
doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
|
|
||||||
doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class);
|
|
||||||
mMobileNetworkRepository = MobileNetworkRepository.getInstance(mContext);
|
|
||||||
mLifecycleOwner = () -> mLifecycle;
|
|
||||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
|
||||||
mMobileNetworkRepository.addRegister(mLifecycleOwner, mMobileNetworkCallback,
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
||||||
|
|
||||||
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
|
|
||||||
when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
|
|
||||||
when(mEuiccManager.isEnabled()).thenReturn(true);
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
|
|
||||||
|
|
||||||
mController = new MobileNetworkSummaryController(mContext, mLifecycle, mLifecycleOwner);
|
|
||||||
mPreference = spy(new AddPreference(mContext, null));
|
|
||||||
mPreference.setKey(mController.getPreferenceKey());
|
|
||||||
when(mPreferenceScreen.findPreference(eq(mController.getPreferenceKey()))).thenReturn(
|
|
||||||
mPreference);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
mMobileNetworkRepository.removeRegister(mMobileNetworkCallback);
|
|
||||||
SubscriptionUtil.setActiveSubscriptionsForTesting(null);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getSummary_noSubscriptions_returnSummaryCorrectly() {
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mController.getSummary()).isEqualTo("Add a network");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getSummary_noSubscriptionsNoEuiccMgr_correctSummaryAndClickHandler() {
|
|
||||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
|
||||||
assertThat(TextUtils.isEmpty(mController.getSummary())).isTrue();
|
|
||||||
assertThat(mPreference.getOnPreferenceClickListener()).isNull();
|
|
||||||
assertThat(mPreference.getFragment()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void getSummary_oneSubscription_correctSummaryAndClickHandler() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
|
||||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
assertThat(mController.getSummary()).isEqualTo("sub1");
|
|
||||||
assertThat(mPreference.getFragment()).isNull();
|
|
||||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
|
||||||
doNothing().when(mContext).startActivity(intentCaptor.capture());
|
|
||||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
|
||||||
Intent intent = intentCaptor.getValue();
|
|
||||||
assertThat(intent.getComponent().getClassName()).isEqualTo(
|
|
||||||
MobileNetworkActivity.class.getName());
|
|
||||||
assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void getSummary_oneInactivePSim_cannotDisablePsim_correctSummaryAndClickHandler() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
|
||||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
|
|
||||||
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1");
|
|
||||||
|
|
||||||
assertThat(mPreference.getFragment()).isNull();
|
|
||||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
|
||||||
verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void getSummary_oneInactivePSim_canDisablePsim_correctSummaryAndClickHandler() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
|
||||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
|
|
||||||
when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true);
|
|
||||||
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mController.getSummary()).isEqualTo("sub1");
|
|
||||||
|
|
||||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
|
||||||
doNothing().when(mContext).startActivity(intentCaptor.capture());
|
|
||||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
|
||||||
Intent intent = intentCaptor.getValue();
|
|
||||||
assertThat(intent.getComponent().getClassName()).isEqualTo(
|
|
||||||
MobileNetworkActivity.class.getName());
|
|
||||||
assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
|
|
||||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addButton_noSubscriptionsNoEuiccMgr_noAddClickListener() {
|
|
||||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addButton_oneSubscriptionNoEuiccMgr_noAddClickListener() {
|
|
||||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addButton_noSubscriptions_noAddClickListener() {
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void addButton_oneSubscription_hasAddClickListener() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference).setOnAddClickListener(notNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void addButton_twoSubscriptions_hasAddClickListener() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference).setOnAddClickListener(notNull());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void addButton_oneSubscriptionAirplaneModeTurnedOn_addButtonGetsDisabled() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
|
||||||
mController.onAirplaneModeChanged(true);
|
|
||||||
|
|
||||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
|
||||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
|
||||||
assertThat(captor.getValue()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void onResume_oneSubscriptionAirplaneMode_isDisabled() {
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isFalse();
|
|
||||||
|
|
||||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
|
||||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
|
||||||
assertThat(captor.getValue()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onAvailableSubInfoChanged_noSubscriptionEsimDisabled_isDisabled() {
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
|
|
||||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
|
|
||||||
mController.onAvailableSubInfoChanged(null);
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOn_isDisabled() {
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isTrue();
|
|
||||||
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
|
||||||
mController.onAirplaneModeChanged(true);
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOff_isEnabled() {
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
|
||||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
|
||||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isFalse();
|
|
||||||
|
|
||||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
|
|
||||||
mController.onAirplaneModeChanged(false);
|
|
||||||
|
|
||||||
assertThat(mPreference.isEnabled()).isTrue();
|
|
||||||
|
|
||||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
|
||||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(eq(false));
|
|
||||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
|
||||||
assertThat(captor.getValue()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onResume_disabledByAdmin_prefStaysDisabled() {
|
|
||||||
mPreference.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
mController.onResume();
|
|
||||||
verify(mPreference, never()).setEnabled(eq(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,35 +18,77 @@ package com.android.settings.notification;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.media.RingtoneManager;
|
import android.media.RingtoneManager;
|
||||||
|
import android.platform.test.annotations.DisableFlags;
|
||||||
|
import android.platform.test.annotations.EnableFlags;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
|
||||||
|
import com.android.server.notification.Flags;
|
||||||
|
import com.android.settings.R;
|
||||||
|
|
||||||
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.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class NotificationRingtonePreferenceControllerTest {
|
public class NotificationRingtonePreferenceControllerTest {
|
||||||
|
|
||||||
private NotificationRingtonePreferenceController mController;
|
private NotificationRingtonePreferenceController mController;
|
||||||
|
@Mock private Context mMockContext;
|
||||||
|
@Mock private Resources mMockResources;
|
||||||
|
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mController = new NotificationRingtonePreferenceController(RuntimeEnvironment.application);
|
when(mMockContext.getResources()).thenReturn(mMockResources);
|
||||||
|
mController = new NotificationRingtonePreferenceController(mMockContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
|
||||||
public void isAvailable_byDefault_isTrue() {
|
public void isAvailable_byDefault_isTrue() {
|
||||||
|
when(mMockResources
|
||||||
|
.getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
|
||||||
|
.thenReturn(false);
|
||||||
|
when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
|
||||||
|
.thenReturn(true);
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isTrue();
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "mcc999")
|
@Config(qualifiers = "mcc999")
|
||||||
|
@DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
|
||||||
public void isAvailable_whenNotVisible_isFalse() {
|
public void isAvailable_whenNotVisible_isFalse() {
|
||||||
|
when(mMockResources
|
||||||
|
.getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
|
||||||
|
.thenReturn(false);
|
||||||
|
when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
|
||||||
|
.thenReturn(false);
|
||||||
|
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
|
||||||
|
public void isAvailable_whenFlagsNotificationVibrationInSoundUri_isFalse() {
|
||||||
|
when(mMockResources
|
||||||
|
.getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
|
||||||
|
.thenReturn(true);
|
||||||
|
when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
|
||||||
|
.thenReturn(true);
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isFalse();
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package com.android.settings.shortcut;
|
package com.android.settings.shortcut;
|
||||||
|
|
||||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
@@ -101,10 +101,10 @@ public class CreateShortcutPreferenceControllerTest {
|
|||||||
when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
|
when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
|
||||||
.thenReturn(new Intent().putExtra("d1", "d2"));
|
.thenReturn(new Intent().putExtra("d1", "d2"));
|
||||||
|
|
||||||
final Intent intent = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
|
final Intent intent = new Intent(Shortcuts.SHORTCUT_PROBE)
|
||||||
.setClass(mContext, Settings.ManageApplicationsActivity.class);
|
.setClass(mContext, Settings.ManageApplicationsActivity.class);
|
||||||
final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
|
final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
|
||||||
final Intent result = mController.createResultIntent(intent, ri, "mock");
|
final Intent result = mController.createResultIntent(ri);
|
||||||
|
|
||||||
assertThat(result.getStringExtra("d1")).isEqualTo("d2");
|
assertThat(result.getStringExtra("d1")).isEqualTo("d2");
|
||||||
assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull();
|
assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull();
|
||||||
@@ -131,7 +131,7 @@ public class CreateShortcutPreferenceControllerTest {
|
|||||||
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||||
|
|
||||||
mPackageManager.setResolveInfosForIntent(
|
mPackageManager.setResolveInfosForIntent(
|
||||||
new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
|
new Intent(Shortcuts.SHORTCUT_PROBE),
|
||||||
Arrays.asList(ri1, ri2));
|
Arrays.asList(ri1, ri2));
|
||||||
|
|
||||||
doReturn(false).when(mController).canShowWifiHotspot();
|
doReturn(false).when(mController).canShowWifiHotspot();
|
||||||
@@ -158,7 +158,7 @@ public class CreateShortcutPreferenceControllerTest {
|
|||||||
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||||
|
|
||||||
mPackageManager.setResolveInfosForIntent(
|
mPackageManager.setResolveInfosForIntent(
|
||||||
new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
|
new Intent(Shortcuts.SHORTCUT_PROBE),
|
||||||
Arrays.asList(ri1, ri2));
|
Arrays.asList(ri1, ri2));
|
||||||
|
|
||||||
doReturn(false).when(mController).canShowWifiHotspot();
|
doReturn(false).when(mController).canShowWifiHotspot();
|
||||||
@@ -276,7 +276,7 @@ public class CreateShortcutPreferenceControllerTest {
|
|||||||
ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||||
|
|
||||||
mPackageManager.setResolveInfosForIntent(
|
mPackageManager.setResolveInfosForIntent(
|
||||||
new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
|
new Intent(Shortcuts.SHORTCUT_PROBE),
|
||||||
Arrays.asList(ri));
|
Arrays.asList(ri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.shortcut;
|
||||||
|
|
||||||
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ShortcutInfo;
|
||||||
|
|
||||||
|
import com.android.settings.Settings;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class ShortcutsTest {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = RuntimeEnvironment.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shortcutsUpdateTask() {
|
||||||
|
final Intent intent = new Intent(SHORTCUT_PROBE)
|
||||||
|
.setClass(mContext, Settings.ManageApplicationsActivity.class);
|
||||||
|
final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
|
||||||
|
assertThat(ri).isNotNull();
|
||||||
|
|
||||||
|
ShortcutInfo shortcut = Shortcuts.createShortcutInfo(mContext, ri);
|
||||||
|
|
||||||
|
assertThat(shortcut.getLabel()).isNotNull();
|
||||||
|
assertThat(shortcut.getLabel().toString()).isEqualTo("App info");
|
||||||
|
|
||||||
|
assertThat(shortcut.getIntent()).isNotNull();
|
||||||
|
assertThat(shortcut.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN);
|
||||||
|
assertThat(shortcut.getIntent().getCategories()).contains("com.android.settings.SHORTCUT");
|
||||||
|
assertThat(shortcut.getIntent().getComponent()).isEqualTo(
|
||||||
|
new ComponentName(mContext, Settings.ManageApplicationsActivity.class));
|
||||||
|
assertThat(shortcut.getIcon()).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,30 +16,30 @@
|
|||||||
|
|
||||||
package com.android.settings.shortcut;
|
package com.android.settings.shortcut;
|
||||||
|
|
||||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
|
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
|
||||||
|
|
||||||
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.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
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;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.Flags;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.pm.ShortcutManager;
|
import android.content.pm.ShortcutManager;
|
||||||
|
import android.platform.test.annotations.DisableFlags;
|
||||||
|
import android.platform.test.annotations.EnableFlags;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
|
||||||
import com.android.settings.Settings;
|
import com.android.settings.Settings;
|
||||||
|
|
||||||
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.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -48,17 +48,17 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
import org.robolectric.shadow.api.Shadow;
|
|
||||||
import org.robolectric.shadows.ShadowPackageManager;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class ShortcutsUpdateTaskTest {
|
public class ShortcutsUpdaterTest {
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private ShadowPackageManager mPackageManager;
|
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ShortcutManager mShortcutManager;
|
private ShortcutManager mShortcutManager;
|
||||||
@@ -68,29 +68,12 @@ public class ShortcutsUpdateTaskTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
mPackageManager = Shadow.extract(mContext.getPackageManager());
|
doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shortcutsUpdateTask() {
|
public void updatePinnedShortcuts_updatesAllShortcuts() {
|
||||||
mContext = spy(RuntimeEnvironment.application);
|
|
||||||
doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
|
|
||||||
final Intent shortcut1 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
|
|
||||||
.setComponent(new ComponentName(
|
|
||||||
mContext, Settings.ManageApplicationsActivity.class));
|
|
||||||
final ResolveInfo ri1 = mock(ResolveInfo.class);
|
|
||||||
ri1.nonLocalizedLabel = "label1";
|
|
||||||
|
|
||||||
final Intent shortcut2 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
|
|
||||||
.setComponent(new ComponentName(
|
|
||||||
mContext, Settings.SoundSettingsActivity.class));
|
|
||||||
final ResolveInfo ri2 = mock(ResolveInfo.class);
|
|
||||||
ri2.nonLocalizedLabel = "label2";
|
|
||||||
|
|
||||||
mPackageManager.addResolveInfoForIntent(shortcut1, ri1);
|
|
||||||
mPackageManager.addResolveInfoForIntent(shortcut2, ri2);
|
|
||||||
|
|
||||||
final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
|
final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
|
||||||
makeShortcut("d1"),
|
makeShortcut("d1"),
|
||||||
makeShortcut("d2"),
|
makeShortcut("d2"),
|
||||||
@@ -99,7 +82,7 @@ public class ShortcutsUpdateTaskTest {
|
|||||||
makeShortcut(Settings.SoundSettingsActivity.class));
|
makeShortcut(Settings.SoundSettingsActivity.class));
|
||||||
when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
|
when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
|
||||||
|
|
||||||
new ShortcutsUpdateTask(mContext).doInBackground();
|
ShortcutsUpdater.updatePinnedShortcuts(mContext);
|
||||||
|
|
||||||
verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
|
verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
|
||||||
|
|
||||||
@@ -108,6 +91,52 @@ public class ShortcutsUpdateTaskTest {
|
|||||||
assertThat(updates).hasSize(2);
|
assertThat(updates).hasSize(2);
|
||||||
assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
|
assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
|
||||||
assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
|
assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
|
||||||
|
assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("App info");
|
||||||
|
assertThat(updates.get(1).getShortLabel().toString()).isEqualTo("Sound & vibration");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||||
|
public void updatePinnedShortcuts_withModesFlag_replacesDndByModes() {
|
||||||
|
List<ShortcutInfo> shortcuts = List.of(
|
||||||
|
makeShortcut(Settings.ZenModeSettingsActivity.class));
|
||||||
|
when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts);
|
||||||
|
|
||||||
|
ShortcutsUpdater.updatePinnedShortcuts(mContext);
|
||||||
|
|
||||||
|
verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
|
||||||
|
final List<ShortcutInfo> updates = mListCaptor.getValue();
|
||||||
|
assertThat(updates).hasSize(1);
|
||||||
|
|
||||||
|
// Id hasn't changed, but intent and label has.
|
||||||
|
ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class);
|
||||||
|
ComponentName modesCn = new ComponentName(mContext, Settings.ModesSettingsActivity.class);
|
||||||
|
assertThat(updates.get(0).getId()).isEqualTo(
|
||||||
|
SHORTCUT_ID_PREFIX + zenCn.flattenToShortString());
|
||||||
|
assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(modesCn);
|
||||||
|
assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Modes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisableFlags(Flags.FLAG_MODES_UI)
|
||||||
|
public void updatePinnedShortcuts_withoutModesFlag_leavesDndAlone() {
|
||||||
|
List<ShortcutInfo> shortcuts = List.of(
|
||||||
|
makeShortcut(Settings.ZenModeSettingsActivity.class));
|
||||||
|
when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts);
|
||||||
|
|
||||||
|
ShortcutsUpdater.updatePinnedShortcuts(mContext);
|
||||||
|
|
||||||
|
verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
|
||||||
|
final List<ShortcutInfo> updates = mListCaptor.getValue();
|
||||||
|
assertThat(updates).hasSize(1);
|
||||||
|
|
||||||
|
// Nothing has changed.
|
||||||
|
ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class);
|
||||||
|
assertThat(updates.get(0).getId()).isEqualTo(
|
||||||
|
SHORTCUT_ID_PREFIX + zenCn.flattenToShortString());
|
||||||
|
assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(zenCn);
|
||||||
|
assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Do Not Disturb");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ShortcutInfo makeShortcut(Class<?> className) {
|
private ShortcutInfo makeShortcut(Class<?> className) {
|
||||||
@@ -118,7 +118,7 @@ public class WallpaperSuggestionActivityTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaper() {
|
public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaperAndLaunchedSettingsSearch() {
|
||||||
WallpaperSuggestionActivity activity = Robolectric.buildActivity(
|
WallpaperSuggestionActivity activity = Robolectric.buildActivity(
|
||||||
WallpaperSuggestionActivity.class, new Intent(Intent.ACTION_MAIN).setComponent(
|
WallpaperSuggestionActivity.class, new Intent(Intent.ACTION_MAIN).setComponent(
|
||||||
new ComponentName(RuntimeEnvironment.application,
|
new ComponentName(RuntimeEnvironment.application,
|
||||||
@@ -127,6 +127,8 @@ public class WallpaperSuggestionActivityTest {
|
|||||||
|
|
||||||
assertThat(intent).isNotNull();
|
assertThat(intent).isNotNull();
|
||||||
assertThat(intent.getStringExtra(WALLPAPER_FLAVOR)).isEqualTo("focus_wallpaper");
|
assertThat(intent.getStringExtra(WALLPAPER_FLAVOR)).isEqualTo("focus_wallpaper");
|
||||||
|
assertThat(intent.getStringExtra(WALLPAPER_LAUNCH_SOURCE))
|
||||||
|
.isEqualTo("app_launched_settings_search");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settingslib.RestrictedPreference
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.stub
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MobileNetworkSummaryControllerTest {
|
||||||
|
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private val preference = RestrictedPreference(context).apply { key = KEY }
|
||||||
|
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
|
||||||
|
|
||||||
|
private val mockMobileNetworkSummaryRepository = mock<MobileNetworkSummaryRepository>()
|
||||||
|
private val airplaneModeOnFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
|
private val controller =
|
||||||
|
MobileNetworkSummaryController(
|
||||||
|
context = context,
|
||||||
|
preferenceKey = KEY,
|
||||||
|
repository = mockMobileNetworkSummaryRepository,
|
||||||
|
airplaneModeOnFlow = airplaneModeOnFlow,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
preferenceScreen.addPreference(preference)
|
||||||
|
controller.displayPreference(preferenceScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_noSubscriptions(): Unit = runBlocking {
|
||||||
|
mockMobileNetworkSummaryRepository.stub {
|
||||||
|
on { subscriptionsStateFlow() } doReturn
|
||||||
|
flowOf(MobileNetworkSummaryRepository.NoSubscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.summary).isNull()
|
||||||
|
assertThat(preference.isEnabled).isFalse()
|
||||||
|
assertThat(preference.onPreferenceClickListener).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_addNetwork(): Unit = runBlocking {
|
||||||
|
mockMobileNetworkSummaryRepository.stub {
|
||||||
|
on { subscriptionsStateFlow() } doReturn
|
||||||
|
flowOf(MobileNetworkSummaryRepository.AddNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.summary)
|
||||||
|
.isEqualTo(context.getString(R.string.mobile_network_summary_add_a_network))
|
||||||
|
assertThat(preference.isEnabled).isTrue()
|
||||||
|
assertThat(preference.onPreferenceClickListener).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_hasSubscriptions(): Unit = runBlocking {
|
||||||
|
mockMobileNetworkSummaryRepository.stub {
|
||||||
|
on { subscriptionsStateFlow() } doReturn
|
||||||
|
flowOf(
|
||||||
|
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||||
|
displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.summary).isEqualTo("$DISPLAY_NAME_1, $DISPLAY_NAME_2")
|
||||||
|
assertThat(preference.isEnabled).isTrue()
|
||||||
|
assertThat(preference.fragment).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_addNetworkAndAirplaneModeOn(): Unit = runBlocking {
|
||||||
|
mockMobileNetworkSummaryRepository.stub {
|
||||||
|
on { subscriptionsStateFlow() } doReturn
|
||||||
|
flowOf(MobileNetworkSummaryRepository.AddNetwork)
|
||||||
|
}
|
||||||
|
airplaneModeOnFlow.value = true
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onViewCreated_hasSubscriptionsAndAirplaneModeOn(): Unit = runBlocking {
|
||||||
|
mockMobileNetworkSummaryRepository.stub {
|
||||||
|
on { subscriptionsStateFlow() } doReturn
|
||||||
|
flowOf(
|
||||||
|
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||||
|
displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
airplaneModeOnFlow.value = true
|
||||||
|
|
||||||
|
controller.onViewCreated(TestLifecycleOwner())
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
assertThat(preference.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val KEY = "test_key"
|
||||||
|
const val DISPLAY_NAME_1 = "Display Name 1"
|
||||||
|
const val DISPLAY_NAME_2 = "Display Name 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.telephony.SubscriptionInfo
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.android.settings.network.telephony.SubscriptionRepository
|
||||||
|
import com.android.settings.network.telephony.euicc.EuiccRepository
|
||||||
|
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.stub
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class MobileNetworkSummaryRepositoryTest {
|
||||||
|
|
||||||
|
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
|
private val mockSubscriptionRepository = mock<SubscriptionRepository>()
|
||||||
|
private val mockEuiccRepository = mock<EuiccRepository>()
|
||||||
|
|
||||||
|
private val repository =
|
||||||
|
MobileNetworkSummaryRepository(
|
||||||
|
context = context,
|
||||||
|
subscriptionRepository = mockSubscriptionRepository,
|
||||||
|
euiccRepository = mockEuiccRepository,
|
||||||
|
getDisplayName = { it.displayName.toString() },
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun subscriptionsStateFlow_noSubscriptionsAndShowEuicc_returnsAddNetwork() = runBlocking {
|
||||||
|
mockSubscriptionRepository.stub {
|
||||||
|
on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
|
||||||
|
}
|
||||||
|
mockEuiccRepository.stub { on { showEuiccSettings() } doReturn true }
|
||||||
|
|
||||||
|
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||||
|
|
||||||
|
assertThat(state).isEqualTo(MobileNetworkSummaryRepository.AddNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun subscriptionsStateFlow_noSubscriptionsAndHideEuicc_returnsNoSubscriptions() = runBlocking {
|
||||||
|
mockSubscriptionRepository.stub {
|
||||||
|
on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
|
||||||
|
}
|
||||||
|
mockEuiccRepository.stub { on { showEuiccSettings() } doReturn false }
|
||||||
|
|
||||||
|
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||||
|
|
||||||
|
assertThat(state).isEqualTo(MobileNetworkSummaryRepository.NoSubscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun subscriptionsStateFlow_hasSubscriptions_returnsHasSubscriptions() = runBlocking {
|
||||||
|
mockSubscriptionRepository.stub {
|
||||||
|
on { selectableSubscriptionInfoListFlow() } doReturn
|
||||||
|
flowOf(
|
||||||
|
listOf(
|
||||||
|
SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_1).build(),
|
||||||
|
SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_2).build(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||||
|
|
||||||
|
assertThat(state)
|
||||||
|
.isEqualTo(
|
||||||
|
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||||
|
listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val DISPLAY_NAME_1 = "Sub 1"
|
||||||
|
const val DISPLAY_NAME_2 = "Sub 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,9 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.doReturn
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.doThrow
|
||||||
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
|
import org.mockito.kotlin.stub
|
||||||
@@ -134,6 +136,15 @@ class VoNrRepositoryTest {
|
|||||||
assertThat(isVoNrEnabled).isTrue()
|
assertThat(isVoNrEnabled).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isVoNrEnabledFlow_noPhoneProcess_noCrash() = runBlocking {
|
||||||
|
mockTelephonyManager.stub { on { isVoNrEnabled } doThrow IllegalStateException("no Phone") }
|
||||||
|
|
||||||
|
val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
|
||||||
|
|
||||||
|
assertThat(isVoNrEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setVoNrEnabled(): Unit = runBlocking {
|
fun setVoNrEnabled(): Unit = runBlocking {
|
||||||
repository.setVoNrEnabled(SUB_ID, true)
|
repository.setVoNrEnabled(SUB_ID, true)
|
||||||
@@ -141,7 +152,20 @@ class VoNrRepositoryTest {
|
|||||||
verify(mockTelephonyManager).setVoNrEnabled(true)
|
verify(mockTelephonyManager).setVoNrEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setVoNrEnabled_noPhoneProcess_noCrash(): Unit = runBlocking {
|
||||||
|
mockTelephonyManager.stub {
|
||||||
|
on {
|
||||||
|
setVoNrEnabled(any())
|
||||||
|
} doThrow IllegalStateException("no Phone")
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.setVoNrEnabled(SUB_ID, true)
|
||||||
|
|
||||||
|
verify(mockTelephonyManager).setVoNrEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val SUB_ID = 1
|
const val SUB_ID = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user