diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9026aa3cd9b..4c0a639ee09 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -370,6 +370,9 @@
%1$s wants to access your SIM card. Granting access to the SIM card will disable data connectivity on your device for the duration of the connection. Give access to %2$s?
+
+ Visible as ^1 to other devices
+
Date & time
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
new file mode 100644
index 00000000000..ba05c3a0fea
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
@@ -0,0 +1,169 @@
+/*
+ * 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.app.Fragment;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnResume;
+import com.android.settings.core.lifecycle.events.OnStart;
+import com.android.settings.core.lifecycle.events.OnStop;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Controller that shows and updates the bluetooth device name
+ */
+public class BluetoothDeviceNamePreferenceController extends PreferenceController implements
+ LifecycleObserver, OnStart, OnStop {
+ private static final String TAG = "BluetoothNamePrefCtrl";
+
+ public static final String KEY_DEVICE_NAME = "device_name";
+ private int mAccentColor;
+ private Fragment mFragment;
+ private LocalBluetoothManager mLocalManager;
+ private LocalBluetoothAdapter mLocalAdapter;
+ private Preference mPreference;
+
+ public BluetoothDeviceNamePreferenceController(Context context, Fragment fragment,
+ Lifecycle lifecycle) {
+ this(context, fragment, (LocalBluetoothAdapter) null);
+
+ mLocalManager = Utils.getLocalBtManager(context);
+ if (mLocalManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ mLocalAdapter = mLocalManager.getBluetoothAdapter();
+ lifecycle.addObserver(this);
+ }
+
+ @VisibleForTesting
+ BluetoothDeviceNamePreferenceController(Context context, Fragment fragment,
+ LocalBluetoothAdapter localAdapter) {
+ super(context);
+ mAccentColor = com.android.settingslib.Utils.getColorAccent(context);
+ mFragment = fragment;
+ mLocalAdapter = localAdapter;
+ }
+
+ @Override
+ public void onStart() {
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED));
+ }
+
+ @Override
+ public void onStop() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mLocalAdapter != null;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_DEVICE_NAME;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ updateDeviceName(preference, mLocalAdapter.getName());
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (KEY_DEVICE_NAME.equals(preference.getKey())) {
+ new BluetoothNameDialogFragment().show(mFragment.getFragmentManager(), "rename device");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Create preference to show bluetooth device name
+ *
+ * @param screen to add the preference in
+ * @param order to decide position of the preference
+ * @return bluetooth preference that created in this method
+ */
+ public Preference createBluetoothDeviceNamePreference(PreferenceScreen screen, int order) {
+ mPreference = new Preference(screen.getContext());
+ mPreference.setOrder(order);
+ mPreference.setKey(KEY_DEVICE_NAME);
+ screen.addPreference(mPreference);
+
+ return mPreference;
+ }
+
+ /**
+ * Update device summary with {@code deviceName}, where {@code deviceName} has accent color
+ *
+ * @param preference to set the summary for
+ * @param deviceName bluetooth device name to show in the summary
+ */
+ public void updateDeviceName(final Preference preference, final String deviceName) {
+ if (deviceName == null) {
+ // TODO: show error message in preference subtitle
+ return;
+ }
+ final Spannable spannableName = new SpannableString(deviceName);
+ spannableName.setSpan(new ForegroundColorSpan(mAccentColor), 0,
+ spannableName.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ final CharSequence summary = TextUtils.expandTemplate(
+ mContext.getText(R.string.bluetooth_device_name_summary), spannableName);
+
+ preference.setSummary(summary);
+ }
+
+ /**
+ * Receiver that listens to {@link BluetoothAdapter#ACTION_LOCAL_NAME_CHANGED} and updates the
+ * device name if possible
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (TextUtils.equals(action, BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
+ if (mPreference != null && mLocalAdapter != null && mLocalAdapter.isEnabled()) {
+ updateDeviceName(mPreference, mLocalAdapter.getName());
+ }
+ }
+ }
+ };
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
index 5a3bc36b325..484d4b3d5da 100644
--- a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
@@ -58,7 +58,7 @@ public final class BluetoothNameDialogFragment extends InstrumentedDialogFragmen
// accessed from inner class (not private to avoid thunks)
static final String TAG = "BluetoothNameDialogFragment";
- final LocalBluetoothAdapter mLocalAdapter;
+ LocalBluetoothAdapter mLocalAdapter;
EditText mDeviceNameView;
// This flag is set when the name is updated by code, to distinguish from user changes
@@ -85,16 +85,19 @@ public final class BluetoothNameDialogFragment extends InstrumentedDialogFragmen
}
};
- public BluetoothNameDialogFragment() {
- LocalBluetoothManager localManager = Utils.getLocalBtManager(getActivity());
- mLocalAdapter = localManager.getBluetoothAdapter();
- }
-
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DIALOG_BLUETOOTH_RENAME;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LocalBluetoothManager localManager = Utils.getLocalBtManager(getActivity());
+ mLocalAdapter = localManager.getBluetoothAdapter();
+ }
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String deviceName = mLocalAdapter.getName();
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index d41e1e0eaa8..5b5082d2bb4 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -77,8 +77,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
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 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 =
@@ -94,6 +93,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
private PreferenceGroup mPairedDevicesCategory;
private PreferenceGroup mAvailableDevicesCategory;
+ private Preference mDeviceNamePreference;
private boolean mAvailableDevicesCategoryIsPresent;
private boolean mInitialScanStarted;
@@ -102,6 +102,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
private SwitchBar mSwitchBar;
private final IntentFilter mIntentFilter;
+ private BluetoothDeviceNamePreferenceController mDeviceNamePrefController;
// For Search
private static final String DATA_KEY_REFERENCE = "main_toggle_bluetooth";
@@ -116,25 +117,10 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
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.setTitle(res.getString(
- R.string.bluetooth_is_visible_message,
- bidiFormatter.unicodeWrap(mLocalAdapter.getName())));
- }
- }
};
public BluetoothSettings() {
@@ -172,14 +158,18 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
@Override
void addPreferencesForActivity() {
final Context prefContext = getPrefContext();
+
+ mDeviceNamePreference = mDeviceNamePrefController.createBluetoothDeviceNamePreference(
+ getPreferenceScreen(), 1 /* order */);
+
mPairedDevicesCategory = new PreferenceCategory(prefContext);
mPairedDevicesCategory.setKey(KEY_PAIRED_DEVICES);
- mPairedDevicesCategory.setOrder(1);
+ mPairedDevicesCategory.setOrder(2);
getPreferenceScreen().addPreference(mPairedDevicesCategory);
mAvailableDevicesCategory = new BluetoothProgressCategory(prefContext);
mAvailableDevicesCategory.setSelectable(false);
- mAvailableDevicesCategory.setOrder(2);
+ mAvailableDevicesCategory.setOrder(3);
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
mMyDevicePreference = mFooterPreferenceMixin.createFooterPreference();
@@ -244,9 +234,6 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
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);
@@ -263,13 +250,6 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
}
return true;
- case MENU_ID_RENAME_DEVICE:
- mMetricsFeatureProvider.action(getActivity(),
- MetricsEvent.ACTION_BLUETOOTH_RENAME);
- new BluetoothNameDialogFragment().show(
- getFragmentManager(), "rename device");
- return true;
-
case MENU_ID_SHOW_RECEIVED:
mMetricsFeatureProvider.action(getActivity(),
MetricsEvent.ACTION_BLUETOOTH_FILES);
@@ -334,6 +314,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
break;
}
getPreferenceScreen().removeAll();
+ getPreferenceScreen().addPreference(mDeviceNamePreference);
getPreferenceScreen().addPreference(mPairedDevicesCategory);
getPreferenceScreen().addPreference(mAvailableDevicesCategory);
getPreferenceScreen().addPreference(mMyDevicePreference);
@@ -539,7 +520,12 @@ public class BluetoothSettings extends DeviceListPreferenceFragment implements I
@Override
protected List getPreferenceControllers(Context context) {
- return null;
+ List controllers = new ArrayList<>();
+ mDeviceNamePrefController = new BluetoothDeviceNamePreferenceController(context,
+ this, getLifecycle());
+ controllers.add(mDeviceNamePrefController);
+
+ return controllers;
}
@VisibleForTesting
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java
new file mode 100644
index 00000000000..8d5e7892507
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceControllerTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.StrictMode;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.lifecycle.Lifecycle;
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothDeviceNamePreferenceControllerTest {
+ private static final String DEVICE_NAME = "Nightshade";
+ private static final int ORDER = 1;
+
+ private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Fragment mFragment;
+ @Mock
+ private Lifecycle mLifecycle;
+ @Mock
+ private LocalBluetoothAdapter mLocalAdapter;
+ @Mock
+ private FragmentManager mFragmentManager;
+ @Mock
+ private FragmentTransaction mFragmentTransaction;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ private Preference mPreference;
+
+ private BluetoothDeviceNamePreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
+
+ doReturn(mContext).when(mPreferenceScreen).getContext();
+ mPreference = new Preference(mContext);
+ mPreference.setKey(BluetoothDeviceNamePreferenceController.KEY_DEVICE_NAME);
+ mController = new BluetoothDeviceNamePreferenceController(
+ mContext, mFragment, mLocalAdapter);
+ }
+
+ @Test
+ public void testUpdateDeviceName_showSummaryWithDeviceName() {
+ mController.updateDeviceName(mPreference, DEVICE_NAME);
+
+ final CharSequence summary = mPreference.getSummary();
+ final Object[] spans = ((SpannableStringBuilder) summary).getSpans(0, summary.length(),
+ Object.class);
+ assertThat(summary.toString())
+ .isEqualTo("Visible as Nightshade to other devices");
+
+ // Test summary only has one color span
+ assertThat(spans).asList().hasSize(1);
+ assertThat(spans[0]).isInstanceOf(ForegroundColorSpan.class);
+ }
+
+ @Test
+ public void testCreateBluetoothDeviceNamePreference() {
+ Preference preference = mController.createBluetoothDeviceNamePreference(mPreferenceScreen,
+ ORDER);
+
+ assertThat(preference.getKey()).isEqualTo(mController.KEY_DEVICE_NAME);
+ assertThat(preference.getOrder()).isEqualTo(ORDER);
+ verify(mPreferenceScreen).addPreference(preference);
+ }
+
+ @Test
+ public void testOnStart_receiverRegistered() {
+ mController.onStart();
+ verify(mContext).registerReceiver(eq(mController.mReceiver), any());
+ }
+
+ @Test
+ public void testOnStop_receiverUnregistered() {
+ // register it first
+ mContext.registerReceiver(mController.mReceiver, null);
+
+ mController.onStop();
+ verify(mContext).unregisterReceiver(mController.mReceiver);
+ }
+
+ @Test
+ public void testHandlePreferenceTreeClick_startDialogFragment() {
+ when(mFragment.getFragmentManager().beginTransaction()).thenReturn(mFragmentTransaction);
+
+ mController.handlePreferenceTreeClick(mPreference);
+
+ verify(mFragmentTransaction).add(any(), anyString());
+ verify(mFragmentTransaction).commit();
+ }
+
+}