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 17package com.android.settings.bluetooth; 18 19import android.app.AlertDialog; 20import android.bluetooth.BluetoothDevice; 21import android.bluetooth.BluetoothProfile; 22import android.content.Context; 23import android.content.DialogInterface; 24import android.os.Bundle; 25import android.preference.CheckBoxPreference; 26import android.preference.EditTextPreference; 27import android.preference.Preference; 28import android.preference.PreferenceGroup; 29import android.preference.PreferenceScreen; 30import android.text.Html; 31import android.text.TextUtils; 32import android.util.Log; 33import android.view.View; 34import android.widget.EditText; 35import android.text.TextWatcher; 36import android.app.Dialog; 37import android.widget.Button; 38import android.text.Editable; 39import com.android.settings.R; 40import com.android.settings.SettingsPreferenceFragment; 41 42import 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 */ 49public 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