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.Dialog;
21import android.app.DialogFragment;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothProfile;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.os.Bundle;
27import android.support.v7.preference.CheckBoxPreference;
28import android.support.v7.preference.EditTextPreference;
29import android.text.Html;
30import android.text.TextUtils;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.view.ViewGroup;
36import android.widget.CheckBox;
37import android.widget.EditText;
38import android.widget.TextView;
39
40import com.android.settings.R;
41import com.android.settingslib.bluetooth.CachedBluetoothDevice;
42import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
43import com.android.settingslib.bluetooth.LocalBluetoothManager;
44import com.android.settingslib.bluetooth.LocalBluetoothProfile;
45import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
46import com.android.settingslib.bluetooth.MapProfile;
47import com.android.settingslib.bluetooth.PanProfile;
48import com.android.settingslib.bluetooth.PbapServerProfile;
49
50import java.util.HashMap;
51
52public final class DeviceProfilesSettings extends DialogFragment implements
53        CachedBluetoothDevice.Callback, DialogInterface.OnClickListener, OnClickListener {
54    private static final String TAG = "DeviceProfilesSettings";
55
56    public static final String ARG_DEVICE_ADDRESS = "device_address";
57
58    private static final String KEY_PROFILE_CONTAINER = "profile_container";
59    private static final String KEY_UNPAIR = "unpair";
60    private static final String KEY_PBAP_SERVER = "PBAP Server";
61
62    private CachedBluetoothDevice mCachedDevice;
63    private LocalBluetoothManager mManager;
64    private LocalBluetoothProfileManager mProfileManager;
65
66    private ViewGroup mProfileContainer;
67    private TextView mProfileLabel;
68    private EditTextPreference mDeviceNamePref;
69
70    private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs
71            = new HashMap<LocalBluetoothProfile, CheckBoxPreference>();
72
73    private AlertDialog mDisconnectDialog;
74    private boolean mProfileGroupIsRemoved;
75
76    private View mRootView;
77
78    @Override
79    public void onCreate(Bundle savedInstanceState) {
80        super.onCreate(savedInstanceState);
81
82        mManager = Utils.getLocalBtManager(getActivity());
83        CachedBluetoothDeviceManager deviceManager = mManager.getCachedDeviceManager();
84
85        String address = getArguments().getString(ARG_DEVICE_ADDRESS);
86        BluetoothDevice remoteDevice = mManager.getBluetoothAdapter().getRemoteDevice(address);
87
88        mCachedDevice = deviceManager.findDevice(remoteDevice);
89        if (mCachedDevice == null) {
90            mCachedDevice = deviceManager.addDevice(mManager.getBluetoothAdapter(),
91                    mManager.getProfileManager(), remoteDevice);
92        }
93        mProfileManager = mManager.getProfileManager();
94    }
95
96    @Override
97    public Dialog onCreateDialog(Bundle savedInstanceState) {
98        mRootView = LayoutInflater.from(getContext()).inflate(R.layout.device_profiles_settings,
99                null);
100        mProfileContainer = (ViewGroup) mRootView.findViewById(R.id.profiles_section);
101        mProfileLabel = (TextView) mRootView.findViewById(R.id.profiles_label);
102        final EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
103        deviceName.setText(mCachedDevice.getName(), TextView.BufferType.EDITABLE);
104        return new AlertDialog.Builder(getContext())
105                .setView(mRootView)
106                .setNegativeButton(R.string.forget, this)
107                .setPositiveButton(R.string.okay, this)
108                .setTitle(R.string.bluetooth_preference_paired_devices)
109                .create();
110    }
111
112    @Override
113    public void onClick(DialogInterface dialog, int which) {
114        switch (which) {
115            case DialogInterface.BUTTON_POSITIVE:
116                EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
117                mCachedDevice.setName(deviceName.getText().toString());
118                break;
119            case DialogInterface.BUTTON_NEGATIVE:
120                mCachedDevice.unpair();
121                com.android.settings.bluetooth.Utils.updateSearchIndex(getContext(),
122                        BluetoothSettings.class.getName(), mCachedDevice.getName(),
123                        getString(R.string.bluetooth_settings),
124                        R.drawable.ic_settings_bluetooth, false);
125                break;
126        }
127    }
128
129    @Override
130    public void onDestroy() {
131        super.onDestroy();
132        if (mDisconnectDialog != null) {
133            mDisconnectDialog.dismiss();
134            mDisconnectDialog = null;
135        }
136        if (mCachedDevice != null) {
137            mCachedDevice.unregisterCallback(this);
138        }
139    }
140
141    @Override
142    public void onSaveInstanceState(Bundle outState) {
143        super.onSaveInstanceState(outState);
144    }
145
146    @Override
147    public void onResume() {
148        super.onResume();
149
150        mManager.setForegroundActivity(getActivity());
151        if (mCachedDevice != null) {
152            mCachedDevice.registerCallback(this);
153            if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
154                dismiss();
155                return;
156            }
157            addPreferencesForProfiles();
158            refresh();
159        }
160    }
161
162    @Override
163    public void onPause() {
164        super.onPause();
165
166        if (mCachedDevice != null) {
167            mCachedDevice.unregisterCallback(this);
168        }
169
170        mManager.setForegroundActivity(null);
171    }
172
173    private void addPreferencesForProfiles() {
174        mProfileContainer.removeAllViews();
175        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
176            CheckBox pref = createProfilePreference(profile);
177            mProfileContainer.addView(pref);
178        }
179
180        final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
181        // Only provide PBAP cabability if the client device has requested PBAP.
182        if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
183            final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
184            CheckBox pbapPref = createProfilePreference(psp);
185            mProfileContainer.addView(pbapPref);
186        }
187
188        final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
189        final int mapPermission = mCachedDevice.getMessagePermissionChoice();
190        if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
191            CheckBox mapPreference = createProfilePreference(mapProfile);
192            mProfileContainer.addView(mapPreference);
193        }
194
195        showOrHideProfileGroup();
196    }
197
198    private void showOrHideProfileGroup() {
199        int numProfiles = mProfileContainer.getChildCount();
200        if (!mProfileGroupIsRemoved && numProfiles == 0) {
201            mProfileContainer.setVisibility(View.GONE);
202            mProfileLabel.setVisibility(View.GONE);
203            mProfileGroupIsRemoved = true;
204        } else if (mProfileGroupIsRemoved && numProfiles != 0) {
205            mProfileContainer.setVisibility(View.VISIBLE);
206            mProfileLabel.setVisibility(View.VISIBLE);
207            mProfileGroupIsRemoved = false;
208        }
209    }
210
211    /**
212     * Creates a checkbox preference for the particular profile. The key will be
213     * the profile's name.
214     *
215     * @param profile The profile for which the preference controls.
216     * @return A preference that allows the user to choose whether this profile
217     *         will be connected to.
218     */
219    private CheckBox createProfilePreference(LocalBluetoothProfile profile) {
220        CheckBox pref = new CheckBox(getActivity());
221        pref.setTag(profile.toString());
222        pref.setText(profile.getNameResource(mCachedDevice.getDevice()));
223        pref.setOnClickListener(this);
224
225        refreshProfilePreference(pref, profile);
226
227        return pref;
228    }
229
230    @Override
231    public void onClick(View v) {
232        if (v instanceof CheckBox) {
233            LocalBluetoothProfile prof = getProfileOf(v);
234            onProfileClicked(prof, (CheckBox) v);
235        }
236    }
237
238    private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) {
239        BluetoothDevice device = mCachedDevice.getDevice();
240
241        if (KEY_PBAP_SERVER.equals(profilePref.getTag())) {
242            final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
243                == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
244                : CachedBluetoothDevice.ACCESS_ALLOWED;
245            mCachedDevice.setPhonebookPermissionChoice(newPermission);
246            profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED);
247            return;
248        }
249
250        if (!profilePref.isChecked()) {
251            // Recheck it, until the dialog is done.
252            profilePref.setChecked(true);
253            askDisconnect(mManager.getForegroundActivity(), profile);
254        } else {
255            if (profile instanceof MapProfile) {
256                mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
257            }
258            if (profile.isPreferred(device)) {
259                // profile is preferred but not connected: disable auto-connect
260                if (profile instanceof PanProfile) {
261                    mCachedDevice.connectProfile(profile);
262                } else {
263                    profile.setPreferred(device, false);
264                }
265            } else {
266                profile.setPreferred(device, true);
267                mCachedDevice.connectProfile(profile);
268            }
269            refreshProfilePreference(profilePref, profile);
270        }
271    }
272
273    private void askDisconnect(Context context,
274            final LocalBluetoothProfile profile) {
275        // local reference for callback
276        final CachedBluetoothDevice device = mCachedDevice;
277        String name = device.getName();
278        if (TextUtils.isEmpty(name)) {
279            name = context.getString(R.string.bluetooth_device);
280        }
281
282        String profileName = context.getString(profile.getNameResource(device.getDevice()));
283
284        String title = context.getString(R.string.bluetooth_disable_profile_title);
285        String message = context.getString(R.string.bluetooth_disable_profile_message,
286                profileName, name);
287
288        DialogInterface.OnClickListener disconnectListener =
289                new DialogInterface.OnClickListener() {
290            public void onClick(DialogInterface dialog, int which) {
291                device.disconnect(profile);
292                profile.setPreferred(device.getDevice(), false);
293                if (profile instanceof MapProfile) {
294                    device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
295                }
296                refreshProfilePreference(findProfile(profile.toString()), profile);
297            }
298        };
299
300        mDisconnectDialog = Utils.showDisconnectDialog(context,
301                mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
302    }
303
304    @Override
305    public void onDeviceAttributesChanged() {
306        refresh();
307    }
308
309    private void refresh() {
310        final EditText deviceNameField = (EditText) mRootView.findViewById(R.id.name);
311        if (deviceNameField != null) {
312            deviceNameField.setText(mCachedDevice.getName());
313        }
314
315        refreshProfiles();
316    }
317
318    private void refreshProfiles() {
319        for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
320            CheckBox profilePref = findProfile(profile.toString());
321            if (profilePref == null) {
322                profilePref = createProfilePreference(profile);
323                mProfileContainer.addView(profilePref);
324            } else {
325                refreshProfilePreference(profilePref, profile);
326            }
327        }
328        for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
329            CheckBox profilePref = findProfile(profile.toString());
330            if (profilePref != null) {
331                Log.d(TAG, "Removing " + profile.toString() + " from profile list");
332                mProfileContainer.removeView(profilePref);
333            }
334        }
335
336        showOrHideProfileGroup();
337    }
338
339    private CheckBox findProfile(String profile) {
340        return (CheckBox) mProfileContainer.findViewWithTag(profile);
341    }
342
343    private void refreshProfilePreference(CheckBox profilePref,
344            LocalBluetoothProfile profile) {
345        BluetoothDevice device = mCachedDevice.getDevice();
346
347        // Gray out checkbox while connecting and disconnecting.
348        profilePref.setEnabled(!mCachedDevice.isBusy());
349
350        if (profile instanceof MapProfile) {
351            profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
352                    == CachedBluetoothDevice.ACCESS_ALLOWED);
353
354        } else if (profile instanceof PbapServerProfile) {
355            profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
356                    == CachedBluetoothDevice.ACCESS_ALLOWED);
357
358        } else if (profile instanceof PanProfile) {
359            profilePref.setChecked(profile.getConnectionStatus(device) ==
360                    BluetoothProfile.STATE_CONNECTED);
361
362        } else {
363            profilePref.setChecked(profile.isPreferred(device));
364        }
365    }
366
367    private LocalBluetoothProfile getProfileOf(View v) {
368        if (!(v instanceof CheckBox)) {
369            return null;
370        }
371        String key = (String) v.getTag();
372        if (TextUtils.isEmpty(key)) return null;
373
374        try {
375            return mProfileManager.getProfileByName(key);
376        } catch (IllegalArgumentException ignored) {
377            return null;
378        }
379    }
380}
381