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);
    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) {
    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             profile.setPreferred(device, true);
    264             mCachedDevice.connectProfile(profile);
    265         }
    266     }
    267 
    268     private void askDisconnect(Context context,
    269             final LocalBluetoothProfile profile) {
    270         // local reference for callback
    271         final CachedBluetoothDevice device = mCachedDevice;
    272         String name = device.getName();
    273         if (TextUtils.isEmpty(name)) {
    274             name = context.getString(R.string.bluetooth_device);
    275         }
    276 
    277         String profileName = context.getString(profile.getNameResource(device.getDevice()));
    278 
    279         String title = context.getString(R.string.bluetooth_disable_profile_title);
    280         String message = context.getString(R.string.bluetooth_disable_profile_message,
    281                 profileName, name);
    282 
    283         DialogInterface.OnClickListener disconnectListener =
    284                 new DialogInterface.OnClickListener() {
    285             public void onClick(DialogInterface dialog, int which) {
    286                 device.disconnect(profile);
    287                 profile.setPreferred(device.getDevice(), false);
    288             }
    289         };
    290 
    291         mDisconnectDialog = Utils.showDisconnectDialog(context,
    292                 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
    293     }
    294 
    295     public void onDeviceAttributesChanged() {
    296         refresh();
    297     }
    298 
    299     private void refresh() {
    300         String deviceName = mCachedDevice.getName();
    301         mDeviceNamePref.setSummary(deviceName);
    302         mDeviceNamePref.setText(deviceName);
    303 
    304         refreshProfiles();
    305     }
    306 
    307     private void refreshProfiles() {
    308         for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
    309             CheckBoxPreference profilePref = (CheckBoxPreference)findPreference(profile.toString());
    310             if (profilePref == null) {
    311                 profilePref = createProfilePreference(profile);
    312                 mProfileContainer.addPreference(profilePref);
    313             } else {
    314                 refreshProfilePreference(profilePref, profile);
    315             }
    316         }
    317         for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
    318             Preference profilePref = findPreference(profile.toString());
    319             if (profilePref != null) {
    320                 Log.d(TAG, "Removing " + profile.toString() + " from profile list");
    321                 mProfileContainer.removePreference(profilePref);
    322             }
    323         }
    324         showOrHideProfileGroup();
    325     }
    326 
    327     private void refreshProfilePreference(CheckBoxPreference profilePref,
    328             LocalBluetoothProfile profile) {
    329         BluetoothDevice device = mCachedDevice.getDevice();
    330 
    331         /*
    332          * Gray out checkbox while connecting and disconnecting
    333          */
    334         profilePref.setEnabled(!mCachedDevice.isBusy());
    335         profilePref.setChecked(profile.isPreferred(device));
    336         profilePref.setSummary(profile.getSummaryResourceForDevice(device));
    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     private void unpairDevice() {
    358         mCachedDevice.unpair();
    359     }
    360 
    361     private boolean getAutoConnect(LocalBluetoothProfile prof) {
    362         return prof.isPreferred(mCachedDevice.getDevice());
    363     }
    364 }
    365