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.app.Dialog; 21import android.app.DialogFragment; 22import android.bluetooth.BluetoothDevice; 23import android.bluetooth.BluetoothProfile; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.os.Bundle; 27import android.support.v7.preference.CheckBoxPreference; 28import android.support.v7.preference.EditTextPreference; 29import android.text.Html; 30import android.text.TextUtils; 31import android.util.Log; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.View.OnClickListener; 35import android.view.ViewGroup; 36import android.widget.CheckBox; 37import android.widget.EditText; 38import android.widget.TextView; 39 40import com.android.settings.R; 41import com.android.settingslib.bluetooth.CachedBluetoothDevice; 42import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 43import com.android.settingslib.bluetooth.LocalBluetoothManager; 44import com.android.settingslib.bluetooth.LocalBluetoothProfile; 45import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 46import com.android.settingslib.bluetooth.MapProfile; 47import com.android.settingslib.bluetooth.PanProfile; 48import com.android.settingslib.bluetooth.PbapServerProfile; 49 50import java.util.HashMap; 51 52public final class DeviceProfilesSettings extends DialogFragment implements 53 CachedBluetoothDevice.Callback, DialogInterface.OnClickListener, OnClickListener { 54 private static final String TAG = "DeviceProfilesSettings"; 55 56 public static final String ARG_DEVICE_ADDRESS = "device_address"; 57 58 private static final String KEY_PROFILE_CONTAINER = "profile_container"; 59 private static final String KEY_UNPAIR = "unpair"; 60 private static final String KEY_PBAP_SERVER = "PBAP Server"; 61 62 private CachedBluetoothDevice mCachedDevice; 63 private LocalBluetoothManager mManager; 64 private LocalBluetoothProfileManager mProfileManager; 65 66 private ViewGroup mProfileContainer; 67 private TextView mProfileLabel; 68 private EditTextPreference mDeviceNamePref; 69 70 private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs 71 = new HashMap<LocalBluetoothProfile, CheckBoxPreference>(); 72 73 private AlertDialog mDisconnectDialog; 74 private boolean mProfileGroupIsRemoved; 75 76 private View mRootView; 77 78 @Override 79 public void onCreate(Bundle savedInstanceState) { 80 super.onCreate(savedInstanceState); 81 82 mManager = Utils.getLocalBtManager(getActivity()); 83 CachedBluetoothDeviceManager deviceManager = mManager.getCachedDeviceManager(); 84 85 String address = getArguments().getString(ARG_DEVICE_ADDRESS); 86 BluetoothDevice remoteDevice = mManager.getBluetoothAdapter().getRemoteDevice(address); 87 88 mCachedDevice = deviceManager.findDevice(remoteDevice); 89 if (mCachedDevice == null) { 90 mCachedDevice = deviceManager.addDevice(mManager.getBluetoothAdapter(), 91 mManager.getProfileManager(), remoteDevice); 92 } 93 mProfileManager = mManager.getProfileManager(); 94 } 95 96 @Override 97 public Dialog onCreateDialog(Bundle savedInstanceState) { 98 mRootView = LayoutInflater.from(getContext()).inflate(R.layout.device_profiles_settings, 99 null); 100 mProfileContainer = (ViewGroup) mRootView.findViewById(R.id.profiles_section); 101 mProfileLabel = (TextView) mRootView.findViewById(R.id.profiles_label); 102 final EditText deviceName = (EditText) mRootView.findViewById(R.id.name); 103 deviceName.setText(mCachedDevice.getName(), TextView.BufferType.EDITABLE); 104 return new AlertDialog.Builder(getContext()) 105 .setView(mRootView) 106 .setNegativeButton(R.string.forget, this) 107 .setPositiveButton(R.string.okay, this) 108 .setTitle(R.string.bluetooth_preference_paired_devices) 109 .create(); 110 } 111 112 @Override 113 public void onClick(DialogInterface dialog, int which) { 114 switch (which) { 115 case DialogInterface.BUTTON_POSITIVE: 116 EditText deviceName = (EditText) mRootView.findViewById(R.id.name); 117 mCachedDevice.setName(deviceName.getText().toString()); 118 break; 119 case DialogInterface.BUTTON_NEGATIVE: 120 mCachedDevice.unpair(); 121 com.android.settings.bluetooth.Utils.updateSearchIndex(getContext(), 122 BluetoothSettings.class.getName(), mCachedDevice.getName(), 123 getString(R.string.bluetooth_settings), 124 R.drawable.ic_settings_bluetooth, false); 125 break; 126 } 127 } 128 129 @Override 130 public void onDestroy() { 131 super.onDestroy(); 132 if (mDisconnectDialog != null) { 133 mDisconnectDialog.dismiss(); 134 mDisconnectDialog = null; 135 } 136 if (mCachedDevice != null) { 137 mCachedDevice.unregisterCallback(this); 138 } 139 } 140 141 @Override 142 public void onSaveInstanceState(Bundle outState) { 143 super.onSaveInstanceState(outState); 144 } 145 146 @Override 147 public void onResume() { 148 super.onResume(); 149 150 mManager.setForegroundActivity(getActivity()); 151 if (mCachedDevice != null) { 152 mCachedDevice.registerCallback(this); 153 if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) { 154 dismiss(); 155 return; 156 } 157 addPreferencesForProfiles(); 158 refresh(); 159 } 160 } 161 162 @Override 163 public void onPause() { 164 super.onPause(); 165 166 if (mCachedDevice != null) { 167 mCachedDevice.unregisterCallback(this); 168 } 169 170 mManager.setForegroundActivity(null); 171 } 172 173 private void addPreferencesForProfiles() { 174 mProfileContainer.removeAllViews(); 175 for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { 176 CheckBox pref = createProfilePreference(profile); 177 mProfileContainer.addView(pref); 178 } 179 180 final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice(); 181 // Only provide PBAP cabability if the client device has requested PBAP. 182 if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { 183 final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile(); 184 CheckBox pbapPref = createProfilePreference(psp); 185 mProfileContainer.addView(pbapPref); 186 } 187 188 final MapProfile mapProfile = mManager.getProfileManager().getMapProfile(); 189 final int mapPermission = mCachedDevice.getMessagePermissionChoice(); 190 if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) { 191 CheckBox mapPreference = createProfilePreference(mapProfile); 192 mProfileContainer.addView(mapPreference); 193 } 194 195 showOrHideProfileGroup(); 196 } 197 198 private void showOrHideProfileGroup() { 199 int numProfiles = mProfileContainer.getChildCount(); 200 if (!mProfileGroupIsRemoved && numProfiles == 0) { 201 mProfileContainer.setVisibility(View.GONE); 202 mProfileLabel.setVisibility(View.GONE); 203 mProfileGroupIsRemoved = true; 204 } else if (mProfileGroupIsRemoved && numProfiles != 0) { 205 mProfileContainer.setVisibility(View.VISIBLE); 206 mProfileLabel.setVisibility(View.VISIBLE); 207 mProfileGroupIsRemoved = false; 208 } 209 } 210 211 /** 212 * Creates a checkbox preference for the particular profile. The key will be 213 * the profile's name. 214 * 215 * @param profile The profile for which the preference controls. 216 * @return A preference that allows the user to choose whether this profile 217 * will be connected to. 218 */ 219 private CheckBox createProfilePreference(LocalBluetoothProfile profile) { 220 CheckBox pref = new CheckBox(getActivity()); 221 pref.setTag(profile.toString()); 222 pref.setText(profile.getNameResource(mCachedDevice.getDevice())); 223 pref.setOnClickListener(this); 224 225 refreshProfilePreference(pref, profile); 226 227 return pref; 228 } 229 230 @Override 231 public void onClick(View v) { 232 if (v instanceof CheckBox) { 233 LocalBluetoothProfile prof = getProfileOf(v); 234 onProfileClicked(prof, (CheckBox) v); 235 } 236 } 237 238 private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) { 239 BluetoothDevice device = mCachedDevice.getDevice(); 240 241 if (KEY_PBAP_SERVER.equals(profilePref.getTag())) { 242 final int newPermission = mCachedDevice.getPhonebookPermissionChoice() 243 == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED 244 : CachedBluetoothDevice.ACCESS_ALLOWED; 245 mCachedDevice.setPhonebookPermissionChoice(newPermission); 246 profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED); 247 return; 248 } 249 250 if (!profilePref.isChecked()) { 251 // Recheck it, until the dialog is done. 252 profilePref.setChecked(true); 253 askDisconnect(mManager.getForegroundActivity(), profile); 254 } else { 255 if (profile instanceof MapProfile) { 256 mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED); 257 } 258 if (profile.isPreferred(device)) { 259 // profile is preferred but not connected: disable auto-connect 260 if (profile instanceof PanProfile) { 261 mCachedDevice.connectProfile(profile); 262 } else { 263 profile.setPreferred(device, false); 264 } 265 } else { 266 profile.setPreferred(device, true); 267 mCachedDevice.connectProfile(profile); 268 } 269 refreshProfilePreference(profilePref, profile); 270 } 271 } 272 273 private void askDisconnect(Context context, 274 final LocalBluetoothProfile profile) { 275 // local reference for callback 276 final CachedBluetoothDevice device = mCachedDevice; 277 String name = device.getName(); 278 if (TextUtils.isEmpty(name)) { 279 name = context.getString(R.string.bluetooth_device); 280 } 281 282 String profileName = context.getString(profile.getNameResource(device.getDevice())); 283 284 String title = context.getString(R.string.bluetooth_disable_profile_title); 285 String message = context.getString(R.string.bluetooth_disable_profile_message, 286 profileName, name); 287 288 DialogInterface.OnClickListener disconnectListener = 289 new DialogInterface.OnClickListener() { 290 public void onClick(DialogInterface dialog, int which) { 291 device.disconnect(profile); 292 profile.setPreferred(device.getDevice(), false); 293 if (profile instanceof MapProfile) { 294 device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED); 295 } 296 refreshProfilePreference(findProfile(profile.toString()), profile); 297 } 298 }; 299 300 mDisconnectDialog = Utils.showDisconnectDialog(context, 301 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message)); 302 } 303 304 @Override 305 public void onDeviceAttributesChanged() { 306 refresh(); 307 } 308 309 private void refresh() { 310 final EditText deviceNameField = (EditText) mRootView.findViewById(R.id.name); 311 if (deviceNameField != null) { 312 deviceNameField.setText(mCachedDevice.getName()); 313 } 314 315 refreshProfiles(); 316 } 317 318 private void refreshProfiles() { 319 for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { 320 CheckBox profilePref = findProfile(profile.toString()); 321 if (profilePref == null) { 322 profilePref = createProfilePreference(profile); 323 mProfileContainer.addView(profilePref); 324 } else { 325 refreshProfilePreference(profilePref, profile); 326 } 327 } 328 for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) { 329 CheckBox profilePref = findProfile(profile.toString()); 330 if (profilePref != null) { 331 Log.d(TAG, "Removing " + profile.toString() + " from profile list"); 332 mProfileContainer.removeView(profilePref); 333 } 334 } 335 336 showOrHideProfileGroup(); 337 } 338 339 private CheckBox findProfile(String profile) { 340 return (CheckBox) mProfileContainer.findViewWithTag(profile); 341 } 342 343 private void refreshProfilePreference(CheckBox profilePref, 344 LocalBluetoothProfile profile) { 345 BluetoothDevice device = mCachedDevice.getDevice(); 346 347 // Gray out checkbox while connecting and disconnecting. 348 profilePref.setEnabled(!mCachedDevice.isBusy()); 349 350 if (profile instanceof MapProfile) { 351 profilePref.setChecked(mCachedDevice.getMessagePermissionChoice() 352 == CachedBluetoothDevice.ACCESS_ALLOWED); 353 354 } else if (profile instanceof PbapServerProfile) { 355 profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice() 356 == CachedBluetoothDevice.ACCESS_ALLOWED); 357 358 } else if (profile instanceof PanProfile) { 359 profilePref.setChecked(profile.getConnectionStatus(device) == 360 BluetoothProfile.STATE_CONNECTED); 361 362 } else { 363 profilePref.setChecked(profile.isPreferred(device)); 364 } 365 } 366 367 private LocalBluetoothProfile getProfileOf(View v) { 368 if (!(v instanceof CheckBox)) { 369 return null; 370 } 371 String key = (String) v.getTag(); 372 if (TextUtils.isEmpty(key)) return null; 373 374 try { 375 return mProfileManager.getProfileByName(key); 376 } catch (IllegalArgumentException ignored) { 377 return null; 378 } 379 } 380} 381