Snap for 12391343 from 6c584d7f42
to 24Q4-release
Change-Id: I7adbc4e226402d8fe76ca61f1ef6e6b84e9c5e66
This commit is contained in:
@@ -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>
|
||||
|
@@ -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"/>
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -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 class SettingsBackupHelper extends BackupAgentHelper {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -93,6 +93,7 @@ public class UdfpsEnrollEnrollingView extends GlifLayout {
|
||||
} else if (mShouldUseReverseLandscape) {
|
||||
swapHeaderAndContent();
|
||||
}
|
||||
mUdfpsEnrollView.setVisibility(View.VISIBLE);
|
||||
setOnHoverListener();
|
||||
}
|
||||
|
||||
|
@@ -18,32 +18,94 @@ package com.android.settings.bluetooth;
|
||||
|
||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
|
||||
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.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 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
return;
|
||||
}
|
||||
updateBluetooth();
|
||||
mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,6 +143,26 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
disableScanning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
var unused = ThreadUtils.postOnBackgroundThread(() -> {
|
||||
mDevicesWithMetadataChangedListener.forEach(
|
||||
device -> {
|
||||
try {
|
||||
if (mBluetoothAdapter != null) {
|
||||
mBluetoothAdapter.removeOnMetadataChangedListener(device,
|
||||
mMetadataListener);
|
||||
mDevicesWithMetadataChangedListener.remove(device);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(getLogTag(), "Fail to remove listener: " + e);
|
||||
}
|
||||
});
|
||||
mDevicesWithMetadataChangedListener.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBluetoothStateChanged(int bluetoothState) {
|
||||
super.onBluetoothStateChanged(bluetoothState);
|
||||
@@ -92,16 +175,37 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
@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 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
}
|
||||
|
||||
@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 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
// 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 abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
|
||||
disableScanning();
|
||||
super.onDevicePreferenceClick(btPreference);
|
||||
// Clean up the previous bond value
|
||||
mJustBonded = null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -165,8 +286,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
* {@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 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column
|
||||
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.semantics
|
||||
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 @@ fun MultiTogglePreferenceGroup(
|
||||
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 @@ fun MultiTogglePreferenceGroup(
|
||||
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 @@ fun MultiTogglePreferenceGroup(
|
||||
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 @@ private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.Mu
|
||||
}
|
||||
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 @@ private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.Mu
|
||||
text = toggle.label,
|
||||
fontSize = 12.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Visible,
|
||||
modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
|
||||
}
|
||||
}
|
||||
|
@@ -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.SettingsActivity;
|
||||
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,13 +97,38 @@ public class AudioSharingDashboardFragment extends DashboardFragment
|
||||
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 @@ public class AudioSharingDashboardFragment extends DashboardFragment
|
||||
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() {
|
||||
|
@@ -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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
// 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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
/**
|
||||
* 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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
.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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
v -> {
|
||||
if (sListener != null) {
|
||||
sListener.onItemClick(deviceItem);
|
||||
logDialogPositiveBtnClick();
|
||||
}
|
||||
logDialogPositiveBtnClick();
|
||||
dismiss();
|
||||
})
|
||||
.setCustomNegativeButton(
|
||||
@@ -182,8 +200,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
(AudioSharingDeviceItem item) -> {
|
||||
if (sListener != null) {
|
||||
sListener.onItemClick(item);
|
||||
logDialogPositiveBtnClick();
|
||||
}
|
||||
logDialogPositiveBtnClick();
|
||||
dismiss();
|
||||
},
|
||||
AudioSharingDeviceAdapter.ActionType.SHARE))
|
||||
@@ -196,8 +214,8 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
|
||||
private void onCancelClick() {
|
||||
if (sListener != null) {
|
||||
sListener.onCancelClick();
|
||||
logDialogNegativeBtnClick();
|
||||
}
|
||||
logDialogNegativeBtnClick();
|
||||
dismiss();
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,6 @@ import androidx.fragment.app.FragmentManager;
|
||||
|
||||
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 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
|
||||
*
|
||||
* @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 @@ public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFr
|
||||
}
|
||||
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);
|
||||
|
@@ -115,10 +115,6 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
|
||||
@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());
|
||||
@@ -132,6 +128,26 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (mHandler != null) {
|
||||
Log.d(TAG, "onStart, postTimeOut for auto dismiss");
|
||||
mHandler.postDelayed(() -> {
|
||||
Log.d(TAG, "Try to auto dismiss dialog after timeout");
|
||||
try {
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog != null) {
|
||||
Log.d(TAG, "Dialog is not null, dismiss");
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
Log.d(TAG, "Fail to dismiss: " + e.getMessage());
|
||||
}
|
||||
}, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
|
@@ -56,6 +56,7 @@ import com.android.settingslib.bluetooth.BluetoothCallback;
|
||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||
import com.android.settingslib.bluetooth.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 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||
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 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||
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);
|
||||
@@ -638,6 +651,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||
private void showDialog(Pair<Integer, Object>[] eventData) {
|
||||
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(
|
||||
@@ -648,6 +668,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||
|
||||
@Override
|
||||
public void onCancelClick() {
|
||||
// Could go to other pages, dismiss the loading dialog.
|
||||
dismissLoadingStateDialogIfNeeded();
|
||||
cleanUp();
|
||||
}
|
||||
@@ -669,8 +690,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
|
||||
@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;
|
||||
}
|
||||
|
@@ -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.LocalePicker;
|
||||
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 @@ class LocaleDragAndDropAdapter
|
||||
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 @@ class LocaleDragAndDropAdapter
|
||||
|
||||
LocalePicker.updateLocales(mLocalesToSetNext);
|
||||
mLocalesSetLast = mLocalesToSetNext;
|
||||
new ShortcutsUpdateTask(mContext).execute();
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> ShortcutsUpdater.updatePinnedShortcuts(mContext));
|
||||
|
||||
mLocalesToSetNext = null;
|
||||
|
||||
|
@@ -1,219 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network;
|
||||
|
||||
import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
|
||||
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.network.telephony.SimRepository;
|
||||
import com.android.settings.network.telephony.euicc.EuiccRepository;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
|
||||
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
|
||||
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MobileNetworkSummaryController extends AbstractPreferenceController implements
|
||||
LifecycleObserver, PreferenceControllerMixin,
|
||||
MobileNetworkRepository.MobileNetworkCallback {
|
||||
private static final String TAG = "MobileNetSummaryCtlr";
|
||||
|
||||
private static final String KEY = "mobile_network_list";
|
||||
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
private RestrictedPreference mPreference;
|
||||
|
||||
private MobileNetworkRepository mMobileNetworkRepository;
|
||||
private List<SubscriptionInfoEntity> mSubInfoEntityList;
|
||||
private List<UiccInfoEntity> mUiccInfoEntityList;
|
||||
private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList;
|
||||
private boolean mIsAirplaneModeOn;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
|
||||
/**
|
||||
* This controls the summary text and click behavior of the "Mobile network" item on the
|
||||
* Network & internet page. There are 3 separate cases depending on the number of mobile network
|
||||
* subscriptions:
|
||||
* <ul>
|
||||
* <li>No subscription: click action begins a UI flow to add a network subscription, and
|
||||
* the summary text indicates this</li>
|
||||
*
|
||||
* <li>One subscription: click action takes you to details for that one network, and
|
||||
* the summary text is the network name</li>
|
||||
*
|
||||
* <li>More than one subscription: click action takes you to a page listing the subscriptions,
|
||||
* and the summary text gives the count of SIMs</li>
|
||||
* </ul>
|
||||
*/
|
||||
public MobileNetworkSummaryController(Context context, Lifecycle lifecycle,
|
||||
LifecycleOwner lifecycleOwner) {
|
||||
super(context);
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
mLifecycleOwner = lifecycleOwner;
|
||||
mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
|
||||
mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn();
|
||||
if (lifecycle != null) {
|
||||
lifecycle.addObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(ON_RESUME)
|
||||
public void onResume() {
|
||||
mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
||||
mMobileNetworkRepository.updateEntity();
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(ON_PAUSE)
|
||||
public void onPause() {
|
||||
mMobileNetworkRepository.removeRegister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
|
||||
if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
|
||||
mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
|
||||
mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
|
||||
if (new EuiccRepository(mContext).showEuiccSettings()) {
|
||||
return mContext.getResources().getString(
|
||||
R.string.mobile_network_summary_add_a_network);
|
||||
}
|
||||
// set empty string to override previous text for carrier when SIM available
|
||||
return "";
|
||||
} else if (mSubInfoEntityList.size() == 1) {
|
||||
SubscriptionInfoEntity info = mSubInfoEntityList.get(0);
|
||||
CharSequence displayName = info.uniqueName;
|
||||
if (info.isEmbedded || mUiccInfoEntityList.get(0).isActive
|
||||
|| mMobileNetworkInfoEntityList.get(0).showToggleForPhysicalSim) {
|
||||
return displayName;
|
||||
}
|
||||
return mContext.getString(R.string.mobile_network_tap_to_activate, displayName);
|
||||
} else {
|
||||
return mSubInfoEntityList.stream()
|
||||
.map(SubscriptionInfoEntity::getUniqueDisplayName)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
|
||||
private void logPreferenceClick(Preference preference) {
|
||||
mMetricsFeatureProvider.logClickedPreference(preference,
|
||||
preference.getExtras().getInt(DashboardFragment.CATEGORY));
|
||||
}
|
||||
|
||||
private void startAddSimFlow() {
|
||||
final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION);
|
||||
intent.setPackage(com.android.settings.Utils.PHONE_PACKAGE_NAME);
|
||||
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
private void initPreference() {
|
||||
refreshSummary(mPreference);
|
||||
mPreference.setOnPreferenceClickListener(null);
|
||||
mPreference.setFragment(null);
|
||||
mPreference.setEnabled(!mIsAirplaneModeOn);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (mPreference == null || mPreference.isDisabledByAdmin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
initPreference();
|
||||
if (((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty())
|
||||
|| (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
|
||||
|| (mMobileNetworkInfoEntityList == null
|
||||
|| mMobileNetworkInfoEntityList.isEmpty()))) {
|
||||
if (new EuiccRepository(mContext).showEuiccSettings()) {
|
||||
mPreference.setOnPreferenceClickListener((Preference pref) -> {
|
||||
logPreferenceClick(pref);
|
||||
startAddSimFlow();
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
mPreference.setEnabled(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return new SimRepository(mContext).showMobileNetworkPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
|
||||
if (mIsAirplaneModeOn != airplaneModeEnabled) {
|
||||
mIsAirplaneModeOn = airplaneModeEnabled;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
|
||||
mSubInfoEntityList = subInfoEntityList;
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
|
||||
mUiccInfoEntityList = uiccInfoEntityList;
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllMobileNetworkInfoChanged(
|
||||
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
|
||||
mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
|
||||
update();
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.android.settings.R
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
import com.android.settings.dashboard.DashboardFragment
|
||||
import com.android.settings.network.telephony.SimRepository
|
||||
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settings.spa.network.startAddSimFlow
|
||||
import com.android.settingslib.RestrictedPreference
|
||||
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
|
||||
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* This controls the summary text and click behavior of the "Mobile network" item on the Network &
|
||||
* internet page. There are 2 separate cases depending on the number of mobile network
|
||||
* subscriptions:
|
||||
* - No subscription: click action begins a UI flow to add a network subscription, and the summary
|
||||
* text indicates this
|
||||
* - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary
|
||||
* text gives the count of SIMs
|
||||
*/
|
||||
class MobileNetworkSummaryController
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
private val context: Context,
|
||||
preferenceKey: String,
|
||||
private val repository: MobileNetworkSummaryRepository =
|
||||
MobileNetworkSummaryRepository(context),
|
||||
private val airplaneModeOnFlow: Flow<Boolean> =
|
||||
context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
|
||||
) : BasePreferenceController(context, preferenceKey) {
|
||||
private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
|
||||
private var preference: RestrictedPreference? = null
|
||||
|
||||
private var isAirplaneModeOn = false
|
||||
|
||||
override fun getAvailabilityStatus() =
|
||||
if (SimRepository(mContext).showMobileNetworkPage()) AVAILABLE
|
||||
else CONDITIONALLY_UNAVAILABLE
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
super.displayPreference(screen)
|
||||
preference = screen.findPreference(preferenceKey)
|
||||
}
|
||||
|
||||
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
|
||||
repository
|
||||
.subscriptionsStateFlow()
|
||||
.collectLatestWithLifecycle(viewLifecycleOwner, action = ::update)
|
||||
airplaneModeOnFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
|
||||
isAirplaneModeOn = it
|
||||
updateEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) {
|
||||
val preference = preference ?: return
|
||||
preference.onPreferenceClickListener = null
|
||||
preference.fragment = null
|
||||
when (state) {
|
||||
MobileNetworkSummaryRepository.AddNetwork -> {
|
||||
preference.summary =
|
||||
context.getString(R.string.mobile_network_summary_add_a_network)
|
||||
preference.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
logPreferenceClick()
|
||||
startAddSimFlow(context)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
MobileNetworkSummaryRepository.NoSubscriptions -> {
|
||||
preference.summary = null
|
||||
}
|
||||
|
||||
is MobileNetworkSummaryRepository.HasSubscriptions -> {
|
||||
preference.summary = state.displayNames.joinToString(", ")
|
||||
preference.fragment = MobileNetworkListFragment::class.java.canonicalName
|
||||
}
|
||||
}
|
||||
updateEnabled()
|
||||
}
|
||||
|
||||
private fun updateEnabled() {
|
||||
val preference = preference ?: return
|
||||
if (preference.isDisabledByAdmin) return
|
||||
preference.isEnabled =
|
||||
(preference.onPreferenceClickListener != null || preference.fragment != null) &&
|
||||
!isAirplaneModeOn
|
||||
}
|
||||
|
||||
private fun logPreferenceClick() {
|
||||
val preference = preference ?: return
|
||||
metricsFeatureProvider.logClickedPreference(
|
||||
preference,
|
||||
preference.extras.getInt(DashboardFragment.CATEGORY),
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionInfo
|
||||
import com.android.settings.network.telephony.SubscriptionRepository
|
||||
import com.android.settings.network.telephony.euicc.EuiccRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class MobileNetworkSummaryRepository(
|
||||
private val context: Context,
|
||||
private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
|
||||
private val euiccRepository: EuiccRepository = EuiccRepository(context),
|
||||
private val getDisplayName: (SubscriptionInfo) -> String = { subInfo ->
|
||||
SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context).toString()
|
||||
},
|
||||
) {
|
||||
sealed interface SubscriptionsState
|
||||
|
||||
data object AddNetwork : SubscriptionsState
|
||||
|
||||
data object NoSubscriptions : SubscriptionsState
|
||||
|
||||
data class HasSubscriptions(val displayNames: List<String>) : SubscriptionsState
|
||||
|
||||
fun subscriptionsStateFlow(): Flow<SubscriptionsState> =
|
||||
subDisplayNamesFlow()
|
||||
.map { displayNames ->
|
||||
if (displayNames.isEmpty()) {
|
||||
if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions
|
||||
} else {
|
||||
HasSubscriptions(displayNames)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
|
||||
private fun subDisplayNamesFlow(): Flow<List<String>> =
|
||||
subscriptionRepository
|
||||
.selectableSubscriptionInfoListFlow()
|
||||
.map { subInfos -> subInfos.map(getDisplayName) }
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
@@ -19,7 +19,7 @@ import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsDumpService;
|
||||
@@ -69,12 +69,11 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
||||
|
||||
@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 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
||||
|
||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
||||
|
||||
controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
|
||||
controllers.add(vpnPreferenceController);
|
||||
controllers.add(privateDnsPreferenceController);
|
||||
|
||||
@@ -114,8 +112,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements
|
||||
@Override
|
||||
public List<AbstractPreferenceController> createPreferenceControllers(Context
|
||||
context) {
|
||||
return buildPreferenceControllers(context, null /* lifecycle */,
|
||||
null /* LifecycleOwner */);
|
||||
return buildPreferenceControllers(context, null /* lifecycle */);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ package com.android.settings.network.telephony
|
||||
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 @@ class VoNrRepository(
|
||||
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 @@ class VoNrRepository(
|
||||
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 @@ class VoNrRepository(
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ package com.android.settings.notification;
|
||||
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 @@ public class NotificationRingtonePreferenceController extends RingtonePreference
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
if (isVibrationInSoundUriEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return mContext.getResources().getBoolean(R.bool.config_show_notification_ringtone);
|
||||
}
|
||||
|
||||
@@ -43,4 +47,9 @@ public class NotificationRingtonePreferenceController extends RingtonePreference
|
||||
public int getRingtoneType() {
|
||||
return RingtoneManager.TYPE_NOTIFICATION;
|
||||
}
|
||||
|
||||
private boolean isVibrationInSoundUriEnabled() {
|
||||
return Flags.notificationVibrationInSoundUri() && mContext.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
|
||||
}
|
||||
}
|
||||
|
@@ -16,27 +16,19 @@
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
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;
|
||||
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 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
||||
|
||||
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 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
||||
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 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
||||
* 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 @@ public class CreateShortcutPreferenceController extends BasePreferenceController
|
||||
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;
|
||||
}
|
||||
|
118
src/com/android/settings/shortcut/Shortcuts.java
Normal file
118
src/com/android/settings/shortcut/Shortcuts.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
||||
|
||||
class Shortcuts {
|
||||
|
||||
private static final String TAG = "Shortcuts";
|
||||
|
||||
static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
|
||||
static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory("com.android.settings.SHORTCUT")
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
static ShortcutInfo createShortcutInfo(Context context, ResolveInfo target) {
|
||||
checkArgument(target.activityInfo != null);
|
||||
String shortcutId = SHORTCUT_ID_PREFIX
|
||||
+ target.activityInfo.getComponentName().flattenToShortString();
|
||||
|
||||
return createShortcutInfo(context, shortcutId, target);
|
||||
}
|
||||
|
||||
static ShortcutInfo createShortcutInfo(Context context, String id, ResolveInfo target) {
|
||||
Intent intent = new Intent(SHORTCUT_PROBE)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
.setClassName(target.activityInfo.packageName, target.activityInfo.name);
|
||||
if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
|
||||
CharSequence label = target.loadLabel(context.getPackageManager());
|
||||
Icon maskableIcon = getMaskableIcon(context, target.activityInfo);
|
||||
|
||||
return new ShortcutInfo.Builder(context, id)
|
||||
.setIntent(intent)
|
||||
.setShortLabel(label)
|
||||
.setIcon(maskableIcon)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Icon getMaskableIcon(Context context, ActivityInfo activityInfo) {
|
||||
if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
|
||||
return Icon.createWithAdaptiveBitmap(createIcon(
|
||||
context,
|
||||
activityInfo.applicationInfo, activityInfo.icon,
|
||||
R.layout.shortcut_badge_maskable,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
|
||||
} else {
|
||||
return Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
||||
}
|
||||
}
|
||||
|
||||
static Bitmap createIcon(Context context, ApplicationInfo app, int resource, int layoutRes,
|
||||
int size) {
|
||||
final Context themedContext = new ContextThemeWrapper(context,
|
||||
android.R.style.Theme_Material);
|
||||
final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null);
|
||||
final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
|
||||
view.measure(spec, spec);
|
||||
final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
Drawable iconDrawable;
|
||||
try {
|
||||
iconDrawable = context.getPackageManager().getResourcesForApplication(app)
|
||||
.getDrawable(resource, themedContext.getTheme());
|
||||
if (iconDrawable instanceof LayerDrawable) {
|
||||
iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
|
||||
}
|
||||
((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
|
||||
Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
|
||||
((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
|
||||
}
|
||||
|
||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import android.app.Flags;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
public class ShortcutsUpdateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = "ShortcutsUpdateReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
||||
if (!Flags.modesApi() || !Flags.modesUi()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
PendingResult pendingResult = goAsync();
|
||||
|
||||
ThreadUtils.getBackgroundExecutor().execute(() -> {
|
||||
try {
|
||||
ShortcutsUpdater.updatePinnedShortcuts(context);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error trying to update Settings shortcuts", e);
|
||||
} finally {
|
||||
pendingResult.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
|
||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_PROBE;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShortcutsUpdateTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public ShortcutsUpdateTask(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void doInBackground(Void... params) {
|
||||
ShortcutManager sm = mContext.getSystemService(ShortcutManager.class);
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
|
||||
List<ShortcutInfo> updates = new ArrayList<>();
|
||||
for (ShortcutInfo info : sm.getPinnedShortcuts()) {
|
||||
if (!info.getId().startsWith(SHORTCUT_ID_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
ComponentName cn = ComponentName.unflattenFromString(
|
||||
info.getId().substring(SHORTCUT_ID_PREFIX.length()));
|
||||
ResolveInfo ri = pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
|
||||
if (ri == null) {
|
||||
continue;
|
||||
}
|
||||
updates.add(new ShortcutInfo.Builder(mContext, info.getId())
|
||||
.setShortLabel(ri.loadLabel(pm)).build());
|
||||
}
|
||||
if (!updates.isEmpty()) {
|
||||
sm.updateShortcuts(updates);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
96
src/com/android/settings/shortcut/ShortcutsUpdater.java
Normal file
96
src/com/android/settings/shortcut/ShortcutsUpdater.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
|
||||
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import android.app.Flags;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.Settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShortcutsUpdater {
|
||||
|
||||
/**
|
||||
* Update label, icon, and intent of pinned shortcuts to Settings subpages.
|
||||
*
|
||||
* <p>Should be called whenever any of those could have changed, such as after changing locale,
|
||||
* restoring a backup from a different device, or when flags controlling available features
|
||||
* may have flipped.
|
||||
*/
|
||||
public static void updatePinnedShortcuts(Context context) {
|
||||
ShortcutManager sm = checkNotNull(context.getSystemService(ShortcutManager.class));
|
||||
|
||||
List<ShortcutInfo> updates = new ArrayList<>();
|
||||
for (ShortcutInfo info : sm.getPinnedShortcuts()) {
|
||||
ResolveInfo resolvedActivity = resolveActivity(context, info);
|
||||
if (resolvedActivity != null) {
|
||||
// Id is preserved to update an existing shortcut, but the activity it opens might
|
||||
// be different, according to maybeGetReplacingComponent.
|
||||
updates.add(Shortcuts.createShortcutInfo(context, info.getId(), resolvedActivity));
|
||||
}
|
||||
}
|
||||
if (!updates.isEmpty()) {
|
||||
sm.updateShortcuts(updates);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ResolveInfo resolveActivity(Context context, ShortcutInfo shortcut) {
|
||||
if (!shortcut.getId().startsWith(SHORTCUT_ID_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ComponentName cn = ComponentName.unflattenFromString(
|
||||
shortcut.getId().substring(SHORTCUT_ID_PREFIX.length()));
|
||||
if (cn == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the componentName is obsolete and has been replaced by a different one.
|
||||
cn = maybeGetReplacingComponent(context, cn);
|
||||
PackageManager pm = context.getPackageManager();
|
||||
return pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ComponentName maybeGetReplacingComponent(Context context, ComponentName cn) {
|
||||
// ZenModeSettingsActivity is replaced by ModesSettingsActivity and will be deleted
|
||||
// soon (so we shouldn't use ZenModeSettingsActivity.class).
|
||||
if (Flags.modesApi() && Flags.modesUi()
|
||||
&& cn.getClassName().endsWith("Settings$ZenModeSettingsActivity")) {
|
||||
return new ComponentName(context, Settings.ModesSettingsActivity.class);
|
||||
}
|
||||
|
||||
return cn;
|
||||
}
|
||||
}
|
@@ -137,7 +137,7 @@ private fun AddSim() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAddSimFlow(context: Context) {
|
||||
fun startAddSimFlow(context: Context) {
|
||||
val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
|
||||
intent.setPackage(Utils.PHONE_PACKAGE_NAME)
|
||||
intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
|
||||
|
@@ -41,19 +41,20 @@ public class WallpaperSuggestionActivity extends StyleSuggestionActivityBase imp
|
||||
private static final String WALLPAPER_FOCUS = "focus_wallpaper";
|
||||
private static final String WALLPAPER_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
@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);
|
||||
@@ -155,8 +187,88 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
verify(mFragment).showBluetoothTurnedOnToast();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingDisabled_finish() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
setUpFragmentWithPairAndJoinSharingIntent(false);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
|
||||
|
||||
verify(mFragment).finish();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
|
||||
assertThat(dialog).isNotNull();
|
||||
TextView message = dialog.findViewById(R.id.message);
|
||||
assertThat(message).isNotNull();
|
||||
// TODO: use stringr res once finalized
|
||||
assertThat(message.getText().toString()).isEqualTo(
|
||||
"Connecting to " + TEST_DEVICE_ADDRESS + "...");
|
||||
verify(mFragment, never()).finish();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingDisabled_doNothing() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
setUpFragmentWithPairAndJoinSharingIntent(false);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||
|
||||
verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(any(BluetoothDevice.class),
|
||||
any(Executor.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_bonding_pairAndJoinSharingEnabled_addListener() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||
|
||||
verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mBluetoothDevice),
|
||||
any(Executor.class),
|
||||
any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingDisabled_doNothing() {
|
||||
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
|
||||
|
||||
verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(
|
||||
any(BluetoothDevice.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingEnabled_removeListener() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
mFragment.mSelectedList.add(mBluetoothDevice);
|
||||
setUpFragmentWithPairAndJoinSharingIntent(true);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
|
||||
mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
|
||||
|
||||
verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mBluetoothDevice),
|
||||
any(BluetoothAdapter.OnMetadataChangedListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
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 @@ public class BluetoothDevicePairingDetailBaseTest {
|
||||
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 {
|
||||
|
||||
|
@@ -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 androidx.test.core.app.ApplicationProvider;
|
||||
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 @@ public class AudioSharingDashboardFragmentTest {
|
||||
@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();
|
||||
}
|
||||
@@ -100,13 +126,73 @@ public class AudioSharingDashboardFragmentTest {
|
||||
verify(mSwitchBar).show();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActivityResult_shareThenPairWithBadCode_doNothing() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mFragment.setControllers(
|
||||
mVolumeGroupController,
|
||||
mCallAudioController,
|
||||
mPlaySoundController,
|
||||
mStreamsCategoryController,
|
||||
mSwitchBarController);
|
||||
Intent data = new Intent();
|
||||
Bundle extras = new Bundle();
|
||||
BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
|
||||
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
|
||||
data.putExtras(extras);
|
||||
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActivityResult_shareThenPairWithNoDevice_doNothing() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mFragment.setControllers(
|
||||
mVolumeGroupController,
|
||||
mCallAudioController,
|
||||
mPlaySoundController,
|
||||
mStreamsCategoryController,
|
||||
mSwitchBarController);
|
||||
Intent data = new Intent();
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, null);
|
||||
data.putExtras(extras);
|
||||
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActivityResult_shareThenPairWithDevice_handleAutoAddSource() {
|
||||
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
|
||||
mFragment.setControllers(
|
||||
mVolumeGroupController,
|
||||
mCallAudioController,
|
||||
mPlaySoundController,
|
||||
mStreamsCategoryController,
|
||||
mSwitchBarController);
|
||||
Intent data = new Intent();
|
||||
Bundle extras = new Bundle();
|
||||
BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
|
||||
extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
|
||||
data.putExtras(extras);
|
||||
mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_OK, data);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
verify(mSwitchBarController).handleAutoAddSourceAfterPair(device);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onAudioSharingStateChanged_updateVisibilityForControllers() {
|
||||
mFragment.setControllers(
|
||||
mVolumeGroupController,
|
||||
mCallAudioController,
|
||||
mPlaySoundController,
|
||||
mStreamsCategoryController);
|
||||
mStreamsCategoryController,
|
||||
mSwitchBarController);
|
||||
mFragment.onAudioSharingStateChanged();
|
||||
verify(mVolumeGroupController).updateVisibility();
|
||||
verify(mCallAudioController).updateVisibility();
|
||||
@@ -120,7 +206,8 @@ public class AudioSharingDashboardFragmentTest {
|
||||
mVolumeGroupController,
|
||||
mCallAudioController,
|
||||
mPlaySoundController,
|
||||
mStreamsCategoryController);
|
||||
mStreamsCategoryController,
|
||||
mSwitchBarController);
|
||||
mFragment.onAudioSharingProfilesConnected();
|
||||
verify(mVolumeGroupController).onAudioSharingProfilesConnected();
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import android.widget.Button;
|
||||
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 @@ public class AudioSharingDialogFragmentTest {
|
||||
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 @@ public class AudioSharingDialogFragmentTest {
|
||||
@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 @@ public class AudioSharingDialogFragmentTest {
|
||||
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 @@ public class AudioSharingDialogFragmentTest {
|
||||
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 @@ public class AudioSharingDialogFragmentTest {
|
||||
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();
|
||||
@@ -359,9 +372,6 @@ public class AudioSharingDialogFragmentTest {
|
||||
mParent,
|
||||
list,
|
||||
new AudioSharingDialogFragment.DialogEventListener() {
|
||||
@Override
|
||||
public void onItemClick(AudioSharingDeviceItem item) {}
|
||||
|
||||
@Override
|
||||
public void onCancelClick() {
|
||||
isCancelBtnClicked.set(true);
|
||||
|
@@ -18,7 +18,6 @@ package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
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 androidx.fragment.app.FragmentActivity;
|
||||
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.Before;
|
||||
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 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
||||
@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 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
||||
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 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
||||
@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 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
||||
@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 @@ public class AudioSharingIncompatibleDialogFragmentTest {
|
||||
@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 class AudioSharingIncompatibleDialogFragmentTest {
|
||||
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();
|
||||
|
@@ -934,6 +934,19 @@ public class AudioSharingSwitchBarControllerTest {
|
||||
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);
|
||||
|
@@ -1,333 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.notNull;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.euicc.EuiccManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.Settings.MobileNetworkActivity;
|
||||
import com.android.settings.widget.AddPreference;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MobileNetworkSummaryControllerTest {
|
||||
|
||||
@Mock
|
||||
private TelephonyManager mTelephonyManager;
|
||||
@Mock
|
||||
private SubscriptionManager mSubscriptionManager;
|
||||
@Mock
|
||||
private EuiccManager mEuiccManager;
|
||||
@Mock
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
@Mock
|
||||
private MobileNetworkRepository mMobileNetworkRepository;
|
||||
@Mock
|
||||
private MobileNetworkRepository.MobileNetworkCallback mMobileNetworkCallback;
|
||||
|
||||
private AddPreference mPreference;
|
||||
private Context mContext;
|
||||
private MobileNetworkSummaryController mController;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private Lifecycle mLifecycle;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
|
||||
doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
|
||||
doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class);
|
||||
mMobileNetworkRepository = MobileNetworkRepository.getInstance(mContext);
|
||||
mLifecycleOwner = () -> mLifecycle;
|
||||
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||
mMobileNetworkRepository.addRegister(mLifecycleOwner, mMobileNetworkCallback,
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
||||
|
||||
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
|
||||
when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
|
||||
when(mEuiccManager.isEnabled()).thenReturn(true);
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
|
||||
|
||||
mController = new MobileNetworkSummaryController(mContext, mLifecycle, mLifecycleOwner);
|
||||
mPreference = spy(new AddPreference(mContext, null));
|
||||
mPreference.setKey(mController.getPreferenceKey());
|
||||
when(mPreferenceScreen.findPreference(eq(mController.getPreferenceKey()))).thenReturn(
|
||||
mPreference);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mMobileNetworkRepository.removeRegister(mMobileNetworkCallback);
|
||||
SubscriptionUtil.setActiveSubscriptionsForTesting(null);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSummary_noSubscriptions_returnSummaryCorrectly() {
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mController.getSummary()).isEqualTo("Add a network");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSummary_noSubscriptionsNoEuiccMgr_correctSummaryAndClickHandler() {
|
||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
||||
assertThat(TextUtils.isEmpty(mController.getSummary())).isTrue();
|
||||
assertThat(mPreference.getOnPreferenceClickListener()).isNull();
|
||||
assertThat(mPreference.getFragment()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getSummary_oneSubscription_correctSummaryAndClickHandler() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
assertThat(mController.getSummary()).isEqualTo("sub1");
|
||||
assertThat(mPreference.getFragment()).isNull();
|
||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
doNothing().when(mContext).startActivity(intentCaptor.capture());
|
||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
||||
Intent intent = intentCaptor.getValue();
|
||||
assertThat(intent.getComponent().getClassName()).isEqualTo(
|
||||
MobileNetworkActivity.class.getName());
|
||||
assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getSummary_oneInactivePSim_cannotDisablePsim_correctSummaryAndClickHandler() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1");
|
||||
|
||||
assertThat(mPreference.getFragment()).isNull();
|
||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
||||
verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getSummary_oneInactivePSim_canDisablePsim_correctSummaryAndClickHandler() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
when(sub1.getSubscriptionId()).thenReturn(1);
|
||||
when(sub1.getDisplayName()).thenReturn("sub1");
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
|
||||
when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mController.getSummary()).isEqualTo("sub1");
|
||||
|
||||
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
doNothing().when(mContext).startActivity(intentCaptor.capture());
|
||||
mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
|
||||
Intent intent = intentCaptor.getValue();
|
||||
assertThat(intent.getComponent().getClassName()).isEqualTo(
|
||||
MobileNetworkActivity.class.getName());
|
||||
assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addButton_noSubscriptionsNoEuiccMgr_noAddClickListener() {
|
||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addButton_oneSubscriptionNoEuiccMgr_noAddClickListener() {
|
||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addButton_noSubscriptions_noAddClickListener() {
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference, never()).setOnAddClickListener(notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void addButton_oneSubscription_hasAddClickListener() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference).setOnAddClickListener(notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void addButton_twoSubscriptions_hasAddClickListener() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference).setOnAddClickListener(notNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void addButton_oneSubscriptionAirplaneModeTurnedOn_addButtonGetsDisabled() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
||||
mController.onAirplaneModeChanged(true);
|
||||
|
||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
||||
assertThat(captor.getValue()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void onResume_oneSubscriptionAirplaneMode_isDisabled() {
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
|
||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
||||
assertThat(captor.getValue()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onAvailableSubInfoChanged_noSubscriptionEsimDisabled_isDisabled() {
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
|
||||
when(mEuiccManager.isEnabled()).thenReturn(false);
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
|
||||
mController.onAvailableSubInfoChanged(null);
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOn_isDisabled() {
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isTrue();
|
||||
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
||||
mController.onAirplaneModeChanged(true);
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOff_isEnabled() {
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
|
||||
final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
|
||||
SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
|
||||
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
|
||||
mController.onAirplaneModeChanged(false);
|
||||
|
||||
assertThat(mPreference.isEnabled()).isTrue();
|
||||
|
||||
final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
|
||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(eq(false));
|
||||
verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
|
||||
assertThat(captor.getValue()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onResume_disabledByAdmin_prefStaysDisabled() {
|
||||
mPreference.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
mController.onResume();
|
||||
verify(mPreference, never()).setEnabled(eq(true));
|
||||
}
|
||||
}
|
@@ -18,35 +18,77 @@ package com.android.settings.notification;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
|
||||
import static 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();
|
||||
}
|
||||
|
||||
|
@@ -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 @@ public class CreateShortcutPreferenceControllerTest {
|
||||
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 @@ public class CreateShortcutPreferenceControllerTest {
|
||||
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 @@ public class CreateShortcutPreferenceControllerTest {
|
||||
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 @@ public class CreateShortcutPreferenceControllerTest {
|
||||
ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
|
||||
mPackageManager.setResolveInfosForIntent(
|
||||
new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
|
||||
new Intent(Shortcuts.SHORTCUT_PROBE),
|
||||
Arrays.asList(ri));
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
|
||||
import com.android.settings.Settings;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ShortcutsTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.getApplication();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortcutsUpdateTask() {
|
||||
final Intent intent = new Intent(SHORTCUT_PROBE)
|
||||
.setClass(mContext, Settings.ManageApplicationsActivity.class);
|
||||
final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
|
||||
assertThat(ri).isNotNull();
|
||||
|
||||
ShortcutInfo shortcut = Shortcuts.createShortcutInfo(mContext, ri);
|
||||
|
||||
assertThat(shortcut.getLabel()).isNotNull();
|
||||
assertThat(shortcut.getLabel().toString()).isEqualTo("App info");
|
||||
|
||||
assertThat(shortcut.getIntent()).isNotNull();
|
||||
assertThat(shortcut.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN);
|
||||
assertThat(shortcut.getIntent().getCategories()).contains("com.android.settings.SHORTCUT");
|
||||
assertThat(shortcut.getIntent().getComponent()).isEqualTo(
|
||||
new ComponentName(mContext, Settings.ManageApplicationsActivity.class));
|
||||
assertThat(shortcut.getIcon()).isNotNull();
|
||||
}
|
||||
}
|
@@ -16,30 +16,30 @@
|
||||
|
||||
package com.android.settings.shortcut;
|
||||
|
||||
import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
|
||||
import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static 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.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 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;
|
||||
@@ -48,17 +48,17 @@ 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 {
|
||||
public class ShortcutsUpdaterTest {
|
||||
|
||||
private Context mContext;
|
||||
private ShadowPackageManager mPackageManager;
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
|
||||
@Mock
|
||||
private ShortcutManager mShortcutManager;
|
||||
@@ -68,29 +68,12 @@ public class ShortcutsUpdateTaskTest {
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = RuntimeEnvironment.application;
|
||||
mPackageManager = Shadow.extract(mContext.getPackageManager());
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
public void updatePinnedShortcuts_updatesAllShortcuts() {
|
||||
final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
|
||||
makeShortcut("d1"),
|
||||
makeShortcut("d2"),
|
||||
@@ -99,7 +82,7 @@ public class ShortcutsUpdateTaskTest {
|
||||
makeShortcut(Settings.SoundSettingsActivity.class));
|
||||
when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
|
||||
|
||||
new ShortcutsUpdateTask(mContext).doInBackground();
|
||||
ShortcutsUpdater.updatePinnedShortcuts(mContext);
|
||||
|
||||
verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
|
||||
|
||||
@@ -108,6 +91,52 @@ public class ShortcutsUpdateTaskTest {
|
||||
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) {
|
@@ -118,7 +118,7 @@ public class WallpaperSuggestionActivityTest {
|
||||
}
|
||||
|
||||
@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 @@ public class WallpaperSuggestionActivityTest {
|
||||
|
||||
assertThat(intent).isNotNull();
|
||||
assertThat(intent.getStringExtra(WALLPAPER_FLAVOR)).isEqualTo("focus_wallpaper");
|
||||
assertThat(intent.getStringExtra(WALLPAPER_LAUNCH_SOURCE))
|
||||
.isEqualTo("app_launched_settings_search");
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.testing.TestLifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.RestrictedPreference
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MobileNetworkSummaryControllerTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private val preference = RestrictedPreference(context).apply { key = KEY }
|
||||
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
|
||||
|
||||
private val mockMobileNetworkSummaryRepository = mock<MobileNetworkSummaryRepository>()
|
||||
private val airplaneModeOnFlow = MutableStateFlow(false)
|
||||
|
||||
private val controller =
|
||||
MobileNetworkSummaryController(
|
||||
context = context,
|
||||
preferenceKey = KEY,
|
||||
repository = mockMobileNetworkSummaryRepository,
|
||||
airplaneModeOnFlow = airplaneModeOnFlow,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
preferenceScreen.addPreference(preference)
|
||||
controller.displayPreference(preferenceScreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_noSubscriptions(): Unit = runBlocking {
|
||||
mockMobileNetworkSummaryRepository.stub {
|
||||
on { subscriptionsStateFlow() } doReturn
|
||||
flowOf(MobileNetworkSummaryRepository.NoSubscriptions)
|
||||
}
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
assertThat(preference.summary).isNull()
|
||||
assertThat(preference.isEnabled).isFalse()
|
||||
assertThat(preference.onPreferenceClickListener).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_addNetwork(): Unit = runBlocking {
|
||||
mockMobileNetworkSummaryRepository.stub {
|
||||
on { subscriptionsStateFlow() } doReturn
|
||||
flowOf(MobileNetworkSummaryRepository.AddNetwork)
|
||||
}
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
assertThat(preference.summary)
|
||||
.isEqualTo(context.getString(R.string.mobile_network_summary_add_a_network))
|
||||
assertThat(preference.isEnabled).isTrue()
|
||||
assertThat(preference.onPreferenceClickListener).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_hasSubscriptions(): Unit = runBlocking {
|
||||
mockMobileNetworkSummaryRepository.stub {
|
||||
on { subscriptionsStateFlow() } doReturn
|
||||
flowOf(
|
||||
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||
displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
assertThat(preference.summary).isEqualTo("$DISPLAY_NAME_1, $DISPLAY_NAME_2")
|
||||
assertThat(preference.isEnabled).isTrue()
|
||||
assertThat(preference.fragment).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_addNetworkAndAirplaneModeOn(): Unit = runBlocking {
|
||||
mockMobileNetworkSummaryRepository.stub {
|
||||
on { subscriptionsStateFlow() } doReturn
|
||||
flowOf(MobileNetworkSummaryRepository.AddNetwork)
|
||||
}
|
||||
airplaneModeOnFlow.value = true
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
assertThat(preference.isEnabled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onViewCreated_hasSubscriptionsAndAirplaneModeOn(): Unit = runBlocking {
|
||||
mockMobileNetworkSummaryRepository.stub {
|
||||
on { subscriptionsStateFlow() } doReturn
|
||||
flowOf(
|
||||
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||
displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||
)
|
||||
)
|
||||
}
|
||||
airplaneModeOnFlow.value = true
|
||||
|
||||
controller.onViewCreated(TestLifecycleOwner())
|
||||
delay(100)
|
||||
|
||||
assertThat(preference.isEnabled).isFalse()
|
||||
}
|
||||
|
||||
|
||||
private companion object {
|
||||
const val KEY = "test_key"
|
||||
const val DISPLAY_NAME_1 = "Display Name 1"
|
||||
const val DISPLAY_NAME_2 = "Display Name 2"
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.network
|
||||
|
||||
import android.content.Context
|
||||
import android.telephony.SubscriptionInfo
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.network.telephony.SubscriptionRepository
|
||||
import com.android.settings.network.telephony.euicc.EuiccRepository
|
||||
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MobileNetworkSummaryRepositoryTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
private val mockSubscriptionRepository = mock<SubscriptionRepository>()
|
||||
private val mockEuiccRepository = mock<EuiccRepository>()
|
||||
|
||||
private val repository =
|
||||
MobileNetworkSummaryRepository(
|
||||
context = context,
|
||||
subscriptionRepository = mockSubscriptionRepository,
|
||||
euiccRepository = mockEuiccRepository,
|
||||
getDisplayName = { it.displayName.toString() },
|
||||
)
|
||||
|
||||
@Test
|
||||
fun subscriptionsStateFlow_noSubscriptionsAndShowEuicc_returnsAddNetwork() = runBlocking {
|
||||
mockSubscriptionRepository.stub {
|
||||
on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
|
||||
}
|
||||
mockEuiccRepository.stub { on { showEuiccSettings() } doReturn true }
|
||||
|
||||
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(state).isEqualTo(MobileNetworkSummaryRepository.AddNetwork)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionsStateFlow_noSubscriptionsAndHideEuicc_returnsNoSubscriptions() = runBlocking {
|
||||
mockSubscriptionRepository.stub {
|
||||
on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
|
||||
}
|
||||
mockEuiccRepository.stub { on { showEuiccSettings() } doReturn false }
|
||||
|
||||
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(state).isEqualTo(MobileNetworkSummaryRepository.NoSubscriptions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subscriptionsStateFlow_hasSubscriptions_returnsHasSubscriptions() = runBlocking {
|
||||
mockSubscriptionRepository.stub {
|
||||
on { selectableSubscriptionInfoListFlow() } doReturn
|
||||
flowOf(
|
||||
listOf(
|
||||
SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_1).build(),
|
||||
SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_2).build(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(state)
|
||||
.isEqualTo(
|
||||
MobileNetworkSummaryRepository.HasSubscriptions(
|
||||
listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val DISPLAY_NAME_1 = "Sub 1"
|
||||
const val DISPLAY_NAME_2 = "Sub 2"
|
||||
}
|
||||
}
|
@@ -27,7 +27,9 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.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
|
||||
@@ -134,6 +136,15 @@ class VoNrRepositoryTest {
|
||||
assertThat(isVoNrEnabled).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isVoNrEnabledFlow_noPhoneProcess_noCrash() = runBlocking {
|
||||
mockTelephonyManager.stub { on { isVoNrEnabled } doThrow IllegalStateException("no Phone") }
|
||||
|
||||
val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
|
||||
|
||||
assertThat(isVoNrEnabled).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setVoNrEnabled(): Unit = runBlocking {
|
||||
repository.setVoNrEnabled(SUB_ID, true)
|
||||
@@ -141,7 +152,20 @@ class VoNrRepositoryTest {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user