• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.bluetooth;
18 
19 import android.content.Context;
20 import android.media.AudioDeviceAttributes;
21 import android.media.AudioDeviceInfo;
22 import android.media.AudioManager;
23 import android.media.Spatializer;
24 import android.text.TextUtils;
25 import android.util.Log;
26 
27 import androidx.annotation.VisibleForTesting;
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceCategory;
30 import androidx.preference.PreferenceFragmentCompat;
31 import androidx.preference.PreferenceScreen;
32 import androidx.preference.SwitchPreference;
33 
34 import com.android.settings.R;
35 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
36 import com.android.settingslib.core.lifecycle.Lifecycle;
37 
38 /**
39  * The controller of the Spatial audio setting in the bluetooth detail settings.
40  */
41 public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsController
42         implements Preference.OnPreferenceClickListener {
43 
44     private static final String TAG = "BluetoothSpatialAudioController";
45     private static final String KEY_SPATIAL_AUDIO_GROUP = "spatial_audio_group";
46     private static final String KEY_SPATIAL_AUDIO = "spatial_audio";
47     private static final String KEY_HEAD_TRACKING = "head_tracking";
48 
49     private final Spatializer mSpatializer;
50 
51     @VisibleForTesting
52     PreferenceCategory mProfilesContainer;
53     @VisibleForTesting
54     AudioDeviceAttributes mAudioDevice;
55 
56     private boolean mIsAvailable;
57 
BluetoothDetailsSpatialAudioController( Context context, PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle)58     public BluetoothDetailsSpatialAudioController(
59             Context context,
60             PreferenceFragmentCompat fragment,
61             CachedBluetoothDevice device,
62             Lifecycle lifecycle) {
63         super(context, fragment, device, lifecycle);
64         AudioManager audioManager = context.getSystemService(AudioManager.class);
65         mSpatializer = audioManager.getSpatializer();
66         getAvailableDevice();
67 
68     }
69 
70     @Override
isAvailable()71     public boolean isAvailable() {
72         return mIsAvailable;
73     }
74 
75     @Override
onPreferenceClick(Preference preference)76     public boolean onPreferenceClick(Preference preference) {
77         SwitchPreference switchPreference = (SwitchPreference) preference;
78         String key = switchPreference.getKey();
79         if (TextUtils.equals(key, KEY_SPATIAL_AUDIO)) {
80             if (switchPreference.isChecked()) {
81                 mSpatializer.addCompatibleAudioDevice(mAudioDevice);
82             } else {
83                 mSpatializer.removeCompatibleAudioDevice(mAudioDevice);
84             }
85             refresh();
86             return true;
87         } else if (TextUtils.equals(key, KEY_HEAD_TRACKING)) {
88             mSpatializer.setHeadTrackerEnabled(switchPreference.isChecked(), mAudioDevice);
89             return true;
90         } else {
91             Log.w(TAG, "invalid key name.");
92             return false;
93         }
94     }
95 
96     @Override
getPreferenceKey()97     public String getPreferenceKey() {
98         return KEY_SPATIAL_AUDIO_GROUP;
99     }
100 
101     @Override
init(PreferenceScreen screen)102     protected void init(PreferenceScreen screen) {
103         mProfilesContainer = screen.findPreference(getPreferenceKey());
104         mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
105         refresh();
106     }
107 
108     @Override
refresh()109     protected void refresh() {
110         SwitchPreference spatialAudioPref = mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO);
111         if (spatialAudioPref == null) {
112             spatialAudioPref = createSpatialAudioPreference(mProfilesContainer.getContext());
113             mProfilesContainer.addPreference(spatialAudioPref);
114         }
115 
116         boolean isSpatialAudioOn = mSpatializer.getCompatibleAudioDevices().contains(mAudioDevice);
117         Log.d(TAG, "refresh() isSpatialAudioOn : " + isSpatialAudioOn);
118         spatialAudioPref.setChecked(isSpatialAudioOn);
119 
120         SwitchPreference headTrackingPref = mProfilesContainer.findPreference(KEY_HEAD_TRACKING);
121         if (headTrackingPref == null) {
122             headTrackingPref = createHeadTrackingPreference(mProfilesContainer.getContext());
123             mProfilesContainer.addPreference(headTrackingPref);
124         }
125 
126         boolean isHeadTrackingAvailable =
127                 isSpatialAudioOn && mSpatializer.hasHeadTracker(mAudioDevice);
128         Log.d(TAG, "refresh() has head tracker : " + mSpatializer.hasHeadTracker(mAudioDevice));
129         headTrackingPref.setVisible(isHeadTrackingAvailable);
130         if (isHeadTrackingAvailable) {
131             headTrackingPref.setChecked(mSpatializer.isHeadTrackerEnabled(mAudioDevice));
132         }
133     }
134 
135     @VisibleForTesting
createSpatialAudioPreference(Context context)136     SwitchPreference createSpatialAudioPreference(Context context) {
137         SwitchPreference pref = new SwitchPreference(context);
138         pref.setKey(KEY_SPATIAL_AUDIO);
139         pref.setTitle(context.getString(R.string.bluetooth_details_spatial_audio_title));
140         pref.setSummary(context.getString(R.string.bluetooth_details_spatial_audio_summary));
141         pref.setOnPreferenceClickListener(this);
142         return pref;
143     }
144 
145     @VisibleForTesting
createHeadTrackingPreference(Context context)146     SwitchPreference createHeadTrackingPreference(Context context) {
147         SwitchPreference pref = new SwitchPreference(context);
148         pref.setKey(KEY_HEAD_TRACKING);
149         pref.setTitle(context.getString(R.string.bluetooth_details_head_tracking_title));
150         pref.setSummary(context.getString(R.string.bluetooth_details_head_tracking_summary));
151         pref.setOnPreferenceClickListener(this);
152         return pref;
153     }
154 
getAvailableDevice()155     private void getAvailableDevice() {
156         AudioDeviceAttributes a2dpDevice = new AudioDeviceAttributes(
157                 AudioDeviceAttributes.ROLE_OUTPUT,
158                 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
159                 mCachedDevice.getAddress());
160         AudioDeviceAttributes bleHeadsetDevice = new AudioDeviceAttributes(
161                 AudioDeviceAttributes.ROLE_OUTPUT,
162                 AudioDeviceInfo.TYPE_BLE_HEADSET,
163                 mCachedDevice.getAddress());
164         AudioDeviceAttributes bleSpeakerDevice = new AudioDeviceAttributes(
165                 AudioDeviceAttributes.ROLE_OUTPUT,
166                 AudioDeviceInfo.TYPE_BLE_SPEAKER,
167                 mCachedDevice.getAddress());
168         AudioDeviceAttributes bleBroadcastDevice = new AudioDeviceAttributes(
169                 AudioDeviceAttributes.ROLE_OUTPUT,
170                 AudioDeviceInfo.TYPE_BLE_BROADCAST,
171                 mCachedDevice.getAddress());
172         AudioDeviceAttributes hearingAidDevice = new AudioDeviceAttributes(
173                 AudioDeviceAttributes.ROLE_OUTPUT,
174                 AudioDeviceInfo.TYPE_HEARING_AID,
175                 mCachedDevice.getAddress());
176 
177         mIsAvailable = true;
178         if (mSpatializer.isAvailableForDevice(bleHeadsetDevice)) {
179             mAudioDevice = bleHeadsetDevice;
180         } else if (mSpatializer.isAvailableForDevice(bleSpeakerDevice)) {
181             mAudioDevice = bleSpeakerDevice;
182         } else if (mSpatializer.isAvailableForDevice(bleBroadcastDevice)) {
183             mAudioDevice = bleBroadcastDevice;
184         } else if (mSpatializer.isAvailableForDevice(a2dpDevice)) {
185             mAudioDevice = a2dpDevice;
186         } else {
187             mIsAvailable = mSpatializer.isAvailableForDevice(hearingAidDevice);
188             mAudioDevice = hearingAidDevice;
189         }
190 
191         Log.d(TAG, "getAvailableDevice() device : "
192                 + mCachedDevice.getDevice().getAnonymizedAddress()
193                 + ", type : " + mAudioDevice.getType()
194                 + ", is available : " + mIsAvailable);
195     }
196 
197     @VisibleForTesting
setAvailableDevice(AudioDeviceAttributes audioDevice)198     void setAvailableDevice(AudioDeviceAttributes audioDevice) {
199         mAudioDevice = audioDevice;
200         mIsAvailable = mSpatializer.isAvailableForDevice(audioDevice);
201     }
202 }
203