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.Activity;
     22 import android.app.AlertDialog;
     23 import android.bluetooth.BluetoothAdapter;
     24 import android.bluetooth.BluetoothDevice;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.DialogInterface;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.res.Resources;
     31 import android.content.SharedPreferences;
     32 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     33 import android.os.Bundle;
     34 import android.preference.Preference;
     35 import android.preference.PreferenceCategory;
     36 import android.preference.PreferenceFragment;
     37 import android.preference.PreferenceGroup;
     38 import android.preference.PreferenceScreen;
     39 import android.util.Log;
     40 import android.view.LayoutInflater;
     41 import android.view.Menu;
     42 import android.view.MenuInflater;
     43 import android.view.MenuItem;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 import android.view.WindowManager;
     47 import android.view.inputmethod.InputMethodManager;
     48 import android.widget.EditText;
     49 import android.widget.TextView;
     50 
     51 import com.android.settings.R;
     52 import com.android.settings.SettingsActivity;
     53 import com.android.settings.search.BaseSearchIndexProvider;
     54 import com.android.settings.search.Index;
     55 import com.android.settings.search.Indexable;
     56 import com.android.settings.search.SearchIndexableRaw;
     57 import com.android.settings.widget.SwitchBar;
     58 
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 import java.util.Set;
     62 
     63 /**
     64  * BluetoothSettings is the Settings screen for Bluetooth configuration and
     65  * connection management.
     66  */
     67 public final class BluetoothSettings extends DeviceListPreferenceFragment implements Indexable {
     68     private static final String TAG = "BluetoothSettings";
     69 
     70     private static final int MENU_ID_SCAN = Menu.FIRST;
     71     private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1;
     72     private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 2;
     73 
     74     /* Private intent to show the list of received files */
     75     private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
     76             "android.btopp.intent.action.OPEN_RECEIVED_FILES";
     77 
     78     private static View mSettingsDialogView = null;
     79 
     80     private BluetoothEnabler mBluetoothEnabler;
     81 
     82     private PreferenceGroup mPairedDevicesCategory;
     83     private PreferenceGroup mAvailableDevicesCategory;
     84     private boolean mAvailableDevicesCategoryIsPresent;
     85 
     86     private boolean mInitialScanStarted;
     87     private boolean mInitiateDiscoverable;
     88 
     89     private TextView mEmptyView;
     90     private SwitchBar mSwitchBar;
     91 
     92     private final IntentFilter mIntentFilter;
     93 
     94 
     95     // accessed from inner class (not private to avoid thunks)
     96     Preference mMyDevicePreference;
     97 
     98     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     99         @Override
    100         public void onReceive(Context context, Intent intent) {
    101             final String action = intent.getAction();
    102             final int state =
    103                 intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
    104 
    105             if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
    106                 updateDeviceName(context);
    107             }
    108 
    109             if (state == BluetoothAdapter.STATE_ON) {
    110                 mInitiateDiscoverable = true;
    111             }
    112         }
    113 
    114         private void updateDeviceName(Context context) {
    115             if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
    116                 mMyDevicePreference.setSummary(context.getResources().getString(
    117                             R.string.bluetooth_is_visible_message, mLocalAdapter.getName()));
    118             }
    119         }
    120     };
    121 
    122     public BluetoothSettings() {
    123         super(DISALLOW_CONFIG_BLUETOOTH);
    124         mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
    125     }
    126 
    127     @Override
    128     public void onActivityCreated(Bundle savedInstanceState) {
    129         super.onActivityCreated(savedInstanceState);
    130         mInitialScanStarted = (savedInstanceState != null);    // don't auto start scan after rotation
    131         mInitiateDiscoverable = true;
    132 
    133         mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
    134         getListView().setEmptyView(mEmptyView);
    135 
    136         final SettingsActivity activity = (SettingsActivity) getActivity();
    137         mSwitchBar = activity.getSwitchBar();
    138 
    139         mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar);
    140         mBluetoothEnabler.setupSwitchBar();
    141     }
    142 
    143     @Override
    144     public void onDestroyView() {
    145         super.onDestroyView();
    146 
    147         mBluetoothEnabler.teardownSwitchBar();
    148     }
    149 
    150     @Override
    151     void addPreferencesForActivity() {
    152         addPreferencesFromResource(R.xml.bluetooth_settings);
    153 
    154         setHasOptionsMenu(true);
    155     }
    156 
    157     @Override
    158     public void onResume() {
    159         // resume BluetoothEnabler before calling super.onResume() so we don't get
    160         // any onDeviceAdded() callbacks before setting up view in updateContent()
    161         if (mBluetoothEnabler != null) {
    162             mBluetoothEnabler.resume(getActivity());
    163         }
    164         super.onResume();
    165 
    166         mInitiateDiscoverable = true;
    167 
    168         if (isUiRestricted()) {
    169             setDeviceListGroup(getPreferenceScreen());
    170             removeAllDevices();
    171             mEmptyView.setText(R.string.bluetooth_empty_list_user_restricted);
    172             return;
    173         }
    174 
    175         getActivity().registerReceiver(mReceiver, mIntentFilter);
    176         if (mLocalAdapter != null) {
    177             updateContent(mLocalAdapter.getBluetoothState());
    178         }
    179     }
    180 
    181     @Override
    182     public void onPause() {
    183         super.onPause();
    184         if (mBluetoothEnabler != null) {
    185             mBluetoothEnabler.pause();
    186         }
    187 
    188         // Make the device only visible to connected devices.
    189         mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
    190 
    191         if (isUiRestricted()) {
    192             return;
    193         }
    194 
    195         getActivity().unregisterReceiver(mReceiver);
    196     }
    197 
    198     @Override
    199     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    200         if (mLocalAdapter == null) return;
    201         // If the user is not allowed to configure bluetooth, do not show the menu.
    202         if (isUiRestricted()) return;
    203 
    204         boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
    205         boolean isDiscovering = mLocalAdapter.isDiscovering();
    206         int textId = isDiscovering ? R.string.bluetooth_searching_for_devices :
    207             R.string.bluetooth_search_for_devices;
    208         menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId)
    209                 .setEnabled(bluetoothIsEnabled && !isDiscovering)
    210                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    211         menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
    212                 .setEnabled(bluetoothIsEnabled)
    213                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    214         menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
    215                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
    216         super.onCreateOptionsMenu(menu, inflater);
    217     }
    218 
    219     @Override
    220     public boolean onOptionsItemSelected(MenuItem item) {
    221         switch (item.getItemId()) {
    222             case MENU_ID_SCAN:
    223                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
    224                     startScanning();
    225                 }
    226                 return true;
    227 
    228             case MENU_ID_RENAME_DEVICE:
    229                 new BluetoothNameDialogFragment().show(
    230                         getFragmentManager(), "rename device");
    231                 return true;
    232 
    233             case MENU_ID_SHOW_RECEIVED:
    234                 Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
    235                 getActivity().sendBroadcast(intent);
    236                 return true;
    237         }
    238         return super.onOptionsItemSelected(item);
    239     }
    240 
    241     private void startScanning() {
    242         if (isUiRestricted()) {
    243             return;
    244         }
    245 
    246         if (!mAvailableDevicesCategoryIsPresent) {
    247             getPreferenceScreen().addPreference(mAvailableDevicesCategory);
    248             mAvailableDevicesCategoryIsPresent = true;
    249         }
    250 
    251         if (mAvailableDevicesCategory != null) {
    252             setDeviceListGroup(mAvailableDevicesCategory);
    253             removeAllDevices();
    254         }
    255 
    256         mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
    257         mAvailableDevicesCategory.removeAll();
    258         mInitialScanStarted = true;
    259         mLocalAdapter.startScanning(true);
    260     }
    261 
    262     @Override
    263     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
    264         mLocalAdapter.stopScanning();
    265         super.onDevicePreferenceClick(btPreference);
    266     }
    267 
    268     private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
    269             BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) {
    270         preferenceGroup.setTitle(titleId);
    271         getPreferenceScreen().addPreference(preferenceGroup);
    272         setFilter(filter);
    273         setDeviceListGroup(preferenceGroup);
    274         if (addCachedDevices) {
    275             addCachedDevices();
    276         }
    277         preferenceGroup.setEnabled(true);
    278     }
    279 
    280     private void updateContent(int bluetoothState) {
    281         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    282         int messageId = 0;
    283 
    284         switch (bluetoothState) {
    285             case BluetoothAdapter.STATE_ON:
    286                 preferenceScreen.removeAll();
    287                 preferenceScreen.setOrderingAsAdded(true);
    288                 mDevicePreferenceMap.clear();
    289 
    290                 if (isUiRestricted()) {
    291                     messageId = R.string.bluetooth_empty_list_user_restricted;
    292                     break;
    293                 }
    294 
    295                 // Paired devices category
    296                 if (mPairedDevicesCategory == null) {
    297                     mPairedDevicesCategory = new PreferenceCategory(getActivity());
    298                 } else {
    299                     mPairedDevicesCategory.removeAll();
    300                 }
    301                 addDeviceCategory(mPairedDevicesCategory,
    302                         R.string.bluetooth_preference_paired_devices,
    303                         BluetoothDeviceFilter.BONDED_DEVICE_FILTER, true);
    304                 int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
    305 
    306                 if (isUiRestricted() || numberOfPairedDevices <= 0) {
    307                     preferenceScreen.removePreference(mPairedDevicesCategory);
    308                 }
    309 
    310                 // Available devices category
    311                 if (mAvailableDevicesCategory == null) {
    312                     mAvailableDevicesCategory = new BluetoothProgressCategory(getActivity());
    313                     mAvailableDevicesCategory.setSelectable(false);
    314                 } else {
    315                     mAvailableDevicesCategory.removeAll();
    316                 }
    317                 addDeviceCategory(mAvailableDevicesCategory,
    318                         R.string.bluetooth_preference_found_devices,
    319                         BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER, mInitialScanStarted);
    320                 int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount();
    321 
    322                 if (!mInitialScanStarted) {
    323                     startScanning();
    324                 }
    325 
    326                 if (mMyDevicePreference == null) {
    327                     mMyDevicePreference = new Preference(getActivity());
    328                 }
    329 
    330                 mMyDevicePreference.setSummary(getResources().getString(
    331                             R.string.bluetooth_is_visible_message, mLocalAdapter.getName()));
    332                 mMyDevicePreference.setSelectable(false);
    333                 preferenceScreen.addPreference(mMyDevicePreference);
    334 
    335                 getActivity().invalidateOptionsMenu();
    336 
    337                 // mLocalAdapter.setScanMode is internally synchronized so it is okay for multiple
    338                 // threads to execute.
    339                 if (mInitiateDiscoverable) {
    340                     // Make the device visible to other devices.
    341                     mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
    342                     mInitiateDiscoverable = false;
    343                 }
    344                 return; // not break
    345 
    346             case BluetoothAdapter.STATE_TURNING_OFF:
    347                 messageId = R.string.bluetooth_turning_off;
    348                 break;
    349 
    350             case BluetoothAdapter.STATE_OFF:
    351                 messageId = R.string.bluetooth_empty_list_bluetooth_off;
    352                 if (isUiRestricted()) {
    353                     messageId = R.string.bluetooth_empty_list_user_restricted;
    354                 }
    355                 break;
    356 
    357             case BluetoothAdapter.STATE_TURNING_ON:
    358                 messageId = R.string.bluetooth_turning_on;
    359                 mInitialScanStarted = false;
    360                 break;
    361         }
    362 
    363         setDeviceListGroup(preferenceScreen);
    364         removeAllDevices();
    365         mEmptyView.setText(messageId);
    366         if (!isUiRestricted()) {
    367             getActivity().invalidateOptionsMenu();
    368         }
    369     }
    370 
    371     @Override
    372     public void onBluetoothStateChanged(int bluetoothState) {
    373         super.onBluetoothStateChanged(bluetoothState);
    374         updateContent(bluetoothState);
    375     }
    376 
    377     @Override
    378     public void onScanningStateChanged(boolean started) {
    379         super.onScanningStateChanged(started);
    380         // Update options' enabled state
    381         if (getActivity() != null) {
    382             getActivity().invalidateOptionsMenu();
    383         }
    384     }
    385 
    386     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
    387         setDeviceListGroup(getPreferenceScreen());
    388         removeAllDevices();
    389         updateContent(mLocalAdapter.getBluetoothState());
    390     }
    391 
    392     private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
    393         public void onClick(View v) {
    394             // User clicked on advanced options icon for a device in the list
    395             if (!(v.getTag() instanceof CachedBluetoothDevice)) {
    396                 Log.w(TAG, "onClick() called for other View: " + v);
    397                 return;
    398             }
    399 
    400             final CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
    401             final Activity activity = getActivity();
    402             DeviceProfilesSettings profileFragment = (DeviceProfilesSettings)activity.
    403                 getFragmentManager().findFragmentById(R.id.bluetooth_fragment_settings);
    404 
    405             if (mSettingsDialogView != null){
    406                 ViewGroup parent = (ViewGroup) mSettingsDialogView.getParent();
    407                 if (parent != null) {
    408                     parent.removeView(mSettingsDialogView);
    409                 }
    410             }
    411 
    412             if (profileFragment == null) {
    413                 LayoutInflater inflater = getActivity().getLayoutInflater();
    414                 mSettingsDialogView = inflater.inflate(R.layout.bluetooth_device_settings, null);
    415                 profileFragment = (DeviceProfilesSettings)activity.getFragmentManager()
    416                     .findFragmentById(R.id.bluetooth_fragment_settings);
    417 
    418                 // To enable scrolling we store the name field in a seperate header and add to
    419                 // the ListView of the profileFragment.
    420                 View header = inflater.inflate(R.layout.bluetooth_device_settings_header, null);
    421                 profileFragment.getListView().addHeaderView(header);
    422             }
    423 
    424             final View dialogLayout = mSettingsDialogView;
    425             AlertDialog.Builder settingsDialog = new AlertDialog.Builder(activity);
    426             profileFragment.setDevice(device);
    427             final EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
    428             deviceName.setText(device.getName(), TextView.BufferType.EDITABLE);
    429 
    430             final DeviceProfilesSettings dpsFragment = profileFragment;
    431             final Context context = v.getContext();
    432             settingsDialog.setView(dialogLayout);
    433             settingsDialog.setTitle(R.string.bluetooth_preference_paired_devices);
    434             settingsDialog.setPositiveButton(R.string.okay,
    435                     new DialogInterface.OnClickListener() {
    436                 @Override
    437                 public void onClick(DialogInterface dialog, int which) {
    438                     EditText deviceName = (EditText)dialogLayout.findViewById(R.id.name);
    439                     device.setName(deviceName.getText().toString());
    440                 }
    441             });
    442 
    443             settingsDialog.setNegativeButton(R.string.forget,
    444                     new DialogInterface.OnClickListener() {
    445                 @Override
    446                 public void onClick(DialogInterface dialog, int which) {
    447                     device.unpair();
    448                     com.android.settings.bluetooth.Utils.updateSearchIndex(activity,
    449                             BluetoothSettings.class.getName(), device.getName(),
    450                             context.getResources().getString(R.string.bluetooth_settings),
    451                             R.drawable.ic_settings_bluetooth2, false);
    452                 }
    453             });
    454 
    455             // We must ensure that the fragment gets destroyed to avoid duplicate fragments.
    456             settingsDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
    457                 public void onDismiss(final DialogInterface dialog) {
    458                     if (!activity.isDestroyed()) {
    459                         activity.getFragmentManager().beginTransaction().remove(dpsFragment)
    460                             .commitAllowingStateLoss();
    461                     }
    462                 }
    463             });
    464 
    465             AlertDialog dialog = settingsDialog.create();
    466             dialog.create();
    467             dialog.show();
    468 
    469             // We must ensure that clicking on the EditText will bring up the keyboard.
    470             dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    471                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    472         }
    473     };
    474 
    475     /**
    476      * Add a listener, which enables the advanced settings icon.
    477      * @param preference the newly added preference
    478      */
    479     @Override
    480     void initDevicePreference(BluetoothDevicePreference preference) {
    481         CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
    482         if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
    483             // Only paired device have an associated advanced settings screen
    484             preference.setOnSettingsClickListener(mDeviceProfilesListener);
    485         }
    486     }
    487 
    488     @Override
    489     protected int getHelpResource() {
    490         return R.string.help_url_bluetooth;
    491     }
    492 
    493     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
    494         new BaseSearchIndexProvider() {
    495             @Override
    496             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
    497 
    498                 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
    499 
    500                 final Resources res = context.getResources();
    501 
    502                 // Add fragment title
    503                 SearchIndexableRaw data = new SearchIndexableRaw(context);
    504                 data.title = res.getString(R.string.bluetooth_settings);
    505                 data.screenTitle = res.getString(R.string.bluetooth_settings);
    506                 result.add(data);
    507 
    508                 // Add cached paired BT devices
    509                 LocalBluetoothManager lbtm = LocalBluetoothManager.getInstance(context);
    510                 // LocalBluetoothManager.getInstance can return null if the device does not
    511                 // support bluetooth (e.g. the emulator).
    512                 if (lbtm != null) {
    513                     Set<BluetoothDevice> bondedDevices =
    514                             lbtm.getBluetoothAdapter().getBondedDevices();
    515 
    516                     for (BluetoothDevice device : bondedDevices) {
    517                         data = new SearchIndexableRaw(context);
    518                         data.title = device.getName();
    519                         data.screenTitle = res.getString(R.string.bluetooth_settings);
    520                         data.enabled = enabled;
    521                         result.add(data);
    522                     }
    523                 }
    524                 return result;
    525             }
    526         };
    527 }
    528