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