• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.bluetooth.BluetoothCsipSetCoordinator;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothProfile;
22 import android.content.Context;
23 import android.provider.DeviceConfig;
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.settings.core.SettingsUIDeviceConfig;
36 import com.android.settingslib.bluetooth.A2dpProfile;
37 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
38 import com.android.settingslib.bluetooth.HeadsetProfile;
39 import com.android.settingslib.bluetooth.LeAudioProfile;
40 import com.android.settingslib.bluetooth.LocalBluetoothManager;
41 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
42 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
43 import com.android.settingslib.bluetooth.MapProfile;
44 import com.android.settingslib.bluetooth.PanProfile;
45 import com.android.settingslib.bluetooth.PbapServerProfile;
46 import com.android.settingslib.core.lifecycle.Lifecycle;
47 
48 import java.util.ArrayList;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Map;
52 
53 /**
54  * This class adds switches for toggling the individual profiles that a Bluetooth device
55  * supports, such as "Phone audio", "Media audio", "Contact sharing", etc.
56  */
57 public class BluetoothDetailsProfilesController extends BluetoothDetailsController
58         implements Preference.OnPreferenceClickListener,
59         LocalBluetoothProfileManager.ServiceListener {
60     private static final String TAG = "BtDetailsProfilesCtrl";
61 
62     private static final String KEY_PROFILES_GROUP = "bluetooth_profiles";
63     private static final String KEY_BOTTOM_PREFERENCE = "bottom_preference";
64     private static final int ORDINAL = 99;
65 
66     @VisibleForTesting
67     static final String HIGH_QUALITY_AUDIO_PREF_TAG = "A2dpProfileHighQualityAudio";
68 
69     private LocalBluetoothManager mManager;
70     private LocalBluetoothProfileManager mProfileManager;
71     private CachedBluetoothDevice mCachedDevice;
72     private List<CachedBluetoothDevice> mAllOfCachedDevices;
73     private Map<String, List<CachedBluetoothDevice>> mProfileDeviceMap =
74             new HashMap<String, List<CachedBluetoothDevice>>();
75     private boolean mIsLeContactSharingEnabled = false;
76 
77     @VisibleForTesting
78     PreferenceCategory mProfilesContainer;
79 
BluetoothDetailsProfilesController(Context context, PreferenceFragmentCompat fragment, LocalBluetoothManager manager, CachedBluetoothDevice device, Lifecycle lifecycle)80     public BluetoothDetailsProfilesController(Context context, PreferenceFragmentCompat fragment,
81             LocalBluetoothManager manager, CachedBluetoothDevice device, Lifecycle lifecycle) {
82         super(context, fragment, device, lifecycle);
83         mManager = manager;
84         mProfileManager = mManager.getProfileManager();
85         mCachedDevice = device;
86         mAllOfCachedDevices = getAllOfCachedBluetoothDevices();
87         lifecycle.addObserver(this);
88     }
89 
90     @Override
init(PreferenceScreen screen)91     protected void init(PreferenceScreen screen) {
92         mProfilesContainer = (PreferenceCategory)screen.findPreference(getPreferenceKey());
93         mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
94         mIsLeContactSharingEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
95                 SettingsUIDeviceConfig.BT_LE_AUDIO_CONTACT_SHARING_ENABLED, true);
96         // Call refresh here even though it will get called later in onResume, to avoid the
97         // list of switches appearing to "pop" into the page.
98         refresh();
99     }
100 
101     /**
102      * Creates a switch preference for the particular profile.
103      *
104      * @param context The context to use when creating the SwitchPreference
105      * @param profile The profile for which the preference controls.
106      * @return A preference that allows the user to choose whether this profile
107      * will be connected to.
108      */
createProfilePreference(Context context, LocalBluetoothProfile profile)109     private SwitchPreference createProfilePreference(Context context,
110             LocalBluetoothProfile profile) {
111         SwitchPreference pref = new SwitchPreference(context);
112         pref.setKey(profile.toString());
113         pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
114         pref.setOnPreferenceClickListener(this);
115         pref.setOrder(profile.getOrdinal());
116         return pref;
117     }
118 
119     /**
120      * Refreshes the state for an existing SwitchPreference for a profile.
121      * If the LeAudio profile is enabled on the LeAudio devices, then the SwitchPreferences of
122      * A2dp profile and Headset profile are graied out.
123      */
refreshProfilePreference(SwitchPreference profilePref, LocalBluetoothProfile profile)124     private void refreshProfilePreference(SwitchPreference profilePref,
125             LocalBluetoothProfile profile) {
126         BluetoothDevice device = mCachedDevice.getDevice();
127         boolean isLeAudioEnabled = isLeAudioEnabled();
128         if (profile instanceof A2dpProfile
129                 || profile instanceof HeadsetProfile) {
130             if (isLeAudioEnabled) {
131                 // If the LeAudio profile is enabled on the LeAudio devices, then the
132                 // SwitchPreferences of A2dp profile and Headset profile are grayed out.
133                 Log.d(TAG, "LE is enabled, gray out " + profile.toString());
134                 profilePref.setEnabled(false);
135             } else {
136                 List<CachedBluetoothDevice> deviceList = mProfileDeviceMap.get(
137                         profile.toString());
138                 boolean isBusy = deviceList != null
139                         && deviceList.stream().anyMatch(item -> item.isBusy());
140                 profilePref.setEnabled(!isBusy);
141             }
142         } else if (profile instanceof LeAudioProfile) {
143             List<CachedBluetoothDevice> leAudioDeviceList = mProfileDeviceMap.get(
144                     profile.toString());
145             boolean isBusy = leAudioDeviceList != null
146                     && leAudioDeviceList.stream().anyMatch(item -> item.isBusy());
147             if (isLeAudioEnabled && !isBusy) {
148                 LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile();
149                 LocalBluetoothProfile headset = mProfileManager.getHeadsetProfile();
150                 // If the LeAudio profile is enabled on the LeAudio devices, then the
151                 // SwitchPreferences of A2dp profile and Headset profile are graied out.
152                 grayOutPreferenceWhenLeAudioIsEnabled(a2dp);
153                 grayOutPreferenceWhenLeAudioIsEnabled(headset);
154             }
155             profilePref.setEnabled(!isBusy);
156         } else if (profile instanceof PbapServerProfile
157                 && isLeAudioEnabled
158                 && !mIsLeContactSharingEnabled) {
159             profilePref.setEnabled(false);
160         } else {
161             profilePref.setEnabled(!mCachedDevice.isBusy());
162         }
163 
164         if (profile instanceof MapProfile) {
165             profilePref.setChecked(device.getMessageAccessPermission()
166                     == BluetoothDevice.ACCESS_ALLOWED);
167         } else if (profile instanceof PbapServerProfile) {
168             profilePref.setChecked(device.getPhonebookAccessPermission()
169                     == BluetoothDevice.ACCESS_ALLOWED);
170         } else if (profile instanceof PanProfile) {
171             profilePref.setChecked(profile.getConnectionStatus(device) ==
172                     BluetoothProfile.STATE_CONNECTED);
173         } else {
174             profilePref.setChecked(profile.isEnabled(device));
175         }
176 
177         if (profile instanceof A2dpProfile) {
178             A2dpProfile a2dp = (A2dpProfile) profile;
179             SwitchPreference highQualityPref = (SwitchPreference) mProfilesContainer.findPreference(
180                     HIGH_QUALITY_AUDIO_PREF_TAG);
181             if (highQualityPref != null) {
182                 if (a2dp.isEnabled(device) && a2dp.supportsHighQualityAudio(device)) {
183                     highQualityPref.setVisible(true);
184                     highQualityPref.setTitle(a2dp.getHighQualityAudioOptionLabel(device));
185                     highQualityPref.setChecked(a2dp.isHighQualityAudioEnabled(device));
186                     highQualityPref.setEnabled(!mCachedDevice.isBusy() && !isLeAudioEnabled);
187                 } else {
188                     highQualityPref.setVisible(false);
189                 }
190             }
191         }
192     }
193 
isLeAudioEnabled()194     private boolean isLeAudioEnabled(){
195         LocalBluetoothProfile leAudio = mProfileManager.getLeAudioProfile();
196         if (leAudio != null) {
197             List<CachedBluetoothDevice> leAudioDeviceList = mProfileDeviceMap.get(
198                     leAudio.toString());
199             if (leAudioDeviceList != null
200                     && leAudioDeviceList.stream()
201                     .anyMatch(item -> leAudio.isEnabled(item.getDevice()))) {
202                 return true;
203             }
204         }
205         return false;
206     }
207 
grayOutPreferenceWhenLeAudioIsEnabled(LocalBluetoothProfile profile)208     private void grayOutPreferenceWhenLeAudioIsEnabled(LocalBluetoothProfile profile) {
209         if (profile != null) {
210             SwitchPreference pref = mProfilesContainer.findPreference(profile.toString());
211             if (pref != null) {
212                 Log.d(TAG, "LE is enabled, gray out " + profile.toString());
213                 pref.setEnabled(false);
214             }
215         }
216     }
217 
218     /**
219      * Helper method to enable a profile for a device.
220      */
enableProfile(LocalBluetoothProfile profile)221     private void enableProfile(LocalBluetoothProfile profile) {
222         final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
223         if (profile instanceof PbapServerProfile) {
224             bluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
225             // We don't need to do the additional steps below for this profile.
226             return;
227         }
228         if (profile instanceof MapProfile) {
229             bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
230         }
231 
232         if (profile instanceof LeAudioProfile) {
233             enableLeAudioProfile(profile);
234             return;
235         }
236 
237         profile.setEnabled(bluetoothDevice, true);
238     }
239 
240     /**
241      * Helper method to disable a profile for a device
242      */
disableProfile(LocalBluetoothProfile profile)243     private void disableProfile(LocalBluetoothProfile profile) {
244         if (profile instanceof LeAudioProfile) {
245             disableLeAudioProfile(profile);
246             return;
247         }
248 
249         final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
250         profile.setEnabled(bluetoothDevice, false);
251 
252         if (profile instanceof MapProfile) {
253             bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
254         } else if (profile instanceof PbapServerProfile) {
255             bluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
256         }
257     }
258 
259     /**
260      * When the pref for a bluetooth profile is clicked on, we want to toggle the enabled/disabled
261      * state for that profile.
262      */
263     @Override
onPreferenceClick(Preference preference)264     public boolean onPreferenceClick(Preference preference) {
265         LocalBluetoothProfile profile = mProfileManager.getProfileByName(preference.getKey());
266         if (profile == null) {
267             // It might be the PbapServerProfile, which is not stored by name.
268             PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
269             if (TextUtils.equals(preference.getKey(), psp.toString())) {
270                 profile = psp;
271             } else {
272                 return false;
273             }
274         }
275         SwitchPreference profilePref = (SwitchPreference) preference;
276         if (profilePref.isChecked()) {
277             enableProfile(profile);
278         } else {
279             disableProfile(profile);
280         }
281         refreshProfilePreference(profilePref, profile);
282         return true;
283     }
284 
285     /**
286      * Helper to get the list of connectable and special profiles.
287      */
getProfiles()288     private List<LocalBluetoothProfile> getProfiles() {
289         List<LocalBluetoothProfile> result = new ArrayList<LocalBluetoothProfile>();
290         mProfileDeviceMap.clear();
291         if (mAllOfCachedDevices == null || mAllOfCachedDevices.isEmpty()) {
292             return result;
293         }
294         for (CachedBluetoothDevice cachedItem : mAllOfCachedDevices) {
295             List<LocalBluetoothProfile> tmpResult = cachedItem.getConnectableProfiles();
296             for (LocalBluetoothProfile profile : tmpResult) {
297                 if (mProfileDeviceMap.containsKey(profile.toString())) {
298                     mProfileDeviceMap.get(profile.toString()).add(cachedItem);
299                     Log.d(TAG, "getProfiles: " + profile.toString() + " add device "
300                             + cachedItem.getDevice().getAnonymizedAddress());
301                 } else {
302                     List<CachedBluetoothDevice> tmpCachedDeviceList =
303                             new ArrayList<CachedBluetoothDevice>();
304                     tmpCachedDeviceList.add(cachedItem);
305                     mProfileDeviceMap.put(profile.toString(), tmpCachedDeviceList);
306                     result.add(profile);
307                 }
308             }
309         }
310 
311         final BluetoothDevice device = mCachedDevice.getDevice();
312         final int pbapPermission = device.getPhonebookAccessPermission();
313         // Only provide PBAP cabability if the client device has requested PBAP.
314         if (pbapPermission != BluetoothDevice.ACCESS_UNKNOWN) {
315             final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
316             result.add(psp);
317         }
318 
319         final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
320         final int mapPermission = device.getMessageAccessPermission();
321         if (mapPermission != BluetoothDevice.ACCESS_UNKNOWN) {
322             result.add(mapProfile);
323         }
324         Log.d(TAG, "getProfiles:result:" + result);
325         return result;
326     }
327 
getAllOfCachedBluetoothDevices()328     private List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices() {
329         List<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>();
330         if (mCachedDevice == null) {
331             return cachedBluetoothDevices;
332         }
333         cachedBluetoothDevices.add(mCachedDevice);
334         if (mCachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
335             for (CachedBluetoothDevice member : mCachedDevice.getMemberDevice()) {
336                 cachedBluetoothDevices.add(member);
337             }
338         }
339         return cachedBluetoothDevices;
340     }
341 
342     /**
343      * When user disable the Le Audio profile, the system needs to do two things.
344      * 1) Disable the Le Audio profile, VCP and CSIP for each of the Le Audio devices.
345      * 2) Enable the A2dp profile and Headset profile for the associated device. The system
346      * can't enable the A2dp profile and Headset profile if the Le Audio profile is enabled.
347      *
348      * @param profile the LeAudio profile
349      */
disableLeAudioProfile(LocalBluetoothProfile profile)350     private void disableLeAudioProfile(LocalBluetoothProfile profile) {
351         if (profile == null || mProfileDeviceMap.get(profile.toString()) == null) {
352             Log.e(TAG, "There is no the LE profile or no device in mProfileDeviceMap. Do nothing.");
353             return;
354         }
355         LocalBluetoothProfile vcp = mProfileManager.getVolumeControlProfile();
356         LocalBluetoothProfile csip = mProfileManager.getCsipSetCoordinatorProfile();
357         LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile();
358         LocalBluetoothProfile headset = mProfileManager.getHeadsetProfile();
359 
360         for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) {
361             Log.d(TAG,
362                     "User disable LE device: " + leAudioDevice.getDevice().getAnonymizedAddress());
363             profile.setEnabled(leAudioDevice.getDevice(), false);
364             if (vcp != null) {
365                 vcp.setEnabled(leAudioDevice.getDevice(), false);
366             }
367             if (csip != null) {
368                 csip.setEnabled(leAudioDevice.getDevice(), false);
369             }
370         }
371 
372         enableProfileAfterUserDisablesLeAudio(a2dp);
373         enableProfileAfterUserDisablesLeAudio(headset);
374     }
375 
376     /**
377      * When user enable the Le Audio profile, the system needs to do two things.
378      * 1) Disable the A2dp profile and Headset profile for the associated device. The system
379      * can't enable the Le Audio if the A2dp profile and Headset profile are enabled.
380      * 2) Enable the Le Audio profile, VCP and CSIP for each of the Le Audio devices.
381      *
382      * @param profile the LeAudio profile
383      */
enableLeAudioProfile(LocalBluetoothProfile profile)384     private void enableLeAudioProfile(LocalBluetoothProfile profile) {
385         if (profile == null || mProfileDeviceMap.get(profile.toString()) == null) {
386             Log.e(TAG, "There is no the LE profile or no device in mProfileDeviceMap. Do nothing.");
387             return;
388         }
389         LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile();
390         LocalBluetoothProfile headset = mProfileManager.getHeadsetProfile();
391         LocalBluetoothProfile vcp = mProfileManager.getVolumeControlProfile();
392         LocalBluetoothProfile csip = mProfileManager.getCsipSetCoordinatorProfile();
393 
394         disableProfileBeforeUserEnablesLeAudio(a2dp);
395         disableProfileBeforeUserEnablesLeAudio(headset);
396 
397         for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) {
398             Log.d(TAG,
399                     "User enable LE device: " + leAudioDevice.getDevice().getAnonymizedAddress());
400             profile.setEnabled(leAudioDevice.getDevice(), true);
401             if (vcp != null) {
402                 vcp.setEnabled(leAudioDevice.getDevice(), true);
403             }
404             if (csip != null) {
405                 csip.setEnabled(leAudioDevice.getDevice(), true);
406             }
407         }
408     }
409 
disableProfileBeforeUserEnablesLeAudio(LocalBluetoothProfile profile)410     private void disableProfileBeforeUserEnablesLeAudio(LocalBluetoothProfile profile) {
411         if (profile != null && mProfileDeviceMap.get(profile.toString()) != null) {
412             Log.d(TAG, "Disable " + profile.toString() + " before user enables LE");
413             for (CachedBluetoothDevice profileDevice : mProfileDeviceMap.get(profile.toString())) {
414                 if (profile.isEnabled(profileDevice.getDevice())) {
415                     profile.setEnabled(profileDevice.getDevice(), false);
416                 } else {
417                     Log.d(TAG, "The " + profile.toString() + " profile is disabled. Do nothing.");
418                 }
419             }
420         }
421     }
422 
enableProfileAfterUserDisablesLeAudio(LocalBluetoothProfile profile)423     private void enableProfileAfterUserDisablesLeAudio(LocalBluetoothProfile profile) {
424         if (profile != null && mProfileDeviceMap.get(profile.toString()) != null) {
425             Log.d(TAG, "enable " + profile.toString() + "after user disables LE");
426             for (CachedBluetoothDevice profileDevice : mProfileDeviceMap.get(profile.toString())) {
427                 if (!profile.isEnabled(profileDevice.getDevice())) {
428                     profile.setEnabled(profileDevice.getDevice(), true);
429                 } else {
430                     Log.d(TAG, "The " + profile.toString() + " profile is enabled. Do nothing.");
431                 }
432             }
433         }
434     }
435 
436     /**
437      * This is a helper method to be called after adding a Preference for a profile. If that
438      * profile happened to be A2dp and the device supports high quality audio, it will add a
439      * separate preference for controlling whether to actually use high quality audio.
440      *
441      * @param profile the profile just added
442      */
maybeAddHighQualityAudioPref(LocalBluetoothProfile profile)443     private void maybeAddHighQualityAudioPref(LocalBluetoothProfile profile) {
444         if (!(profile instanceof A2dpProfile)) {
445             return;
446         }
447         BluetoothDevice device = mCachedDevice.getDevice();
448         A2dpProfile a2dp = (A2dpProfile) profile;
449         if (a2dp.isProfileReady() && a2dp.supportsHighQualityAudio(device)) {
450             SwitchPreference highQualityAudioPref = new SwitchPreference(
451                     mProfilesContainer.getContext());
452             highQualityAudioPref.setKey(HIGH_QUALITY_AUDIO_PREF_TAG);
453             highQualityAudioPref.setVisible(false);
454             highQualityAudioPref.setOnPreferenceClickListener(clickedPref -> {
455                 boolean enable = ((SwitchPreference) clickedPref).isChecked();
456                 a2dp.setHighQualityAudioEnabled(mCachedDevice.getDevice(), enable);
457                 return true;
458             });
459             mProfilesContainer.addPreference(highQualityAudioPref);
460         }
461     }
462 
463     @Override
onPause()464     public void onPause() {
465         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
466             item.unregisterCallback(this);
467         }
468         mProfileManager.removeServiceListener(this);
469     }
470 
471     @Override
onResume()472     public void onResume() {
473         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
474             item.registerCallback(this);
475         }
476         mProfileManager.addServiceListener(this);
477     }
478 
479     @Override
onDeviceAttributesChanged()480     public void onDeviceAttributesChanged() {
481         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
482             item.unregisterCallback(this);
483         }
484         mAllOfCachedDevices = getAllOfCachedBluetoothDevices();
485         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
486             item.registerCallback(this);
487         }
488 
489         super.onDeviceAttributesChanged();
490     }
491 
492     @Override
onServiceConnected()493     public void onServiceConnected() {
494         refresh();
495     }
496 
497     @Override
onServiceDisconnected()498     public void onServiceDisconnected() {
499         refresh();
500     }
501 
502     /**
503      * Refreshes the state of the switches for all profiles, possibly adding or removing switches as
504      * needed.
505      */
506     @Override
refresh()507     protected void refresh() {
508         for (LocalBluetoothProfile profile : getProfiles()) {
509             if (profile == null || !profile.isProfileReady()) {
510                 continue;
511             }
512             SwitchPreference pref = mProfilesContainer.findPreference(
513                     profile.toString());
514             if (pref == null) {
515                 pref = createProfilePreference(mProfilesContainer.getContext(), profile);
516                 mProfilesContainer.addPreference(pref);
517                 maybeAddHighQualityAudioPref(profile);
518             }
519             refreshProfilePreference(pref, profile);
520         }
521         for (LocalBluetoothProfile removedProfile : mCachedDevice.getRemovedProfiles()) {
522             final SwitchPreference pref = mProfilesContainer.findPreference(
523                     removedProfile.toString());
524             if (pref != null) {
525                 mProfilesContainer.removePreference(pref);
526             }
527         }
528 
529         Preference preference = mProfilesContainer.findPreference(KEY_BOTTOM_PREFERENCE);
530         if (preference == null) {
531             preference = new Preference(mContext);
532             preference.setLayoutResource(R.layout.preference_bluetooth_profile_category);
533             preference.setEnabled(false);
534             preference.setKey(KEY_BOTTOM_PREFERENCE);
535             preference.setOrder(ORDINAL);
536             preference.setSelectable(false);
537             mProfilesContainer.addPreference(preference);
538         }
539     }
540 
541     @Override
getPreferenceKey()542     public String getPreferenceKey() {
543         return KEY_PROFILES_GROUP;
544     }
545 }
546