Files
app_Settings/src/com/android/settings/bluetooth/BluetoothSettings.java
Roozbeh Pournader 77893a4c4b BidiFormat adapter name for bluetooth_is_visible_message
Previously, adapter names which tend to be LTR and may appear at the
beginning of the string could cause the detected bidi direction of the whole
string (which would be RTL in RTL locales) to get miscalculated as LTR.

With this fix, not only the detected bidi direction would be correct,
but also odd adapter names would not impact the bidi layout of the
whole string.

Bug: 28816891
Change-Id: I1d25edbeeff7e9c013bb6c741e7f706431e721dc
2016-06-15 14:46:11 -07:00

613 lines
23 KiB
Java

/*
* 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 android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.text.BidiFormatter;
import android.text.Spannable;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settings.LinkifyUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.location.ScanningSettings;
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 com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
/**
* 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_SHOW_RECEIVED = Menu.FIRST + 2;
/* 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 static final String KEY_PAIRED_DEVICES = "paired_devices";
private static View mSettingsDialogView = null;
private BluetoothEnabler mBluetoothEnabler;
private PreferenceGroup mPairedDevicesCategory;
private PreferenceGroup mAvailableDevicesCategory;
private boolean mAvailableDevicesCategoryIsPresent;
private boolean mInitialScanStarted;
private boolean mInitiateDiscoverable;
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) {
final String action = intent.getAction();
final int state =
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
updateDeviceName(context);
}
if (state == BluetoothAdapter.STATE_ON) {
mInitiateDiscoverable = true;
}
}
private void updateDeviceName(Context context) {
if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
final Resources res = context.getResources();
final Locale locale = res.getConfiguration().getLocales().get(0);
final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale);
mMyDevicePreference.setSummary(res.getString(
R.string.bluetooth_is_visible_message,
bidiFormatter.unicodeWrap(mLocalAdapter.getName())));
}
}
};
public BluetoothSettings() {
super(DISALLOW_CONFIG_BLUETOOTH);
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
}
@Override
protected int getMetricsCategory() {
return MetricsEvent.BLUETOOTH;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mInitialScanStarted = false;
mInitiateDiscoverable = true;
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);
mPairedDevicesCategory = new PreferenceCategory(getPrefContext());
mPairedDevicesCategory.setKey(KEY_PAIRED_DEVICES);
mPairedDevicesCategory.setOrder(1);
getPreferenceScreen().addPreference(mPairedDevicesCategory);
mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity());
mAvailableDevicesCategory.setSelectable(false);
mAvailableDevicesCategory.setOrder(2);
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
mMyDevicePreference = new Preference(getPrefContext());
mMyDevicePreference.setSelectable(false);
mMyDevicePreference.setOrder(3);
getPreferenceScreen().addPreference(mMyDevicePreference);
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();
mInitiateDiscoverable = true;
if (isUiRestricted()) {
setDeviceListGroup(getPreferenceScreen());
if (!isUiRestrictedByOnlyAdmin()) {
getEmptyTextView().setText(R.string.bluetooth_empty_list_user_restricted);
}
removeAllDevices();
return;
}
getActivity().registerReceiver(mReceiver, mIntentFilter);
if (mLocalAdapter != null) {
updateContent(mLocalAdapter.getBluetoothState());
}
}
@Override
public void onPause() {
super.onPause();
if (mBluetoothEnabler != null) {
mBluetoothEnabler.pause();
}
// Make the device only visible to connected devices.
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
if (isUiRestricted()) {
return;
}
getActivity().unregisterReceiver(mReceiver);
}
@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 (isUiRestricted()) 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_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) {
MetricsLogger.action(getActivity(), MetricsEvent.ACTION_BLUETOOTH_SCAN);
startScanning();
}
return true;
case MENU_ID_RENAME_DEVICE:
MetricsLogger.action(getActivity(), MetricsEvent.ACTION_BLUETOOTH_RENAME);
new BluetoothNameDialogFragment().show(
getFragmentManager(), "rename device");
return true;
case MENU_ID_SHOW_RECEIVED:
MetricsLogger.action(getActivity(), MetricsEvent.ACTION_BLUETOOTH_FILES);
Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
getActivity().sendBroadcast(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
private void startScanning() {
if (isUiRestricted()) {
return;
}
if (!mAvailableDevicesCategoryIsPresent) {
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
mAvailableDevicesCategoryIsPresent = true;
}
if (mAvailableDevicesCategory != null) {
setDeviceListGroup(mAvailableDevicesCategory);
removeAllDevices();
}
mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
mAvailableDevicesCategory.removeAll();
mInitialScanStarted = true;
mLocalAdapter.startScanning(true);
}
@Override
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
mLocalAdapter.stopScanning();
super.onDevicePreferenceClick(btPreference);
}
private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) {
cacheRemoveAllPrefs(preferenceGroup);
preferenceGroup.setTitle(titleId);
setFilter(filter);
setDeviceListGroup(preferenceGroup);
if (addCachedDevices) {
addCachedDevices();
}
preferenceGroup.setEnabled(true);
removeCachedPrefs(preferenceGroup);
}
private void updateContent(int bluetoothState) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
int messageId = 0;
switch (bluetoothState) {
case BluetoothAdapter.STATE_ON:
mDevicePreferenceMap.clear();
if (isUiRestricted()) {
messageId = R.string.bluetooth_empty_list_user_restricted;
break;
}
if (getPreferenceScreen().getPreferenceCount() == 0) {
getPreferenceScreen().addPreference(mPairedDevicesCategory);
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
getPreferenceScreen().addPreference(mMyDevicePreference);
}
// Paired devices category
addDeviceCategory(mPairedDevicesCategory,
R.string.bluetooth_preference_paired_devices,
BluetoothDeviceFilter.BONDED_DEVICE_FILTER, true);
int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
if (isUiRestricted() || numberOfPairedDevices <= 0) {
if (preferenceScreen.findPreference(KEY_PAIRED_DEVICES) != null) {
preferenceScreen.removePreference(mPairedDevicesCategory);
}
} else {
if (preferenceScreen.findPreference(KEY_PAIRED_DEVICES) == null) {
preferenceScreen.addPreference(mPairedDevicesCategory);
}
}
// Available devices category
addDeviceCategory(mAvailableDevicesCategory,
R.string.bluetooth_preference_found_devices,
BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER, mInitialScanStarted);
if (!mInitialScanStarted) {
startScanning();
}
final Resources res = getResources();
final Locale locale = res.getConfiguration().getLocales().get(0);
final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale);
mMyDevicePreference.setSummary(res.getString(
R.string.bluetooth_is_visible_message,
bidiFormatter.unicodeWrap(mLocalAdapter.getName())));
getActivity().invalidateOptionsMenu();
// mLocalAdapter.setScanMode is internally synchronized so it is okay for multiple
// threads to execute.
if (mInitiateDiscoverable) {
// Make the device visible to other devices.
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
mInitiateDiscoverable = false;
}
return; // not break
case BluetoothAdapter.STATE_TURNING_OFF:
messageId = R.string.bluetooth_turning_off;
break;
case BluetoothAdapter.STATE_OFF:
setOffMessage();
if (isUiRestricted()) {
messageId = R.string.bluetooth_empty_list_user_restricted;
}
break;
case BluetoothAdapter.STATE_TURNING_ON:
messageId = R.string.bluetooth_turning_on;
mInitialScanStarted = false;
break;
}
setDeviceListGroup(preferenceScreen);
removeAllDevices();
if (messageId != 0) {
getEmptyTextView().setText(messageId);
}
if (!isUiRestricted()) {
getActivity().invalidateOptionsMenu();
}
}
private void setOffMessage() {
final TextView emptyView = getEmptyTextView();
if (emptyView == null) {
return;
}
final CharSequence briefText = getText(R.string.bluetooth_empty_list_bluetooth_off);
final ContentResolver resolver = getActivity().getContentResolver();
final boolean bleScanningMode = Settings.Global.getInt(
resolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;
if (!bleScanningMode) {
// Show only the brief text if the scanning mode has been turned off.
emptyView.setText(briefText, TextView.BufferType.SPANNABLE);
} else {
final StringBuilder contentBuilder = new StringBuilder();
contentBuilder.append(briefText);
contentBuilder.append("\n\n");
contentBuilder.append(getText(R.string.ble_scan_notify_text));
LinkifyUtils.linkify(emptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
@Override
public void onClick() {
final SettingsActivity activity =
(SettingsActivity) BluetoothSettings.this.getActivity();
activity.startPreferencePanel(ScanningSettings.class.getName(), null,
R.string.location_scanning_screen_title, null, null, 0);
}
});
}
getPreferenceScreen().removeAll();
Spannable boldSpan = (Spannable) emptyView.getText();
boldSpan.setSpan(
new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
@Override
public void onBluetoothStateChanged(int bluetoothState) {
super.onBluetoothStateChanged(bluetoothState);
// If BT is turned off/on staying in the same BT Settings screen
// discoverability to be set again
if (BluetoothAdapter.STATE_ON == bluetoothState)
mInitiateDiscoverable = true;
updateContent(bluetoothState);
}
@Override
public void onScanningStateChanged(boolean started) {
super.onScanningStateChanged(started);
// Update options' enabled state
if (getActivity() != null) {
getActivity().invalidateOptionsMenu();
}
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
setDeviceListGroup(getPreferenceScreen());
removeAllDevices();
updateContent(mLocalAdapter.getBluetoothState());
}
private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// User clicked on advanced options icon for a device in the list
if (!(v.getTag() instanceof CachedBluetoothDevice)) {
Log.w(TAG, "onClick() called for other View: " + v);
return;
}
final CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
Bundle args = new Bundle();
args.putString(DeviceProfilesSettings.ARG_DEVICE_ADDRESS,
device.getDevice().getAddress());
DeviceProfilesSettings profileSettings = new DeviceProfilesSettings();
profileSettings.setArguments(args);
profileSettings.show(getFragmentManager(),
DeviceProfilesSettings.class.getSimpleName());
}
};
/**
* 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;
}
private static class SummaryProvider
implements SummaryLoader.SummaryProvider, BluetoothCallback {
private final LocalBluetoothManager mBluetoothManager;
private final Context mContext;
private final SummaryLoader mSummaryLoader;
private boolean mEnabled;
private boolean mConnected;
public SummaryProvider(Context context, SummaryLoader summaryLoader) {
mBluetoothManager = Utils.getLocalBtManager(context);
mContext = context;
mSummaryLoader = summaryLoader;
}
@Override
public void setListening(boolean listening) {
BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
if (defaultAdapter == null) return;
if (listening) {
mEnabled = defaultAdapter.isEnabled();
mConnected =
defaultAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
mSummaryLoader.setSummary(this, getSummary());
mBluetoothManager.getEventManager().registerCallback(this);
} else {
mBluetoothManager.getEventManager().unregisterCallback(this);
}
}
private CharSequence getSummary() {
return mContext.getString(!mEnabled ? R.string.bluetooth_disabled
: mConnected ? R.string.bluetooth_connected
: R.string.bluetooth_disconnected);
}
@Override
public void onBluetoothStateChanged(int bluetoothState) {
mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
mSummaryLoader.setSummary(this, getSummary());
}
@Override
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
mConnected = state == BluetoothAdapter.STATE_CONNECTED;
mSummaryLoader.setSummary(this, getSummary());
}
@Override
public void onScanningStateChanged(boolean started) {
}
@Override
public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
}
@Override
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
SummaryLoader summaryLoader) {
return new SummaryProvider(activity, summaryLoader);
}
};
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
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 = Utils.getLocalBtManager(context);
// LocalBluetoothManager.getInstance can return null if the device does not
// support bluetooth (e.g. the emulator).
if (lbtm != null) {
Set<BluetoothDevice> 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;
}
};
}