[Audiosharing] Implement name and password row.
Bug: 308368124 Test: manual Change-Id: I86d0e771ece0ea7003a50ee0cc9305814d85fecb
This commit is contained in:
@@ -45,9 +45,15 @@
|
||||
|
||||
<com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
|
||||
android:key="audio_sharing_stream_name"
|
||||
android:summary="********"
|
||||
android:title="Stream name"
|
||||
android:title="Name"
|
||||
settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
|
||||
|
||||
<com.android.settings.widget.ValidatedEditTextPreference
|
||||
android:key="audio_sharing_stream_password"
|
||||
android:summary="********"
|
||||
android:title="Password"
|
||||
settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPasswordPreferenceController" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="audio_sharing_stream_compatibility"
|
||||
android:title="Improve compatibility"
|
||||
|
@@ -19,6 +19,8 @@ package com.android.settings.connecteddevice.audiosharing;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
@@ -30,6 +32,7 @@ import com.android.settings.widget.ValidatedEditTextPreference;
|
||||
|
||||
public class AudioSharingNamePreference extends ValidatedEditTextPreference {
|
||||
private static final String TAG = "AudioSharingNamePreference";
|
||||
private boolean mShowQrCodeIcon = false;
|
||||
|
||||
public AudioSharingNamePreference(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
@@ -58,17 +61,50 @@ public class AudioSharingNamePreference extends ValidatedEditTextPreference {
|
||||
setWidgetLayoutResource(R.layout.preference_widget_qrcode);
|
||||
}
|
||||
|
||||
void setShowQrCodeIcon(boolean show) {
|
||||
mShowQrCodeIcon = show;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
final ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
|
||||
|
||||
ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
|
||||
View divider =
|
||||
holder.findViewById(
|
||||
com.android.settingslib.widget.preference.twotarget.R.id
|
||||
.two_target_divider);
|
||||
|
||||
if (shareButton != null && divider != null) {
|
||||
if (mShowQrCodeIcon) {
|
||||
configureVisibleStateForQrCodeIcon(shareButton, divider);
|
||||
} else {
|
||||
configureInvisibleStateForQrCodeIcon(shareButton, divider);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "onBindViewHolder() : shareButton or divider is null!");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureVisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
|
||||
divider.setVisibility(View.VISIBLE);
|
||||
shareButton.setVisibility(View.VISIBLE);
|
||||
shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
|
||||
shareButton.setOnClickListener(
|
||||
unused ->
|
||||
shareButton.setOnClickListener(unused -> launchAudioSharingQrCodeFragment());
|
||||
}
|
||||
|
||||
private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
|
||||
divider.setVisibility(View.INVISIBLE);
|
||||
shareButton.setVisibility(View.INVISIBLE);
|
||||
shareButton.setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void launchAudioSharingQrCodeFragment() {
|
||||
new SubSettingLauncher(getContext())
|
||||
.setTitleText("Audio sharing QR code")
|
||||
.setDestination(AudioStreamsQrCodeFragment.class.getName())
|
||||
.setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
|
||||
.launch());
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
|
@@ -16,25 +16,128 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
|
||||
|
||||
import android.bluetooth.BluetoothLeBroadcast;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.widget.ValidatedEditTextPreference;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class AudioSharingNamePreferenceController extends BasePreferenceController
|
||||
implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener {
|
||||
implements ValidatedEditTextPreference.Validator,
|
||||
Preference.OnPreferenceChangeListener,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
private static final String TAG = "AudioSharingNamePreferenceController";
|
||||
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final String PREF_KEY = "audio_sharing_stream_name";
|
||||
|
||||
private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
|
||||
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
|
||||
new BluetoothLeBroadcast.Callback() {
|
||||
@Override
|
||||
public void onBroadcastMetadataChanged(
|
||||
int broadcastId, BluetoothLeBroadcastMetadata metadata) {
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"onBroadcastMetadataChanged() broadcastId : "
|
||||
+ broadcastId
|
||||
+ " metadata: "
|
||||
+ metadata);
|
||||
}
|
||||
updateQrCodeIcon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStartFailed(int reason) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStarted(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStopFailed(int reason) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStopped(int reason, int broadcastId) {
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"onBroadcastStopped() reason : "
|
||||
+ reason
|
||||
+ " broadcastId: "
|
||||
+ broadcastId);
|
||||
}
|
||||
updateQrCodeIcon(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBroadcastUpdateFailed(int reason, int broadcastId) {
|
||||
Log.w(TAG, "onBroadcastUpdateFailed() reason : " + reason);
|
||||
// Do nothing if update failed.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBroadcastUpdated(int reason, int broadcastId) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onBroadcastUpdated() reason : " + reason);
|
||||
}
|
||||
updateBroadcastName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStarted(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStopped(int reason, int broadcastId) {}
|
||||
};
|
||||
|
||||
@Nullable private final LocalBluetoothManager mLocalBtManager;
|
||||
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
|
||||
private final Executor mExecutor;
|
||||
private final AudioSharingNameTextValidator mAudioSharingNameTextValidator;
|
||||
@Nullable private AudioSharingNamePreference mPreference;
|
||||
|
||||
public AudioSharingNamePreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mLocalBtManager = Utils.getLocalBluetoothManager(context);
|
||||
mBroadcast =
|
||||
(mLocalBtManager != null)
|
||||
? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile()
|
||||
: null;
|
||||
mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
if (mBroadcast != null) {
|
||||
mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
if (mBroadcast != null) {
|
||||
mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,6 +145,17 @@ public class AudioSharingNamePreferenceController extends BasePreferenceControll
|
||||
return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
if (mPreference != null) {
|
||||
mPreference.setValidator(this);
|
||||
updateBroadcastName();
|
||||
updateQrCodeIcon(isBroadcasting(mLocalBtManager));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return PREF_KEY;
|
||||
@@ -49,10 +163,59 @@ public class AudioSharingNamePreferenceController extends BasePreferenceControll
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
// TODO: update broadcast when name is changed.
|
||||
if (mPreference != null
|
||||
&& mPreference.getSummary() != null
|
||||
&& ((String) newValue).contentEquals(mPreference.getSummary())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var unused =
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
if (mBroadcast != null) {
|
||||
mBroadcast.setProgramInfo((String) newValue);
|
||||
if (isBroadcasting(mLocalBtManager)) {
|
||||
// Update broadcast, UI update will be handled after callback
|
||||
mBroadcast.updateBroadcast();
|
||||
} else {
|
||||
// Directly update UI if no ongoing broadcast
|
||||
updateBroadcastName();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateBroadcastName() {
|
||||
if (mPreference != null) {
|
||||
var unused =
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
if (mBroadcast != null) {
|
||||
String name = mBroadcast.getProgramInfo();
|
||||
ThreadUtils.postOnMainThread(
|
||||
() -> {
|
||||
if (mPreference != null) {
|
||||
mPreference.setText(name);
|
||||
mPreference.setSummary(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateQrCodeIcon(boolean show) {
|
||||
if (mPreference != null) {
|
||||
ThreadUtils.postOnMainThread(
|
||||
() -> {
|
||||
if (mPreference != null) {
|
||||
mPreference.setShowQrCodeIcon(show);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTextValid(String value) {
|
||||
return mAudioSharingNameTextValidator.isTextValid(value);
|
||||
|
@@ -18,10 +18,27 @@ package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
import com.android.settings.widget.ValidatedEditTextPreference;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Validator for Audio Sharing Name, which should be a UTF-8 encoded string containing a minimum of
|
||||
* 4 characters and a maximum of 32 human-readable characters.
|
||||
*/
|
||||
public class AudioSharingNameTextValidator implements ValidatedEditTextPreference.Validator {
|
||||
private static final int MIN_LENGTH = 4;
|
||||
private static final int MAX_LENGTH = 32;
|
||||
|
||||
@Override
|
||||
public boolean isTextValid(String value) {
|
||||
// TODO: Add validate rule if applicable.
|
||||
return true;
|
||||
if (value == null || value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
return isValidUTF8(value);
|
||||
}
|
||||
|
||||
private static boolean isValidUTF8(String value) {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
|
||||
return value.equals(reconstructedString);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.connecteddevice.audiosharing;
|
||||
|
||||
import android.bluetooth.BluetoothLeBroadcast;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.widget.ValidatedEditTextPreference;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class AudioSharingPasswordPreferenceController extends BasePreferenceController
|
||||
implements ValidatedEditTextPreference.Validator,
|
||||
Preference.OnPreferenceChangeListener,
|
||||
DefaultLifecycleObserver {
|
||||
private static final String PREF_KEY = "audio_sharing_stream_password";
|
||||
|
||||
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
|
||||
new BluetoothLeBroadcast.Callback() {
|
||||
@Override
|
||||
public void onBroadcastMetadataChanged(
|
||||
int broadcastId, BluetoothLeBroadcastMetadata metadata) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStartFailed(int reason) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStarted(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStopFailed(int reason) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastStopped(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onBroadcastUpdated(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStarted(int reason, int broadcastId) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStopped(int reason, int broadcastId) {}
|
||||
};
|
||||
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
|
||||
private final Executor mExecutor;
|
||||
private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
|
||||
@Nullable private ValidatedEditTextPreference mPreference;
|
||||
|
||||
public AudioSharingPasswordPreferenceController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
mBroadcast =
|
||||
Utils.getLocalBtManager(context).getProfileManager().getLeAudioBroadcastProfile();
|
||||
mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
if (mBroadcast != null) {
|
||||
mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {
|
||||
if (mBroadcast != null) {
|
||||
mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
if (mPreference != null) {
|
||||
mPreference.setValidator(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return PREF_KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
// TODO(chelseahao): implement
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTextValid(String value) {
|
||||
return mAudioSharingPasswordValidator.isTextValid(value);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.connecteddevice.audiosharing;
|
||||
|
||||
import com.android.settings.widget.ValidatedEditTextPreference;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Validator for Audio Sharing Password, which should be a UTF-8 string that has at least 4 octets
|
||||
* and should not exceed 16 octets.
|
||||
*/
|
||||
public class AudioSharingPasswordValidator implements ValidatedEditTextPreference.Validator {
|
||||
private static final int MIN_OCTETS = 4;
|
||||
private static final int MAX_OCTETS = 16;
|
||||
|
||||
@Override
|
||||
public boolean isTextValid(String value) {
|
||||
if (value == null
|
||||
|| getOctetsCount(value) < MIN_OCTETS
|
||||
|| getOctetsCount(value) > MAX_OCTETS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidUTF8(value);
|
||||
}
|
||||
|
||||
private static int getOctetsCount(String value) {
|
||||
return value.getBytes(StandardCharsets.UTF_8).length;
|
||||
}
|
||||
|
||||
private static boolean isValidUTF8(String value) {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
|
||||
return value.equals(reconstructedString);
|
||||
}
|
||||
}
|
@@ -336,7 +336,7 @@ public class AudioSharingUtils {
|
||||
}
|
||||
|
||||
/** Returns if the broadcast is on-going. */
|
||||
public static boolean isBroadcasting(LocalBluetoothManager manager) {
|
||||
public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
|
||||
if (manager == null) return false;
|
||||
LocalBluetoothLeBroadcast broadcast =
|
||||
manager.getProfileManager().getLeAudioBroadcastProfile();
|
||||
|
Reference in New Issue
Block a user