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