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.ActionBar; 20import android.app.Activity; 21import android.bluetooth.BluetoothAdapter; 22import android.bluetooth.BluetoothDevice; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.os.Bundle; 28import android.preference.Preference; 29import android.preference.PreferenceActivity; 30import android.preference.PreferenceCategory; 31import android.preference.PreferenceGroup; 32import android.preference.PreferenceScreen; 33import android.util.Log; 34import android.view.Gravity; 35import android.view.LayoutInflater; 36import android.view.Menu; 37import android.view.MenuInflater; 38import android.view.MenuItem; 39import android.view.View; 40import android.view.ViewGroup; 41import android.widget.Switch; 42import android.widget.TextView; 43 44import com.android.settings.ProgressCategory; 45import com.android.settings.R; 46 47/** 48 * BluetoothSettings is the Settings screen for Bluetooth configuration and 49 * connection management. 50 */ 51public final class BluetoothSettings extends DeviceListPreferenceFragment { 52 private static final String TAG = "BluetoothSettings"; 53 54 private static final int MENU_ID_SCAN = Menu.FIRST; 55 private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1; 56 private static final int MENU_ID_VISIBILITY_TIMEOUT = Menu.FIRST + 2; 57 private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 3; 58 59 /* Private intent to show the list of received files */ 60 private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = 61 "android.btopp.intent.action.OPEN_RECEIVED_FILES"; 62 63 private BluetoothEnabler mBluetoothEnabler; 64 65 private BluetoothDiscoverableEnabler mDiscoverableEnabler; 66 67 private PreferenceGroup mPairedDevicesCategory; 68 69 private PreferenceGroup mAvailableDevicesCategory; 70 private boolean mAvailableDevicesCategoryIsPresent; 71 private boolean mActivityStarted; 72 73 private TextView mEmptyView; 74 75 private final IntentFilter mIntentFilter; 76 77 // accessed from inner class (not private to avoid thunks) 78 Preference mMyDevicePreference; 79 80 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 81 @Override 82 public void onReceive(Context context, Intent intent) { 83 String action = intent.getAction(); 84 if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) { 85 updateDeviceName(); 86 } 87 } 88 89 private void updateDeviceName() { 90 if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) { 91 mMyDevicePreference.setTitle(mLocalAdapter.getName()); 92 } 93 } 94 }; 95 96 public BluetoothSettings() { 97 mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); 98 } 99 100 @Override 101 public void onActivityCreated(Bundle savedInstanceState) { 102 super.onActivityCreated(savedInstanceState); 103 mActivityStarted = (savedInstanceState == null); // don't auto start scan after rotation 104 105 mEmptyView = (TextView) getView().findViewById(android.R.id.empty); 106 getListView().setEmptyView(mEmptyView); 107 } 108 109 @Override 110 void addPreferencesForActivity() { 111 addPreferencesFromResource(R.xml.bluetooth_settings); 112 113 Activity activity = getActivity(); 114 115 Switch actionBarSwitch = new Switch(activity); 116 117 if (activity instanceof PreferenceActivity) { 118 PreferenceActivity preferenceActivity = (PreferenceActivity) activity; 119 if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) { 120 final int padding = activity.getResources().getDimensionPixelSize( 121 R.dimen.action_bar_switch_padding); 122 actionBarSwitch.setPadding(0, 0, padding, 0); 123 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, 124 ActionBar.DISPLAY_SHOW_CUSTOM); 125 activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams( 126 ActionBar.LayoutParams.WRAP_CONTENT, 127 ActionBar.LayoutParams.WRAP_CONTENT, 128 Gravity.CENTER_VERTICAL | Gravity.RIGHT)); 129 } 130 } 131 132 mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch); 133 134 setHasOptionsMenu(true); 135 } 136 137 @Override 138 public void onResume() { 139 // resume BluetoothEnabler before calling super.onResume() so we don't get 140 // any onDeviceAdded() callbacks before setting up view in updateContent() 141 mBluetoothEnabler.resume(); 142 super.onResume(); 143 144 if (mDiscoverableEnabler != null) { 145 mDiscoverableEnabler.resume(); 146 } 147 getActivity().registerReceiver(mReceiver, mIntentFilter); 148 149 updateContent(mLocalAdapter.getBluetoothState(), mActivityStarted); 150 } 151 152 @Override 153 public void onPause() { 154 super.onPause(); 155 mBluetoothEnabler.pause(); 156 getActivity().unregisterReceiver(mReceiver); 157 if (mDiscoverableEnabler != null) { 158 mDiscoverableEnabler.pause(); 159 } 160 } 161 162 @Override 163 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 164 boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON; 165 boolean isDiscovering = mLocalAdapter.isDiscovering(); 166 int textId = isDiscovering ? R.string.bluetooth_searching_for_devices : 167 R.string.bluetooth_search_for_devices; 168 menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId) 169 .setEnabled(bluetoothIsEnabled && !isDiscovering) 170 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 171 menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device) 172 .setEnabled(bluetoothIsEnabled) 173 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 174 menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout) 175 .setEnabled(bluetoothIsEnabled) 176 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 177 menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files) 178 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 179 } 180 181 @Override 182 public boolean onOptionsItemSelected(MenuItem item) { 183 switch (item.getItemId()) { 184 case MENU_ID_SCAN: 185 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { 186 startScanning(); 187 } 188 return true; 189 190 case MENU_ID_RENAME_DEVICE: 191 new BluetoothNameDialogFragment().show( 192 getFragmentManager(), "rename device"); 193 return true; 194 195 case MENU_ID_VISIBILITY_TIMEOUT: 196 new BluetoothVisibilityTimeoutFragment().show( 197 getFragmentManager(), "visibility timeout"); 198 return true; 199 200 case MENU_ID_SHOW_RECEIVED: 201 Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); 202 getActivity().sendBroadcast(intent); 203 return true; 204 } 205 return super.onOptionsItemSelected(item); 206 } 207 208 private void startScanning() { 209 if (!mAvailableDevicesCategoryIsPresent) { 210 getPreferenceScreen().addPreference(mAvailableDevicesCategory); 211 } 212 mLocalAdapter.startScanning(true); 213 } 214 215 @Override 216 void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { 217 mLocalAdapter.stopScanning(); 218 super.onDevicePreferenceClick(btPreference); 219 } 220 221 private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, 222 BluetoothDeviceFilter.Filter filter) { 223 preferenceGroup.setTitle(titleId); 224 getPreferenceScreen().addPreference(preferenceGroup); 225 setFilter(filter); 226 setDeviceListGroup(preferenceGroup); 227 addCachedDevices(); 228 preferenceGroup.setEnabled(true); 229 } 230 231 private void updateContent(int bluetoothState, boolean scanState) { 232 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 233 int messageId = 0; 234 235 switch (bluetoothState) { 236 case BluetoothAdapter.STATE_ON: 237 preferenceScreen.removeAll(); 238 preferenceScreen.setOrderingAsAdded(true); 239 mDevicePreferenceMap.clear(); 240 241 // This device 242 if (mMyDevicePreference == null) { 243 mMyDevicePreference = new Preference(getActivity()); 244 } 245 mMyDevicePreference.setTitle(mLocalAdapter.getName()); 246 if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) { 247 mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones 248 } else { 249 mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc. 250 } 251 mMyDevicePreference.setPersistent(false); 252 mMyDevicePreference.setEnabled(true); 253 preferenceScreen.addPreference(mMyDevicePreference); 254 255 if (mDiscoverableEnabler == null) { 256 mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), 257 mLocalAdapter, mMyDevicePreference); 258 mDiscoverableEnabler.resume(); 259 LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler( 260 mDiscoverableEnabler); 261 } 262 263 // Paired devices category 264 if (mPairedDevicesCategory == null) { 265 mPairedDevicesCategory = new PreferenceCategory(getActivity()); 266 } else { 267 mPairedDevicesCategory.removeAll(); 268 } 269 addDeviceCategory(mPairedDevicesCategory, 270 R.string.bluetooth_preference_paired_devices, 271 BluetoothDeviceFilter.BONDED_DEVICE_FILTER); 272 int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount(); 273 274 mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices); 275 276 // Available devices category 277 if (mAvailableDevicesCategory == null) { 278 mAvailableDevicesCategory = new ProgressCategory(getActivity(), null); 279 } else { 280 mAvailableDevicesCategory.removeAll(); 281 } 282 addDeviceCategory(mAvailableDevicesCategory, 283 R.string.bluetooth_preference_found_devices, 284 BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); 285 int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount(); 286 mAvailableDevicesCategoryIsPresent = true; 287 288 if (numberOfAvailableDevices == 0) { 289 preferenceScreen.removePreference(mAvailableDevicesCategory); 290 mAvailableDevicesCategoryIsPresent = false; 291 } 292 293 if (numberOfPairedDevices == 0) { 294 preferenceScreen.removePreference(mPairedDevicesCategory); 295 if (scanState == true) { 296 mActivityStarted = false; 297 startScanning(); 298 } else { 299 if (!mAvailableDevicesCategoryIsPresent) { 300 getPreferenceScreen().addPreference(mAvailableDevicesCategory); 301 } 302 } 303 } 304 getActivity().invalidateOptionsMenu(); 305 return; // not break 306 307 case BluetoothAdapter.STATE_TURNING_OFF: 308 messageId = R.string.bluetooth_turning_off; 309 break; 310 311 case BluetoothAdapter.STATE_OFF: 312 messageId = R.string.bluetooth_empty_list_bluetooth_off; 313 break; 314 315 case BluetoothAdapter.STATE_TURNING_ON: 316 messageId = R.string.bluetooth_turning_on; 317 break; 318 } 319 320 setDeviceListGroup(preferenceScreen); 321 removeAllDevices(); 322 mEmptyView.setText(messageId); 323 getActivity().invalidateOptionsMenu(); 324 } 325 326 @Override 327 public void onBluetoothStateChanged(int bluetoothState) { 328 super.onBluetoothStateChanged(bluetoothState); 329 updateContent(bluetoothState, true); 330 } 331 332 @Override 333 public void onScanningStateChanged(boolean started) { 334 super.onScanningStateChanged(started); 335 // Update options' enabled state 336 getActivity().invalidateOptionsMenu(); 337 } 338 339 public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { 340 setDeviceListGroup(getPreferenceScreen()); 341 removeAllDevices(); 342 updateContent(mLocalAdapter.getBluetoothState(), false); 343 } 344 345 private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() { 346 public void onClick(View v) { 347 // User clicked on advanced options icon for a device in the list 348 if (v.getTag() instanceof CachedBluetoothDevice) { 349 CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag(); 350 351 Bundle args = new Bundle(1); 352 args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice()); 353 354 ((PreferenceActivity) getActivity()).startPreferencePanel( 355 DeviceProfilesSettings.class.getName(), args, 356 R.string.bluetooth_device_advanced_title, null, null, 0); 357 } else { 358 Log.w(TAG, "onClick() called for other View: " + v); // TODO remove 359 } 360 } 361 }; 362 363 /** 364 * Add a listener, which enables the advanced settings icon. 365 * @param preference the newly added preference 366 */ 367 @Override 368 void initDevicePreference(BluetoothDevicePreference preference) { 369 CachedBluetoothDevice cachedDevice = preference.getCachedDevice(); 370 if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { 371 // Only paired device have an associated advanced settings screen 372 preference.setOnSettingsClickListener(mDeviceProfilesListener); 373 } 374 } 375} 376