blob: a76ed460585da1381ee4bdc82d2afcf14aba8d50 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import android.app.AlertDialog;
import android.app.Dialog;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.MapProfile;
import com.android.settingslib.bluetooth.PanProfile;
import com.android.settingslib.bluetooth.PbapServerProfile;
public final class DeviceProfilesSettings extends InstrumentedDialogFragment implements
CachedBluetoothDevice.Callback, DialogInterface.OnClickListener, OnClickListener {
private static final String TAG = "DeviceProfilesSettings";
public static final String ARG_DEVICE_ADDRESS = "device_address";
private static final String KEY_PROFILE_CONTAINER = "profile_container";
private static final String KEY_UNPAIR = "unpair";
private static final String KEY_PBAP_SERVER = "PBAP Server";
@VisibleForTesting
static final String HIGH_QUALITY_AUDIO_PREF_TAG = "A2dpProfileHighQualityAudio";
private CachedBluetoothDevice mCachedDevice;
private LocalBluetoothManager mManager;
private LocalBluetoothProfileManager mProfileManager;
private ViewGroup mProfileContainer;
private TextView mProfileLabel;
private AlertDialog mDisconnectDialog;
private boolean mProfileGroupIsRemoved;
private View mRootView;
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DIALOG_BLUETOOTH_PAIRED_DEVICE_PROFILE;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mManager = Utils.getLocalBtManager(getActivity());
CachedBluetoothDeviceManager deviceManager = mManager.getCachedDeviceManager();
String address = getArguments().getString(ARG_DEVICE_ADDRESS);
BluetoothDevice remoteDevice = mManager.getBluetoothAdapter().getRemoteDevice(address);
mCachedDevice = deviceManager.findDevice(remoteDevice);
if (mCachedDevice == null) {
mCachedDevice = deviceManager.addDevice(mManager.getBluetoothAdapter(),
mManager.getProfileManager(), remoteDevice);
}
mProfileManager = mManager.getProfileManager();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mRootView = LayoutInflater.from(getContext()).inflate(R.layout.device_profiles_settings,
null);
mProfileContainer = (ViewGroup) mRootView.findViewById(R.id.profiles_section);
mProfileLabel = (TextView) mRootView.findViewById(R.id.profiles_label);
final EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
deviceName.setText(mCachedDevice.getName(), TextView.BufferType.EDITABLE);
return new AlertDialog.Builder(getContext())
.setView(mRootView)
.setNeutralButton(R.string.forget, this)
.setPositiveButton(R.string.okay, this)
.setTitle(R.string.bluetooth_preference_paired_devices)
.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
EditText deviceName = (EditText) mRootView.findViewById(R.id.name);
mCachedDevice.setName(deviceName.getText().toString());
break;
case DialogInterface.BUTTON_NEUTRAL:
mCachedDevice.unpair();
break;
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mDisconnectDialog != null) {
mDisconnectDialog.dismiss();
mDisconnectDialog = null;
}
if (mCachedDevice != null) {
mCachedDevice.unregisterCallback(this);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public void onResume() {
super.onResume();
mManager.setForegroundActivity(getActivity());
if (mCachedDevice != null) {
mCachedDevice.registerCallback(this);
if (mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE) {
dismiss();
return;
}
addPreferencesForProfiles();
refresh();
}
}
@Override
public void onPause() {
super.onPause();
if (mCachedDevice != null) {
mCachedDevice.unregisterCallback(this);
}
mManager.setForegroundActivity(null);
}
private void addPreferencesForProfiles() {
mProfileContainer.removeAllViews();
for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
CheckBox pref = createProfilePreference(profile);
mProfileContainer.addView(pref);
if (profile instanceof A2dpProfile) {
BluetoothDevice device = mCachedDevice.getDevice();
A2dpProfile a2dpProfile = (A2dpProfile) profile;
if (a2dpProfile.supportsHighQualityAudio(device)) {
CheckBox highQualityPref = new CheckBox(getActivity());
highQualityPref.setTag(HIGH_QUALITY_AUDIO_PREF_TAG);
highQualityPref.setOnClickListener(v -> {
a2dpProfile.setHighQualityAudioEnabled(device, highQualityPref.isChecked());
});
highQualityPref.setVisibility(View.GONE);
mProfileContainer.addView(highQualityPref);
}
refreshProfilePreference(pref, profile);
}
}
final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
// Only provide PBAP cabability if the client device has requested PBAP.
if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
CheckBox pbapPref = createProfilePreference(psp);
mProfileContainer.addView(pbapPref);
}
final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
final int mapPermission = mCachedDevice.getMessagePermissionChoice();
if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
CheckBox mapPreference = createProfilePreference(mapProfile);
mProfileContainer.addView(mapPreference);
}
showOrHideProfileGroup();
}
private void showOrHideProfileGroup() {
int numProfiles = mProfileContainer.getChildCount();
if (!mProfileGroupIsRemoved && numProfiles == 0) {
mProfileContainer.setVisibility(View.GONE);
mProfileLabel.setVisibility(View.GONE);
mProfileGroupIsRemoved = true;
} else if (mProfileGroupIsRemoved && numProfiles != 0) {
mProfileContainer.setVisibility(View.VISIBLE);
mProfileLabel.setVisibility(View.VISIBLE);
mProfileGroupIsRemoved = false;
}
}
/**
* Creates a checkbox preference for the particular profile. The key will be
* the profile's name.
*
* @param profile The profile for which the preference controls.
* @return A preference that allows the user to choose whether this profile
* will be connected to.
*/
private CheckBox createProfilePreference(LocalBluetoothProfile profile) {
CheckBox pref = new CheckBox(getActivity());
pref.setTag(profile.toString());
pref.setText(profile.getNameResource(mCachedDevice.getDevice()));
pref.setOnClickListener(this);
refreshProfilePreference(pref, profile);
return pref;
}
@Override
public void onClick(View v) {
if (v instanceof CheckBox) {
LocalBluetoothProfile prof = getProfileOf(v);
onProfileClicked(prof, (CheckBox) v);
}
}
private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) {
BluetoothDevice device = mCachedDevice.getDevice();
if (KEY_PBAP_SERVER.equals(profilePref.getTag())) {
final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
== CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
: CachedBluetoothDevice.ACCESS_ALLOWED;
mCachedDevice.setPhonebookPermissionChoice(newPermission);
profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED);
return;
}
if (!profilePref.isChecked()) {
// Recheck it, until the dialog is done.
profilePref.setChecked(true);
askDisconnect(mManager.getForegroundActivity(), profile);
} else {
if (profile instanceof MapProfile) {
mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
}
if (profile.isPreferred(device)) {
// profile is preferred but not connected: disable auto-connect
if (profile instanceof PanProfile) {
mCachedDevice.connectProfile(profile);
} else {
profile.setPreferred(device, false);
}
} else {
profile.setPreferred(device, true);
mCachedDevice.connectProfile(profile);
}
refreshProfilePreference(profilePref, profile);
}
}
private void askDisconnect(Context context,
final LocalBluetoothProfile profile) {
// local reference for callback
final CachedBluetoothDevice device = mCachedDevice;
String name = device.getName();
if (TextUtils.isEmpty(name)) {
name = context.getString(R.string.bluetooth_device);
}
String profileName = context.getString(profile.getNameResource(device.getDevice()));
String title = context.getString(R.string.bluetooth_disable_profile_title);
String message = context.getString(R.string.bluetooth_disable_profile_message,
profileName, name);
DialogInterface.OnClickListener disconnectListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
device.disconnect(profile);
profile.setPreferred(device.getDevice(), false);
if (profile instanceof MapProfile) {
device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
}
refreshProfilePreference(findProfile(profile.toString()), profile);
}
};
mDisconnectDialog = Utils.showDisconnectDialog(context,
mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
}
@Override
public void onDeviceAttributesChanged() {
refresh();
}
private void refresh() {
final EditText deviceNameField = (EditText) mRootView.findViewById(R.id.name);
if (deviceNameField != null) {
deviceNameField.setText(mCachedDevice.getName());
}
refreshProfiles();
}
private void refreshProfiles() {
for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
CheckBox profilePref = findProfile(profile.toString());
if (profilePref == null) {
profilePref = createProfilePreference(profile);
mProfileContainer.addView(profilePref);
} else {
refreshProfilePreference(profilePref, profile);
}
}
for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
CheckBox profilePref = findProfile(profile.toString());
if (profilePref != null) {
Log.d(TAG, "Removing " + profile.toString() + " from profile list");
mProfileContainer.removeView(profilePref);
}
}
showOrHideProfileGroup();
}
private CheckBox findProfile(String profile) {
return (CheckBox) mProfileContainer.findViewWithTag(profile);
}
private void refreshProfilePreference(CheckBox profilePref,
LocalBluetoothProfile profile) {
BluetoothDevice device = mCachedDevice.getDevice();
// Gray out checkbox while connecting and disconnecting.
profilePref.setEnabled(!mCachedDevice.isBusy());
if (profile instanceof MapProfile) {
profilePref.setChecked(mCachedDevice.getMessagePermissionChoice()
== CachedBluetoothDevice.ACCESS_ALLOWED);
} else if (profile instanceof PbapServerProfile) {
profilePref.setChecked(mCachedDevice.getPhonebookPermissionChoice()
== CachedBluetoothDevice.ACCESS_ALLOWED);
} else if (profile instanceof PanProfile) {
profilePref.setChecked(profile.getConnectionStatus(device) ==
BluetoothProfile.STATE_CONNECTED);
} else {
profilePref.setChecked(profile.isPreferred(device));
}
if (profile instanceof A2dpProfile) {
A2dpProfile a2dpProfile = (A2dpProfile) profile;
View v = mProfileContainer.findViewWithTag(HIGH_QUALITY_AUDIO_PREF_TAG);
if (v instanceof CheckBox) {
CheckBox highQualityPref = (CheckBox) v;
highQualityPref.setText(a2dpProfile.getHighQualityAudioOptionLabel(device));
highQualityPref.setChecked(a2dpProfile.isHighQualityAudioEnabled(device));
if (a2dpProfile.isPreferred(device)) {
v.setVisibility(View.VISIBLE);
v.setEnabled(!mCachedDevice.isBusy());
} else {
v.setVisibility(View.GONE);
}
}
}
}
private LocalBluetoothProfile getProfileOf(View v) {
if (!(v instanceof CheckBox)) {
return null;
}
String key = (String) v.getTag();
if (TextUtils.isEmpty(key)) return null;
try {
return mProfileManager.getProfileByName(key);
} catch (IllegalArgumentException ignored) {
return null;
}
}
}