diff --git a/res/xml/bluetooth_settings_obsolete.xml b/res/xml/bluetooth_settings_obsolete.xml new file mode 100644 index 00000000000..55a099cb19e --- /dev/null +++ b/res/xml/bluetooth_settings_obsolete.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index d70cc335ce8..ecbcbd1119a 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -19,7 +19,6 @@ android:title="@string/connected_devices_dashboard_title"> 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); - } - } + // Removed paired bluetooth device indexing. See BluetoothSettingsObsolete.java. return result; } + + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + if (!FeatureFactory.getFactory(context).getBluetoothFeatureProvider( + context).isPairingPageEnabled()) { + keys.add(DATA_KEY_REFERENCE); + } + return keys; + } }; } diff --git a/src/com/android/settings/bluetooth/BluetoothSettingsObsolete.java b/src/com/android/settings/bluetooth/BluetoothSettingsObsolete.java new file mode 100644 index 00000000000..207d313a46c --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothSettingsObsolete.java @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2017 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.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.annotation.VisibleForTesting; +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.nano.MetricsProto.MetricsEvent; +import com.android.settings.LinkifyUtils; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.core.PreferenceController; +import com.android.settings.dashboard.SummaryLoader; +import com.android.settings.location.ScanningSettings; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableRaw; +import com.android.settings.widget.GearPreference; +import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener; +import com.android.settings.widget.SwitchBar; +import com.android.settings.widget.SwitchBarController; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.widget.FooterPreference; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * BluetoothSettingsObsolete is the Settings screen for Bluetooth configuration and + * connection management. + * + * This fragment stores old implementation of {@link BluetoothSettings} and is + * deprecated, please use {@link BluetoothSettings} instead. + */ +@Deprecated +public class BluetoothSettingsObsolete extends DeviceListPreferenceObsoleteFragment + implements Indexable { + private static final String TAG = "BluetoothSettingsObsolete"; + + 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 static final String BTOPP_PACKAGE = + "com.android.bluetooth"; + + private static final String KEY_PAIRED_DEVICES = "paired_devices"; + + private static View mSettingsDialogView = null; + + private BluetoothEnabler mBluetoothEnabler; + + private PreferenceGroup mPairedDevicesCategory; + private PreferenceGroup mAvailableDevicesCategory; + private Preference mDeviceNamePreference; + private boolean mAvailableDevicesCategoryIsPresent; + + private boolean mInitialScanStarted; + private boolean mInitiateDiscoverable; + + private SwitchBar mSwitchBar; + + private final IntentFilter mIntentFilter; + private BluetoothDeviceNamePreferenceController mDeviceNamePrefController; + + // For Search + @VisibleForTesting + static final String DATA_KEY_REFERENCE = "main_toggle_bluetooth_obsolete"; + + // accessed from inner class (not private to avoid thunks) + FooterPreference 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 (state == BluetoothAdapter.STATE_ON) { + mInitiateDiscoverable = true; + } + } + }; + + public BluetoothSettingsObsolete() { + super(DISALLOW_CONFIG_BLUETOOTH); + mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); + } + + @Override + public 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, new SwitchBarController(mSwitchBar), + mMetricsFeatureProvider, Utils.getLocalBtManager(activity), + MetricsEvent.ACTION_BLUETOOTH_TOGGLE); + mBluetoothEnabler.setupSwitchController(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + mBluetoothEnabler.teardownSwitchController(); + } + + @Override + void addPreferencesForActivity() { + final Context prefContext = getPrefContext(); + + mDeviceNamePreference = mDeviceNamePrefController.createBluetoothDeviceNamePreference( + getPreferenceScreen(), 1 /* order */); + + mPairedDevicesCategory = new PreferenceCategory(prefContext); + mPairedDevicesCategory.setKey(KEY_PAIRED_DEVICES); + mPairedDevicesCategory.setOrder(2); + getPreferenceScreen().addPreference(mPairedDevicesCategory); + + mAvailableDevicesCategory = new BluetoothProgressCategory(prefContext); + mAvailableDevicesCategory.setSelectable(false); + mAvailableDevicesCategory.setOrder(3); + getPreferenceScreen().addPreference(mAvailableDevicesCategory); + + mMyDevicePreference = mFooterPreferenceMixin.createFooterPreference(); + mMyDevicePreference.setSelectable(false); + + setHasOptionsMenu(true); + } + + @Override + public void onStart() { + // resume BluetoothEnabler before calling super.onStart() so we don't get + // any onDeviceAdded() callbacks before setting up view in updateContent() + if (mBluetoothEnabler != null) { + mBluetoothEnabler.resume(getActivity()); + } + super.onStart(); + + 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 onStop() { + super.onStop(); + 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_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) { + mMetricsFeatureProvider.action(getActivity(), + MetricsEvent.ACTION_BLUETOOTH_SCAN); + startScanning(); + } + return true; + + case MENU_ID_SHOW_RECEIVED: + mMetricsFeatureProvider.action(getActivity(), + MetricsEvent.ACTION_BLUETOOTH_FILES); + Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES); + intent.setPackage(BTOPP_PACKAGE); + 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; + } + getPreferenceScreen().removeAll(); + getPreferenceScreen().addPreference(mDeviceNamePreference); + 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(); + } + + updateMyDevicePreference(mMyDevicePreference); + 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) BluetoothSettingsObsolete.this.getActivity(); + activity.startPreferencePanel(BluetoothSettingsObsolete.this, + ScanningSettings.class.getName(), null, + R.string.location_scanning_screen_title, null, null, 0); + } + }); + } + getPreferenceScreen().removeAll(); + setTextSpan(emptyView.getText(), briefText); + } + + @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()); + } + + @VisibleForTesting + void setTextSpan(CharSequence text, CharSequence briefText) { + if (text instanceof Spannable) { + Spannable boldSpan = (Spannable) text; + boldSpan.setSpan( + new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0, + briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + @VisibleForTesting + void updateMyDevicePreference(Preference myDevicePreference) { + final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); + + myDevicePreference.setTitle(getString( + R.string.bluetooth_footer_mac_message, + bidiFormatter.unicodeWrap(mLocalAdapter.getAddress()))); + } + + @VisibleForTesting + void setLocalBluetoothAdapter(LocalBluetoothAdapter localAdapter) { + mLocalAdapter = localAdapter; + } + + private final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { + // User clicked on advanced options icon for a device in the list + if (!(pref instanceof BluetoothDevicePreference)) { + Log.w(TAG, "onClick() called for other View: " + pref); + return; + } + final CachedBluetoothDevice device = + ((BluetoothDevicePreference) pref).getBluetoothDevice(); + if (device == null) { + Log.w(TAG, "No BT device attached with this pref: " + pref); + return; + } + final Bundle args = new Bundle(); + args.putString(DeviceProfilesSettings.ARG_DEVICE_ADDRESS, + device.getDevice().getAddress()); + final 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.setOnGearClickListener(mDeviceProfilesListener); + } + } + + @Override + protected int getHelpResource() { + return R.string.help_url_bluetooth; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bluetooth_settings_obsolete; + } + + @Override + protected List getPreferenceControllers(Context context) { + List controllers = new ArrayList<>(); + mDeviceNamePrefController = new BluetoothDeviceNamePreferenceController(context, + this, getLifecycle()); + controllers.add(mDeviceNamePrefController); + + return controllers; + } + + @VisibleForTesting + static class SummaryProvider implements SummaryLoader.SummaryProvider, OnSummaryChangeListener { + + private final LocalBluetoothManager mBluetoothManager; + private final Context mContext; + private final SummaryLoader mSummaryLoader; + + @VisibleForTesting + BluetoothSummaryUpdater mSummaryUpdater; + + public SummaryProvider(Context context, SummaryLoader summaryLoader, + LocalBluetoothManager bluetoothManager) { + mBluetoothManager = bluetoothManager; + mContext = context; + mSummaryLoader = summaryLoader; + mSummaryUpdater = new BluetoothSummaryUpdater(mContext, this, mBluetoothManager); + } + + @Override + public void setListening(boolean listening) { + mSummaryUpdater.register(listening); + } + + @Override + public void onSummaryChanged(String summary) { + if (mSummaryLoader != null) { + mSummaryLoader.setSummary(this, summary); + } + } + } + + 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, Utils.getLocalBtManager(activity)); + } + }; + + 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); + data.key = DATA_KEY_REFERENCE; + 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 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; + } + + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + if (FeatureFactory.getFactory(context).getBluetoothFeatureProvider( + context).isPairingPageEnabled()) { + keys.add(DATA_KEY_REFERENCE); + } + return keys; + } + + }; +} diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java index 40343167943..c35e652eac1 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java @@ -41,6 +41,7 @@ import java.util.WeakHashMap; * @see BluetoothSettings * @see DevicePickerFragment */ +// TODO: Refactor this fragment public abstract class DeviceListPreferenceFragment extends RestrictedDashboardFragment implements BluetoothCallback { diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceObsoleteFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceObsoleteFragment.java new file mode 100644 index 00000000000..84d8558e8f3 --- /dev/null +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceObsoleteFragment.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2017 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroup; +import android.util.Log; + +import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.Collection; +import java.util.WeakHashMap; + +/** + * Parent class for settings fragments that contain a list of Bluetooth + * devices. + * + * This fragment stores old implementation of {@link DeviceListPreferenceFragment} and is + * deprecated, please use {@link DeviceListPreferenceFragment} instead. + * + * @see BluetoothSettingsObsolete + * @see DevicePickerFragment + */ +@Deprecated +public abstract class DeviceListPreferenceObsoleteFragment extends + RestrictedDashboardFragment implements BluetoothCallback { + + private static final String TAG = "DeviceListPreferenceFragment"; + + private static final String KEY_BT_DEVICE_LIST = "bt_device_list"; + private static final String KEY_BT_SCAN = "bt_scan"; + + private BluetoothDeviceFilter.Filter mFilter; + + BluetoothDevice mSelectedDevice; + + LocalBluetoothAdapter mLocalAdapter; + LocalBluetoothManager mLocalManager; + + private PreferenceGroup mDeviceListGroup; + + final WeakHashMap mDevicePreferenceMap = + new WeakHashMap(); + + DeviceListPreferenceObsoleteFragment(String restrictedKey) { + super(restrictedKey); + mFilter = BluetoothDeviceFilter.ALL_FILTER; + } + + final void setFilter(BluetoothDeviceFilter.Filter filter) { + mFilter = filter; + } + + final void setFilter(int filterType) { + mFilter = BluetoothDeviceFilter.getFilter(filterType); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mLocalManager = Utils.getLocalBtManager(getActivity()); + if (mLocalManager == null) { + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + mLocalAdapter = mLocalManager.getBluetoothAdapter(); + + addPreferencesForActivity(); + + mDeviceListGroup = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST); + } + + void setDeviceListGroup(PreferenceGroup preferenceGroup) { + mDeviceListGroup = preferenceGroup; + } + + /** Add preferences from the subclass. */ + abstract void addPreferencesForActivity(); + + @Override + public void onStart() { + super.onStart(); + if (mLocalManager == null || isUiRestricted()) return; + + mLocalManager.setForegroundActivity(getActivity()); + mLocalManager.getEventManager().registerCallback(this); + + updateProgressUi(mLocalAdapter.isDiscovering()); + } + + @Override + public void onStop() { + super.onStop(); + if (mLocalManager == null || isUiRestricted()) { + return; + } + + removeAllDevices(); + mLocalManager.setForegroundActivity(null); + mLocalManager.getEventManager().unregisterCallback(this); + } + + void removeAllDevices() { + mLocalAdapter.stopScanning(); + mDevicePreferenceMap.clear(); + mDeviceListGroup.removeAll(); + } + + void addCachedDevices() { + Collection cachedDevices = + mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); + for (CachedBluetoothDevice cachedDevice : cachedDevices) { + onDeviceAdded(cachedDevice); + } + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (KEY_BT_SCAN.equals(preference.getKey())) { + mLocalAdapter.startScanning(true); + return true; + } + + if (preference instanceof BluetoothDevicePreference) { + BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference; + CachedBluetoothDevice device = btPreference.getCachedDevice(); + mSelectedDevice = device.getDevice(); + onDevicePreferenceClick(btPreference); + return true; + } + + return super.onPreferenceTreeClick(preference); + } + + void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + btPreference.onClicked(); + } + + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + if (mDevicePreferenceMap.get(cachedDevice) != null) { + return; + } + + // Prevent updates while the list shows one of the state messages + if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return; + + if (mFilter.matches(cachedDevice.getDevice())) { + createDevicePreference(cachedDevice); + } + } + + void createDevicePreference(CachedBluetoothDevice cachedDevice) { + if (mDeviceListGroup == null) { + Log.w(TAG, "Trying to create a device preference before the list group/category " + + "exists!"); + return; + } + + String key = cachedDevice.getDevice().getAddress(); + BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key); + + if (preference == null) { + preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice); + preference.setKey(key); + mDeviceListGroup.addPreference(preference); + } else { + // Tell the preference it is being re-used in case there is new info in the + // cached device. + preference.rebind(); + } + + initDevicePreference(preference); + mDevicePreferenceMap.put(cachedDevice, preference); + } + + /** + * Overridden in {@link BluetoothSettings} to add a listener. + * @param preference the newly added preference + */ + void initDevicePreference(BluetoothDevicePreference preference) { + // Does nothing by default + } + + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { + BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); + if (preference != null) { + mDeviceListGroup.removePreference(preference); + } + } + + public void onScanningStateChanged(boolean started) { + updateProgressUi(started); + } + + private void updateProgressUi(boolean start) { + if (mDeviceListGroup instanceof BluetoothProgressCategory) { + ((BluetoothProgressCategory) mDeviceListGroup).setProgress(start); + } + } + + public void onBluetoothStateChanged(int bluetoothState) { + if (bluetoothState == BluetoothAdapter.STATE_OFF) { + updateProgressUi(false); + } + } + + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { } +} diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index 8bf820262b7..cb8be506ef0 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -40,7 +40,7 @@ import java.util.List; * BluetoothSettings is the Settings screen for Bluetooth configuration and * connection management. */ -public final class DevicePickerFragment extends DeviceListPreferenceFragment { +public final class DevicePickerFragment extends DeviceListPreferenceObsoleteFragment { private static final int MENU_ID_REFRESH = Menu.FIRST; private static final String TAG = "DevicePickerFragment"; diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java index 6b172c27907..27492ce812f 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java @@ -23,6 +23,7 @@ import android.support.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController; import com.android.settings.bluetooth.Utils; import com.android.settings.core.PreferenceController; @@ -71,7 +72,8 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { controllers.add(mUsbPrefController); final BluetoothMasterSwitchPreferenceController bluetoothPreferenceController = new BluetoothMasterSwitchPreferenceController( - context, Utils.getLocalBtManager(context)); + context, Utils.getLocalBtManager(context), this, + (SettingsActivity) getActivity()); lifecycle.addObserver(bluetoothPreferenceController); controllers.add(bluetoothPreferenceController); return controllers; diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index ce8135c33df..ab1af49ff83 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -40,6 +40,7 @@ import com.android.settings.applications.assist.ManageAssist; import com.android.settings.backup.BackupSettingsActivity; import com.android.settings.backup.BackupSettingsFragment; import com.android.settings.bluetooth.BluetoothSettings; +import com.android.settings.bluetooth.BluetoothSettingsObsolete; import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment; import com.android.settings.datausage.DataUsageMeteredSettings; import com.android.settings.datausage.DataUsageSummary; @@ -110,6 +111,7 @@ public final class SearchIndexableResources { addIndex(SavedAccessPointsWifiSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_wireless); addIndex(BluetoothSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_bluetooth); + addIndex(BluetoothSettingsObsolete.class, NO_DATA_RES_ID, R.drawable.ic_settings_bluetooth); addIndex(SimSettings.class, NO_DATA_RES_ID, R.drawable.ic_sim_sd); addIndex(DataUsageSummary.class, NO_DATA_RES_ID, R.drawable.ic_settings_data_usage); addIndex(DataUsageMeteredSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_data_usage); diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java index 706f33b3619..30afbd0b48d 100644 --- a/src/com/android/settings/search2/CursorToSearchResultConverter.java +++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java @@ -74,6 +74,7 @@ class CursorToSearchResultConverter { private static final String[] whiteList = { "main_toggle_wifi", "main_toggle_bluetooth", + "main_toggle_bluetooth_obsolete", "toggle_airplane", "tether_settings", "battery_saver", diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java index 177130e714b..f0fb91ccbac 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java @@ -16,14 +16,18 @@ package com.android.settings.bluetooth; +import android.app.Fragment; import android.content.Context; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.widget.MasterSwitchPreference; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -38,7 +42,10 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,17 +61,26 @@ public class BluetoothMasterSwitchPreferenceControllerTest { private MasterSwitchPreference mPreference; @Mock private RestrictionUtils mRestrictionUtils; + @Mock + private Fragment mFragment; + @Mock + private SettingsActivity mActivity; private Context mContext; private BluetoothMasterSwitchPreferenceController mController; + private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application.getApplicationContext(); + mContext = spy(RuntimeEnvironment.application.getApplicationContext()); + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + mController = new BluetoothMasterSwitchPreferenceController( - mContext, mBluetoothManager, mRestrictionUtils); + mContext, mBluetoothManager, mRestrictionUtils, mFragment, mActivity); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); } @Test @@ -84,7 +100,7 @@ public class BluetoothMasterSwitchPreferenceControllerTest { mController.onPause(); verify(mBluetoothManager.getEventManager()).unregisterCallback( - any(BluetoothCallback.class)); + any(BluetoothCallback.class)); } @Test @@ -114,4 +130,22 @@ public class BluetoothMasterSwitchPreferenceControllerTest { verify(mPreference).setSummary("test summary"); } + @Test + public void testHandlePreferenceTreeClick_pairPageEnabled_showNewPage() { + when(mFeatureFactory.bluetoothFeatureProvider.isPairingPageEnabled()).thenReturn(true); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mActivity).startPreferencePanelAsUser(eq(mFragment), + eq(BluetoothSettings.class.getName()), any(), eq(R.string.bluetooth), any(), any()); + } + + @Test + public void testHandlePreferenceTreeClick_pairPageDisabled_showOldPage() { + mController.handlePreferenceTreeClick(mPreference); + + verify(mActivity).startPreferencePanelAsUser(eq(mFragment), + eq(BluetoothSettingsObsolete.class.getName()), any(), eq(R.string.bluetooth), any(), + any()); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsObsoleteTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsObsoleteTest.java new file mode 100644 index 00000000000..549eeb774a0 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsObsoleteTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 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 com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BluetoothSettingsObsoleteTest { + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + private BluetoothSettingsObsolete mFragment; + private FakeFeatureFactory mFeatureFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + + mFragment = spy(new BluetoothSettingsObsolete()); + doReturn(mContext).when(mFragment).getContext(); + } + + @Test + public void testSearchIndexProvider_pairPageEnabled_keyAdded() { + doReturn(true).when(mFeatureFactory.bluetoothFeatureProvider).isPairingPageEnabled(); + + final List keys = mFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys( + mContext); + + assertThat(keys).contains(BluetoothSettingsObsolete.DATA_KEY_REFERENCE); + } + + @Test + public void testSearchIndexProvider_pairPageDisabled_keyNotAdded() { + final List keys = mFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys( + mContext); + + assertThat(keys).doesNotContain(BluetoothSettingsObsolete.DATA_KEY_REFERENCE); + } + +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsTest.java index 35207f51736..c0aa723ec76 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsTest.java @@ -31,16 +31,20 @@ import android.support.v7.preference.Preference; import com.android.settings.R; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.List; + @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BluetoothSettingsTest { @@ -50,17 +54,21 @@ public class BluetoothSettingsTest { private UserManager mUserManager; @Mock private Resources mResource; - @Mock + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock private LocalBluetoothAdapter mLocalAdapter; private BluetoothSettings mFragment; private Preference mMyDevicePreference; + private FakeFeatureFactory mFeatureFactory; @Before public void setUp() { MockitoAnnotations.initMocks(this); + FakeFeatureFactory.setupForTest(mContext); + mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); + mFragment = spy(new BluetoothSettings()); doReturn(mContext).when(mFragment).getContext(); doReturn(mResource).when(mFragment).getResources(); @@ -86,4 +94,22 @@ public class BluetoothSettingsTest { assertThat(mMyDevicePreference.getTitle()).isEqualTo(FOOTAGE_MAC_STRING); } + @Test + public void testSearchIndexProvider_pairPageEnabled_keyNotAdded() { + doReturn(true).when(mFeatureFactory.bluetoothFeatureProvider).isPairingPageEnabled(); + + final List keys = mFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys( + mContext); + + assertThat(keys).doesNotContain(BluetoothSettings.DATA_KEY_REFERENCE); + } + + @Test + public void testSearchIndexProvider_pairPageDisabled_keyAdded() { + final List keys = mFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys( + mContext); + + assertThat(keys).contains(BluetoothSettings.DATA_KEY_REFERENCE); + } + } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index 742157074d2..72cbf4ec8ce 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -22,10 +22,10 @@ import android.nfc.NfcManager; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.nfc.NfcPreferenceController; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.dashboard.SummaryLoader; -import com.android.settings.nfc.NfcPreferenceController; import com.android.settings.testutils.XmlTestUtils; import com.android.settingslib.drawer.CategoryKey;