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.ActionBar; 20 import android.app.Activity; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.Bundle; 28 import android.preference.Preference; 29 import android.preference.PreferenceActivity; 30 import android.preference.PreferenceCategory; 31 import android.preference.PreferenceGroup; 32 import android.preference.PreferenceScreen; 33 import android.util.Log; 34 import android.view.Gravity; 35 import android.view.LayoutInflater; 36 import android.view.Menu; 37 import android.view.MenuInflater; 38 import android.view.MenuItem; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.widget.Switch; 42 import android.widget.TextView; 43 44 import com.android.settings.ProgressCategory; 45 import com.android.settings.R; 46 47 /** 48 * BluetoothSettings is the Settings screen for Bluetooth configuration and 49 * connection management. 50 */ 51 public 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 if (mBluetoothEnabler != null) { 142 mBluetoothEnabler.resume(); 143 } 144 super.onResume(); 145 146 if (mDiscoverableEnabler != null) { 147 mDiscoverableEnabler.resume(); 148 } 149 getActivity().registerReceiver(mReceiver, mIntentFilter); 150 if (mLocalAdapter != null) { 151 updateContent(mLocalAdapter.getBluetoothState(), mActivityStarted); 152 } 153 } 154 155 @Override 156 public void onPause() { 157 super.onPause(); 158 if (mBluetoothEnabler != null) { 159 mBluetoothEnabler.pause(); 160 } 161 getActivity().unregisterReceiver(mReceiver); 162 if (mDiscoverableEnabler != null) { 163 mDiscoverableEnabler.pause(); 164 } 165 } 166 167 @Override 168 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 169 if (mLocalAdapter == null) return; 170 boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON; 171 boolean isDiscovering = mLocalAdapter.isDiscovering(); 172 int textId = isDiscovering ? R.string.bluetooth_searching_for_devices : 173 R.string.bluetooth_search_for_devices; 174 menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId) 175 .setEnabled(bluetoothIsEnabled && !isDiscovering) 176 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 177 menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device) 178 .setEnabled(bluetoothIsEnabled) 179 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 180 menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout) 181 .setEnabled(bluetoothIsEnabled) 182 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 183 menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files) 184 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 185 super.onCreateOptionsMenu(menu, inflater); 186 } 187 188 @Override 189 public boolean onOptionsItemSelected(MenuItem item) { 190 switch (item.getItemId()) { 191 case MENU_ID_SCAN: 192 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { 193 startScanning(); 194 } 195 return true; 196 197 case MENU_ID_RENAME_DEVICE: 198 new BluetoothNameDialogFragment().show( 199 getFragmentManager(), "rename device"); 200 return true; 201 202 case MENU_ID_VISIBILITY_TIMEOUT: 203 new BluetoothVisibilityTimeoutFragment().show( 204 getFragmentManager(), "visibility timeout"); 205 return true; 206 207 case MENU_ID_SHOW_RECEIVED: 208 Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); 209 getActivity().sendBroadcast(intent); 210 return true; 211 } 212 return super.onOptionsItemSelected(item); 213 } 214 215 private void startScanning() { 216 if (!mAvailableDevicesCategoryIsPresent) { 217 getPreferenceScreen().addPreference(mAvailableDevicesCategory); 218 } 219 mLocalAdapter.startScanning(true); 220 } 221 222 @Override 223 void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { 224 mLocalAdapter.stopScanning(); 225 super.onDevicePreferenceClick(btPreference); 226 } 227 228 private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, 229 BluetoothDeviceFilter.Filter filter) { 230 preferenceGroup.setTitle(titleId); 231 getPreferenceScreen().addPreference(preferenceGroup); 232 setFilter(filter); 233 setDeviceListGroup(preferenceGroup); 234 addCachedDevices(); 235 preferenceGroup.setEnabled(true); 236 } 237 238 private void updateContent(int bluetoothState, boolean scanState) { 239 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 240 int messageId = 0; 241 242 switch (bluetoothState) { 243 case BluetoothAdapter.STATE_ON: 244 preferenceScreen.removeAll(); 245 preferenceScreen.setOrderingAsAdded(true); 246 mDevicePreferenceMap.clear(); 247 248 // This device 249 if (mMyDevicePreference == null) { 250 mMyDevicePreference = new Preference(getActivity()); 251 } 252 mMyDevicePreference.setTitle(mLocalAdapter.getName()); 253 if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) { 254 mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones 255 } else { 256 mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc. 257 } 258 mMyDevicePreference.setPersistent(false); 259 mMyDevicePreference.setEnabled(true); 260 preferenceScreen.addPreference(mMyDevicePreference); 261 262 if (mDiscoverableEnabler == null) { 263 mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), 264 mLocalAdapter, mMyDevicePreference); 265 mDiscoverableEnabler.resume(); 266 LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler( 267 mDiscoverableEnabler); 268 } 269 270 // Paired devices category 271 if (mPairedDevicesCategory == null) { 272 mPairedDevicesCategory = new PreferenceCategory(getActivity()); 273 } else { 274 mPairedDevicesCategory.removeAll(); 275 } 276 addDeviceCategory(mPairedDevicesCategory, 277 R.string.bluetooth_preference_paired_devices, 278 BluetoothDeviceFilter.BONDED_DEVICE_FILTER); 279 int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount(); 280 281 mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices); 282 283 // Available devices category 284 if (mAvailableDevicesCategory == null) { 285 mAvailableDevicesCategory = new ProgressCategory(getActivity(), null); 286 } else { 287 mAvailableDevicesCategory.removeAll(); 288 } 289 addDeviceCategory(mAvailableDevicesCategory, 290 R.string.bluetooth_preference_found_devices, 291 BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); 292 int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount(); 293 mAvailableDevicesCategoryIsPresent = true; 294 295 if (numberOfAvailableDevices == 0) { 296 preferenceScreen.removePreference(mAvailableDevicesCategory); 297 mAvailableDevicesCategoryIsPresent = false; 298 } 299 300 if (numberOfPairedDevices == 0) { 301 preferenceScreen.removePreference(mPairedDevicesCategory); 302 if (scanState == true) { 303 mActivityStarted = false; 304 startScanning(); 305 } else { 306 if (!mAvailableDevicesCategoryIsPresent) { 307 getPreferenceScreen().addPreference(mAvailableDevicesCategory); 308 } 309 } 310 } 311 getActivity().invalidateOptionsMenu(); 312 return; // not break 313 314 case BluetoothAdapter.STATE_TURNING_OFF: 315 messageId = R.string.bluetooth_turning_off; 316 break; 317 318 case BluetoothAdapter.STATE_OFF: 319 messageId = R.string.bluetooth_empty_list_bluetooth_off; 320 break; 321 322 case BluetoothAdapter.STATE_TURNING_ON: 323 messageId = R.string.bluetooth_turning_on; 324 break; 325 } 326 327 setDeviceListGroup(preferenceScreen); 328 removeAllDevices(); 329 mEmptyView.setText(messageId); 330 getActivity().invalidateOptionsMenu(); 331 } 332 333 @Override 334 public void onBluetoothStateChanged(int bluetoothState) { 335 super.onBluetoothStateChanged(bluetoothState); 336 updateContent(bluetoothState, true); 337 } 338 339 @Override 340 public void onScanningStateChanged(boolean started) { 341 super.onScanningStateChanged(started); 342 // Update options' enabled state 343 getActivity().invalidateOptionsMenu(); 344 } 345 346 public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { 347 setDeviceListGroup(getPreferenceScreen()); 348 removeAllDevices(); 349 updateContent(mLocalAdapter.getBluetoothState(), false); 350 } 351 352 private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() { 353 public void onClick(View v) { 354 // User clicked on advanced options icon for a device in the list 355 if (v.getTag() instanceof CachedBluetoothDevice) { 356 CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag(); 357 358 Bundle args = new Bundle(1); 359 args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice()); 360 361 ((PreferenceActivity) getActivity()).startPreferencePanel( 362 DeviceProfilesSettings.class.getName(), args, 363 R.string.bluetooth_device_advanced_title, null, null, 0); 364 } else { 365 Log.w(TAG, "onClick() called for other View: " + v); // TODO remove 366 } 367 } 368 }; 369 370 /** 371 * Add a listener, which enables the advanced settings icon. 372 * @param preference the newly added preference 373 */ 374 @Override 375 void initDevicePreference(BluetoothDevicePreference preference) { 376 CachedBluetoothDevice cachedDevice = preference.getCachedDevice(); 377 if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { 378 // Only paired device have an associated advanced settings screen 379 preference.setOnSettingsClickListener(mDeviceProfilesListener); 380 } 381 } 382 383 @Override 384 protected int getHelpResource() { 385 return R.string.help_url_bluetooth; 386 } 387 } 388