diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 5c18d66c949..2a1341ee7c7 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -27,7 +27,7 @@ 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.annotation.VisibleForTesting; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceScreen; @@ -58,6 +58,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Set; @@ -105,7 +106,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final int state = - intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) { updateDeviceName(context); @@ -122,8 +123,8 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem final Locale locale = res.getConfiguration().getLocales().get(0); final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale); mMyDevicePreference.setTitle(res.getString( - R.string.bluetooth_is_visible_message, - bidiFormatter.unicodeWrap(mLocalAdapter.getName()))); + R.string.bluetooth_is_visible_message, + bidiFormatter.unicodeWrap(mLocalAdapter.getName()))); } } }; @@ -230,7 +231,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem 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; + R.string.bluetooth_search_for_devices; menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId) .setEnabled(bluetoothIsEnabled && !isDiscovering) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); @@ -356,8 +357,8 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem final Locale locale = res.getConfiguration().getLocales().get(0); final BidiFormatter bidiFormatter = BidiFormatter.getInstance(locale); mMyDevicePreference.setTitle(res.getString( - R.string.bluetooth_is_visible_message, - bidiFormatter.unicodeWrap(mLocalAdapter.getName()))); + R.string.bluetooth_is_visible_message, + bidiFormatter.unicodeWrap(mLocalAdapter.getName()))); getActivity().invalidateOptionsMenu(); @@ -438,8 +439,9 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem 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) + if (BluetoothAdapter.STATE_ON == bluetoothState) { mInitiateDiscoverable = true; + } updateContent(bluetoothState); } @@ -481,6 +483,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem /** * Add a listener, which enables the advanced settings icon. + * * @param preference the newly added preference */ @Override @@ -497,7 +500,8 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem return R.string.help_url_bluetooth; } - private static class SummaryProvider + @VisibleForTesting + static class SummaryProvider implements SummaryLoader.SummaryProvider, BluetoothCallback { private final LocalBluetoothManager mBluetoothManager; @@ -505,10 +509,11 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem private final SummaryLoader mSummaryLoader; private boolean mEnabled; - private boolean mConnected; + private int mConnectionState; - public SummaryProvider(Context context, SummaryLoader summaryLoader) { - mBluetoothManager = Utils.getLocalBtManager(context); + public SummaryProvider(Context context, SummaryLoader summaryLoader, + LocalBluetoothManager bluetoothManager) { + mBluetoothManager = bluetoothManager; mContext = context; mSummaryLoader = summaryLoader; } @@ -519,8 +524,7 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem if (defaultAdapter == null) return; if (listening) { mEnabled = defaultAdapter.isEnabled(); - mConnected = - defaultAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED; + mConnectionState = defaultAdapter.getConnectionState(); mSummaryLoader.setSummary(this, getSummary()); mBluetoothManager.getEventManager().registerCallback(this); } else { @@ -529,20 +533,26 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem } private CharSequence getSummary() { - return mContext.getString(!mEnabled ? R.string.bluetooth_disabled - : mConnected ? R.string.bluetooth_connected - : R.string.bluetooth_disconnected); + if (!mEnabled) { + return mContext.getString(R.string.bluetooth_disabled); + } else if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) { + return mContext.getString(R.string.bluetooth_connected); + } else { + return mContext.getString(R.string.bluetooth_disconnected); + } } @Override public void onBluetoothStateChanged(int bluetoothState) { - mEnabled = bluetoothState == BluetoothAdapter.STATE_ON; + mEnabled = bluetoothState == BluetoothAdapter.STATE_ON + || bluetoothState == BluetoothAdapter.STATE_TURNING_ON; mSummaryLoader.setSummary(this, getSummary()); } @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { - mConnected = state == BluetoothAdapter.STATE_CONNECTED; + mConnectionState = state; + updateConnected(); mSummaryLoader.setSummary(this, getSummary()); } @@ -565,49 +575,84 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment implem public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { } + + private void updateConnected() { + // Make sure our connection state is up to date. + int state = mBluetoothManager.getBluetoothAdapter().getConnectionState(); + if (state != mConnectionState) { + mConnectionState = state; + return; + } + final Collection devices = getDevices(); + if (devices == null) { + mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; + return; + } + if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) { + CachedBluetoothDevice connectedDevice = null; + for (CachedBluetoothDevice device : devices) { + if (device.isConnected()) { + connectedDevice = device; + } + } + if (connectedDevice == null) { + // If somehow we think we are connected, but have no connected devices, we + // aren't connected. + mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; + } + } + } + + private Collection getDevices() { + return mBluetoothManager != null + ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy() + : null; + } } 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); + 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) { + new BaseSearchIndexProvider() { + @Override + public List getRawDataToIndex(Context context, + boolean enabled) { - final List result = new ArrayList(); + final List result = new ArrayList(); - final Resources res = context.getResources(); + 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 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 bondedDevices = - lbtm.getBluetoothAdapter().getBondedDevices(); + // 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); + 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; } - return result; - } - }; + }; } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index ab1ec13d50a..9afe4b25e34 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -330,5 +330,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mDashboardTilePrefKeys.remove(key); mProgressiveDisclosureMixin.removePreference(screen, key); } + mSummaryLoader.setListening(true); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java new file mode 100644 index 00000000000..7ac7cb189e4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 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.content.Context; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.dashboard.SummaryLoader; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +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 org.robolectric.shadows.ShadowBluetoothAdapter; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class BluetoothSettingsSummaryProviderTest { + + private Context mContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocalBluetoothManager mBluetoothManager; + @Mock + private SummaryLoader mSummaryLoader; + + private BluetoothSettings.SummaryProvider mSummaryProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application.getApplicationContext(); + mSummaryProvider = new BluetoothSettings.SummaryProvider(mContext, mSummaryLoader, + mBluetoothManager); + } + + @Test + public void setListening_shouldUpdateSummary() { + mSummaryProvider.setListening(true); + + verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryProvider); + verify(mSummaryLoader).setSummary(eq(mSummaryProvider), anyString()); + } + + @Test + public void setNotListening_shouldUnregister() { + mSummaryProvider.setListening(false); + + verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryProvider); + } + + @Test + public void updateSummary_btDisabled_shouldShowDisabledMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().disable(); + mSummaryProvider.setListening(true); + + verify(mSummaryLoader).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disabled)); + } + + @Test + public void updateSummary_btEnabled_noDevice_shouldShowDisconnectedMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + mSummaryProvider.setListening(true); + + verify(mSummaryLoader).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disconnected)); + } + + @Test + public void updateState_btEnabled_noDevice_shouldShowDisconnectedMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON); + + verify(mSummaryLoader).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disconnected)); + } + + @Test + public void updateState_btDisabled_shouldShowDisabledMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_OFF); + + verify(mSummaryLoader).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disabled)); + } + + @Test + public void updateConnectionState_disconnected_shouldShowDisconnectedMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + when(mBluetoothManager.getBluetoothAdapter().getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_DISCONNECTED); + + mSummaryProvider.setListening(true); + mSummaryProvider.onConnectionStateChanged(null /* device */, + BluetoothAdapter.STATE_DISCONNECTED); + + verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disconnected)); + } + + + @Test + public void updateConnectionState_connected_shouldShowConnectedMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + when(mBluetoothManager.getBluetoothAdapter().getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_CONNECTED); + final List devices = new ArrayList<>(); + devices.add(mock(CachedBluetoothDevice.class)); + when(devices.get(0).isConnected()).thenReturn(true); + when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) + .thenReturn(devices); + + mSummaryProvider.setListening(true); + mSummaryProvider.onConnectionStateChanged(null /* device */, + BluetoothAdapter.STATE_CONNECTED); + + verify(mSummaryLoader).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_connected)); + } + + @Test + public void updateConnectionState_inconsistentState_shouldShowDisconnectedMessage() { + ShadowBluetoothAdapter.getDefaultAdapter().enable(); + when(mBluetoothManager.getBluetoothAdapter().getConnectionState()) + .thenReturn(BluetoothAdapter.STATE_CONNECTED); + + mSummaryProvider.setListening(true); + mSummaryProvider.onConnectionStateChanged(null /* device */, + BluetoothAdapter.STATE_CONNECTED); + + verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider, + mContext.getString(R.string.bluetooth_disconnected)); + } + +}