Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 911d85a9 authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge "[Audiosharing] Implement name and password row." into main

parents 24045ade 9f23b4e4
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -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"
+44 −8
Original line number Diff line number Diff line
@@ -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();
    }
}
+167 −4
Original line number Diff line number Diff line
@@ -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);
+19 −2
Original line number Diff line number Diff line
@@ -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);
    }
}
+130 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading