• 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             // MAP and PBAP profiles would be added based on permission access
176             if (!((profile instanceof PbapServerProfile) ||
177                 (profile instanceof MapProfile))) {
178                 mProfileContainer.addView(pref);
179             }
180 
181             if (profile instanceof A2dpProfile) {
182                 BluetoothDevice device = mCachedDevice.getDevice();
183                 A2dpProfile a2dpProfile = (A2dpProfile) profile;
184                 if (a2dpProfile.supportsHighQualityAudio(device)) {
185                     CheckBox highQualityPref = new CheckBox(getActivity());
186                     highQualityPref.setTag(HIGH_QUALITY_AUDIO_PREF_TAG);
187                     highQualityPref.setOnClickListener(v -> {
188                         a2dpProfile.setHighQualityAudioEnabled(device, highQualityPref.isChecked());
189                     });
190                     highQualityPref.setVisibility(View.GONE);
191                     mProfileContainer.addView(highQualityPref);
192                 }
193                 refreshProfilePreference(pref, profile);
194             }
195         }
196 
197         final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
198         Log.d(TAG, "addPreferencesForProfiles: pbapPermission = " + pbapPermission);
199         // Only provide PBAP cabability if the client device has requested PBAP.
200         if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
201             final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
202             CheckBox pbapPref = createProfilePreference(psp);
203             mProfileContainer.addView(pbapPref);
204         }
205 
206         final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
207         final int mapPermission = mCachedDevice.getMessagePermissionChoice();
208         Log.d(TAG, "addPreferencesForProfiles: mapPermission = " + mapPermission);
209         if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
210             CheckBox mapPreference = createProfilePreference(mapProfile);
211             mProfileContainer.addView(mapPreference);
212         }
213 
214         showOrHideProfileGroup();
215     }
216 
showOrHideProfileGroup()217     private void showOrHideProfileGroup() {
218         int numProfiles = mProfileContainer.getChildCount();
219         if (!mProfileGroupIsRemoved && numProfiles == 0) {
220             mProfileContainer.setVisibility(View.GONE);
221             mProfileLabel.setVisibility(View.GONE);
222             mProfileGroupIsRemoved = true;
223         } else if (mProfileGroupIsRemoved && numProfiles != 0) {
224             mProfileContainer.setVisibility(View.VISIBLE);
225             mProfileLabel.setVisibility(View.VISIBLE);
226             mProfileGroupIsRemoved = false;
227         }
228     }
229 
230     /**
231      * Creates a checkbox preference for the particular profile. The key will be
232      * the profile's name.
233      *
234      * @param profile The profile for which the preference controls.
235      * @return A preference that allows the user to choose whether this profile
236      *         will be connected to.
237      */
createProfilePreference(LocalBluetoothProfile profile)238     private CheckBox createProfilePreference(LocalBluetoothProfile profile) {
239         CheckBox pref = new CheckBox(getActivity());
240         pref.setTag(profile.toString());
241         pref.setText(profile.getNameResource(mCachedDevice.getDevice()));
242         pref.setOnClickListener(this);
243 
244         refreshProfilePreference(pref, profile);
245 
246         return pref;
247     }
248 
249     @Override
onClick(View v)250     public void onClick(View v) {
251         if (v instanceof CheckBox) {
252             LocalBluetoothProfile prof = getProfileOf(v);
253             onProfileClicked(prof, (CheckBox) v);
254         }
255     }
256 
onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref)257     private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) {
258         BluetoothDevice device = mCachedDevice.getDevice();
259 
260         if (!profilePref.isChecked()) {
261             // Recheck it, until the dialog is done.
262             profilePref.setChecked(true);
263             askDisconnect(mManager.getForegroundActivity(), profile);
264         } else {
265             if (profile instanceof MapProfile) {
266                 mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
267             }
268             if (profile instanceof PbapServerProfile) {
269                 mCachedDevice.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
270                 refreshProfilePreference(profilePref, profile);
271                 // PBAP server is not preffered profile and cannot initiate connection, so return
272                 return;
273             }
274             if (profile.isPreferred(device)) {
275                 // profile is preferred but not connected: disable auto-connect
276                 if (profile instanceof PanProfile) {
277                     mCachedDevice.connectProfile(profile);
278                 } else {
279                     profile.setPreferred(device, false);
280                 }
281             } else {
282                 profile.setPreferred(device, true);
283                 mCachedDevice.connectProfile(profile);
284             }
285             refreshProfilePreference(profilePref, profile);
286         }
287     }
288 
askDisconnect(Context context, final LocalBluetoothProfile profile)289     private void askDisconnect(Context context,
290             final LocalBluetoothProfile profile) {
291         // local reference for callback
292         final CachedBluetoothDevice device = mCachedDevice;
293         String name = device.getName();
294         if (TextUtils.isEmpty(name)) {
295             name = context.getString(R.string.bluetooth_device);
296         }
297 
298         String profileName = context.getString(profile.getNameResource(device.getDevice()));
299 
300         String title = context.getString(R.string.bluetooth_disable_profile_title);
301         String message = context.getString(R.string.bluetooth_disable_profile_message,
302                 profileName, name);
303 
304         DialogInterface.OnClickListener disconnectListener =
305                 new DialogInterface.OnClickListener() {
306             public void onClick(DialogInterface dialog, int which) {
307 
308                 // Disconnect only when user has selected OK otherwise ignore
309                 if (which == DialogInterface.BUTTON_POSITIVE) {
310                     device.disconnect(profile);
311                     profile.setPreferred(device.getDevice(), false);
312                     if (profile instanceof MapProfile) {
313                         device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
314                     }
315                     if (profile instanceof PbapServerProfile) {
316                         device.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_REJECTED);
317                     }
318                 }
319                 refreshProfilePreference(findProfile(profile.toString()), profile);
320             }
321         };
322 
323         mDisconnectDialog = Utils.showDisconnectDialog(context,
324                 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
325     }
326 
327     @Override
onDeviceAttributesChanged()328     public void onDeviceAttributesChanged() {
329         refresh();
330     }
331 
refresh()332     private void refresh() {
333         final EditText deviceNameField = (EditText) mRootView.findViewById(R.id.name);
334         if (deviceNameField != null) {
335             deviceNameField.setText(mCachedDevice.getName());
336             com.android.settings.Utils.setEditTextCursorPosition(deviceNameField);
337         }
338 
339         refreshProfiles();
340     }
341 
refreshProfiles()342     private void refreshProfiles() {
343         for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
344             CheckBox profilePref = findProfile(profile.toString());
345             if (profilePref == null) {
346                 profilePref = createProfilePreference(profile);
347                 mProfileContainer.addView(profilePref);
348             } else {
349                 refreshProfilePreference(profilePref, profile);
350             }
351         }
352         for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
353             CheckBox profilePref = findProfile(profile.toString());
354             if (profilePref != null) {
355 
356                 if (profile instanceof PbapServerProfile) {
357                     final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
358                     Log.d(TAG, "refreshProfiles: pbapPermission = " + pbapPermission);
359                     if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN)
360                         continue;
361                 }
362                 if (profile instanceof MapProfile) {
363                     final int mapPermission = mCachedDevice.getMessagePermissionChoice();
364                     Log.d(TAG, "refreshProfiles: mapPermission = " + mapPermission);
365                     if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN)
366                         continue;
367                 }
368                 Log.d(TAG, "Removing " + profile.toString() + " from profile list");
369                 mProfileContainer.removeView(profilePref);
370             }
371         }
372 
373         showOrHideProfileGroup();
374     }
375 
findProfile(String profile)376     private CheckBox findProfile(String profile) {
377         return (CheckBox) mProfileContainer.findViewWithTag(profile);
378     }
379 
refreshProfilePreference(CheckBox profilePref, LocalBluetoothProfile profile)380     private void refreshProfilePreference(CheckBox profilePref,
381             LocalBluetoothProfile profile) {
382         BluetoothDevice device = mCachedDevice.getDevice();
383 
384         // Gray out checkbox while connecting and disconnecting.
385         profilePref.setEnabled(!mCachedDevice.isBusy());
386 
387         if (profile instanceof MapProfile) {
388             profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
389                     == CachedBluetoothDevice.ACCESS_ALLOWED);
390 
391         } else if (profile instanceof PbapServerProfile) {
392             profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
393                     == CachedBluetoothDevice.ACCESS_ALLOWED);
394 
395         } else if (profile instanceof PanProfile) {
396             profilePref.setChecked(profile.getConnectionStatus(device) ==
397                     BluetoothProfile.STATE_CONNECTED);
398 
399         } else {
400             profilePref.setChecked(profile.isPreferred(device));
401         }
402         if (profile instanceof A2dpProfile) {
403             A2dpProfile a2dpProfile = (A2dpProfile) profile;
404             View v = mProfileContainer.findViewWithTag(HIGH_QUALITY_AUDIO_PREF_TAG);
405             if (v instanceof CheckBox) {
406                 CheckBox highQualityPref = (CheckBox) v;
407                 highQualityPref.setText(a2dpProfile.getHighQualityAudioOptionLabel(device));
408                 highQualityPref.setChecked(a2dpProfile.isHighQualityAudioEnabled(device));
409 
410                 if (a2dpProfile.isPreferred(device)) {
411                     v.setVisibility(View.VISIBLE);
412                     v.setEnabled(!mCachedDevice.isBusy());
413                 } else {
414                     v.setVisibility(View.GONE);
415                 }
416             }
417         }
418     }
419 
getProfileOf(View v)420     private LocalBluetoothProfile getProfileOf(View v) {
421         if (!(v instanceof CheckBox)) {
422             return null;
423         }
424         String key = (String) v.getTag();
425         if (TextUtils.isEmpty(key)) return null;
426 
427         try {
428             return mProfileManager.getProfileByName(key);
429         } catch (IllegalArgumentException ignored) {
430             return null;
431         }
432     }
433 }
434