Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 631120f1 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add bluetooth visible item"

parents b823fcc4 ae86781e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -370,6 +370,9 @@
    <!-- Bluetooth SIM access permission Alert Activity text [CHAR LIMIT=none] -->
    <string name="bluetooth_sap_acceptance_dialog_text"><xliff:g id="device_name">%1$s</xliff:g> 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 <xliff:g id="device_name">%2$s?</xliff:g></string>
    <!-- Description for bluetooth device name summary [CHAR LIMIT=none] -->
    <string name="bluetooth_device_name_summary">Visible as <xliff:g id="device_name">^1</xliff:g> to other devices</string>
    <!-- Date & time settings screen title -->
    <string name="date_and_time">Date &amp; time</string>
    <!-- The title of the activity to pick a time zone. -->
+169 −0
Original line number Diff line number Diff line
/*
 * 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());
                }
            }
        }
    };
}
+9 −6
Original line number Diff line number Diff line
@@ -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();
+16 −30
Original line number Diff line number Diff line
@@ -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<PreferenceController> getPreferenceControllers(Context context) {
        return null;
        List<PreferenceController> controllers = new ArrayList<>();
        mDeviceNamePrefController = new BluetoothDeviceNamePreferenceController(context,
                this, getLifecycle());
        controllers.add(mDeviceNamePrefController);

        return controllers;
    }

    @VisibleForTesting
+139 −0
Original line number Diff line number Diff line
/*
 * 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();
    }

}