There was a fundamental flow in the BT code. Basically BluetoothSettings is using a singleton BluetoothDiscoverableEnabler. BluetoothDiscoverableEnabler is keeping (thru its constructor) a reference on a Context for registering/unregistering some broadcast receiver. BUMMER! When you change orientation (or more generally the device Configuration), your Context is no more the same! Hence the crash as we were trying to unregister a Receiver on a Context that is no more valid. Fix that issue by passing an updated Context to the BluetoothDiscoverableEnabler.resume() API. Bug #12991455 Change-Id: I77db15d2b59b6dd973907e26f9e6bb022202a8b5
409 lines
16 KiB
Java
Executable File
409 lines
16 KiB
Java
Executable File
/*
|
|
* 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.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;
|
|
|
|
/**
|
|
* BluetoothSettings is the Settings screen for Bluetooth configuration and
|
|
* connection management.
|
|
*/
|
|
public final class BluetoothSettings extends DeviceListPreferenceFragment {
|
|
private static final String TAG = "BluetoothSettings";
|
|
|
|
private static final int MENU_ID_SCAN = Menu.FIRST;
|
|
private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 1;
|
|
|
|
/* 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 Switch mSwitch;
|
|
|
|
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 Activity activity = getActivity();
|
|
final int padding = activity.getResources().getDimensionPixelSize(
|
|
R.dimen.action_bar_switch_padding);
|
|
mSwitch = new Switch(activity);
|
|
mSwitch.setPaddingRelative(0, 0, padding, 0);
|
|
|
|
mBluetoothEnabler = new BluetoothEnabler(activity, mSwitch);
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
|
|
final SettingsActivity activity = (SettingsActivity) getActivity();
|
|
|
|
if (!activity.onIsHidingHeaders()) {
|
|
activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
|
|
ActionBar.DISPLAY_SHOW_CUSTOM);
|
|
activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams(
|
|
ActionBar.LayoutParams.WRAP_CONTENT,
|
|
ActionBar.LayoutParams.WRAP_CONTENT,
|
|
Gravity.CENTER_VERTICAL | Gravity.END));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onStop() {
|
|
super.onStop();
|
|
final SettingsActivity activity = (SettingsActivity) getActivity();
|
|
if (!activity.onIsHidingHeaders()) {
|
|
activity.getActionBar().setDisplayOptions(0, ActionBar.DISPLAY_SHOW_CUSTOM);
|
|
activity.getActionBar().setCustomView(null);
|
|
}
|
|
}
|
|
|
|
@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();
|
|
}
|
|
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_IF_ROOM);
|
|
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_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 BluetoothLocalDevicePreference(
|
|
getActivity(), mLocalDeviceProfilesListener);
|
|
}
|
|
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);
|
|
}
|
|
|
|
// Listener for local device profile fragment.
|
|
private final View.OnClickListener mLocalDeviceProfilesListener = new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
if (isRestrictedAndNotPinProtected()) return;
|
|
|
|
((SettingsActivity) getActivity()).startPreferencePanel(
|
|
LocalDeviceProfilesSettings.class.getName(), null,
|
|
0, mLocalAdapter.getName(), null, 0);
|
|
}
|
|
};
|
|
|
|
|
|
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;
|
|
}
|
|
}
|