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