Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.bluetooth;
     18 
     19 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
     20 
     21 import android.app.ActionBar;
     22 import android.app.Activity;
     23 import android.bluetooth.BluetoothAdapter;
     24 import android.bluetooth.BluetoothDevice;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.os.Bundle;
     30 import android.preference.Preference;
     31 import android.preference.PreferenceActivity;
     32 import android.preference.PreferenceCategory;
     33 import android.preference.PreferenceGroup;
     34 import android.preference.PreferenceScreen;
     35 import android.util.Log;
     36 import android.view.Gravity;
     37 import android.view.Menu;
     38 import android.view.MenuInflater;
     39 import android.view.MenuItem;
     40 import android.view.View;
     41 import android.widget.Switch;
     42 import android.widget.TextView;
     43 
     44 import com.android.settings.R;
     45 
     46 /**
     47  * BluetoothSettings is the Settings screen for Bluetooth configuration and
     48  * connection management.
     49  */
     50 public final class BluetoothSettings extends DeviceListPreferenceFragment {
     51     private static final String TAG = "BluetoothSettings";
     52 
     53     private static final int MENU_ID_SCAN = Menu.FIRST;
     54     private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1;
     55     private static final int MENU_ID_VISIBILITY_TIMEOUT = Menu.FIRST + 2;
     56     private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 3;
     57 
     58     /* Private intent to show the list of received files */
     59     private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
     60             "android.btopp.intent.action.OPEN_RECEIVED_FILES";
     61 
     62     private BluetoothEnabler mBluetoothEnabler;
     63 
     64     private BluetoothDiscoverableEnabler mDiscoverableEnabler;
     65 
     66     private PreferenceGroup mPairedDevicesCategory;
     67 
     68     private PreferenceGroup mAvailableDevicesCategory;
     69     private boolean mAvailableDevicesCategoryIsPresent;
     70     private boolean mActivityStarted;
     71 
     72     private TextView mEmptyView;
     73 
     74     private final IntentFilter mIntentFilter;
     75 
     76 
     77     // accessed from inner class (not private to avoid thunks)
     78     Preference mMyDevicePreference;
     79 
     80     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     81         @Override
     82         public void onReceive(Context context, Intent intent) {
     83             String action = intent.getAction();
     84             if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
     85                 updateDeviceName();
     86             }
     87         }
     88 
     89         private void updateDeviceName() {
     90             if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
     91                 mMyDevicePreference.setTitle(mLocalAdapter.getName());
     92             }
     93         }
     94     };
     95 
     96     public BluetoothSettings() {
     97         super(DISALLOW_CONFIG_BLUETOOTH);
     98         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
     99     }
    100 
    101     @Override
    102     public void onActivityCreated(Bundle savedInstanceState) {
    103         super.onActivityCreated(savedInstanceState);
    104         mActivityStarted = (savedInstanceState == null);    // don't auto start scan after rotation
    105 
    106         mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
    107         getListView().setEmptyView(mEmptyView);
    108     }
    109 
    110     @Override
    111     void addPreferencesForActivity() {
    112         addPreferencesFromResource(R.xml.bluetooth_settings);
    113 
    114         Activity activity = getActivity();
    115 
    116         Switch actionBarSwitch = new Switch(activity);
    117 
    118         if (activity instanceof PreferenceActivity) {
    119             PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
    120             if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
    121                 final int padding = activity.getResources().getDimensionPixelSize(
    122                         R.dimen.action_bar_switch_padding);
    123                 actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
    124                 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
    125                         ActionBar.DISPLAY_SHOW_CUSTOM);
    126                 activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
    127                         ActionBar.LayoutParams.WRAP_CONTENT,
    128                         ActionBar.LayoutParams.WRAP_CONTENT,
    129                         Gravity.CENTER_VERTICAL | Gravity.END));
    130             }
    131         }
    132 
    133         mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
    134 
    135         setHasOptionsMenu(true);
    136     }
    137 
    138     @Override
    139     public void onResume() {
    140         // resume BluetoothEnabler before calling super.onResume() so we don't get
    141         // any onDeviceAdded() callbacks before setting up view in updateContent()
    142         if (mBluetoothEnabler != null) {
    143             mBluetoothEnabler.resume();
    144         }
    145         super.onResume();
    146 
    147         if (mDiscoverableEnabler != null) {
    148             mDiscoverableEnabler.resume();
    149         }
    150         getActivity().registerReceiver(mReceiver, mIntentFilter);
    151         if (mLocalAdapter != null) {
    152             updateContent(mLocalAdapter.getBluetoothState(), mActivityStarted);
    153         }
    154     }
    155 
    156     @Override
    157     public void onPause() {
    158         super.onPause();
    159         if (mBluetoothEnabler != null) {
    160             mBluetoothEnabler.pause();
    161         }
    162         getActivity().unregisterReceiver(mReceiver);
    163         if (mDiscoverableEnabler != null) {
    164             mDiscoverableEnabler.pause();
    165         }
    166     }
    167 
    168     @Override
    169     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    170         if (mLocalAdapter == null) return;
    171         // If the user is not allowed to configure bluetooth, do not show the menu.
    172         if (isRestrictedAndNotPinProtected()) return;
    173 
    174         boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
    175         boolean isDiscovering = mLocalAdapter.isDiscovering();
    176         int textId = isDiscovering ? R.string.bluetooth_searching_for_devices :
    177             R.string.bluetooth_search_for_devices;
    178         menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId)
    179                 .setEnabled(bluetoothIsEnabled && !isDiscovering)
    180                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    181         menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
    182                 .setEnabled(bluetoothIsEnabled)
    183                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    184         menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout)
    185                 .setEnabled(bluetoothIsEnabled)
    186                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    187         menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
    188                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    189         super.onCreateOptionsMenu(menu, inflater);
    190     }
    191 
    192     @Override
    193     public boolean onOptionsItemSelected(MenuItem item) {
    194         switch (item.getItemId()) {
    195             case MENU_ID_SCAN:
    196                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
    197                     startScanning();
    198                 }
    199                 return true;
    200 
    201             case MENU_ID_RENAME_DEVICE:
    202                 new BluetoothNameDialogFragment().show(
    203                         getFragmentManager(), "rename device");
    204                 return true;
    205 
    206             case MENU_ID_VISIBILITY_TIMEOUT:
    207                 new BluetoothVisibilityTimeoutFragment().show(
    208                         getFragmentManager(), "visibility timeout");
    209                 return true;
    210 
    211             case MENU_ID_SHOW_RECEIVED:
    212                 Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
    213                 getActivity().sendBroadcast(intent);
    214                 return true;
    215         }
    216         return super.onOptionsItemSelected(item);
    217     }
    218 
    219     private void startScanning() {
    220         if (isRestrictedAndNotPinProtected()) return;
    221         if (!mAvailableDevicesCategoryIsPresent) {
    222             getPreferenceScreen().addPreference(mAvailableDevicesCategory);
    223         }
    224         mLocalAdapter.startScanning(true);
    225     }
    226 
    227     @Override
    228     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
    229         mLocalAdapter.stopScanning();
    230         super.onDevicePreferenceClick(btPreference);
    231     }
    232 
    233     private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
    234             BluetoothDeviceFilter.Filter filter) {
    235         preferenceGroup.setTitle(titleId);
    236         getPreferenceScreen().addPreference(preferenceGroup);
    237         setFilter(filter);
    238         setDeviceListGroup(preferenceGroup);
    239         addCachedDevices();
    240         preferenceGroup.setEnabled(true);
    241     }
    242 
    243     private void updateContent(int bluetoothState, boolean scanState) {
    244         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    245         int messageId = 0;
    246 
    247         switch (bluetoothState) {
    248             case BluetoothAdapter.STATE_ON:
    249                 preferenceScreen.removeAll();
    250                 preferenceScreen.setOrderingAsAdded(true);
    251                 mDevicePreferenceMap.clear();
    252 
    253                 // This device
    254                 if (mMyDevicePreference == null) {
    255                     mMyDevicePreference = new Preference(getActivity());
    256                 }
    257                 mMyDevicePreference.setTitle(mLocalAdapter.getName());
    258                 if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
    259                     mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone);    // for phones
    260                 } else {
    261                     mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop);   // for tablets, etc.
    262                 }
    263                 mMyDevicePreference.setPersistent(false);
    264                 mMyDevicePreference.setEnabled(true);
    265                 preferenceScreen.addPreference(mMyDevicePreference);
    266 
    267                 if (!isRestrictedAndNotPinProtected()) {
    268                     if (mDiscoverableEnabler == null) {
    269                         mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
    270                                 mLocalAdapter, mMyDevicePreference);
    271                         mDiscoverableEnabler.resume();
    272                         LocalBluetoothManager.getInstance(getActivity()).setDiscoverableEnabler(
    273                                 mDiscoverableEnabler);
    274                     }
    275                 }
    276 
    277                 // Paired devices category
    278                 if (mPairedDevicesCategory == null) {
    279                     mPairedDevicesCategory = new PreferenceCategory(getActivity());
    280                 } else {
    281                     mPairedDevicesCategory.removeAll();
    282                 }
    283                 addDeviceCategory(mPairedDevicesCategory,
    284                         R.string.bluetooth_preference_paired_devices,
    285                         BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
    286                 int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
    287 
    288                 if (mDiscoverableEnabler != null) {
    289                     mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices);
    290                 }
    291 
    292                 // Available devices category
    293                 if (mAvailableDevicesCategory == null) {
    294                     mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity(), null);
    295                 } else {
    296                     mAvailableDevicesCategory.removeAll();
    297                 }
    298                 if (!isRestrictedAndNotPinProtected()) {
    299                     addDeviceCategory(mAvailableDevicesCategory,
    300                             R.string.bluetooth_preference_found_devices,
    301                             BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
    302                 }
    303                 int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount();
    304                 mAvailableDevicesCategoryIsPresent = true;
    305 
    306                 if (numberOfAvailableDevices == 0) {
    307                     preferenceScreen.removePreference(mAvailableDevicesCategory);
    308                     mAvailableDevicesCategoryIsPresent = false;
    309                 }
    310 
    311                 if (numberOfPairedDevices == 0) {
    312                     preferenceScreen.removePreference(mPairedDevicesCategory);
    313                     if (scanState == true) {
    314                         mActivityStarted = false;
    315                         startScanning();
    316                     } else {
    317                         if (!mAvailableDevicesCategoryIsPresent) {
    318                             getPreferenceScreen().addPreference(mAvailableDevicesCategory);
    319                         }
    320                     }
    321                 }
    322                 getActivity().invalidateOptionsMenu();
    323                 return; // not break
    324 
    325             case BluetoothAdapter.STATE_TURNING_OFF:
    326                 messageId = R.string.bluetooth_turning_off;
    327                 break;
    328 
    329             case BluetoothAdapter.STATE_OFF:
    330                 messageId = R.string.bluetooth_empty_list_bluetooth_off;
    331                 break;
    332 
    333             case BluetoothAdapter.STATE_TURNING_ON:
    334                 messageId = R.string.bluetooth_turning_on;
    335                 break;
    336         }
    337 
    338         setDeviceListGroup(preferenceScreen);
    339         removeAllDevices();
    340         mEmptyView.setText(messageId);
    341         getActivity().invalidateOptionsMenu();
    342     }
    343 
    344     @Override
    345     public void onBluetoothStateChanged(int bluetoothState) {
    346         super.onBluetoothStateChanged(bluetoothState);
    347         updateContent(bluetoothState, true);
    348     }
    349 
    350     @Override
    351     public void onScanningStateChanged(boolean started) {
    352         super.onScanningStateChanged(started);
    353         // Update options' enabled state
    354         getActivity().invalidateOptionsMenu();
    355     }
    356 
    357     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
    358         setDeviceListGroup(getPreferenceScreen());
    359         removeAllDevices();
    360         updateContent(mLocalAdapter.getBluetoothState(), false);
    361     }
    362 
    363     private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
    364         public void onClick(View v) {
    365             // User clicked on advanced options icon for a device in the list
    366             if (v.getTag() instanceof CachedBluetoothDevice) {
    367                 if (isRestrictedAndNotPinProtected()) return;
    368 
    369                 CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
    370 
    371                 Bundle args = new Bundle(1);
    372                 args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice());
    373 
    374                 ((PreferenceActivity) getActivity()).startPreferencePanel(
    375                         DeviceProfilesSettings.class.getName(), args,
    376                         R.string.bluetooth_device_advanced_title, null, null, 0);
    377             } else {
    378                 Log.w(TAG, "onClick() called for other View: " + v); // TODO remove
    379             }
    380         }
    381     };
    382 
    383     /**
    384      * Add a listener, which enables the advanced settings icon.
    385      * @param preference the newly added preference
    386      */
    387     @Override
    388     void initDevicePreference(BluetoothDevicePreference preference) {
    389         CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
    390         if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
    391             // Only paired device have an associated advanced settings screen
    392             preference.setOnSettingsClickListener(mDeviceProfilesListener);
    393         }
    394     }
    395 
    396     @Override
    397     protected int getHelpResource() {
    398         return R.string.help_url_bluetooth;
    399     }
    400 }
    401