Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2011 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.app.AlertDialog;
     20 import android.app.Fragment;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.bluetooth.BluetoothProfile;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.os.Bundle;
     26 import android.preference.CheckBoxPreference;
     27 import android.preference.EditTextPreference;
     28 import android.preference.Preference;
     29 import android.preference.PreferenceGroup;
     30 import android.preference.PreferenceScreen;
     31 import android.text.Html;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 import android.view.View;
     35 import android.widget.EditText;
     36 import android.text.TextWatcher;
     37 import android.app.Dialog;
     38 import android.widget.Button;
     39 import android.text.Editable;
     40 
     41 import com.android.settings.R;
     42 import com.android.settings.SettingsPreferenceFragment;
     43 import com.android.settings.search.Index;
     44 import com.android.settings.search.SearchIndexableRaw;
     45 
     46 import java.util.HashMap;
     47 
     48 /**
     49  * This preference fragment presents the user with all of the profiles
     50  * for a particular device, and allows them to be individually connected
     51  * (or disconnected).
     52  */
     53 public final class DeviceProfilesSettings extends SettingsPreferenceFragment
     54         implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
     55     private static final String TAG = "DeviceProfilesSettings";
     56 
     57     private static final String KEY_PROFILE_CONTAINER = "profile_container";
     58     private static final String KEY_UNPAIR = "unpair";
     59     private static final String KEY_PBAP_SERVER = "PBAP Server";
     60 
     61     private CachedBluetoothDevice mCachedDevice;
     62     private LocalBluetoothManager mManager;
     63     private LocalBluetoothProfileManager mProfileManager;
     64 
     65     private PreferenceGroup mProfileContainer;
     66     private EditTextPreference mDeviceNamePref;
     67 
     68     private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs
     69             = new HashMap<LocalBluetoothProfile, CheckBoxPreference>();
     70 
     71     private AlertDialog mDisconnectDialog;
     72     private boolean mProfileGroupIsRemoved;
     73 
     74     @Override
     75     public void onCreate(Bundle savedInstanceState) {
     76         super.onCreate(savedInstanceState);
     77 
     78         addPreferencesFromResource(R.xml.bluetooth_device_advanced);
     79         getPreferenceScreen().setOrderingAsAdded(false);
     80         mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
     81         mProfileContainer.setLayoutResource(R.layout.bluetooth_preference_category);
     82 
     83         mManager = LocalBluetoothManager.getInstance(getActivity());
     84         CachedBluetoothDeviceManager deviceManager =
     85                 mManager.getCachedDeviceManager();
     86         mProfileManager = mManager.getProfileManager();
     87     }
     88 
     89     @Override
     90     public void onDestroy() {
     91         super.onDestroy();
     92         if (mDisconnectDialog != null) {
     93             mDisconnectDialog.dismiss();
     94             mDisconnectDialog = null;
     95         }
     96         if (mCachedDevice != null) {
     97             mCachedDevice.unregisterCallback(this);
     98         }
     99     }
    100 
    101     @Override
    102     public void onSaveInstanceState(Bundle outState) {
    103         super.onSaveInstanceState(outState);
    104     }
    105 
    106     @Override
    107     public void onResume() {
    108         super.onResume();
    109 
    110         mManager.setForegroundActivity(getActivity());
    111         if (mCachedDevice != null) {
    112             mCachedDevice.registerCallback(this);
    113             if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
    114                 finish();
    115                 return;
    116             }
    117             refresh();
    118         }
    119     }
    120 
    121     @Override
    122     public void onPause() {
    123         super.onPause();
    124 
    125         if (mCachedDevice != null) {
    126             mCachedDevice.unregisterCallback(this);
    127         }
    128 
    129         mManager.setForegroundActivity(null);
    130     }
    131 
    132     public void setDevice(CachedBluetoothDevice cachedDevice) {
    133         mCachedDevice = cachedDevice;
    134 
    135         if (isResumed()) {
    136             mCachedDevice.registerCallback(this);
    137             addPreferencesForProfiles();
    138             refresh();
    139         }
    140     }
    141 
    142     private void addPreferencesForProfiles() {
    143         mProfileContainer.removeAll();
    144         for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
    145             Preference pref = createProfilePreference(profile);
    146             mProfileContainer.addPreference(pref);
    147         }
    148 
    149         final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
    150         // Only provide PBAP cabability if the client device has requested PBAP.
    151         if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
    152             final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
    153             CheckBoxPreference pbapPref = createProfilePreference(psp);
    154             mProfileContainer.addPreference(pbapPref);
    155         }
    156 
    157         final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
    158         final int mapPermission = mCachedDevice.getMessagePermissionChoice();
    159         if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
    160             CheckBoxPreference mapPreference = createProfilePreference(mapProfile);
    161             mProfileContainer.addPreference(mapPreference);
    162         }
    163 
    164         showOrHideProfileGroup();
    165     }
    166 
    167     private void showOrHideProfileGroup() {
    168         int numProfiles = mProfileContainer.getPreferenceCount();
    169         if (!mProfileGroupIsRemoved && numProfiles == 0) {
    170             getPreferenceScreen().removePreference(mProfileContainer);
    171             mProfileGroupIsRemoved = true;
    172         } else if (mProfileGroupIsRemoved && numProfiles != 0) {
    173             getPreferenceScreen().addPreference(mProfileContainer);
    174             mProfileGroupIsRemoved = false;
    175         }
    176     }
    177 
    178     /**
    179      * Creates a checkbox preference for the particular profile. The key will be
    180      * the profile's name.
    181      *
    182      * @param profile The profile for which the preference controls.
    183      * @return A preference that allows the user to choose whether this profile
    184      *         will be connected to.
    185      */
    186     private CheckBoxPreference createProfilePreference(LocalBluetoothProfile profile) {
    187         CheckBoxPreference pref = new CheckBoxPreference(getActivity());
    188         pref.setLayoutResource(R.layout.preference_start_widget);
    189         pref.setKey(profile.toString());
    190         pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
    191         pref.setPersistent(false);
    192         pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal()));
    193         pref.setOnPreferenceChangeListener(this);
    194 
    195         int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
    196         if (iconResource != 0) {
    197             pref.setIcon(getResources().getDrawable(iconResource));
    198         }
    199 
    200         refreshProfilePreference(pref, profile);
    201 
    202         return pref;
    203     }
    204 
    205     public boolean onPreferenceChange(Preference preference, Object newValue) {
    206         if (preference == mDeviceNamePref) {
    207             mCachedDevice.setName((String) newValue);
    208         } else if (preference instanceof CheckBoxPreference) {
    209             LocalBluetoothProfile prof = getProfileOf(preference);
    210             onProfileClicked(prof, (CheckBoxPreference) preference);
    211             return false;   // checkbox will update from onDeviceAttributesChanged() callback
    212         } else {
    213             return false;
    214         }
    215 
    216         return true;
    217     }
    218 
    219     private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
    220         BluetoothDevice device = mCachedDevice.getDevice();
    221 
    222         if (profilePref.getKey().equals(KEY_PBAP_SERVER)) {
    223             final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
    224                 == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
    225                 : CachedBluetoothDevice.ACCESS_ALLOWED;
    226             mCachedDevice.setPhonebookPermissionChoice(newPermission);
    227             profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED);
    228             return;
    229         }
    230 
    231         int status = profile.getConnectionStatus(device);
    232         boolean isConnected =
    233                 status == BluetoothProfile.STATE_CONNECTED;
    234 
    235         if (profilePref.isChecked()) {
    236             askDisconnect(mManager.getForegroundActivity(), profile);
    237         } else {
    238             if (profile instanceof MapProfile) {
    239                 mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
    240                 refreshProfilePreference(profilePref, profile);
    241             }
    242             if (profile.isPreferred(device)) {
    243                 // profile is preferred but not connected: disable auto-connect
    244                 profile.setPreferred(device, false);
    245                 refreshProfilePreference(profilePref, profile);
    246             } else {
    247                 profile.setPreferred(device, true);
    248                 mCachedDevice.connectProfile(profile);
    249             }
    250         }
    251     }
    252 
    253     private void askDisconnect(Context context,
    254             final LocalBluetoothProfile profile) {
    255         // local reference for callback
    256         final CachedBluetoothDevice device = mCachedDevice;
    257         String name = device.getName();
    258         if (TextUtils.isEmpty(name)) {
    259             name = context.getString(R.string.bluetooth_device);
    260         }
    261 
    262         String profileName = context.getString(profile.getNameResource(device.getDevice()));
    263 
    264         String title = context.getString(R.string.bluetooth_disable_profile_title);
    265         String message = context.getString(R.string.bluetooth_disable_profile_message,
    266                 profileName, name);
    267 
    268         DialogInterface.OnClickListener disconnectListener =
    269                 new DialogInterface.OnClickListener() {
    270             public void onClick(DialogInterface dialog, int which) {
    271                 device.disconnect(profile);
    272                 profile.setPreferred(device.getDevice(), false);
    273                 if (profile instanceof MapProfile) {
    274                     device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
    275                     refreshProfilePreference(
    276                             (CheckBoxPreference)findPreference(profile.toString()), profile);
    277                 }
    278             }
    279         };
    280 
    281         mDisconnectDialog = Utils.showDisconnectDialog(context,
    282                 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
    283     }
    284 
    285     @Override
    286     public void onDeviceAttributesChanged() {
    287         refresh();
    288     }
    289 
    290     private void refresh() {
    291         final EditText deviceNameField = (EditText) getView().findViewById(R.id.name);
    292         if (deviceNameField != null) {
    293             deviceNameField.setText(mCachedDevice.getName());
    294         }
    295 
    296         refreshProfiles();
    297     }
    298 
    299     private void refreshProfiles() {
    300         for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
    301             CheckBoxPreference profilePref = (CheckBoxPreference)findPreference(profile.toString());
    302             if (profilePref == null) {
    303                 profilePref = createProfilePreference(profile);
    304                 mProfileContainer.addPreference(profilePref);
    305             } else {
    306                 refreshProfilePreference(profilePref, profile);
    307             }
    308         }
    309         for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
    310             Preference profilePref = findPreference(profile.toString());
    311             if (profilePref != null) {
    312                 Log.d(TAG, "Removing " + profile.toString() + " from profile list");
    313                 mProfileContainer.removePreference(profilePref);
    314             }
    315         }
    316 
    317         showOrHideProfileGroup();
    318     }
    319 
    320     private void refreshProfilePreference(CheckBoxPreference profilePref,
    321             LocalBluetoothProfile profile) {
    322         BluetoothDevice device = mCachedDevice.getDevice();
    323 
    324         // Gray out checkbox while connecting and disconnecting.
    325         profilePref.setEnabled(!mCachedDevice.isBusy());
    326 
    327         if (profile instanceof MapProfile) {
    328             profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
    329                     == CachedBluetoothDevice.ACCESS_ALLOWED);
    330         } else if (profile instanceof PbapServerProfile) {
    331             // Handle PBAP specially.
    332             profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
    333                     == CachedBluetoothDevice.ACCESS_ALLOWED);
    334         } else {
    335             profilePref.setChecked(profile.isPreferred(device));
    336         }
    337     }
    338 
    339     private LocalBluetoothProfile getProfileOf(Preference pref) {
    340         if (!(pref instanceof CheckBoxPreference)) {
    341             return null;
    342         }
    343         String key = pref.getKey();
    344         if (TextUtils.isEmpty(key)) return null;
    345 
    346         try {
    347             return mProfileManager.getProfileByName(pref.getKey());
    348         } catch (IllegalArgumentException ignored) {
    349             return null;
    350         }
    351     }
    352 
    353     private int getProfilePreferenceIndex(int profIndex) {
    354         return mProfileContainer.getOrder() + profIndex * 10;
    355     }
    356 }
    357