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