• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.development;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothStatusCodes;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.provider.Settings;
24 import android.sysprop.BluetoothProperties;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.VisibleForTesting;
30 import androidx.preference.Preference;
31 import androidx.preference.SwitchPreferenceCompat;
32 
33 import com.android.settings.core.PreferenceControllerMixin;
34 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
35 import com.android.settingslib.flags.Flags;
36 import com.android.settingslib.utils.ThreadUtils;
37 
38 /** Preference controller to enable / disable the Bluetooth LE audio sharing UI flow */
39 public class BluetoothLeAudioUiPreferenceController extends DeveloperOptionsPreferenceController
40         implements Preference.OnPreferenceChangeListener,
41                 PreferenceControllerMixin,
42                 BluetoothLeAudioModePreferenceController.OnModeChangeListener {
43     private static final String TAG = "BluetoothLeAudioUiPreferenceController";
44     private static final String PREFERENCE_KEY = "bluetooth_leaudio_broadcast_ui";
45 
46     @VisibleForTesting
47     static final String VALUE_KEY = "bluetooth_le_audio_sharing_ui_preview_enabled";
48 
49     @VisibleForTesting static final int VALUE_OFF = 0;
50     @VisibleForTesting static final int VALUE_ON = 1;
51     @VisibleForTesting static final int VALUE_UNSET = -1;
52     @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
53     private final BluetoothAdapter mBluetoothAdapter;
54     private boolean mCurrentSettingsValue = false;
55     private boolean mShouldToggleCurrentValue = false;
56 
BluetoothLeAudioUiPreferenceController( @onNull Context context, @Nullable DevelopmentSettingsDashboardFragment fragment)57     public BluetoothLeAudioUiPreferenceController(
58             @NonNull Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
59         super(context);
60         mFragment = fragment;
61         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
62     }
63 
64     @Override
isAvailable()65     public boolean isAvailable() {
66         return Flags.audioSharingDeveloperOption()
67                 && BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false)
68                 && BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
69     }
70 
71     @Override
onPreferenceChange(@onNull Preference preference, @Nullable Object newValue)72     public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
73         if (mFragment != null && newValue != null && (boolean) newValue != mCurrentSettingsValue) {
74             mShouldToggleCurrentValue = true;
75             BluetoothRebootDialog.show(mFragment);
76         }
77         return false;
78     }
79 
80     @Override
updateState(@onNull Preference preference)81     public void updateState(@NonNull Preference preference) {
82         if (mBluetoothAdapter == null) {
83             return;
84         }
85         var unused = ThreadUtils.postOnBackgroundThread(
86                 () -> {
87                     boolean shouldEnable =
88                             mBluetoothAdapter.isEnabled()
89                                     && mBluetoothAdapter.isLeAudioBroadcastSourceSupported()
90                                             == BluetoothStatusCodes.FEATURE_SUPPORTED
91                                     && mBluetoothAdapter.isLeAudioBroadcastAssistantSupported()
92                                             == BluetoothStatusCodes.FEATURE_SUPPORTED;
93                     boolean valueOn =
94                             Settings.Global.getInt(
95                                             mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET)
96                                     == VALUE_ON;
97                     mContext.getMainExecutor()
98                             .execute(
99                                     () -> {
100                                         if (!shouldEnable && valueOn) {
101                                             Log.e(
102                                                     TAG,
103                                                     "Error state: toggle disabled but current"
104                                                             + " settings value is true.");
105                                         }
106                                         mCurrentSettingsValue = valueOn;
107                                         preference.setEnabled(shouldEnable);
108                                         ((SwitchPreferenceCompat) preference).setChecked(valueOn);
109                                     });
110                 });
111     }
112 
113     @Override
getPreferenceKey()114     public @NonNull String getPreferenceKey() {
115         return PREFERENCE_KEY;
116     }
117 
118     /** Called when the RebootDialog confirm is clicked. */
onRebootDialogConfirmed()119     public void onRebootDialogConfirmed() {
120         if (isAvailable() && mShouldToggleCurrentValue) {
121             // Blocking, ensure reboot happens after value is saved.
122             Log.d(TAG, "onRebootDialogConfirmed(): setting value to " + !mCurrentSettingsValue);
123             toggleSetting(mContext.getContentResolver(), !mCurrentSettingsValue);
124         }
125     }
126 
127     /** Called when the RebootDialog cancel is clicked. */
onRebootDialogCanceled()128     public void onRebootDialogCanceled() {
129         mShouldToggleCurrentValue = false;
130     }
131 
132     @Override
onBroadcastDisabled()133     public void onBroadcastDisabled() {
134         if (isAvailable() && mCurrentSettingsValue) {
135             Log.d(TAG, "onBroadcastDisabled(): setting value to false");
136             // Blocking, ensure reboot happens after value is saved.
137             toggleSetting(mContext.getContentResolver(), false);
138         }
139     }
140 
toggleSetting(ContentResolver contentResolver, boolean valueOn)141     private static void toggleSetting(ContentResolver contentResolver, boolean valueOn) {
142         Settings.Global.putInt(contentResolver, VALUE_KEY, valueOn ? VALUE_ON : VALUE_OFF);
143     }
144 }
145