Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fd40e90..cc4d898 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5362,6 +5362,14 @@
</intent-filter>
</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. -->
</application>
</manifest>
diff --git a/aconfig/catalyst/about_phone.aconfig b/aconfig/catalyst/about_phone.aconfig
index 0863041..5403b65 100644
--- a/aconfig/catalyst/about_phone.aconfig
+++ b/aconfig/catalyst/about_phone.aconfig
@@ -7,3 +7,11 @@
description: "Flag for Legal information"
bug: "323791114"
}
+
+flag {
+ name: "catalyst_firmware_version"
+ namespace: "android_settings"
+ description: "Flag for Android version"
+ bug: "323791114"
+}
+
diff --git a/res/drawable/ic_android_satellite_24px.xml b/res/drawable/ic_android_satellite_24px.xml
index 15f2884..b08f19b 100644
--- a/res/drawable/ic_android_satellite_24px.xml
+++ b/res/drawable/ic_android_satellite_24px.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
- android:tint="?attr/colorControlNormal">
+ android:tint="?android:attr/colorControlNormal">
<path
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"/>
diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml
index d19aa7f..4ff40ac 100644
--- a/res/values-eu/arrays.xml
+++ b/res/values-eu/arrays.xml
@@ -506,7 +506,7 @@
<item msgid="2548100558260478605">"Pertsona taldea"</item>
<item msgid="2270003903304578284">"Bonbilla"</item>
<item msgid="4793496619091161864">"Egutegia"</item>
- <item msgid="5673625795644364100">"Ez molestatzeko modua"</item>
+ <item msgid="5673625795644364100">"Ez molestatzeko"</item>
<item msgid="1423820834865831361">"Pertsona bat korrika"</item>
<item msgid="2037298830718732608">"Golfa"</item>
<item msgid="2197835014443491074">"Gimnasioko pisua"</item>
diff --git a/res/values-gl/arrays.xml b/res/values-gl/arrays.xml
index 055461f..49cbf33 100644
--- a/res/values-gl/arrays.xml
+++ b/res/values-gl/arrays.xml
@@ -53,10 +53,10 @@
<item msgid="1656176059757237036">"Conectando..."</item>
<item msgid="3249903732481917388">"Autenticando…"</item>
<item msgid="7789156794775399931">"Obtendo enderezo IP..."</item>
- <item msgid="492518447401534767">"Conectada"</item>
+ <item msgid="492518447401534767">"Conectado"</item>
<item msgid="4322989558568666518">"Suspendida"</item>
<item msgid="286164147080824297">"Desconectando..."</item>
- <item msgid="1628460745311787730">"Desconectada"</item>
+ <item msgid="1628460745311787730">"Desconectado"</item>
<item msgid="4013828218541488949">"Incorrecta"</item>
<item msgid="5473541238056528086">"Bloqueada"</item>
<item msgid="1114588261171522603">"Evitando conexión deficiente temporalmente"</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cd847f7..20b70aa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8030,10 +8030,10 @@
<string name="spatial_audio_multi_toggle_off">Off</string>
<!-- 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]-->
- <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]-->
<string name="zen_mode_settings_schedules_summary">
@@ -8249,8 +8249,7 @@
<!-- 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>
<!-- 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
- background, which may be easier on the eyes and confers significant battery savings on some devices</string>
+ <string name="mode_dark_theme_summary">Switch the device theme to use a dark background, which is easier on the eyes</string>
<!-- [CHAR LIMIT=NONE] Zen mode settings: Summary for sound interruption settings -->
<string name="mode_display_settings_summary">
{count, plural, offset:2
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index e4ebe788..292f182 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -52,9 +52,8 @@
android:order="-15"
settings:keywords="@string/keywords_more_mobile_networks"
settings:userRestriction="no_config_mobile_networks"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
settings:useAdminDisabledSummary="true"
- settings:searchable="@bool/config_show_sim_info"/>
+ settings:controller="com.android.settings.network.MobileNetworkSummaryController" />
<com.android.settingslib.RestrictedSwitchPreference
android:key="airplane_mode"
diff --git a/src/com/android/settings/backup/SettingsBackupHelper.java b/src/com/android/settings/backup/SettingsBackupHelper.java
index 73760a4..5fdbb00 100644
--- a/src/com/android/settings/backup/SettingsBackupHelper.java
+++ b/src/com/android/settings/backup/SettingsBackupHelper.java
@@ -16,17 +16,19 @@
package com.android.settings.backup;
-
import android.app.backup.BackupAgentHelper;
+import android.util.Log;
import com.android.settings.flags.Flags;
import com.android.settings.onboarding.OnboardingFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.shortcut.CreateShortcutPreferenceController;
+import com.android.settings.shortcut.ShortcutsUpdater;
import com.android.settingslib.datastore.BackupRestoreStorageManager;
/** Backup agent for Settings APK */
public class SettingsBackupHelper extends BackupAgentHelper {
+ private static final String TAG = "SettingsBackupHelper";
+
public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER =
"AccessibilityAppearanceSettingsBackup";
@@ -58,6 +60,10 @@
public void onRestoreFinished() {
super.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);
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
index c798dff..c28f9e0 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
@@ -93,6 +93,7 @@
} else if (mShouldUseReverseLandscape) {
swapHeaderAndContent();
}
+ mUdfpsEnrollView.setVisibility(View.VISIBLE);
setOnHoverListener();
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index d71328e..86f090e 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -18,32 +18,94 @@
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.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.accessibility.AccessibilityStatsLogUtils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingIncompatibleDialogFragment;
import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
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
* device pairing detail page.
*/
public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
+ private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final int AUTO_DISMISS_MESSAGE_ID = 1001;
protected boolean mInitialScanStarted;
@VisibleForTesting
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() {
super(DISALLOW_CONFIG_BLUETOOTH);
@@ -68,6 +130,7 @@
return;
}
updateBluetooth();
+ mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow();
}
@Override
@@ -81,6 +144,26 @@
}
@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
public void onBluetoothStateChanged(int bluetoothState) {
super.onBluetoothStateChanged(bluetoothState);
updateContent(bluetoothState);
@@ -92,16 +175,37 @@
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
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.
finish();
return;
} 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
final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
.getAttribution(getActivity());
final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
pageId);
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) {
BluetoothDevice device = cachedDevice.getDevice();
@@ -114,7 +218,8 @@
}
@Override
- public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
+ public void onProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice, @ConnectionState int state,
int bluetoothProfile) {
// 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
@@ -123,8 +228,22 @@
// removed from paring list.
if (cachedDevice != null && cachedDevice.isConnected()) {
final BluetoothDevice device = cachedDevice.getDevice();
- if (device != null && mSelectedList.contains(device)) {
- finish();
+ if (device != null
+ && 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 {
onDeviceDeleted(cachedDevice);
}
@@ -148,6 +267,8 @@
public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
disableScanning();
super.onDevicePreferenceClick(btPreference);
+ // Clean up the previous bond value
+ mJustBonded = null;
}
@VisibleForTesting
@@ -165,8 +286,8 @@
* {@code bluetoothState} is off.
*
* @param bluetoothState the current Bluetooth state, the possible values that will handle here:
- * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
- * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
*/
@VisibleForTesting
public void updateContent(int bluetoothState) {
@@ -187,4 +308,122 @@
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
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);
+ }
}
diff --git a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
index d29795e..9743737 100644
--- a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
+++ b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
@@ -24,6 +24,7 @@
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -62,6 +63,7 @@
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
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.sp
import androidx.compose.ui.window.DialogProperties
@@ -78,7 +80,11 @@
var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
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(
@@ -102,7 +108,9 @@
Modifier.fillMaxSize().padding(8.dp).semantics {
role = Role.Switch
toggleableState =
- if (preferenceModel.isActive) {
+ if (!preferenceModel.isAllowedChangingState) {
+ ToggleableState.Indeterminate
+ } else if (preferenceModel.isActive) {
ToggleableState.On
} else {
ToggleableState.Off
@@ -110,6 +118,7 @@
contentDescription = preferenceModel.title
},
onClick = { settingIdForPopUp = preferenceModel.id },
+ enabled = preferenceModel.isAllowedChangingState,
shape = RoundedCornerShape(20.dp),
colors = getButtonColors(preferenceModel.isActive),
contentPadding = PaddingValues(0.dp)) {
@@ -254,7 +263,7 @@
}
Spacer(modifier = Modifier.height(12.dp))
Row(
- modifier = Modifier.fillMaxWidth().height(32.dp),
+ modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
) {
@@ -263,6 +272,7 @@
text = toggle.label,
fontSize = 12.sp,
textAlign = TextAlign.Center,
+ overflow = TextOverflow.Visible,
modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index ad41e8a..786e1dc 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -16,10 +16,18 @@
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 com.android.settings.R;
@@ -27,16 +35,21 @@
import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.utils.ThreadUtils;
public class AudioSharingDashboardFragment extends DashboardFragment
implements AudioSharingSwitchBarController.OnAudioSharingStateChangedListener {
private static final String TAG = "AudioSharingDashboardFrag";
+ public static final int SHARE_THEN_PAIR_REQUEST_CODE = 1002;
+
SettingsMainSwitchBar mMainSwitchBar;
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
private AudioStreamsCategoryController mAudioStreamsCategoryController;
+ private AudioSharingSwitchBarController mAudioSharingSwitchBarController;
public AudioSharingDashboardFragment() {
super();
@@ -84,14 +97,39 @@
final SettingsActivity activity = (SettingsActivity) getActivity();
mMainSwitchBar = activity.getSwitchBar();
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
- AudioSharingSwitchBarController switchBarController =
+ mAudioSharingSwitchBarController =
new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
- switchBarController.init(this);
- getSettingsLifecycle().addObserver(switchBarController);
+ mAudioSharingSwitchBarController.init(this);
+ getSettingsLifecycle().addObserver(mAudioSharingSwitchBarController);
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
public void onAudioSharingStateChanged() {
updateVisibilityForAttachedPreferences();
}
@@ -107,11 +145,13 @@
AudioSharingDeviceVolumeGroupController volumeGroupController,
AudioSharingCallAudioPreferenceController callAudioController,
AudioSharingPlaySoundPreferenceController playSoundController,
- AudioStreamsCategoryController streamsCategoryController) {
+ AudioStreamsCategoryController streamsCategoryController,
+ AudioSharingSwitchBarController switchBarController) {
mAudioSharingDeviceVolumeGroupController = volumeGroupController;
mAudioSharingCallAudioPreferenceController = callAudioController;
mAudioSharingPlaySoundPreferenceController = playSoundController;
mAudioStreamsCategoryController = streamsCategoryController;
+ mAudioSharingSwitchBarController = switchBarController;
}
private void updateVisibilityForAttachedPreferences() {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index 1ae541c..1b68eac 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -16,6 +16,9 @@
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.settings.SettingsEnums;
import android.os.Bundle;
@@ -48,19 +51,23 @@
// The host creates an instance of this dialog fragment must implement this interface to receive
// event callbacks.
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.
*
* @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. */
- void onCancelClick();
+ default void onCancelClick() {}
}
@Nullable private static DialogEventListener sListener;
private static Pair<Integer, Object>[] sEventData = new Pair[0];
+ @Nullable private static Fragment sHost;
@Override
public int getMetricsCategory() {
@@ -70,10 +77,10 @@
/**
* 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 listener The callback to handle the user action on this dialog.
- * @param eventData The eventData to log with for dialog onClick events.
+ * @param listener The callback to handle the user action on this dialog.
+ * @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@NonNull Fragment host,
@@ -88,6 +95,7 @@
Log.d(TAG, "Fail to show dialog: " + e.getMessage());
return;
}
+ sHost = host;
sListener = listener;
sEventData = eventData;
AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
@@ -136,23 +144,33 @@
.setCustomPositiveButton(
R.string.audio_sharing_pair_button_label,
v -> {
- dismiss();
- new SubSettingLauncher(getContext())
- .setDestination(BluetoothPairingDetail.class.getName())
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
+ if (sListener != null) {
+ sListener.onPositiveClick();
+ }
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(
R.string.audio_sharing_qrcode_button_label,
v -> {
- dismiss();
+ onCancelClick();
new SubSettingLauncher(getContext())
.setTitleRes(R.string.audio_streams_qr_code_page_title)
.setDestination(AudioStreamsQrCodeFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
- logDialogNegativeBtnClick();
});
} else if (deviceItems.size() == 1) {
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
@@ -166,8 +184,8 @@
v -> {
if (sListener != null) {
sListener.onItemClick(deviceItem);
- logDialogPositiveBtnClick();
}
+ logDialogPositiveBtnClick();
dismiss();
})
.setCustomNegativeButton(
@@ -182,8 +200,8 @@
(AudioSharingDeviceItem item) -> {
if (sListener != null) {
sListener.onItemClick(item);
- logDialogPositiveBtnClick();
}
+ logDialogPositiveBtnClick();
dismiss();
},
AudioSharingDeviceAdapter.ActionType.SHARE))
@@ -196,8 +214,8 @@
private void onCancelClick() {
if (sListener != null) {
sListener.onCancelClick();
- logDialogNegativeBtnClick();
}
+ logDialogNegativeBtnClick();
dismiss();
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
index 5de615e..aceeb94 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
@@ -29,7 +29,6 @@
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingIncompatDlg";
@@ -59,7 +58,7 @@
*
* @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) {
if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
final FragmentManager manager;
@@ -77,7 +76,7 @@
}
Log.d(TAG, "Show up the incompatible device dialog.");
final Bundle bundle = new Bundle();
- bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
+ bundle.putString(BUNDLE_KEY_DEVICE_NAME, deviceName);
AudioSharingIncompatibleDialogFragment dialogFrag =
new AudioSharingIncompatibleDialogFragment();
dialogFrag.setArguments(bundle);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
index 79cc56e..8706590 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
@@ -115,10 +115,6 @@
@NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
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();
String message = args.getString(BUNDLE_KEY_MESSAGE, "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -133,6 +129,26 @@
}
@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
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
if (mHandler != null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index c0f463d..395647c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -56,6 +56,7 @@
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -78,9 +79,9 @@
public class AudioSharingSwitchBarController extends BasePreferenceController
implements DefaultLifecycleObserver,
- OnCheckedChangeListener,
- LocalBluetoothProfileManager.ServiceListener,
- BluetoothCallback {
+ OnCheckedChangeListener,
+ LocalBluetoothProfileManager.ServiceListener,
+ BluetoothCallback {
private static final String TAG = "AudioSharingSwitchCtlr";
private static final String PREF_KEY = "audio_sharing_main_switch";
@@ -464,6 +465,18 @@
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. */
@VisibleForTesting
void setCallbacksRegistered(boolean registered) {
@@ -610,8 +623,8 @@
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
mTargetActiveItem = null;
if (mIntentHandleStage.compareAndSet(
- StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
- StartIntentHandleStage.HANDLED.ordinal())
+ StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
+ StartIntentHandleStage.HANDLED.ordinal())
&& mDeviceItemsForSharing.size() == 1) {
Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
@@ -639,6 +652,13 @@
AudioSharingDialogFragment.DialogEventListener listener =
new AudioSharingDialogFragment.DialogEventListener() {
@Override
+ public void onPositiveClick() {
+ // Could go to other pages, dismiss the loading dialog.
+ dismissLoadingStateDialogIfNeeded();
+ cleanUp();
+ }
+
+ @Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
item.getGroupId(), ImmutableList.of());
@@ -648,6 +668,7 @@
@Override
public void onCancelClick() {
+ // Could go to other pages, dismiss the loading dialog.
dismissLoadingStateDialogIfNeeded();
cleanUp();
}
@@ -669,8 +690,8 @@
@NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& (event.getContentChangeTypes()
- & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
- != 0) {
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
+ != 0) {
Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED");
return false;
}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt
new file mode 100644
index 0000000..58fdefe
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.deviceinfo.firmwareversion
+
+import android.content.Context
+import android.os.Build
+import com.android.settings.R
+import com.android.settings.flags.Flags
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.metadata.PreferenceSummaryProvider
+import com.android.settingslib.metadata.ProvidePreferenceScreen
+import com.android.settingslib.metadata.preferenceHierarchy
+import com.android.settingslib.preference.PreferenceScreenCreator
+
+@ProvidePreferenceScreen
+class FirmwareVersionScreen : PreferenceScreenCreator, PreferenceSummaryProvider {
+
+ override fun isFlagEnabled(context: Context) = Flags.catalystFirmwareVersion()
+
+ override val key: String
+ get() = KEY
+
+ override val title: Int
+ get() = R.string.firmware_version
+
+ override fun getSummary(context: Context): CharSequence? =
+ Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY
+
+ override val keywords: Int
+ get() = R.string.keywords_android_version
+
+ override fun fragmentClass() = FirmwareVersionSettings::class.java
+
+ override fun getPreferenceHierarchy(context: Context) =
+ preferenceHierarchy(this) {
+ +PreferenceWidget("os_firmware_version", R.string.firmware_version)
+ +PreferenceWidget("security_key", R.string.security_patch)
+ +PreferenceWidget("module_version", R.string.module_version)
+ +PreferenceWidget("base_band", R.string.baseband_version)
+ +PreferenceWidget("kernel_version", R.string.kernel_version)
+ +PreferenceWidget("os_build_number", R.string.build_number)
+ }
+
+ private class PreferenceWidget(override val key: String, override val title: Int) :
+ PreferenceMetadata
+
+ companion object {
+ const val KEY = "firmware_version"
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
index e2d3d8a..d447c98 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
@@ -17,6 +17,10 @@
package com.android.settings.deviceinfo.firmwareversion;
import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -27,6 +31,11 @@
public class FirmwareVersionSettings extends DashboardFragment {
@Override
+ public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
+ return FirmwareVersionScreen.KEY;
+ }
+
+ @Override
protected int getPreferenceScreenResId() {
return R.xml.firmware_version;
}
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index ef129ff..907fe7b 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -16,6 +16,8 @@
package com.android.settings.localepicker;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.Canvas;
@@ -41,7 +43,8 @@
import com.android.internal.app.LocaleStore;
import com.android.settings.R;
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.util.ArrayList;
@@ -96,7 +99,7 @@
LocaleDragAndDropAdapter(LocaleListEditor parent, List<LocaleStore.LocaleInfo> feedItemList) {
mFeedItemList = feedItemList;
mCacheItemList = new ArrayList<>(feedItemList);
- mContext = parent.getContext();
+ mContext = checkNotNull(parent.getContext());
final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
mContext.getResources().getDisplayMetrics());
@@ -350,7 +353,8 @@
LocalePicker.updateLocales(mLocalesToSetNext);
mLocalesSetLast = mLocalesToSetNext;
- new ShortcutsUpdateTask(mContext).execute();
+ ThreadUtils.postOnBackgroundThread(
+ () -> ShortcutsUpdater.updatePinnedShortcuts(mContext));
mLocalesToSetNext = null;
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
deleted file mode 100644
index 45d475f..0000000
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ /dev/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();
- }
-}
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.kt b/src/com/android/settings/network/MobileNetworkSummaryController.kt
new file mode 100644
index 0000000..5980bbd
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.kt
@@ -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),
+ )
+ }
+}
diff --git a/src/com/android/settings/network/MobileNetworkSummaryRepository.kt b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt
new file mode 100644
index 0000000..edf557b
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt
@@ -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)
+}
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index aff9130..ee7d440 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.content.Intent;
-import androidx.lifecycle.LifecycleOwner;
+import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.SettingsDumpService;
@@ -69,12 +69,11 @@
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
- return buildPreferenceControllers(context, getSettingsLifecycle(),
- this /* LifecycleOwner */);
+ return buildPreferenceControllers(context, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Lifecycle lifecycle, LifecycleOwner lifecycleOwner) {
+ @Nullable Lifecycle lifecycle) {
final VpnPreferenceController vpnPreferenceController =
new VpnPreferenceController(context);
final PrivateDnsPreferenceController privateDnsPreferenceController =
@@ -87,7 +86,6 @@
final List<AbstractPreferenceController> controllers = new ArrayList<>();
- controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
controllers.add(vpnPreferenceController);
controllers.add(privateDnsPreferenceController);
@@ -114,8 +112,7 @@
@Override
public List<AbstractPreferenceController> createPreferenceControllers(Context
context) {
- return buildPreferenceControllers(context, null /* lifecycle */,
- null /* LifecycleOwner */);
+ return buildPreferenceControllers(context, null /* lifecycle */);
}
};
}
diff --git a/src/com/android/settings/network/telephony/VoNrRepository.kt b/src/com/android/settings/network/telephony/VoNrRepository.kt
index 635c572..7f3823b 100644
--- a/src/com/android/settings/network/telephony/VoNrRepository.kt
+++ b/src/com/android/settings/network/telephony/VoNrRepository.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@@ -37,12 +38,13 @@
fun isVoNrAvailable(subId: Int): Boolean {
if (!nrRepository.isNrAvailable(subId)) return false
data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
+
val carrierConfig =
carrierConfigRepository.transformConfig(subId) {
Config(
isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
isVoNrSettingVisibility =
- getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
+ getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
)
}
return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
@@ -52,7 +54,14 @@
val telephonyManager = context.telephonyManager(subId)
return context
.subscriptionsChangedFlow()
- .map { telephonyManager.isVoNrEnabled }
+ .map {
+ try {
+ telephonyManager.isVoNrEnabled
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "IllegalStateException - isVoNrEnabled : $e")
+ false
+ }
+ }
.conflate()
.onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
.flowOn(Dispatchers.Default)
@@ -61,11 +70,17 @@
suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) =
withContext(Dispatchers.Default) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
- val result = context.telephonyManager(subId).setVoNrEnabled(enabled)
- Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
+ var result = TelephonyManager.ENABLE_VONR_RADIO_INVALID_STATE
+ 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 const val TAG = "VoNrRepository"
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/NotificationRingtonePreferenceController.java b/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
index 00f478f..946b783 100644
--- a/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
+++ b/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.media.RingtoneManager;
+import com.android.server.notification.Flags;
import com.android.settings.R;
public class NotificationRingtonePreferenceController extends RingtonePreferenceControllerBase {
@@ -31,6 +32,9 @@
@Override
public boolean isAvailable() {
+ if (isVibrationInSoundUriEnabled()) {
+ return false;
+ }
return mContext.getResources().getBoolean(R.bool.config_show_notification_ringtone);
}
@@ -43,4 +47,9 @@
public int getRingtoneType() {
return RingtoneManager.TYPE_NOTIFICATION;
}
+
+ private boolean isVibrationInSoundUriEnabled() {
+ return Flags.notificationVibrationInSoundUri() && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
+ }
}
diff --git a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
index 8f74bd9..0e2e6bc 100644
--- a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
+++ b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
@@ -16,27 +16,19 @@
package com.android.settings.shortcut;
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
+
import android.app.Activity;
import android.app.settings.SettingsEnums;
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.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.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -48,7 +40,6 @@
import com.android.settings.Settings.DataUsageSummaryActivity;
import com.android.settings.Settings.TetherSettingsActivity;
import com.android.settings.Settings.WifiTetherSettingsActivity;
-import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.gestures.OneHandedSettingsUtils;
import com.android.settings.network.SubscriptionUtil;
@@ -69,11 +60,6 @@
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 PackageManager mPackageManager;
private final ConnectivityManager mConnectivityManager;
@@ -132,9 +118,7 @@
if (mHost == null) {
return false;
}
- final Intent shortcutIntent = createResultIntent(
- buildShortcutIntent(uiContext, info),
- info, clickTarget.getTitle());
+ final Intent shortcutIntent = createResultIntent(info);
mHost.setResult(Activity.RESULT_OK, shortcutIntent);
logCreateShortcut(info);
mHost.finish();
@@ -149,21 +133,20 @@
* launcher widget using this intent.
*/
@VisibleForTesting
- Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo,
- CharSequence label) {
- ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label);
+ Intent createResultIntent(ResolveInfo resolveInfo) {
+ ShortcutInfo info = Shortcuts.createShortcutInfo(mContext, resolveInfo);
Intent intent = mShortcutManager.createShortcutResultIntent(info);
if (intent == null) {
intent = new Intent();
}
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
- .putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
- .putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+ .putExtra(Intent.EXTRA_SHORTCUT_INTENT, info.getIntent())
+ .putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getShortLabel());
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo.icon != 0) {
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, Shortcuts.createIcon(
mContext,
activityInfo.applicationInfo,
activityInfo.icon,
@@ -239,87 +222,6 @@
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 =
(i1, i2) -> i1.priority - i2.priority;
}
diff --git a/src/com/android/settings/shortcut/Shortcuts.java b/src/com/android/settings/shortcut/Shortcuts.java
new file mode 100644
index 0000000..53544eb
--- /dev/null
+++ b/src/com/android/settings/shortcut/Shortcuts.java
@@ -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;
+ }
+}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java
new file mode 100644
index 0000000..657af5b
--- /dev/null
+++ b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java
@@ -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();
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java b/src/com/android/settings/shortcut/ShortcutsUpdateTask.java
deleted file mode 100644
index 54f7d1c..0000000
--- a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdater.java b/src/com/android/settings/shortcut/ShortcutsUpdater.java
new file mode 100644
index 0000000..90a60fd
--- /dev/null
+++ b/src/com/android/settings/shortcut/ShortcutsUpdater.java
@@ -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;
+ }
+}
diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt
index 276d121..bd55b32 100644
--- a/src/com/android/settings/spa/network/SimsSection.kt
+++ b/src/com/android/settings/spa/network/SimsSection.kt
@@ -137,7 +137,7 @@
}
}
-private fun startAddSimFlow(context: Context) {
+fun startAddSimFlow(context: Context) {
val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
intent.setPackage(Utils.PHONE_PACKAGE_NAME)
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
diff --git a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
index 14ef483..00bd0f2 100644
--- a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
+++ b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
@@ -41,19 +41,20 @@
private static final String WALLPAPER_FOCUS = "focus_wallpaper";
private static final String WALLPAPER_ONLY = "wallpaper_only";
private static final String LAUNCHED_SUW = "app_launched_suw";
-
- private String mWallpaperLaunchExtra;
+ private static final String LAUNCH_SOURCE_SETTINGS_SEARCH = "app_launched_settings_search";
@Override
protected void addExtras(Intent intent) {
+ String wallpaperLaunchExtra =
+ getResources().getString(R.string.config_wallpaper_picker_launch_extra);;
if (WizardManagerHelper.isAnySetupWizard(intent)) {
intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_ONLY);
-
- mWallpaperLaunchExtra =
- getResources().getString(R.string.config_wallpaper_picker_launch_extra);
- intent.putExtra(mWallpaperLaunchExtra, LAUNCHED_SUW);
+ intent.putExtra(wallpaperLaunchExtra, LAUNCHED_SUW);
} 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(wallpaperLaunchExtra, LAUNCH_SOURCE_SETTINGS_SEARCH);
}
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
index 40f7895..e326c1e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
@@ -16,52 +16,80 @@
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 org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
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 static org.robolectric.Shadows.shadowOf;
+import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
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.widget.TextView;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
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.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
+import java.util.concurrent.Executor;
+
/** Tests for {@link BluetoothDevicePairingDetailBase}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowBluetoothAdapter.class,
+ ShadowAlertDialogCompat.class,
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class BluetoothDevicePairingDetailBaseTest {
@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";
@@ -86,8 +114,12 @@
@Before
public void setUp() {
mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
@@ -156,7 +188,87 @@
}
@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
public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(mBluetoothDevice);
mFragment.mSelectedList.add(device);
@@ -165,13 +277,43 @@
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
- BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+ BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
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
public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(device);
@@ -179,13 +321,14 @@
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
- BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+ BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
// not crash
}
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
mFragment.mSelectedList.add(mBluetoothDevice);
mFragment.mSelectedList.add(device);
@@ -194,13 +337,14 @@
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
- BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
+ BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.A2DP);
// not crash
}
@Test
public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
@@ -211,13 +355,14 @@
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
- BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+ BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
}
@Test
public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
final BluetoothDevicePreference preference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
@@ -233,12 +378,26 @@
when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
- mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
- BluetoothAdapter.STATE_CONNECTED);
+ mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.A2DP);
// 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
BluetoothDevicePairingDetailBase {
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
index 7d8846d..1ce3316 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
@@ -16,16 +16,29 @@
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 org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
+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 static org.robolectric.Shadows.shadowOf;
+import android.app.Activity;
import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.View;
import androidx.test.core.app.ApplicationProvider;
@@ -33,24 +46,29 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
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.widget.SettingsMainSwitchBar;
+import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowFragment.class})
+@Config(shadows = {ShadowFragment.class, ShadowBluetoothAdapter.class})
public class AudioSharingDashboardFragmentTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private SettingsActivity mActivity;
@Mock private SettingsMainSwitchBar mSwitchBar;
@@ -59,11 +77,19 @@
@Mock private AudioSharingCallAudioPreferenceController mCallAudioController;
@Mock private AudioSharingPlaySoundPreferenceController mPlaySoundController;
@Mock private AudioStreamsCategoryController mStreamsCategoryController;
+ @Mock private AudioSharingSwitchBarController mSwitchBarController;
private final Context mContext = ApplicationProvider.getApplicationContext();
private AudioSharingDashboardFragment mFragment;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
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);
mFragment = new AudioSharingDashboardFragment();
}
@@ -101,12 +127,72 @@
}
@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
public void onAudioSharingStateChanged_updateVisibilityForControllers() {
mFragment.setControllers(
mVolumeGroupController,
mCallAudioController,
mPlaySoundController,
- mStreamsCategoryController);
+ mStreamsCategoryController,
+ mSwitchBarController);
mFragment.onAudioSharingStateChanged();
verify(mVolumeGroupController).updateVisibility();
verify(mCallAudioController).updateVisibility();
@@ -120,7 +206,8 @@
mVolumeGroupController,
mCallAudioController,
mPlaySoundController,
- mStreamsCategoryController);
+ mStreamsCategoryController,
+ mSwitchBarController);
mFragment.onAudioSharingProfilesConnected();
verify(mVolumeGroupController).onAudioSharingProfilesConnected();
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
index 7227f37..dec85e4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
@@ -34,6 +34,7 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -82,11 +83,6 @@
new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
private static final AudioSharingDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
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_LIST =
@@ -176,8 +172,17 @@
@Test
public void onCreateDialog_noExtraConnectedDevice_pairNewDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AtomicBoolean isPairBtnClicked = new AtomicBoolean(false);
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();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -191,14 +196,24 @@
any(Context.class),
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
eq(TEST_EVENT_DATA));
+ assertThat(isPairBtnClicked.get()).isTrue();
assertThat(dialog.isShowing()).isFalse();
}
@Test
public void onCreateDialog_noExtraConnectedDevice_showQRCode() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ AtomicBoolean isQrCodeBtnClicked = new AtomicBoolean(false);
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();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
@@ -212,6 +227,7 @@
any(Context.class),
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
eq(TEST_EVENT_DATA));
+ assertThat(isQrCodeBtnClicked.get()).isTrue();
assertThat(dialog.isShowing()).isFalse();
}
@@ -286,12 +302,9 @@
list,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
- public void onItemClick(AudioSharingDeviceItem item) {
+ public void onItemClick(@NonNull AudioSharingDeviceItem item) {
isShareBtnClicked.set(true);
}
-
- @Override
- public void onCancelClick() {}
},
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
@@ -360,9 +373,6 @@
list,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
- public void onItemClick(AudioSharingDeviceItem item) {}
-
- @Override
public void onCancelClick() {
isCancelBtnClicked.set(true);
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
index 7f17291..67cb2aa 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.bluetooth.BluetoothAdapter;
@@ -34,7 +33,6 @@
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.flags.Flags;
import org.junit.After;
@@ -42,7 +40,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@@ -62,7 +59,6 @@
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
- @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
private Fragment mParent;
private AudioSharingIncompatibleDialogFragment mFragment;
@@ -76,7 +72,6 @@
BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
- when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
mFragment = new AudioSharingIncompatibleDialogFragment();
mParent = new Fragment();
FragmentController.setupFragment(mParent, FragmentActivity.class, /* containerViewId= */
@@ -97,7 +92,7 @@
@Test
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
EMPTY_EVENT_LISTENER);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -107,7 +102,7 @@
@Test
public void onCreateDialog_unattachedFragment_dialogNotExist() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- AudioSharingIncompatibleDialogFragment.show(new Fragment(), mCachedBluetoothDevice,
+ AudioSharingIncompatibleDialogFragment.show(new Fragment(), TEST_DEVICE_NAME,
EMPTY_EVENT_LISTENER);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -117,7 +112,7 @@
@Test
public void onCreateDialog_flagOn_showDialog() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
EMPTY_EVENT_LISTENER);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -134,7 +129,7 @@
public void onCreateDialog_clickBtn_callbackTriggered() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AtomicBoolean isBtnClicked = new AtomicBoolean(false);
- AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+ AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
() -> isBtnClicked.set(true));
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 0d21f18..eb2083e 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -934,6 +934,19 @@
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() {
Bundle args = new Bundle();
args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
deleted file mode 100644
index 1823d6d..0000000
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
+++ /dev/null
@@ -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));
- }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
index 1aecad5..a04a14d 100644
--- a/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
@@ -18,35 +18,77 @@
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.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.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
public class NotificationRingtonePreferenceControllerTest {
private NotificationRingtonePreferenceController mController;
+ @Mock private Context mMockContext;
+ @Mock private Resources mMockResources;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new NotificationRingtonePreferenceController(RuntimeEnvironment.application);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ mController = new NotificationRingtonePreferenceController(mMockContext);
}
@Test
+ @DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
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();
}
@Test
@Config(qualifiers = "mcc999")
+ @DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
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();
}
diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
index 9727dd1..8442a37 100644
--- a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
@@ -16,7 +16,7 @@
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;
@@ -101,10 +101,10 @@
when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
.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);
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((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull();
@@ -131,7 +131,7 @@
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
mPackageManager.setResolveInfosForIntent(
- new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+ new Intent(Shortcuts.SHORTCUT_PROBE),
Arrays.asList(ri1, ri2));
doReturn(false).when(mController).canShowWifiHotspot();
@@ -158,7 +158,7 @@
ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
mPackageManager.setResolveInfosForIntent(
- new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+ new Intent(Shortcuts.SHORTCUT_PROBE),
Arrays.asList(ri1, ri2));
doReturn(false).when(mController).canShowWifiHotspot();
@@ -276,7 +276,7 @@
ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
mPackageManager.setResolveInfosForIntent(
- new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+ new Intent(Shortcuts.SHORTCUT_PROBE),
Arrays.asList(ri));
}
}
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java
new file mode 100644
index 0000000..a347ff9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java
@@ -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();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java
deleted file mode 100644
index 8352e7a..0000000
--- a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java
+++ /dev/null
@@ -1,121 +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.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-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 com.android.settings.Settings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowPackageManager;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-public class ShortcutsUpdateTaskTest {
-
- private Context mContext;
- private ShadowPackageManager mPackageManager;
-
- @Mock
- private ShortcutManager mShortcutManager;
- @Captor
- private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mPackageManager = Shadow.extract(mContext.getPackageManager());
- }
-
- @Test
- public void shortcutsUpdateTask() {
- 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(
- makeShortcut("d1"),
- makeShortcut("d2"),
- makeShortcut(Settings.ManageApplicationsActivity.class),
- makeShortcut("d3"),
- makeShortcut(Settings.SoundSettingsActivity.class));
- when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
-
- new ShortcutsUpdateTask(mContext).doInBackground();
-
- verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
-
- final List<ShortcutInfo> updates = mListCaptor.getValue();
-
- assertThat(updates).hasSize(2);
- assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
- assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
- }
-
- private ShortcutInfo makeShortcut(Class<?> className) {
- ComponentName cn = new ComponentName(mContext, className);
- return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
- }
-
- private ShortcutInfo makeShortcut(String id) {
- return new ShortcutInfo.Builder(mContext, id).build();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java
new file mode 100644
index 0000000..5324ff5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Flags;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+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 org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ShortcutsUpdaterTest {
+
+ private Context mContext;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ private ShortcutManager mShortcutManager;
+ @Captor
+ private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
+ }
+
+ @Test
+ public void updatePinnedShortcuts_updatesAllShortcuts() {
+ final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
+ makeShortcut("d1"),
+ makeShortcut("d2"),
+ makeShortcut(Settings.ManageApplicationsActivity.class),
+ makeShortcut("d3"),
+ makeShortcut(Settings.SoundSettingsActivity.class));
+ when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
+
+ ShortcutsUpdater.updatePinnedShortcuts(mContext);
+
+ verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
+
+ final List<ShortcutInfo> updates = mListCaptor.getValue();
+
+ assertThat(updates).hasSize(2);
+ assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).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) {
+ ComponentName cn = new ComponentName(mContext, className);
+ return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
+ }
+
+ private ShortcutInfo makeShortcut(String id) {
+ return new ShortcutInfo.Builder(mContext, id).build();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
index 3f6d785..230b443 100644
--- a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
+++ b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
@@ -118,7 +118,7 @@
}
@Test
- public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaper() {
+ public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaperAndLaunchedSettingsSearch() {
WallpaperSuggestionActivity activity = Robolectric.buildActivity(
WallpaperSuggestionActivity.class, new Intent(Intent.ACTION_MAIN).setComponent(
new ComponentName(RuntimeEnvironment.application,
@@ -127,6 +127,8 @@
assertThat(intent).isNotNull();
assertThat(intent.getStringExtra(WALLPAPER_FLAVOR)).isEqualTo("focus_wallpaper");
+ assertThat(intent.getStringExtra(WALLPAPER_LAUNCH_SOURCE))
+ .isEqualTo("app_launched_settings_search");
}
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt
new file mode 100644
index 0000000..69fa9c4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt
@@ -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"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt
new file mode 100644
index 0000000..463af96
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt
@@ -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"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
index 90d0aa5..265cd33 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
@@ -27,7 +27,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@@ -135,13 +137,35 @@
}
@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
fun setVoNrEnabled(): Unit = runBlocking {
repository.setVoNrEnabled(SUB_ID, 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 {
const val SUB_ID = 1
}
-}
+}
\ No newline at end of file