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;
41
42import 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 */
49public 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{
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
81        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
82            // not used
83        }
84
85        // TextWatcher interface
86        public void onTextChanged(CharSequence s, int start, int before, int count) {
87            // not used
88        }
89    }
90
91    @Override
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
135    public void onDestroy() {
136        super.onDestroy();
137        if (mDisconnectDialog != null) {
138            mDisconnectDialog.dismiss();
139            mDisconnectDialog = null;
140        }
141    }
142
143    @Override
144    public void onSaveInstanceState(Bundle outState) {
145        super.onSaveInstanceState(outState);
146        outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice());
147    }
148
149    @Override
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
170    public void onPause() {
171        super.onPause();
172
173        mCachedDevice.unregisterCallback(this);
174        mManager.setForegroundActivity(null);
175    }
176
177    private void addPreferencesForProfiles() {
178        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
179            Preference pref = createProfilePreference(profile);
180            mProfileContainer.addPreference(pref);
181        }
182        showOrHideProfileGroup();
183    }
184
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     */
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
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
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);
245            return false;   // checkbox will update from onDeviceAttributesChanged() callback
246        } else {
247            return false;
248        }
249
250        return true;
251    }
252
253    private void onProfileClicked(LocalBluetoothProfile profile) {
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            profile.setPreferred(device, true);
264            mCachedDevice.connectProfile(profile);
265        }
266    }
267
268    private void askDisconnect(Context context,
269            final LocalBluetoothProfile profile) {
270        // local reference for callback
271        final CachedBluetoothDevice device = mCachedDevice;
272        String name = device.getName();
273        if (TextUtils.isEmpty(name)) {
274            name = context.getString(R.string.bluetooth_device);
275        }
276
277        String profileName = context.getString(profile.getNameResource(device.getDevice()));
278
279        String title = context.getString(R.string.bluetooth_disable_profile_title);
280        String message = context.getString(R.string.bluetooth_disable_profile_message,
281                profileName, name);
282
283        DialogInterface.OnClickListener disconnectListener =
284                new DialogInterface.OnClickListener() {
285            public void onClick(DialogInterface dialog, int which) {
286                device.disconnect(profile);
287                profile.setPreferred(device.getDevice(), false);
288            }
289        };
290
291        mDisconnectDialog = Utils.showDisconnectDialog(context,
292                mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
293    }
294
295    public void onDeviceAttributesChanged() {
296        refresh();
297    }
298
299    private void refresh() {
300        String deviceName = mCachedDevice.getName();
301        mDeviceNamePref.setSummary(deviceName);
302        mDeviceNamePref.setText(deviceName);
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        showOrHideProfileGroup();
325    }
326
327    private void refreshProfilePreference(CheckBoxPreference profilePref,
328            LocalBluetoothProfile profile) {
329        BluetoothDevice device = mCachedDevice.getDevice();
330
331        /*
332         * Gray out checkbox while connecting and disconnecting
333         */
334        profilePref.setEnabled(!mCachedDevice.isBusy());
335        profilePref.setChecked(profile.isPreferred(device));
336        profilePref.setSummary(profile.getSummaryResourceForDevice(device));
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    private void unpairDevice() {
358        mCachedDevice.unpair();
359    }
360
361    private boolean getAutoConnect(LocalBluetoothProfile prof) {
362        return prof.isPreferred(mCachedDevice.getDevice());
363    }
364}
365