Merge "[LeAudio] Add LocalBluetoothLeBroadcast and LocalBluetoothLeBroadcastAssistant"
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
new file mode 100644
index 0000000..bb47c5e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 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.settingslib.bluetooth;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcast}.
+ */
+public class LocalBluetoothLeBroadcast implements BluetoothLeBroadcast.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcast";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private BluetoothLeBroadcast mBluetoothLeBroadcast;
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeAudioContentMetadata.Builder mBuilder;
+    private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcast(Context context, LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
+        mBuilder = new BluetoothLeAudioContentMetadata.Builder();
+    }
+
+    public void startBroadcast(byte[] broadcastCode, String language,
+            String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
+                return;
+            }
+            Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        buildContentMetadata(language, programInfo);
+        mBluetoothLeBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, broadcastCode);
+    }
+
+    public void stopBroadcast() {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
+                return;
+            }
+            Log.d(TAG, "stopBroadcast()");
+        }
+        mBluetoothLeBroadcast.stopBroadcast(mBroadcastId);
+    }
+
+    public void updateBroadcast(String language, String programInfo) {
+        if (DEBUG) {
+            if (mBluetoothLeBroadcast == null) {
+                Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+                return;
+            }
+            Log.d(TAG,
+                    "updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
+        }
+        mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+        mBluetoothLeBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+    }
+
+    private void buildContentMetadata(String language, String programInfo) {
+        mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
+                programInfo).build();
+    }
+
+    public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
+        return new LocalBluetoothLeBroadcastMetadata(mBluetoothLeBroadcastMetadata);
+    }
+
+    @Override
+    public void onBroadcastStarted(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastMetadataChanged(int broadcastId,
+            @NonNull BluetoothLeBroadcastMetadata metadata) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
+        }
+        mBluetoothLeBroadcastMetadata = metadata;
+    }
+
+    @Override
+    public void onBroadcastStopped(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdated(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + broadcastId);
+        }
+    }
+
+    @Override
+    public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+                            + broadcastId);
+        }
+    }
+
+    @Override
+    public void onPlaybackStarted(int reason, int broadcastId) {
+    }
+
+    @Override
+    public void onPlaybackStopped(int reason, int broadcastId) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
new file mode 100644
index 0000000..d904265
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 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.settingslib.bluetooth;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
+ */
+public class LocalBluetoothLeBroadcastAssistant implements
+        BluetoothLeBroadcastAssistant.Callback {
+
+    private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
+    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private LocalBluetoothProfileManager mProfileManager;
+    private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
+    private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
+    private BluetoothLeBroadcastMetadata.Builder mBuilder;
+    private boolean mIsProfileReady;
+
+    private final ServiceListener mServiceListener = new ServiceListener() {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service connected");
+                }
+                mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+                mProfileManager.callServiceConnectedListeners();
+                mIsProfileReady = true;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+                if (DEBUG) {
+                    Log.d(TAG,"Bluetooth service disconnected");
+                }
+                mIsProfileReady = false;
+            }
+        }
+    };
+
+    LocalBluetoothLeBroadcastAssistant(Context context,
+            LocalBluetoothProfileManager profileManager) {
+        mProfileManager = profileManager;
+        BluetoothAdapter.getDefaultAdapter().
+                getProfileProxy(context, mServiceListener,
+                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+        mBuilder = new BluetoothLeBroadcastMetadata.Builder();
+    }
+
+    public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
+            int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
+            int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
+            BluetoothDevice sourceDevice, boolean isGroupOp) {
+        if (DEBUG) {
+            Log.d(TAG, "addSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
+                paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+        mBluetoothLeBroadcastAssistant.addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
+    }
+
+    private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
+            int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
+            byte[] broadcastCode, BluetoothDevice sourceDevice) {
+        mBluetoothLeBroadcastMetadata =
+                mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
+                        .setSourceAdvertisingSid(sourceAdvertisingSid)
+                        .setBroadcastId(broadcastId)
+                        .setPaSyncInterval(paSyncInterval)
+                        .setEncrypted(isEncrypted)
+                        .setBroadcastCode(broadcastCode)
+                        .setPresentationDelayMicros(presentationDelayMicros)
+                        .build();
+    }
+
+    public void removeSource(@NonNull BluetoothDevice sink, int sourceId) {
+        if (DEBUG) {
+            Log.d(TAG, "removeSource()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
+    }
+
+    public void startSearchingForSources(@NonNull List<android.bluetooth.le.ScanFilter> filters) {
+        if (DEBUG) {
+            Log.d(TAG, "startSearchingForSources()");
+        }
+        if (mBluetoothLeBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return ;
+        }
+        mBluetoothLeBroadcastAssistant.startSearchingForSources(filters);
+    }
+
+    @Override
+    public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAdded(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+
+    }
+
+    @Override
+    public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+            @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceAddFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoved(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSourceRemoveFailed(), reason = " + reason + " , sourceId = " + sourceId);
+        }
+    }
+
+    @Override
+    public void onSearchStarted(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStarted(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStartFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStartFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopped(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopped(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSearchStopFailed(int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "onSearchStopFailed(), reason = " + reason);
+        }
+    }
+
+    @Override
+    public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+    }
+
+    @Override
+    public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+    }
+
+    @Override
+    public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+            @NonNull BluetoothLeBroadcastReceiveState state) {
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+}