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