/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.bluetooth; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.app.ActionBar; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Switch; import android.widget.TextView; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; import com.android.settings.widget.SwitchBar; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * BluetoothSettings is the Settings screen for Bluetooth configuration and * connection management. */ public final class BluetoothSettings extends DeviceListPreferenceFragment implements Indexable { private static final String TAG = "BluetoothSettings"; private static final int MENU_ID_SCAN = Menu.FIRST; private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1; private static final int MENU_ID_VISIBILITY_TIMEOUT = Menu.FIRST + 2; private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 3; /* Private intent to show the list of received files */ private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = "android.btopp.intent.action.OPEN_RECEIVED_FILES"; private BluetoothEnabler mBluetoothEnabler; private BluetoothDiscoverableEnabler mDiscoverableEnabler; private PreferenceGroup mPairedDevicesCategory; private PreferenceGroup mAvailableDevicesCategory; private boolean mAvailableDevicesCategoryIsPresent; private boolean mActivityStarted; private TextView mEmptyView; private SwitchBar mSwitchBar; private final IntentFilter mIntentFilter; // accessed from inner class (not private to avoid thunks) Preference mMyDevicePreference; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) { updateDeviceName(); } } private void updateDeviceName() { if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) { mMyDevicePreference.setTitle(mLocalAdapter.getName()); } } }; public BluetoothSettings() { super(DISALLOW_CONFIG_BLUETOOTH); mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mActivityStarted = (savedInstanceState == null); // don't auto start scan after rotation mEmptyView = (TextView) getView().findViewById(android.R.id.empty); getListView().setEmptyView(mEmptyView); final SettingsActivity activity = (SettingsActivity) getActivity(); mSwitchBar = activity.getSwitchBar(); mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar); mBluetoothEnabler.setupSwitchBar(); } @Override public void onDestroyView() { super.onDestroyView(); mBluetoothEnabler.teardownSwitchBar(); } @Override void addPreferencesForActivity() { addPreferencesFromResource(R.xml.bluetooth_settings); setHasOptionsMenu(true); } @Override public void onResume() { // resume BluetoothEnabler before calling super.onResume() so we don't get // any onDeviceAdded() callbacks before setting up view in updateContent() if (mBluetoothEnabler != null) { mBluetoothEnabler.resume(getActivity()); } super.onResume(); if (mDiscoverableEnabler != null) { mDiscoverableEnabler.resume(getActivity()); } getActivity().registerReceiver(mReceiver, mIntentFilter); if (mLocalAdapter != null) { updateContent(mLocalAdapter.getBluetoothState(), mActivityStarted); } } @Override public void onPause() { super.onPause(); if (mBluetoothEnabler != null) { mBluetoothEnabler.pause(); } getActivity().unregisterReceiver(mReceiver); if (mDiscoverableEnabler != null) { mDiscoverableEnabler.pause(); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (mLocalAdapter == null) return; // If the user is not allowed to configure bluetooth, do not show the menu. if (isRestrictedAndNotPinProtected()) return; boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON; boolean isDiscovering = mLocalAdapter.isDiscovering(); int textId = isDiscovering ? R.string.bluetooth_searching_for_devices : R.string.bluetooth_search_for_devices; menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId) .setEnabled(bluetoothIsEnabled && !isDiscovering) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device) .setEnabled(bluetoothIsEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout) .setEnabled(bluetoothIsEnabled) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_ID_SCAN: if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { startScanning(); } return true; case MENU_ID_RENAME_DEVICE: new BluetoothNameDialogFragment().show( getFragmentManager(), "rename device"); return true; case MENU_ID_VISIBILITY_TIMEOUT: new BluetoothVisibilityTimeoutFragment().show( getFragmentManager(), "visibility timeout"); return true; case MENU_ID_SHOW_RECEIVED: Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); getActivity().sendBroadcast(intent); return true; } return super.onOptionsItemSelected(item); } private void startScanning() { if (isRestrictedAndNotPinProtected()) return; if (!mAvailableDevicesCategoryIsPresent) { getPreferenceScreen().addPreference(mAvailableDevicesCategory); } mLocalAdapter.startScanning(true); } @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { mLocalAdapter.stopScanning(); super.onDevicePreferenceClick(btPreference); } private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, BluetoothDeviceFilter.Filter filter) { preferenceGroup.setTitle(titleId); getPreferenceScreen().addPreference(preferenceGroup); setFilter(filter); setDeviceListGroup(preferenceGroup); addCachedDevices(); preferenceGroup.setEnabled(true); } private void updateContent(int bluetoothState, boolean scanState) { final PreferenceScreen preferenceScreen = getPreferenceScreen(); int messageId = 0; switch (bluetoothState) { case BluetoothAdapter.STATE_ON: preferenceScreen.removeAll(); preferenceScreen.setOrderingAsAdded(true); mDevicePreferenceMap.clear(); // This device if (mMyDevicePreference == null) { mMyDevicePreference = new Preference(getActivity()); } mMyDevicePreference.setTitle(mLocalAdapter.getName()); if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) { mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones } else { mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc. } mMyDevicePreference.setPersistent(false); mMyDevicePreference.setEnabled(true); preferenceScreen.addPreference(mMyDevicePreference); if (!isRestrictedAndNotPinProtected()) { if (mDiscoverableEnabler == null) { mDiscoverableEnabler = new BluetoothDiscoverableEnabler(mLocalAdapter, mMyDevicePreference); mDiscoverableEnabler.resume(getActivity()); LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler( mDiscoverableEnabler); } } // Paired devices category if (mPairedDevicesCategory == null) { mPairedDevicesCategory = new PreferenceCategory(getActivity()); } else { mPairedDevicesCategory.removeAll(); } addDeviceCategory(mPairedDevicesCategory, R.string.bluetooth_preference_paired_devices, BluetoothDeviceFilter.BONDED_DEVICE_FILTER); int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount(); if (mDiscoverableEnabler != null) { mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices); } // Available devices category if (mAvailableDevicesCategory == null) { mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity(), null); } else { mAvailableDevicesCategory.removeAll(); } if (!isRestrictedAndNotPinProtected()) { addDeviceCategory(mAvailableDevicesCategory, R.string.bluetooth_preference_found_devices, BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); } int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount(); mAvailableDevicesCategoryIsPresent = true; if (numberOfAvailableDevices == 0) { preferenceScreen.removePreference(mAvailableDevicesCategory); mAvailableDevicesCategoryIsPresent = false; } if (numberOfPairedDevices == 0) { preferenceScreen.removePreference(mPairedDevicesCategory); if (scanState == true) { mActivityStarted = false; startScanning(); } else { if (!mAvailableDevicesCategoryIsPresent) { getPreferenceScreen().addPreference(mAvailableDevicesCategory); } } } getActivity().invalidateOptionsMenu(); return; // not break case BluetoothAdapter.STATE_TURNING_OFF: messageId = R.string.bluetooth_turning_off; break; case BluetoothAdapter.STATE_OFF: messageId = R.string.bluetooth_empty_list_bluetooth_off; break; case BluetoothAdapter.STATE_TURNING_ON: messageId = R.string.bluetooth_turning_on; break; } setDeviceListGroup(preferenceScreen); removeAllDevices(); mEmptyView.setText(messageId); getActivity().invalidateOptionsMenu(); } @Override public void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); updateContent(bluetoothState, true); } @Override public void onScanningStateChanged(boolean started) { super.onScanningStateChanged(started); // Update options' enabled state getActivity().invalidateOptionsMenu(); } public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { setDeviceListGroup(getPreferenceScreen()); removeAllDevices(); updateContent(mLocalAdapter.getBluetoothState(), false); } private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() { public void onClick(View v) { // User clicked on advanced options icon for a device in the list if (v.getTag() instanceof CachedBluetoothDevice) { if (isRestrictedAndNotPinProtected()) return; CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag(); Bundle args = new Bundle(1); args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice()); ((SettingsActivity) getActivity()).startPreferencePanel( DeviceProfilesSettings.class.getName(), args, R.string.bluetooth_device_advanced_title, null, null, 0); } else { Log.w(TAG, "onClick() called for other View: " + v); // TODO remove } } }; /** * Add a listener, which enables the advanced settings icon. * @param preference the newly added preference */ @Override void initDevicePreference(BluetoothDevicePreference preference) { CachedBluetoothDevice cachedDevice = preference.getCachedDevice(); if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { // Only paired device have an associated advanced settings screen preference.setOnSettingsClickListener(mDeviceProfilesListener); } } @Override protected int getHelpResource() { return R.string.help_url_bluetooth; } public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override public List getRawDataToIndex(Context context, boolean enabled) { final List result = new ArrayList(); final Resources res = context.getResources(); // Add fragment title SearchIndexableRaw data = new SearchIndexableRaw(context); data.title = res.getString(R.string.bluetooth_settings); data.screenTitle = res.getString(R.string.bluetooth_settings); result.add(data); // Add cached paired BT devices LocalBluetoothManager lbtm = LocalBluetoothManager.getInstance(context); // LocalBluetoothManager.getInstance can return null if the device does not // support bluetooth (e.g. the emulator). if (lbtm != null) { Set bondedDevices = lbtm.getBluetoothAdapter().getBondedDevices(); for (BluetoothDevice device : bondedDevices) { data = new SearchIndexableRaw(context); data.title = device.getName(); data.screenTitle = res.getString(R.string.bluetooth_settings); data.enabled = enabled; result.add(data); } } return result; } }; }