Home | History | Annotate | Download | only in bluetooth
      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
     76     public int getMetricsCategory() {
     77         return MetricsProto.MetricsEvent.DIALOG_BLUETOOTH_PAIRED_DEVICE_PROFILE;
     78     }
     79 
     80     @Override
     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
     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
    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
    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
    140     public void onSaveInstanceState(Bundle outState) {
    141         super.onSaveInstanceState(outState);
    142     }
    143 
    144     @Override
    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
    161     public void onPause() {
    162         super.onPause();
    163 
    164         if (mCachedDevice != null) {
    165             mCachedDevice.unregisterCallback(this);
    166         }
    167 
    168         mManager.setForegroundActivity(null);
    169     }
    170 
    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 
    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      */
    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
    244     public void onClick(View v) {
    245         if (v instanceof CheckBox) {
    246             LocalBluetoothProfile prof = getProfileOf(v);
    247             onProfileClicked(prof, (CheckBox) v);
    248         }
    249     }
    250 
    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 
    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
    318     public void onDeviceAttributesChanged() {
    319         refresh();
    320     }
    321 
    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 
    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 
    352     private CheckBox findProfile(String profile) {
    353         return (CheckBox) mProfileContainer.findViewWithTag(profile);
    354     }
    355 
    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 
    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