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