BluetoothDevicePreference.java revision c0963dd7c5a35fdb007a2954644c1352060350d1
1/*
2 * Copyright (C) 2008 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 static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
20
21import android.app.AlertDialog;
22import android.bluetooth.BluetoothClass;
23import android.bluetooth.BluetoothDevice;
24import android.bluetooth.BluetoothProfile;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.os.UserManager;
28import android.preference.Preference;
29import android.text.Html;
30import android.text.TextUtils;
31import android.util.Log;
32import android.util.TypedValue;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.widget.ImageView;
36
37import com.android.settings.R;
38import com.android.settings.search.Index;
39import com.android.settings.search.SearchIndexableRaw;
40
41import java.util.List;
42
43/**
44 * BluetoothDevicePreference is the preference type used to display each remote
45 * Bluetooth device in the Bluetooth Settings screen.
46 */
47public final class BluetoothDevicePreference extends Preference implements
48        CachedBluetoothDevice.Callback, OnClickListener {
49    private static final String TAG = "BluetoothDevicePreference";
50
51    private static int sDimAlpha = Integer.MIN_VALUE;
52
53    private final CachedBluetoothDevice mCachedDevice;
54
55    private OnClickListener mOnSettingsClickListener;
56
57    private AlertDialog mDisconnectDialog;
58
59    public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
60        super(context);
61
62        if (sDimAlpha == Integer.MIN_VALUE) {
63            TypedValue outValue = new TypedValue();
64            context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
65            sDimAlpha = (int) (outValue.getFloat() * 255);
66        }
67
68        mCachedDevice = cachedDevice;
69
70        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
71            UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
72            if (! um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH)) {
73                setWidgetLayoutResource(R.layout.preference_bluetooth);
74            }
75        }
76
77        mCachedDevice.registerCallback(this);
78
79        onDeviceAttributesChanged();
80    }
81
82    CachedBluetoothDevice getCachedDevice() {
83        return mCachedDevice;
84    }
85
86    public void setOnSettingsClickListener(OnClickListener listener) {
87        mOnSettingsClickListener = listener;
88    }
89
90    @Override
91    protected void onPrepareForRemoval() {
92        super.onPrepareForRemoval();
93        mCachedDevice.unregisterCallback(this);
94        if (mDisconnectDialog != null) {
95            mDisconnectDialog.dismiss();
96            mDisconnectDialog = null;
97        }
98    }
99
100    public void onDeviceAttributesChanged() {
101        /*
102         * The preference framework takes care of making sure the value has
103         * changed before proceeding. It will also call notifyChanged() if
104         * any preference info has changed from the previous value.
105         */
106        setTitle(mCachedDevice.getName());
107
108        int summaryResId = getConnectionSummary();
109        if (summaryResId != 0) {
110            setSummary(summaryResId);
111        } else {
112            setSummary(null);   // empty summary for unpaired devices
113        }
114
115        int iconResId = getBtClassDrawable();
116        if (iconResId != 0) {
117            setIcon(iconResId);
118        }
119
120        // Used to gray out the item
121        setEnabled(!mCachedDevice.isBusy());
122
123        // This could affect ordering, so notify that
124        notifyHierarchyChanged();
125    }
126
127    @Override
128    protected void onBindView(View view) {
129        // Disable this view if the bluetooth enable/disable preference view is off
130        if (null != findPreferenceInHierarchy("bt_checkbox")) {
131            setDependency("bt_checkbox");
132        }
133
134        if (mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
135            ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
136
137            if (deviceDetails != null) {
138                deviceDetails.setOnClickListener(this);
139                deviceDetails.setTag(mCachedDevice);
140            }
141        }
142
143        super.onBindView(view);
144    }
145
146    public void onClick(View v) {
147        // Should never be null by construction
148        if (mOnSettingsClickListener != null) {
149            mOnSettingsClickListener.onClick(v);
150        }
151    }
152
153    @Override
154    public boolean equals(Object o) {
155        if ((o == null) || !(o instanceof BluetoothDevicePreference)) {
156            return false;
157        }
158        return mCachedDevice.equals(
159                ((BluetoothDevicePreference) o).mCachedDevice);
160    }
161
162    @Override
163    public int hashCode() {
164        return mCachedDevice.hashCode();
165    }
166
167    @Override
168    public int compareTo(Preference another) {
169        if (!(another instanceof BluetoothDevicePreference)) {
170            // Rely on default sort
171            return super.compareTo(another);
172        }
173
174        return mCachedDevice
175                .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
176    }
177
178    void onClicked() {
179        int bondState = mCachedDevice.getBondState();
180
181        if (mCachedDevice.isConnected()) {
182            askDisconnect();
183        } else if (bondState == BluetoothDevice.BOND_BONDED) {
184            mCachedDevice.connect(true);
185        } else if (bondState == BluetoothDevice.BOND_NONE) {
186            pair();
187        }
188    }
189
190    // Show disconnect confirmation dialog for a device.
191    private void askDisconnect() {
192        Context context = getContext();
193        String name = mCachedDevice.getName();
194        if (TextUtils.isEmpty(name)) {
195            name = context.getString(R.string.bluetooth_device);
196        }
197        String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name);
198        String title = context.getString(R.string.bluetooth_disconnect_title);
199
200        DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
201            public void onClick(DialogInterface dialog, int which) {
202                mCachedDevice.disconnect();
203            }
204        };
205
206        mDisconnectDialog = Utils.showDisconnectDialog(context,
207                mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
208    }
209
210    private void pair() {
211        if (!mCachedDevice.startPairing()) {
212            Utils.showError(getContext(), mCachedDevice.getName(),
213                    R.string.bluetooth_pairing_error_message);
214        } else {
215            final Context context = getContext();
216
217            SearchIndexableRaw data = new SearchIndexableRaw(context);
218            data.className = BluetoothSettings.class.getName();
219            data.title = mCachedDevice.getName();
220            data.screenTitle = context.getResources().getString(R.string.bluetooth_settings);
221            data.iconResId = R.drawable.ic_settings_bluetooth2;
222            data.enabled = true;
223
224            Index.getInstance(context).updateFromSearchIndexableData(data);
225        }
226    }
227
228    private int getConnectionSummary() {
229        final CachedBluetoothDevice cachedDevice = mCachedDevice;
230
231        boolean profileConnected = false;       // at least one profile is connected
232        boolean a2dpNotConnected = false;       // A2DP is preferred but not connected
233        boolean headsetNotConnected = false;    // Headset is preferred but not connected
234
235        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
236            int connectionStatus = cachedDevice.getProfileConnectionState(profile);
237
238            switch (connectionStatus) {
239                case BluetoothProfile.STATE_CONNECTING:
240                case BluetoothProfile.STATE_DISCONNECTING:
241                    return Utils.getConnectionStateSummary(connectionStatus);
242
243                case BluetoothProfile.STATE_CONNECTED:
244                    profileConnected = true;
245                    break;
246
247                case BluetoothProfile.STATE_DISCONNECTED:
248                    if (profile.isProfileReady()) {
249                        if (profile instanceof A2dpProfile) {
250                            a2dpNotConnected = true;
251                        } else if (profile instanceof HeadsetProfile) {
252                            headsetNotConnected = true;
253                        }
254                    }
255                    break;
256            }
257        }
258
259        if (profileConnected) {
260            if (a2dpNotConnected && headsetNotConnected) {
261                return R.string.bluetooth_connected_no_headset_no_a2dp;
262            } else if (a2dpNotConnected) {
263                return R.string.bluetooth_connected_no_a2dp;
264            } else if (headsetNotConnected) {
265                return R.string.bluetooth_connected_no_headset;
266            } else {
267                return R.string.bluetooth_connected;
268            }
269        }
270
271        switch (cachedDevice.getBondState()) {
272            case BluetoothDevice.BOND_BONDING:
273                return R.string.bluetooth_pairing;
274
275            case BluetoothDevice.BOND_BONDED:
276            case BluetoothDevice.BOND_NONE:
277            default:
278                return 0;
279        }
280    }
281
282    private int getBtClassDrawable() {
283        BluetoothClass btClass = mCachedDevice.getBtClass();
284        if (btClass != null) {
285            switch (btClass.getMajorDeviceClass()) {
286                case BluetoothClass.Device.Major.COMPUTER:
287                    return R.drawable.ic_bt_laptop;
288
289                case BluetoothClass.Device.Major.PHONE:
290                    return R.drawable.ic_bt_cellphone;
291
292                case BluetoothClass.Device.Major.PERIPHERAL:
293                    return HidProfile.getHidClassDrawable(btClass);
294
295                case BluetoothClass.Device.Major.IMAGING:
296                    return R.drawable.ic_bt_imaging;
297
298                default:
299                    // unrecognized device class; continue
300            }
301        } else {
302            Log.w(TAG, "mBtClass is null");
303        }
304
305        List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles();
306        for (LocalBluetoothProfile profile : profiles) {
307            int resId = profile.getDrawableResource(btClass);
308            if (resId != 0) {
309                return resId;
310            }
311        }
312        if (btClass != null) {
313            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
314                return R.drawable.ic_bt_headphones_a2dp;
315
316            }
317            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
318                return R.drawable.ic_bt_headset_hfp;
319            }
320        }
321        return R.drawable.ic_settings_bluetooth2;
322    }
323}
324