From 4af5178f571ac31f60655aa95f43f5dc770da55a Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Fri, 12 Jan 2024 16:51:25 +0800 Subject: [PATCH 1/7] [Audiosharing] Support play test sound Test: manual Bug: 305620450 Change-Id: Id3f3b99f94a8a91d76fb9442310789b87c39bca0 --- res/drawable/ic_audio_play_sample.xml | 32 ++++++ res/xml/bluetooth_audio_sharing.xml | 7 ++ .../AudioSharingDashboardFragment.java | 4 + ...oSharingPlaySoundPreferenceController.java | 98 +++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 res/drawable/ic_audio_play_sample.xml create mode 100644 src/com/android/settings/connecteddevice/audiosharing/AudioSharingPlaySoundPreferenceController.java diff --git a/res/drawable/ic_audio_play_sample.xml b/res/drawable/ic_audio_play_sample.xml new file mode 100644 index 00000000000..3666c22ce96 --- /dev/null +++ b/res/drawable/ic_audio_play_sample.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml index d5e08bbc7e4..9ffa2b27798 100644 --- a/res/xml/bluetooth_audio_sharing.xml +++ b/res/xml/bluetooth_audio_sharing.xml @@ -31,6 +31,13 @@ android:title="@string/calls_and_alarms_device_title" settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController" /> + + { + if (mRingtone == null) { + Log.d(TAG, "Skip onClick due to ringtone is null"); + return true; + } + try { + mRingtone.setAudioAttributes( + new AudioAttributes.Builder(mRingtone.getAudioAttributes()) + .setFlags(AudioAttributes.FLAG_BYPASS_MUTE) + .addTag("VX_AOSP_SAMPLESOUND") + .build()); + if (!mRingtone.isPlaying()) { + mRingtone.play(); + } + } catch (Throwable e) { + Log.w(TAG, "Fail to play sample, error = " + e); + } + return true; + }); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + super.onStop(owner); + if (mRingtone != null && mRingtone.isPlaying()) { + mRingtone.stop(); + } + } + + @Override + public String getPreferenceKey() { + return PREF_KEY; + } + + private Uri getMediaVolumeUri() { + return Uri.parse( + ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + + mContext.getPackageName() + + "/" + + R.raw.media_volume); + } +} From 3d2c417236e49b851697cc60ab9f92b68092d733 Mon Sep 17 00:00:00 2001 From: Hyunho Date: Fri, 12 Jan 2024 05:31:31 +0000 Subject: [PATCH 2/7] Add the condtions to show the pSIM conversion menu if the value of carrier config and current SIM's carrier is matched, then the pSIM conversion menu will show Bug: b/319527964 Test: manual test (b/319527964#comment3) Change-Id: I82025447f43c0151ba58edd77c6f8b7e8aff660d --- res/values/config.xml | 4 ++++ .../ConvertToEsimPreferenceController.java | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/res/values/config.xml b/res/values/config.xml index 6f784dd7705..f3e2a7ac042 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -793,4 +793,8 @@ false + + + + diff --git a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java index 27b8c16e109..441c2498152 100644 --- a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java +++ b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java @@ -51,6 +51,7 @@ import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class ConvertToEsimPreferenceController extends TelephonyBasePreferenceController implements @@ -111,7 +112,8 @@ public class ConvertToEsimPreferenceController extends TelephonyBasePreferenceCo * To avoid showing users dialogs that can cause confusion, * add conditions to allow conversion in the absence of active eSIM. */ - if (!mContext.getResources().getBoolean(R.bool.config_psim_conversion_menu_enabled)) { + if (!mContext.getResources().getBoolean(R.bool.config_psim_conversion_menu_enabled) + || !isPsimConversionSupport(subId)) { return CONDITIONALLY_UNAVAILABLE; } if (findConversionSupportComponent()) { @@ -238,4 +240,16 @@ public class ConvertToEsimPreferenceController extends TelephonyBasePreferenceCo } return true; } + + private boolean isPsimConversionSupport(int subId) { + SubscriptionManager subscriptionManager = mContext.getSystemService( + SubscriptionManager.class); + SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subId); + if (subInfo == null) { + return false; + } + final int[] supportedCarriers = mContext.getResources().getIntArray( + R.array.config_psim_conversion_menu_enabled_carrier); + return Arrays.stream(supportedCarriers).anyMatch(id -> id == subInfo.getCarrierId()); + } } From f08c87d00258f3e7e653832b128f0e22286ba3f7 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 15 Jan 2024 10:49:40 +0800 Subject: [PATCH 3/7] Fix LinkifySummaryPreferenceTest The spy annotation will trigger the non-existing API. Bug: 313563183 Test: atest SettingsRoboTests:com.android.settings.widget Change-Id: Iec4448b45e5408846962dc49c65ccc64feb5d2ad --- .../widget/LinkifySummaryPreferenceTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/robotests/src/com/android/settings/widget/LinkifySummaryPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/LinkifySummaryPreferenceTest.java index f060588b498..b33a5643b6c 100644 --- a/tests/robotests/src/com/android/settings/widget/LinkifySummaryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/widget/LinkifySummaryPreferenceTest.java @@ -32,21 +32,22 @@ import android.view.View; import android.widget.TextView; import androidx.preference.PreferenceViewHolder; +import androidx.test.core.app.ApplicationProvider; import org.junit.Before; -import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -@Ignore("b/313563183") @RunWith(RobolectricTestRunner.class) public class LinkifySummaryPreferenceTest { - @Spy + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + private PreferenceViewHolder mViewHolder; @Mock private TextView mSummaryTextView; @@ -54,9 +55,7 @@ public class LinkifySummaryPreferenceTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - final Context context = RuntimeEnvironment.application; + final Context context = ApplicationProvider.getApplicationContext(); mPreference = new LinkifySummaryPreference(context, null /* attrs */); final View view = spy(View.inflate(context, mPreference.getLayoutResource(), From f8bc0841bc6c24d467f95914ea66de4dd3e722f1 Mon Sep 17 00:00:00 2001 From: Charlotte Lu Date: Mon, 15 Jan 2024 15:39:49 +0800 Subject: [PATCH 4/7] When apn is read only, hide the save button. Test: Visual Test Fix: 320200491 Change-Id: I8280fdaa3643a982137936473d827b8502a6e023 --- .../network/apn/ApnEditPageProvider.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 52066a17a25..2600618caae 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -101,17 +101,19 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur RegularScaffold( title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit), actions = { - IconButton(onClick = { - if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) - val valid = validateAndSaveApnData( - apnDataInit, - apnData, - context, - uriInit, - networkTypeSelectedOptionsState - ) - if (valid) navController.navigateBack() - }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) } + if (!apnData.customizedConfig.readOnlyApn) { + IconButton(onClick = { + if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true) + val valid = validateAndSaveApnData( + apnDataInit, + apnData, + context, + uriInit, + networkTypeSelectedOptionsState + ) + if (valid) navController.navigateBack() + }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) } + } }, ) { Column { @@ -212,7 +214,9 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur emptyVal = stringResource(R.string.network_type_unspecified), enabled = apnData.networkTypeEnabled ) {} - if (!apnData.newApn) { + if (!apnData.newApn && !apnData.customizedConfig.readOnlyApn + && apnData.customizedConfig.isAddApnAllowed + ) { Preference( object : PreferenceModel { override val title = stringResource(R.string.menu_delete) From 22273161fd42a4cd8460d4b42c13d7806c547312 Mon Sep 17 00:00:00 2001 From: ykhung Date: Mon, 15 Jan 2024 17:10:58 +0800 Subject: [PATCH 5/7] Update the setOverrideDeadline in the legacy AnomalyDetectionJobService Update the setOverrideDeadline based on the suggestion in the b/319721625, and remove the legacy anomaly detection mechanism from the main entry BroadcastReceiver Fix: 319721625 Test: make -j64 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.fuelgauge.batterytip" Change-Id: I1276bfc95d9cf36a60e28612ebb8a295fd58083b --- AndroidManifest.xml | 8 -------- .../fuelgauge/batterytip/AnomalyDetectionJobService.java | 3 ++- .../batterytip/AnomalyDetectionJobServiceTest.java | 3 ++- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9b64fc45487..4230b6a37a7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4697,14 +4697,6 @@ - - - - - - - diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java index b1018ba34cd..a80987ddb17 100644 --- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java +++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java @@ -49,6 +49,7 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.fuelgauge.PowerAllowlistBackend; import com.android.settingslib.utils.ThreadUtils; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -59,7 +60,7 @@ public class AnomalyDetectionJobService extends JobService { private static final int ON = 1; @VisibleForTesting static final int UID_NULL = -1; @VisibleForTesting static final int STATSD_UID_FILED = 1; - @VisibleForTesting static final long MAX_DELAY_MS = TimeUnit.MINUTES.toMillis(30); + @VisibleForTesting static final long MAX_DELAY_MS = Duration.ofDays(1).toMillis(); private final Object mLock = new Object(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java index a67e5d37e19..482f0d0ce02 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java @@ -71,6 +71,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.android.controller.ServiceController; import org.robolectric.annotation.Config; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -133,7 +134,7 @@ public class AnomalyDetectionJobServiceTest { JobInfo pendingJob = pendingJobs.get(0); assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_detection); assertThat(pendingJob.getMaxExecutionDelayMillis()) - .isEqualTo(TimeUnit.MINUTES.toMillis(30)); + .isEqualTo(Duration.ofDays(1).toMillis()); } @Test From 5d0bd7555daf331bd1e6252c4be0dd9a47abb4cf Mon Sep 17 00:00:00 2001 From: chelseahao Date: Thu, 11 Jan 2024 14:30:00 +0800 Subject: [PATCH 6/7] [Audiosharing] Handle sync, add source via qrcode. Bug: 305620450 Test: manual Change-Id: I32c14607035d8f37f44186175657c42307780e7b --- .../audiostreams/AudioStreamPreference.java | 77 ++++- .../AudioStreamsDashboardFragment.java | 14 +- .../audiostreams/AudioStreamsHelper.java | 5 +- ...udioStreamsProgressCategoryController.java | 264 +++++++++++++++--- 4 files changed, 308 insertions(+), 52 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java index ffb0b884fda..678f9524a37 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java @@ -35,21 +35,27 @@ import com.google.common.base.Strings; */ class AudioStreamPreference extends TwoTargetPreference { private boolean mIsConnected = false; + private AudioStream mAudioStream; /** * Update preference UI based on connection status * - * @param isConnected Is this streams connected + * @param isConnected Is this stream connected + * @param summary Summary text + * @param onPreferenceClickListener Click listener for the preference */ void setIsConnected( - boolean isConnected, @Nullable OnPreferenceClickListener onPreferenceClickListener) { + boolean isConnected, + String summary, + @Nullable OnPreferenceClickListener onPreferenceClickListener) { if (mIsConnected == isConnected + && getSummary() == summary && getOnPreferenceClickListener() == onPreferenceClickListener) { // Nothing to update. return; } mIsConnected = isConnected; - setSummary(isConnected ? "Listening now" : ""); + setSummary(summary); setOrder(isConnected ? 0 : 1); setOnPreferenceClickListener(onPreferenceClickListener); notifyChanged(); @@ -60,6 +66,14 @@ class AudioStreamPreference extends TwoTargetPreference { setIcon(R.drawable.ic_bt_audio_sharing); } + void setAudioStreamState(AudioStreamsProgressCategoryController.AudioStreamState state) { + mAudioStream.setState(state); + } + + AudioStreamsProgressCategoryController.AudioStreamState getAudioStreamState() { + return mAudioStream.getState(); + } + @Override protected boolean shouldHideSecondTarget() { return mIsConnected; @@ -71,19 +85,31 @@ class AudioStreamPreference extends TwoTargetPreference { } static AudioStreamPreference fromMetadata( - Context context, BluetoothLeBroadcastMetadata source) { + Context context, + BluetoothLeBroadcastMetadata source, + AudioStreamsProgressCategoryController.AudioStreamState streamState) { AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null); preference.setTitle(getBroadcastName(source)); + preference.setAudioStream(new AudioStream(source.getBroadcastId(), streamState)); return preference; } static AudioStreamPreference fromReceiveState( - Context context, BluetoothLeBroadcastReceiveState state) { + Context context, + BluetoothLeBroadcastReceiveState receiveState, + AudioStreamsProgressCategoryController.AudioStreamState streamState) { AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null); - preference.setTitle(getBroadcastName(state)); + preference.setTitle(getBroadcastName(receiveState)); + preference.setAudioStream( + new AudioStream( + receiveState.getSourceId(), receiveState.getBroadcastId(), streamState)); return preference; } + private void setAudioStream(AudioStream audioStream) { + mAudioStream = audioStream; + } + private static String getBroadcastName(BluetoothLeBroadcastMetadata source) { return source.getSubgroups().stream() .map(s -> s.getContentMetadata().getProgramInfo()) @@ -99,4 +125,43 @@ class AudioStreamPreference extends TwoTargetPreference { .findFirst() .orElse("Broadcast Id: " + state.getBroadcastId()); } + + private static final class AudioStream { + private int mSourceId; + private int mBroadcastId; + private AudioStreamsProgressCategoryController.AudioStreamState mState; + + private AudioStream( + int broadcastId, AudioStreamsProgressCategoryController.AudioStreamState state) { + mBroadcastId = broadcastId; + mState = state; + } + + private AudioStream( + int sourceId, + int broadcastId, + AudioStreamsProgressCategoryController.AudioStreamState state) { + mSourceId = sourceId; + mBroadcastId = broadcastId; + mState = state; + } + + // TODO(chelseahao): use this to handleSourceRemoved + private int getSourceId() { + return mSourceId; + } + + // TODO(chelseahao): use this to handleSourceRemoved + private int getBroadcastId() { + return mBroadcastId; + } + + private AudioStreamsProgressCategoryController.AudioStreamState getState() { + return mState; + } + + private void setState(AudioStreamsProgressCategoryController.AudioStreamState state) { + mState = state; + } + } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java index a418415d5f2..b0af7ddd78a 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java @@ -34,7 +34,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils; public class AudioStreamsDashboardFragment extends DashboardFragment { private static final String TAG = "AudioStreamsDashboardFrag"; private static final boolean DEBUG = BluetoothUtils.D; - private AudioStreamsScanQrCodeController mAudioStreamsScanQrCodeController; + private AudioStreamsProgressCategoryController mAudioStreamsProgressCategoryController; public AudioStreamsDashboardFragment() { super(); @@ -69,8 +69,8 @@ public class AudioStreamsDashboardFragment extends DashboardFragment { @Override public void onAttach(Context context) { super.onAttach(context); - mAudioStreamsScanQrCodeController = use(AudioStreamsScanQrCodeController.class); - mAudioStreamsScanQrCodeController.setFragment(this); + use(AudioStreamsScanQrCodeController.class).setFragment(this); + mAudioStreamsProgressCategoryController = use(AudioStreamsProgressCategoryController.class); } @Override @@ -103,11 +103,13 @@ public class AudioStreamsDashboardFragment extends DashboardFragment { if (DEBUG) { Log.d(TAG, "onActivityResult() broadcastId : " + source.getBroadcastId()); } - if (mAudioStreamsScanQrCodeController == null) { - Log.w(TAG, "onActivityResult() AudioStreamsScanQrCodeController is null!"); + if (mAudioStreamsProgressCategoryController == null) { + Log.w( + TAG, + "onActivityResult() AudioStreamsProgressCategoryController is null!"); return; } - mAudioStreamsScanQrCodeController.addSource(source); + mAudioStreamsProgressCategoryController.setSourceFromQrCode(source); } } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java index 198e8e5f335..2c6eedbfbf7 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java @@ -109,13 +109,14 @@ class AudioStreamsHelper { } /** Retrieves a list of all LE broadcast receive states from active sinks. */ - List getAllSources() { + List getAllConnectedSources() { if (mLeBroadcastAssistant == null) { Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!"); return emptyList(); } return getActiveSinksOnAssistant(mBluetoothManager).stream() .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream()) + .filter(this::isConnected) .toList(); } @@ -124,7 +125,7 @@ class AudioStreamsHelper { return mLeBroadcastAssistant; } - static boolean isConnected(BluetoothLeBroadcastReceiveState state) { + boolean isConnected(BluetoothLeBroadcastReceiveState state) { return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED && state.getBigEncryptionState() == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING; diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java index 3c005b294b5..ab380c811ba 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java @@ -25,6 +25,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Bundle; +import android.os.CountDownTimer; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -71,6 +72,17 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro } }; + enum AudioStreamState { + // When mTimedSourceFromQrCode is present and this source has not been synced. + WAIT_FOR_SYNC, + // When source has been synced but not added to any sink. + SYNCED, + // When addSource is called for this source and waiting for response. + WAIT_FOR_SOURCE_ADD, + // Source is added to active sink. + SOURCE_ADDED, + } + private final Executor mExecutor; private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback; private final AudioStreamsHelper mAudioStreamsHelper; @@ -78,6 +90,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro private final @Nullable LocalBluetoothManager mBluetoothManager; private final ConcurrentHashMap mBroadcastIdToPreferenceMap = new ConcurrentHashMap<>(); + private TimedSourceFromQrCode mTimedSourceFromQrCode; private AudioStreamsProgressCategoryPreference mCategoryPreference; public AudioStreamsProgressCategoryController(Context context, String preferenceKey) { @@ -122,6 +135,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mExecutor.execute(this::stopScanning); } + void setSourceFromQrCode(BluetoothLeBroadcastMetadata source) { + mTimedSourceFromQrCode = + new TimedSourceFromQrCode( + mContext, source, () -> handleSourceLost(source.getBroadcastId())); + } + void setScanning(boolean isScanning) { ThreadUtils.postOnMainThread( () -> { @@ -140,24 +159,90 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro } if (source.isEncrypted()) { ThreadUtils.postOnMainThread( - () -> launchPasswordDialog(source, preference)); + () -> + launchPasswordDialog( + source, (AudioStreamPreference) preference)); } else { mAudioStreamsHelper.addSource(source); + ((AudioStreamPreference) preference) + .setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD); + updatePreferenceConnectionState( + (AudioStreamPreference) preference, + AudioStreamState.WAIT_FOR_SOURCE_ADD, + null); } return true; }; - mBroadcastIdToPreferenceMap.computeIfAbsent( - source.getBroadcastId(), - k -> { - var preference = AudioStreamPreference.fromMetadata(mContext, source); - ThreadUtils.postOnMainThread( - () -> { - preference.setIsConnected(false, addSourceOrShowDialog); - if (mCategoryPreference != null) { - mCategoryPreference.addPreference(preference); - } - }); - return preference; + + var broadcastIdFound = source.getBroadcastId(); + mBroadcastIdToPreferenceMap.compute( + broadcastIdFound, + (k, v) -> { + if (v == null) { + return addNewPreference( + source, AudioStreamState.SYNCED, addSourceOrShowDialog); + } + var fromState = v.getAudioStreamState(); + if (fromState == AudioStreamState.WAIT_FOR_SYNC) { + var pendingSource = mTimedSourceFromQrCode.get(); + if (pendingSource == null) { + Log.w( + TAG, + "handleSourceFound(): unexpected state with null pendingSource:" + + fromState + + " for broadcastId : " + + broadcastIdFound); + v.setAudioStreamState(AudioStreamState.SYNCED); + return v; + } + mAudioStreamsHelper.addSource(pendingSource); + mTimedSourceFromQrCode.consumed(); + v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD); + updatePreferenceConnectionState( + v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null); + } else { + if (fromState != AudioStreamState.SOURCE_ADDED) { + Log.w( + TAG, + "handleSourceFound(): unexpected state : " + + fromState + + " for broadcastId : " + + broadcastIdFound); + } + } + return v; + }); + } + + private void handleSourceFromQrCodeIfExists() { + if (mTimedSourceFromQrCode == null || mTimedSourceFromQrCode.get() == null) { + return; + } + var metadataFromQrCode = mTimedSourceFromQrCode.get(); + mBroadcastIdToPreferenceMap.compute( + metadataFromQrCode.getBroadcastId(), + (k, v) -> { + if (v == null) { + mTimedSourceFromQrCode.waitForConsume(); + return addNewPreference( + metadataFromQrCode, AudioStreamState.WAIT_FOR_SYNC, null); + } + var fromState = v.getAudioStreamState(); + if (fromState == AudioStreamState.SYNCED) { + mAudioStreamsHelper.addSource(metadataFromQrCode); + mTimedSourceFromQrCode.consumed(); + v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD); + updatePreferenceConnectionState( + v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null); + } else { + Log.w( + TAG, + "handleSourceFromQrCode(): unexpected state : " + + fromState + + " for broadcastId : " + + metadataFromQrCode.getBroadcastId()); + } + return v; }); } @@ -174,32 +259,54 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mAudioStreamsHelper.removeSource(broadcastId); } - void handleSourceConnected(BluetoothLeBroadcastReceiveState state) { - if (!AudioStreamsHelper.isConnected(state)) { + void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) { + if (!mAudioStreamsHelper.isConnected(receiveState)) { return; } + var sourceAddedState = AudioStreamState.SOURCE_ADDED; + var broadcastIdConnected = receiveState.getBroadcastId(); mBroadcastIdToPreferenceMap.compute( - state.getBroadcastId(), + broadcastIdConnected, (k, v) -> { - // True if this source has been added either by scanning, or it's currently - // connected to another active sink. - boolean existed = v != null; - AudioStreamPreference preference = - existed ? v : AudioStreamPreference.fromReceiveState(mContext, state); - - ThreadUtils.postOnMainThread( - () -> { - preference.setIsConnected( - true, p -> launchDetailFragment(state.getBroadcastId())); - if (mCategoryPreference != null && !existed) { - mCategoryPreference.addPreference(preference); - } - }); - - return preference; + if (v == null) { + return addNewPreference( + receiveState, + sourceAddedState, + p -> launchDetailFragment(broadcastIdConnected)); + } + var fromState = v.getAudioStreamState(); + if (fromState == AudioStreamState.WAIT_FOR_SOURCE_ADD + || fromState == AudioStreamState.SYNCED + || fromState == AudioStreamState.WAIT_FOR_SYNC) { + if (mTimedSourceFromQrCode != null) { + mTimedSourceFromQrCode.consumed(); + } + } else { + if (fromState != AudioStreamState.SOURCE_ADDED) { + Log.w( + TAG, + "handleSourceConnected(): unexpected state : " + + fromState + + " for broadcastId : " + + broadcastIdConnected); + } + } + v.setAudioStreamState(sourceAddedState); + updatePreferenceConnectionState( + v, sourceAddedState, p -> launchDetailFragment(broadcastIdConnected)); + return v; }); } + private static String getPreferenceSummary(AudioStreamState state) { + return switch (state) { + case WAIT_FOR_SYNC -> "Scanning..."; + case WAIT_FOR_SOURCE_ADD -> "Connecting..."; + case SOURCE_ADDED -> "Listening now"; + default -> ""; + }; + } + void showToast(String msg) { AudioSharingUtils.toastMessage(mContext, msg); } @@ -235,13 +342,15 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mLeBroadcastAssistant.startSearchingForSources(emptyList()); - // Display currently connected streams + // Handle QR code scan and display currently connected streams var unused = ThreadUtils.postOnBackgroundThread( - () -> - mAudioStreamsHelper - .getAllSources() - .forEach(this::handleSourceConnected)); + () -> { + handleSourceFromQrCodeIfExists(); + mAudioStreamsHelper + .getAllConnectedSources() + .forEach(this::handleSourceConnected); + }); } private void stopScanning() { @@ -256,6 +365,43 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mLeBroadcastAssistant.stopSearchingForSources(); } mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); + if (mTimedSourceFromQrCode != null) { + mTimedSourceFromQrCode.consumed(); + } + } + + private AudioStreamPreference addNewPreference( + BluetoothLeBroadcastReceiveState receiveState, + AudioStreamState state, + Preference.OnPreferenceClickListener onClickListener) { + var preference = AudioStreamPreference.fromReceiveState(mContext, receiveState, state); + updatePreferenceConnectionState(preference, state, onClickListener); + return preference; + } + + private AudioStreamPreference addNewPreference( + BluetoothLeBroadcastMetadata metadata, + AudioStreamState state, + Preference.OnPreferenceClickListener onClickListener) { + var preference = AudioStreamPreference.fromMetadata(mContext, metadata, state); + updatePreferenceConnectionState(preference, state, onClickListener); + return preference; + } + + private void updatePreferenceConnectionState( + AudioStreamPreference preference, + AudioStreamState state, + Preference.OnPreferenceClickListener onClickListener) { + ThreadUtils.postOnMainThread( + () -> { + preference.setIsConnected( + state == AudioStreamState.SOURCE_ADDED, + getPreferenceSummary(state), + onClickListener); + if (mCategoryPreference != null) { + mCategoryPreference.addPreference(preference); + } + }); } private boolean launchDetailFragment(int broadcastId) { @@ -282,7 +428,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro return true; } - private void launchPasswordDialog(BluetoothLeBroadcastMetadata source, Preference preference) { + private void launchPasswordDialog( + BluetoothLeBroadcastMetadata source, AudioStreamPreference preference) { View layout = LayoutInflater.from(mContext) .inflate(R.layout.bluetooth_find_broadcast_password_dialog, null); @@ -307,8 +454,49 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro .setBroadcastCode( code.getBytes(StandardCharsets.UTF_8)) .build()); + preference.setAudioStreamState( + AudioStreamState.WAIT_FOR_SOURCE_ADD); + updatePreferenceConnectionState( + preference, AudioStreamState.WAIT_FOR_SOURCE_ADD, null); }) .create(); alertDialog.show(); } + + private static class TimedSourceFromQrCode { + private static final int WAIT_FOR_SYNC_TIMEOUT_MILLIS = 15000; + private final CountDownTimer mTimer; + private BluetoothLeBroadcastMetadata mSourceFromQrCode; + + private TimedSourceFromQrCode( + Context context, + BluetoothLeBroadcastMetadata sourceFromQrCode, + Runnable timeoutAction) { + mSourceFromQrCode = sourceFromQrCode; + mTimer = + new CountDownTimer(WAIT_FOR_SYNC_TIMEOUT_MILLIS, 1000) { + @Override + public void onTick(long millisUntilFinished) {} + + @Override + public void onFinish() { + timeoutAction.run(); + AudioSharingUtils.toastMessage(context, "Audio steam isn't available"); + } + }; + } + + private void waitForConsume() { + mTimer.start(); + } + + private void consumed() { + mTimer.cancel(); + mSourceFromQrCode = null; + } + + private BluetoothLeBroadcastMetadata get() { + return mSourceFromQrCode; + } + } } From f45427bd5951a4fa60c0b409d28a8c01e3b034d0 Mon Sep 17 00:00:00 2001 From: pajacechen Date: Mon, 15 Jan 2024 17:34:46 +0800 Subject: [PATCH 7/7] [Reskin] Replace string for chargin on hold - replace the string for charging on hold in settings main page and battery status in battery settings page Bug: 315748218 Test: Manual Test Flag: NA Change-Id: I130d377912e150d593f6480e2bbdf43048b6916e --- .../fuelgauge/BatteryHeaderPreferenceController.java | 3 ++- .../fuelgauge/TopLevelBatteryPreferenceController.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index d0b57fd347a..6a65dc07c88 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -80,7 +80,8 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController return mContext.getString( com.android.settingslib.R.string.battery_info_status_not_charging); } else if (BatteryUtils.isBatteryDefenderOn(info)) { - return null; + return mContext.getString( + com.android.settingslib.R.string.battery_info_status_charging_on_hold); } else if (info.remainingLabel == null || info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { // Present status only if no remaining time or status anomalous diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index 0f54f3e7d03..365a2cacb89 100644 --- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -153,6 +153,11 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle return mContext.getString( com.android.settingslib.R.string.battery_info_status_not_charging); } + if (BatteryUtils.isBatteryDefenderOn(info)) { + return mContext.getString( + com.android.settingslib.R.string.power_charging_on_hold_settings_home_page, + info.batteryPercentString); + } if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { // Present status only if no remaining time or status anomalous return info.statusLabel;