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