Home | History | Annotate | Download | only in bluetooth
      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.BluetoothDevice;
     20 import android.bluetooth.BluetoothProfile;
     21 import android.content.Context;
     22 import android.support.annotation.VisibleForTesting;
     23 import android.support.v14.preference.PreferenceFragment;
     24 import android.support.v14.preference.SwitchPreference;
     25 import android.support.v7.preference.Preference;
     26 import android.support.v7.preference.PreferenceCategory;
     27 import android.support.v7.preference.PreferenceScreen;
     28 import android.text.TextUtils;
     29 
     30 import com.android.settingslib.bluetooth.A2dpProfile;
     31 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
     32 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     33 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
     34 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
     35 import com.android.settingslib.bluetooth.MapProfile;
     36 import com.android.settingslib.bluetooth.PanProfile;
     37 import com.android.settingslib.bluetooth.PbapServerProfile;
     38 import com.android.settingslib.core.lifecycle.Lifecycle;
     39 
     40 import java.util.List;
     41 
     42 /**
     43  * This class adds switches for toggling the individual profiles that a Bluetooth device
     44  * supports, such as "Phone audio", "Media audio", "Contact sharing", etc.
     45  */
     46 public class BluetoothDetailsProfilesController extends BluetoothDetailsController
     47         implements Preference.OnPreferenceClickListener {
     48     private static final String KEY_PROFILES_GROUP = "bluetooth_profiles";
     49 
     50     @VisibleForTesting
     51     static final String HIGH_QUALITY_AUDIO_PREF_TAG = "A2dpProfileHighQualityAudio";
     52 
     53     private LocalBluetoothManager mManager;
     54     private LocalBluetoothProfileManager mProfileManager;
     55     private CachedBluetoothDevice mCachedDevice;
     56     private PreferenceCategory mProfilesContainer;
     57 
     58     public BluetoothDetailsProfilesController(Context context, PreferenceFragment fragment,
     59             LocalBluetoothManager manager, CachedBluetoothDevice device, Lifecycle lifecycle) {
     60         super(context, fragment, device, lifecycle);
     61         mManager = manager;
     62         mProfileManager = mManager.getProfileManager();
     63         mCachedDevice = device;
     64         lifecycle.addObserver(this);
     65     }
     66 
     67     @Override
     68     protected void init(PreferenceScreen screen) {
     69         mProfilesContainer = (PreferenceCategory)screen.findPreference(getPreferenceKey());
     70         // Call refresh here even though it will get called later in onResume, to avoid the
     71         // list of switches appearing to "pop" into the page.
     72         refresh();
     73     }
     74 
     75     /**
     76      * Creates a switch preference for the particular profile.
     77      *
     78      * @param context The context to use when creating the SwitchPreference
     79      * @param profile The profile for which the preference controls.
     80      * @return A preference that allows the user to choose whether this profile
     81      * will be connected to.
     82      */
     83     private SwitchPreference createProfilePreference(Context context,
     84             LocalBluetoothProfile profile) {
     85         SwitchPreference pref = new SwitchPreference(context);
     86         pref.setKey(profile.toString());
     87         pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
     88         pref.setOnPreferenceClickListener(this);
     89         return pref;
     90     }
     91 
     92     /**
     93      * Refreshes the state for an existing SwitchPreference for a profile.
     94      */
     95     private void refreshProfilePreference(SwitchPreference profilePref,
     96             LocalBluetoothProfile profile) {
     97         BluetoothDevice device = mCachedDevice.getDevice();
     98         profilePref.setEnabled(!mCachedDevice.isBusy());
     99         if (profile instanceof MapProfile) {
    100             profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
    101                     == CachedBluetoothDevice.ACCESS_ALLOWED);
    102         } else if (profile instanceof PbapServerProfile) {
    103             profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
    104                     == CachedBluetoothDevice.ACCESS_ALLOWED);
    105         } else if (profile instanceof PanProfile) {
    106             profilePref.setChecked(profile.getConnectionStatus(device) ==
    107                     BluetoothProfile.STATE_CONNECTED);
    108         } else {
    109             profilePref.setChecked(profile.isPreferred(device));
    110         }
    111 
    112         if (profile instanceof A2dpProfile) {
    113             A2dpProfile a2dp = (A2dpProfile) profile;
    114             SwitchPreference highQualityPref = (SwitchPreference) mProfilesContainer.findPreference(
    115                     HIGH_QUALITY_AUDIO_PREF_TAG);
    116             if (highQualityPref != null) {
    117                 if (a2dp.isPreferred(device) && a2dp.supportsHighQualityAudio(device)) {
    118                     highQualityPref.setVisible(true);
    119                     highQualityPref.setTitle(a2dp.getHighQualityAudioOptionLabel(device));
    120                     highQualityPref.setChecked(a2dp.isHighQualityAudioEnabled(device));
    121                     highQualityPref.setEnabled(!mCachedDevice.isBusy());
    122                 } else {
    123                     highQualityPref.setVisible(false);
    124                 }
    125             }
    126         }
    127     }
    128 
    129     /**
    130      * Helper method to enable a profile for a device.
    131      */
    132     private void enableProfile(LocalBluetoothProfile profile, BluetoothDevice device,
    133             SwitchPreference profilePref) {
    134         if (profile instanceof PbapServerProfile) {
    135             mCachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
    136             // We don't need to do the additional steps below for this profile.
    137             return;
    138         }
    139         if (profile instanceof MapProfile) {
    140             mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
    141         }
    142         profile.setPreferred(device, true);
    143         mCachedDevice.connectProfile(profile);
    144     }
    145 
    146     /**
    147      * Helper method to disable a profile for a device
    148      */
    149     private void disableProfile(LocalBluetoothProfile profile, BluetoothDevice device,
    150             SwitchPreference profilePref) {
    151         if (profile instanceof PbapServerProfile) {
    152             mCachedDevice.setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
    153             // We don't need to do the additional steps below for this profile.
    154             return;
    155         }
    156         mCachedDevice.disconnect(profile);
    157         profile.setPreferred(device, false);
    158         if (profile instanceof MapProfile) {
    159             mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
    160         }
    161     }
    162 
    163     /**
    164      * When the pref for a bluetooth profile is clicked on, we want to toggle the enabled/disabled
    165      * state for that profile.
    166      */
    167     @Override
    168     public boolean onPreferenceClick(Preference preference) {
    169         LocalBluetoothProfile profile = mProfileManager.getProfileByName(preference.getKey());
    170         if (profile == null) {
    171             // It might be the PbapServerProfile, which is not stored by name.
    172             PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
    173             if (TextUtils.equals(preference.getKey(), psp.toString())) {
    174                 profile = psp;
    175             } else {
    176                 return false;
    177             }
    178         }
    179         SwitchPreference profilePref = (SwitchPreference) preference;
    180         BluetoothDevice device = mCachedDevice.getDevice();
    181         if (profilePref.isChecked()) {
    182             enableProfile(profile, device, profilePref);
    183         } else {
    184             disableProfile(profile, device, profilePref);
    185         }
    186         refreshProfilePreference(profilePref, profile);
    187         return true;
    188     }
    189 
    190 
    191     /**
    192      * Helper to get the list of connectable and special profiles.
    193      */
    194     private List<LocalBluetoothProfile> getProfiles() {
    195         List<LocalBluetoothProfile> result = mCachedDevice.getConnectableProfiles();
    196 
    197         final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
    198         // Only provide PBAP cabability if the client device has requested PBAP.
    199         if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
    200             final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
    201             result.add(psp);
    202         }
    203 
    204         final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
    205         final int mapPermission = mCachedDevice.getMessagePermissionChoice();
    206         if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
    207             result.add(mapProfile);
    208         }
    209 
    210         return result;
    211     }
    212 
    213     /**
    214      * This is a helper method to be called after adding a Preference for a profile. If that
    215      * profile happened to be A2dp and the device supports high quality audio, it will add a
    216      * separate preference for controlling whether to actually use high quality audio.
    217      *
    218      * @param profile the profile just added
    219      */
    220     private void maybeAddHighQualityAudioPref(LocalBluetoothProfile profile) {
    221         if (!(profile instanceof A2dpProfile)) {
    222             return;
    223         }
    224         BluetoothDevice device = mCachedDevice.getDevice();
    225         A2dpProfile a2dp = (A2dpProfile) profile;
    226         if (a2dp.supportsHighQualityAudio(device)) {
    227             SwitchPreference highQualityAudioPref = new SwitchPreference(
    228                     mProfilesContainer.getContext());
    229             highQualityAudioPref.setKey(HIGH_QUALITY_AUDIO_PREF_TAG);
    230             highQualityAudioPref.setVisible(false);
    231             highQualityAudioPref.setOnPreferenceClickListener(clickedPref -> {
    232                 boolean enable = ((SwitchPreference) clickedPref).isChecked();
    233                 a2dp.setHighQualityAudioEnabled(mCachedDevice.getDevice(), enable);
    234                 return true;
    235             });
    236             mProfilesContainer.addPreference(highQualityAudioPref);
    237         }
    238     }
    239 
    240     /**
    241      * Refreshes the state of the switches for all profiles, possibly adding or removing switches as
    242      * needed.
    243      */
    244     @Override
    245     protected void refresh() {
    246         for (LocalBluetoothProfile profile : getProfiles()) {
    247             SwitchPreference pref = (SwitchPreference) mProfilesContainer.findPreference(
    248                     profile.toString());
    249             if (pref == null) {
    250                 pref = createProfilePreference(mProfilesContainer.getContext(), profile);
    251                 mProfilesContainer.addPreference(pref);
    252                 maybeAddHighQualityAudioPref(profile);
    253             }
    254             refreshProfilePreference(pref, profile);
    255         }
    256         for (LocalBluetoothProfile removedProfile : mCachedDevice.getRemovedProfiles()) {
    257             SwitchPreference pref = (SwitchPreference) mProfilesContainer.findPreference(
    258                     removedProfile.toString());
    259             if (pref != null) {
    260                 mProfilesContainer.removePreference(pref);
    261             }
    262         }
    263     }
    264 
    265     @Override
    266     public String getPreferenceKey() {
    267         return KEY_PROFILES_GROUP;
    268     }
    269 }
    270