DeviceProfilesSettings.java revision fa46de5c68f078a595aa61d61ceae6b7951c3aa7
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.AlertDialog;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothProfile;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.os.Bundle;
25import android.preference.CheckBoxPreference;
26import android.preference.EditTextPreference;
27import android.preference.Preference;
28import android.preference.PreferenceGroup;
29import android.preference.PreferenceScreen;
30import android.text.Html;
31import android.text.TextUtils;
32import android.util.Log;
33import android.view.View;
34import android.widget.EditText;
35import android.text.TextWatcher;
36import android.app.Dialog;
37import android.widget.Button;
38import android.text.Editable;
39import com.android.settings.R;
40import com.android.settings.SettingsPreferenceFragment;
41import com.android.settings.search.Index;
42import com.android.settings.search.SearchIndexableRaw;
43
44import java.util.HashMap;
45
46/**
47 * This preference fragment presents the user with all of the profiles
48 * for a particular device, and allows them to be individually connected
49 * (or disconnected).
50 */
51public final class DeviceProfilesSettings extends SettingsPreferenceFragment
52        implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
53    private static final String TAG = "DeviceProfilesSettings";
54
55    private static final String KEY_RENAME_DEVICE = "rename_device";
56    private static final String KEY_PROFILE_CONTAINER = "profile_container";
57    private static final String KEY_UNPAIR = "unpair";
58
59    public static final String EXTRA_DEVICE = "device";
60    private RenameEditTextPreference mRenameDeviceNamePref;
61    private LocalBluetoothManager mManager;
62    private CachedBluetoothDevice mCachedDevice;
63    private LocalBluetoothProfileManager mProfileManager;
64
65    private PreferenceGroup mProfileContainer;
66    private EditTextPreference mDeviceNamePref;
67
68    private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs
69            = new HashMap<LocalBluetoothProfile, CheckBoxPreference>();
70
71    private AlertDialog mDisconnectDialog;
72    private boolean mProfileGroupIsRemoved;
73
74    private class RenameEditTextPreference implements TextWatcher{
75        public void afterTextChanged(Editable s) {
76            Dialog d = mDeviceNamePref.getDialog();
77            if (d instanceof AlertDialog) {
78                ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0);
79            }
80        }
81
82        // TextWatcher interface
83        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
84            // not used
85        }
86
87        // TextWatcher interface
88        public void onTextChanged(CharSequence s, int start, int before, int count) {
89            // not used
90        }
91    }
92
93    @Override
94    public void onCreate(Bundle savedInstanceState) {
95        super.onCreate(savedInstanceState);
96
97        BluetoothDevice device;
98        if (savedInstanceState != null) {
99            device = savedInstanceState.getParcelable(EXTRA_DEVICE);
100        } else {
101            Bundle args = getArguments();
102            device = args.getParcelable(EXTRA_DEVICE);
103        }
104
105        addPreferencesFromResource(R.xml.bluetooth_device_advanced);
106        getPreferenceScreen().setOrderingAsAdded(false);
107        mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
108        mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
109
110        if (device == null) {
111            Log.w(TAG, "Activity started without a remote Bluetooth device");
112            finish();
113            return;  // TODO: test this failure path
114        }
115        mRenameDeviceNamePref = new RenameEditTextPreference();
116        mManager = LocalBluetoothManager.getInstance(getActivity());
117        CachedBluetoothDeviceManager deviceManager =
118                mManager.getCachedDeviceManager();
119        mProfileManager = mManager.getProfileManager();
120        mCachedDevice = deviceManager.findDevice(device);
121        if (mCachedDevice == null) {
122            Log.w(TAG, "Device not found, cannot connect to it");
123            finish();
124            return;  // TODO: test this failure path
125        }
126
127        String deviceName = mCachedDevice.getName();
128        mDeviceNamePref.setSummary(deviceName);
129        mDeviceNamePref.setText(deviceName);
130        mDeviceNamePref.setOnPreferenceChangeListener(this);
131
132        // Add a preference for each profile
133        addPreferencesForProfiles();
134    }
135
136    @Override
137    public void onDestroy() {
138        super.onDestroy();
139        if (mDisconnectDialog != null) {
140            mDisconnectDialog.dismiss();
141            mDisconnectDialog = null;
142        }
143    }
144
145    @Override
146    public void onSaveInstanceState(Bundle outState) {
147        super.onSaveInstanceState(outState);
148        outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice());
149    }
150
151    @Override
152    public void onResume() {
153        super.onResume();
154
155        mManager.setForegroundActivity(getActivity());
156        mCachedDevice.registerCallback(this);
157        if(mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
158            finish();
159        refresh();
160        EditText et = mDeviceNamePref.getEditText();
161        if (et != null) {
162            et.addTextChangedListener(mRenameDeviceNamePref);
163            Dialog d = mDeviceNamePref.getDialog();
164            if (d instanceof AlertDialog) {
165                Button b = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
166                b.setEnabled(et.getText().length() > 0);
167            }
168        }
169    }
170
171    @Override
172    public void onPause() {
173        super.onPause();
174
175        mCachedDevice.unregisterCallback(this);
176        mManager.setForegroundActivity(null);
177    }
178
179    private void addPreferencesForProfiles() {
180        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
181            Preference pref = createProfilePreference(profile);
182            mProfileContainer.addPreference(pref);
183        }
184        showOrHideProfileGroup();
185    }
186
187    private void showOrHideProfileGroup() {
188        int numProfiles = mProfileContainer.getPreferenceCount();
189        if (!mProfileGroupIsRemoved && numProfiles == 0) {
190            getPreferenceScreen().removePreference(mProfileContainer);
191            mProfileGroupIsRemoved = true;
192        } else if (mProfileGroupIsRemoved && numProfiles != 0) {
193            getPreferenceScreen().addPreference(mProfileContainer);
194            mProfileGroupIsRemoved = false;
195        }
196    }
197
198    /**
199     * Creates a checkbox preference for the particular profile. The key will be
200     * the profile's name.
201     *
202     * @param profile The profile for which the preference controls.
203     * @return A preference that allows the user to choose whether this profile
204     *         will be connected to.
205     */
206    private CheckBoxPreference createProfilePreference(LocalBluetoothProfile profile) {
207        CheckBoxPreference pref = new CheckBoxPreference(getActivity());
208        pref.setKey(profile.toString());
209        pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
210        pref.setPersistent(false);
211        pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal()));
212        pref.setOnPreferenceChangeListener(this);
213
214        int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
215        if (iconResource != 0) {
216            pref.setIcon(getResources().getDrawable(iconResource));
217        }
218
219        /**
220         * Gray out profile while connecting and disconnecting
221         */
222        pref.setEnabled(!mCachedDevice.isBusy());
223
224        refreshProfilePreference(pref, profile);
225
226        return pref;
227    }
228
229    @Override
230    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
231        String key = preference.getKey();
232        if (key.equals(KEY_UNPAIR)) {
233            unpairDevice();
234            finish();
235            final Context context = preference.getContext();
236
237            SearchIndexableRaw data = new SearchIndexableRaw(context);
238            data.className = BluetoothSettings.class.getName();
239            data.title = mCachedDevice.getName();
240            data.screenTitle = context.getResources().getString(R.string.bluetooth_settings);
241            data.iconResId = R.drawable.ic_settings_bluetooth2;
242            data.enabled = false;
243
244            Index.getInstance(context).updateFromSearchIndexableData(data);
245            return true;
246        }
247
248        return super.onPreferenceTreeClick(screen, preference);
249    }
250
251    public boolean onPreferenceChange(Preference preference, Object newValue) {
252        if (preference == mDeviceNamePref) {
253            mCachedDevice.setName((String) newValue);
254        } else if (preference instanceof CheckBoxPreference) {
255            LocalBluetoothProfile prof = getProfileOf(preference);
256            onProfileClicked(prof, (CheckBoxPreference) preference);
257            return false;   // checkbox will update from onDeviceAttributesChanged() callback
258        } else {
259            return false;
260        }
261
262        return true;
263    }
264
265    private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
266        BluetoothDevice device = mCachedDevice.getDevice();
267
268        int status = profile.getConnectionStatus(device);
269        boolean isConnected =
270                status == BluetoothProfile.STATE_CONNECTED;
271
272        if (isConnected) {
273            askDisconnect(getActivity(), profile);
274        } else {
275            if (profile.isPreferred(device)) {
276                // profile is preferred but not connected: disable auto-connect
277                profile.setPreferred(device, false);
278                refreshProfilePreference(profilePref, profile);
279            } else {
280                profile.setPreferred(device, true);
281                mCachedDevice.connectProfile(profile);
282            }
283        }
284    }
285
286    private void askDisconnect(Context context,
287            final LocalBluetoothProfile profile) {
288        // local reference for callback
289        final CachedBluetoothDevice device = mCachedDevice;
290        String name = device.getName();
291        if (TextUtils.isEmpty(name)) {
292            name = context.getString(R.string.bluetooth_device);
293        }
294
295        String profileName = context.getString(profile.getNameResource(device.getDevice()));
296
297        String title = context.getString(R.string.bluetooth_disable_profile_title);
298        String message = context.getString(R.string.bluetooth_disable_profile_message,
299                profileName, name);
300
301        DialogInterface.OnClickListener disconnectListener =
302                new DialogInterface.OnClickListener() {
303            public void onClick(DialogInterface dialog, int which) {
304                device.disconnect(profile);
305                profile.setPreferred(device.getDevice(), false);
306            }
307        };
308
309        mDisconnectDialog = Utils.showDisconnectDialog(context,
310                mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
311    }
312
313    public void onDeviceAttributesChanged() {
314        refresh();
315    }
316
317    private void refresh() {
318        String deviceName = mCachedDevice.getName();
319        mDeviceNamePref.setSummary(deviceName);
320        mDeviceNamePref.setText(deviceName);
321
322        refreshProfiles();
323    }
324
325    private void refreshProfiles() {
326        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
327            CheckBoxPreference profilePref = (CheckBoxPreference)findPreference(profile.toString());
328            if (profilePref == null) {
329                profilePref = createProfilePreference(profile);
330                mProfileContainer.addPreference(profilePref);
331            } else {
332                refreshProfilePreference(profilePref, profile);
333            }
334        }
335        for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
336            Preference profilePref = findPreference(profile.toString());
337            if (profilePref != null) {
338                Log.d(TAG, "Removing " + profile.toString() + " from profile list");
339                mProfileContainer.removePreference(profilePref);
340            }
341        }
342        showOrHideProfileGroup();
343    }
344
345    private void refreshProfilePreference(CheckBoxPreference profilePref,
346            LocalBluetoothProfile profile) {
347        BluetoothDevice device = mCachedDevice.getDevice();
348
349        /*
350         * Gray out checkbox while connecting and disconnecting
351         */
352        profilePref.setEnabled(!mCachedDevice.isBusy());
353        profilePref.setChecked(profile.isPreferred(device));
354        profilePref.setSummary(profile.getSummaryResourceForDevice(device));
355    }
356
357    private LocalBluetoothProfile getProfileOf(Preference pref) {
358        if (!(pref instanceof CheckBoxPreference)) {
359            return null;
360        }
361        String key = pref.getKey();
362        if (TextUtils.isEmpty(key)) return null;
363
364        try {
365            return mProfileManager.getProfileByName(pref.getKey());
366        } catch (IllegalArgumentException ignored) {
367            return null;
368        }
369    }
370
371    private int getProfilePreferenceIndex(int profIndex) {
372        return mProfileContainer.getOrder() + profIndex * 10;
373    }
374
375    private void unpairDevice() {
376        mCachedDevice.unpair();
377    }
378}
379