Merge tm-dev-plus-aosp-without-vendor@8763363
Bug: 236760014 Merged-In: Ifcb9d4c564839199d998bd503f390f021c6bf3ad Change-Id: I9d69bcbc6916176beece2616f152ebd3d74fc0f8
This commit is contained in:
@@ -99,6 +99,10 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
||||
@VisibleForTesting
|
||||
boolean mIsRegisterCallback = false;
|
||||
@VisibleForTesting
|
||||
boolean mIsLeftDeviceEstimateReady;
|
||||
@VisibleForTesting
|
||||
boolean mIsRightDeviceEstimateReady;
|
||||
@VisibleForTesting
|
||||
final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
|
||||
new BluetoothAdapter.OnMetadataChangedListener() {
|
||||
@Override
|
||||
@@ -226,6 +230,8 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
||||
BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
|
||||
R.string.bluetooth_right_name,
|
||||
RIGHT_DEVICE_ID);
|
||||
|
||||
showBothDevicesBatteryPredictionIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,8 +371,13 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
||||
+ ", ESTIMATE_READY : " + estimateReady
|
||||
+ ", BATTERY_ESTIMATE : " + batteryEstimate);
|
||||
}
|
||||
showBatteryPredictionIfNecessary(estimateReady, batteryEstimate,
|
||||
linearLayout);
|
||||
|
||||
showBatteryPredictionIfNecessary(estimateReady, batteryEstimate, linearLayout);
|
||||
if (batteryId == LEFT_DEVICE_ID) {
|
||||
mIsLeftDeviceEstimateReady = estimateReady == 1;
|
||||
} else if (batteryId == RIGHT_DEVICE_ID) {
|
||||
mIsRightDeviceEstimateReady = estimateReady == 1;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
@@ -380,7 +391,6 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
||||
ThreadUtils.postOnMainThread(() -> {
|
||||
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
|
||||
if (estimateReady == 1) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(
|
||||
StringUtil.formatElapsedTime(
|
||||
mContext,
|
||||
@@ -393,6 +403,24 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void showBothDevicesBatteryPredictionIfNecessary() {
|
||||
TextView leftDeviceTextView =
|
||||
mLayoutPreference.findViewById(R.id.layout_left)
|
||||
.findViewById(R.id.bt_battery_prediction);
|
||||
TextView rightDeviceTextView =
|
||||
mLayoutPreference.findViewById(R.id.layout_right)
|
||||
.findViewById(R.id.bt_battery_prediction);
|
||||
|
||||
boolean isBothDevicesEstimateReady =
|
||||
mIsLeftDeviceEstimateReady && mIsRightDeviceEstimateReady;
|
||||
int visibility = isBothDevicesEstimateReady ? View.VISIBLE : View.GONE;
|
||||
ThreadUtils.postOnMainThread(() -> {
|
||||
leftDeviceTextView.setVisibility(visibility);
|
||||
rightDeviceTextView.setVisibility(visibility);
|
||||
});
|
||||
}
|
||||
|
||||
private void showBatteryIcon(LinearLayout linearLayout, int level, int lowBatteryLevel,
|
||||
boolean charging) {
|
||||
final boolean enableLowBattery = level <= lowBatteryLevel && !charging;
|
||||
|
||||
@@ -52,7 +52,8 @@ public class AlwaysDiscoverable extends BroadcastReceiver {
|
||||
if (mStarted) {
|
||||
return;
|
||||
}
|
||||
mContext.registerReceiver(this, mIntentFilter);
|
||||
mContext.registerReceiver(this, mIntentFilter,
|
||||
Context.RECEIVER_EXPORTED_UNAUDITED);
|
||||
mStarted = true;
|
||||
if (mBluetoothAdapter.getScanMode()
|
||||
!= BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
|
||||
@@ -19,105 +19,93 @@ package com.android.settings.bluetooth;
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.media.MediaOutputConstants;
|
||||
|
||||
/**
|
||||
* This Dialog allowed users to do some actions for broadcast media or find the
|
||||
* nearby broadcast sources.
|
||||
*/
|
||||
public class BluetoothBroadcastDialog extends InstrumentedDialogFragment {
|
||||
public static final String KEY_APP_LABEL = "app_label";
|
||||
public static final String KEY_DEVICE_ADDRESS =
|
||||
BluetoothFindBroadcastsFragment.KEY_DEVICE_ADDRESS;
|
||||
|
||||
private static final String TAG = "BTBroadcastsDialog";
|
||||
|
||||
private static final CharSequence UNKNOWN_APP_LABEL = "unknown";
|
||||
private Context mContext;
|
||||
private CharSequence mCurrentAppLabel = UNKNOWN_APP_LABEL;
|
||||
private String mDeviceAddress;
|
||||
private LocalBluetoothManager mLocalBluetoothManager;
|
||||
private AlertDialog mAlertDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mContext = getActivity();
|
||||
mCurrentAppLabel = getActivity().getIntent().getCharSequenceExtra(KEY_APP_LABEL);
|
||||
mDeviceAddress = getActivity().getIntent().getStringExtra(KEY_DEVICE_ADDRESS);
|
||||
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
|
||||
setShowsDialog(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
final boolean isMediaPlaying = isMediaPlaying();
|
||||
View layout = View.inflate(mContext,
|
||||
com.android.settingslib.R.layout.broadcast_dialog, null);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(isMediaPlaying ? R.string.bluetooth_find_broadcast
|
||||
: R.string.bluetooth_broadcast_dialog_title);
|
||||
builder.setMessage(isMediaPlaying ? R.string.bluetooth_broadcast_dialog_find_message
|
||||
: R.string.bluetooth_broadcast_dialog_broadcast_message);
|
||||
TextView title = layout.findViewById(com.android.settingslib.R.id.dialog_title);
|
||||
TextView subTitle = layout.findViewById(com.android.settingslib.R.id.dialog_subtitle);
|
||||
title.setText(mContext.getString(R.string.bluetooth_broadcast_dialog_title));
|
||||
subTitle.setText(
|
||||
mContext.getString(R.string.bluetooth_broadcast_dialog_broadcast_message));
|
||||
|
||||
ArrayList<String> optionList = new ArrayList<String>();
|
||||
if (!isMediaPlaying) {
|
||||
optionList.add(context.getString(R.string.bluetooth_broadcast_dialog_title));
|
||||
}
|
||||
optionList.add(context.getString(R.string.bluetooth_find_broadcast));
|
||||
optionList.add(context.getString(android.R.string.cancel));
|
||||
|
||||
View content = LayoutInflater.from(context).inflate(
|
||||
R.layout.sim_confirm_dialog_multiple_enabled_profiles_supported, null);
|
||||
|
||||
if (content != null) {
|
||||
Log.i(TAG, "list =" + optionList.toString());
|
||||
|
||||
final ArrayAdapter<String> arrayAdapterItems = new ArrayAdapter<String>(
|
||||
context,
|
||||
R.layout.sim_confirm_dialog_item_multiple_enabled_profiles_supported,
|
||||
optionList);
|
||||
final ListView lvItems = content.findViewById(R.id.carrier_list);
|
||||
if (lvItems != null) {
|
||||
lvItems.setVisibility(View.VISIBLE);
|
||||
lvItems.setAdapter(arrayAdapterItems);
|
||||
lvItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
Log.i(TAG, "list onClick =" + position);
|
||||
Log.i(TAG, "list item =" + optionList.get(position));
|
||||
|
||||
if (position == optionList.size() - 1) {
|
||||
// The last position in the options is the Cancel button. So when
|
||||
// the user clicks the button, we do nothing but dismiss the dialog.
|
||||
dismiss();
|
||||
} else {
|
||||
if (optionList.get(position).equals(
|
||||
context.getString(R.string.bluetooth_find_broadcast))) {
|
||||
launchFindBroadcastsActivity();
|
||||
} else {
|
||||
launchMediaOutputBroadcastDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
builder.setView(content);
|
||||
Button broadcastBtn = layout.findViewById(com.android.settingslib.R.id.positive_btn);
|
||||
if (TextUtils.isEmpty(mCurrentAppLabel)) {
|
||||
broadcastBtn.setText(mContext.getString(R.string.bluetooth_broadcast_dialog_title));
|
||||
} else {
|
||||
Log.i(TAG, "optionList is empty");
|
||||
broadcastBtn.setText(mContext.getString(
|
||||
R.string.bluetooth_broadcast_dialog_broadcast_app,
|
||||
String.valueOf(mCurrentAppLabel)));
|
||||
}
|
||||
broadcastBtn.setOnClickListener((view) -> {
|
||||
launchMediaOutputBroadcastDialog();
|
||||
});
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
return dialog;
|
||||
Button findBroadcastBtn = layout.findViewById(com.android.settingslib.R.id.negative_btn);
|
||||
findBroadcastBtn.setText(mContext.getString(R.string.bluetooth_find_broadcast));
|
||||
findBroadcastBtn.setOnClickListener((view) -> {
|
||||
launchFindBroadcastsActivity();
|
||||
});
|
||||
|
||||
Button cancelBtn = layout.findViewById(com.android.settingslib.R.id.neutral_btn);
|
||||
cancelBtn.setOnClickListener((view) -> {
|
||||
dismiss();
|
||||
getActivity().finish();
|
||||
});
|
||||
|
||||
mAlertDialog = new AlertDialog.Builder(mContext,
|
||||
com.android.settingslib.R.style.Theme_AlertDialog_SettingsLib)
|
||||
.setView(layout)
|
||||
.create();
|
||||
|
||||
return mAlertDialog;
|
||||
}
|
||||
|
||||
private boolean isMediaPlaying() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -130,10 +118,55 @@ public class BluetoothBroadcastDialog extends InstrumentedDialogFragment {
|
||||
}
|
||||
|
||||
private void launchFindBroadcastsActivity() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_DEVICE_ADDRESS, mDeviceAddress);
|
||||
|
||||
new SubSettingLauncher(mContext)
|
||||
.setTitleRes(R.string.bluetooth_find_broadcast_title)
|
||||
.setDestination(BluetoothFindBroadcastsFragment.class.getName())
|
||||
.setArguments(bundle)
|
||||
.setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
|
||||
.launch();
|
||||
dismissVolumePanel();
|
||||
}
|
||||
|
||||
private void launchMediaOutputBroadcastDialog() {
|
||||
if (startBroadcast()) {
|
||||
mContext.sendBroadcast(new Intent()
|
||||
.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
|
||||
.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG)
|
||||
.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
|
||||
getActivity().getPackageName()));
|
||||
dismissVolumePanel();
|
||||
}
|
||||
}
|
||||
|
||||
private LocalBluetoothLeBroadcast getLEAudioBroadcastProfile() {
|
||||
if (mLocalBluetoothManager != null && mLocalBluetoothManager.getProfileManager() != null) {
|
||||
LocalBluetoothLeBroadcast bluetoothLeBroadcast =
|
||||
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
|
||||
if (bluetoothLeBroadcast != null) {
|
||||
return bluetoothLeBroadcast;
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Can not get LE Audio Broadcast Profile");
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean startBroadcast() {
|
||||
LocalBluetoothLeBroadcast btLeBroadcast = getLEAudioBroadcastProfile();
|
||||
if (btLeBroadcast != null) {
|
||||
btLeBroadcast.startBroadcast(String.valueOf(mCurrentAppLabel), null);
|
||||
return true;
|
||||
}
|
||||
Log.d(TAG, "Can not broadcast successfully");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void dismissVolumePanel() {
|
||||
// Dismiss volume panel
|
||||
mContext.sendBroadcast(new Intent()
|
||||
.setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)
|
||||
.setAction(MediaOutputConstants.ACTION_CLOSE_PANEL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.bluetooth.BluetoothLeAudioContentMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.BluetoothLeBroadcastSubgroup;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Preference to display a broadcast source in the Broadcast Source List.
|
||||
*/
|
||||
class BluetoothBroadcastSourcePreference extends Preference {
|
||||
|
||||
private static final int RESOURCE_ID_UNKNOWN_PROGRAM_INFO = R.string.device_info_default;
|
||||
private static final int RESOURCE_ID_ICON = R.drawable.settings_input_antenna;
|
||||
|
||||
private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
|
||||
private BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState;
|
||||
private ImageView mFrictionImageView;
|
||||
private String mTitle;
|
||||
private boolean mStatus;
|
||||
private boolean mIsEncrypted;
|
||||
|
||||
BluetoothBroadcastSourcePreference(@NonNull Context context) {
|
||||
super(context);
|
||||
initUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final PreferenceViewHolder view) {
|
||||
super.onBindViewHolder(view);
|
||||
view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
|
||||
final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
|
||||
imageButton.setVisibility(View.GONE);
|
||||
mFrictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
|
||||
updateStatusButton();
|
||||
}
|
||||
|
||||
private void initUi() {
|
||||
setLayoutResource(R.layout.preference_access_point);
|
||||
setWidgetLayoutResource(R.layout.access_point_friction_widget);
|
||||
mTitle = getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO);
|
||||
mStatus = false;
|
||||
final Drawable drawable = getContext().getDrawable(RESOURCE_ID_ICON);
|
||||
if (drawable != null) {
|
||||
drawable.setTint(Utils.getColorAttrDefaultColor(getContext(),
|
||||
android.R.attr.colorControlNormal));
|
||||
setIcon(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatusButton() {
|
||||
if (mFrictionImageView == null) {
|
||||
return;
|
||||
}
|
||||
if (mStatus || mIsEncrypted) {
|
||||
Drawable drawable;
|
||||
if (mStatus) {
|
||||
drawable = getContext().getDrawable(R.drawable.bluetooth_broadcast_dialog_done);
|
||||
} else {
|
||||
drawable = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
|
||||
}
|
||||
if (drawable != null) {
|
||||
drawable.setTint(Utils.getColorAttrDefaultColor(getContext(),
|
||||
android.R.attr.colorControlNormal));
|
||||
mFrictionImageView.setImageDrawable(drawable);
|
||||
}
|
||||
mFrictionImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mFrictionImageView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the title and status from BluetoothLeBroadcastMetadata.
|
||||
*/
|
||||
public void updateMetadataAndRefreshUi(BluetoothLeBroadcastMetadata source, boolean status) {
|
||||
mBluetoothLeBroadcastMetadata = source;
|
||||
mTitle = getProgramInfo();
|
||||
mIsEncrypted = mBluetoothLeBroadcastMetadata.isEncrypted();
|
||||
mStatus = status || mBluetoothLeBroadcastReceiveState != null;
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the title and status from BluetoothLeBroadcastReceiveState.
|
||||
*/
|
||||
public void updateReceiveStateAndRefreshUi(BluetoothLeBroadcastReceiveState receiveState) {
|
||||
mBluetoothLeBroadcastReceiveState = receiveState;
|
||||
mTitle = getProgramInfo();
|
||||
mStatus = true;
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BluetoothLeBroadcastMetadata.
|
||||
*/
|
||||
public BluetoothLeBroadcastMetadata getBluetoothLeBroadcastMetadata() {
|
||||
return mBluetoothLeBroadcastMetadata;
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
setTitle(mTitle);
|
||||
updateStatusButton();
|
||||
}
|
||||
|
||||
private String getProgramInfo() {
|
||||
if (mBluetoothLeBroadcastReceiveState != null) {
|
||||
List<BluetoothLeAudioContentMetadata> bluetoothLeAudioContentMetadata =
|
||||
mBluetoothLeBroadcastReceiveState.getSubgroupMetadata();
|
||||
if (!bluetoothLeAudioContentMetadata.isEmpty()) {
|
||||
return bluetoothLeAudioContentMetadata.stream()
|
||||
.map(i -> i.getProgramInfo())
|
||||
.findFirst().orElse(
|
||||
getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO));
|
||||
}
|
||||
}
|
||||
if (mBluetoothLeBroadcastMetadata == null) {
|
||||
return getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO);
|
||||
}
|
||||
final List<BluetoothLeBroadcastSubgroup> subgroups =
|
||||
mBluetoothLeBroadcastMetadata.getSubgroups();
|
||||
if (subgroups.isEmpty()) {
|
||||
return getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO);
|
||||
}
|
||||
return subgroups.stream()
|
||||
.map(i -> i.getContentMetadata().getProgramInfo())
|
||||
.filter(i -> !TextUtils.isEmpty(i))
|
||||
.findFirst().orElse(getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the broadcast source is encrypted or not.
|
||||
* @return If true, the broadcast source needs the broadcast code. If false, the broadcast
|
||||
* source does not need the broadcast code.
|
||||
*/
|
||||
public boolean isEncrypted() {
|
||||
return mIsEncrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the BluetoothLeBroadcastReceiveState and reset the state when the user clicks the
|
||||
* "leave broadcast" button.
|
||||
*/
|
||||
public void clearReceiveState() {
|
||||
mBluetoothLeBroadcastReceiveState = null;
|
||||
mTitle = getProgramInfo();
|
||||
mStatus = false;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
@@ -70,7 +71,11 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle
|
||||
mActionButtons
|
||||
.setButton2Text(R.string.bluetooth_device_context_disconnect)
|
||||
.setButton2Icon(R.drawable.ic_settings_close)
|
||||
.setButton2OnClickListener(view -> mCachedDevice.disconnect());
|
||||
.setButton2OnClickListener(view -> {
|
||||
mMetricsFeatureProvider.action(mContext,
|
||||
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_DISCONNECT);
|
||||
mCachedDevice.disconnect();
|
||||
});
|
||||
mConnectButtonInitialized = true;
|
||||
}
|
||||
} else {
|
||||
@@ -79,7 +84,11 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle
|
||||
.setButton2Text(R.string.bluetooth_device_context_connect)
|
||||
.setButton2Icon(R.drawable.ic_add_24dp)
|
||||
.setButton2OnClickListener(
|
||||
view -> mCachedDevice.connect());
|
||||
view -> {
|
||||
mMetricsFeatureProvider.action(mContext,
|
||||
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
|
||||
mCachedDevice.connect();
|
||||
});
|
||||
mConnectButtonInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.settings.bluetooth;
|
||||
|
||||
import static com.android.internal.util.CollectionUtils.filter;
|
||||
|
||||
import android.companion.Association;
|
||||
import android.companion.AssociationInfo;
|
||||
import android.companion.CompanionDeviceManager;
|
||||
import android.companion.ICompanionDeviceManager;
|
||||
import android.content.Context;
|
||||
@@ -29,6 +29,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@@ -88,7 +89,7 @@ public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsCon
|
||||
mProfilesContainer.setLayoutResource(R.layout.preference_companion_app);
|
||||
}
|
||||
|
||||
private List<Association> getAssociations(String address) {
|
||||
private List<AssociationInfo> getAssociations(String address) {
|
||||
return filter(
|
||||
mCompanionDeviceManager.getAllAssociations(),
|
||||
a -> Objects.equal(address, a.getDeviceMacAddress()));
|
||||
@@ -126,8 +127,8 @@ public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsCon
|
||||
try {
|
||||
java.util.Objects.requireNonNull(ICompanionDeviceManager.Stub.asInterface(
|
||||
ServiceManager.getService(
|
||||
Context.COMPANION_DEVICE_SERVICE))).disassociate(
|
||||
address, packageName);
|
||||
Context.COMPANION_DEVICE_SERVICE))).legacyDisassociate(
|
||||
address, packageName, UserHandle.myUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -150,7 +151,7 @@ public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsCon
|
||||
private List<String> getPreferencesNeedToShow(String address, PreferenceCategory container) {
|
||||
List<String> preferencesToRemove = new ArrayList<>();
|
||||
Set<String> packages = getAssociations(address)
|
||||
.stream().map(Association::getPackageName)
|
||||
.stream().map(AssociationInfo::getPackageName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (int i = 0; i < container.getPreferenceCount(); i++) {
|
||||
@@ -185,7 +186,7 @@ public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsCon
|
||||
String address, PreferenceCategory container) {
|
||||
// If the device is FastPair, remove CDM companion apps.
|
||||
final BluetoothFeatureProvider bluetoothFeatureProvider = FeatureFactory.getFactory(context)
|
||||
.getBluetoothFeatureProvider(context);
|
||||
.getBluetoothFeatureProvider();
|
||||
final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
|
||||
SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
|
||||
final Uri settingsUri = bluetoothFeatureProvider.getBluetoothDeviceSettingsUri(
|
||||
|
||||
@@ -22,8 +22,10 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnPause;
|
||||
@@ -40,6 +42,7 @@ public abstract class BluetoothDetailsController extends AbstractPreferenceContr
|
||||
protected final Context mContext;
|
||||
protected final PreferenceFragmentCompat mFragment;
|
||||
protected final CachedBluetoothDevice mCachedDevice;
|
||||
protected final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
|
||||
public BluetoothDetailsController(Context context, PreferenceFragmentCompat fragment,
|
||||
CachedBluetoothDevice device, Lifecycle lifecycle) {
|
||||
@@ -48,6 +51,7 @@ public abstract class BluetoothDetailsController extends AbstractPreferenceContr
|
||||
mFragment = fragment;
|
||||
mCachedDevice = device;
|
||||
lifecycle.addObserver(this);
|
||||
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -75,10 +75,8 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController
|
||||
if (TextUtils.isEmpty(summaryText)) {
|
||||
// If first summary is unavailable, not to show second summary.
|
||||
mHeaderController.setSecondSummary((CharSequence)null);
|
||||
} else {
|
||||
// If both the hearing aids are connected, two device status should be shown.
|
||||
mHeaderController.setSecondSummary(mDeviceManager.getSubDeviceSummary(mCachedDevice));
|
||||
}
|
||||
|
||||
mHeaderController.setLabel(mCachedDevice.getName());
|
||||
mHeaderController.setIcon(pair.first);
|
||||
mHeaderController.setIconContentDescription(pair.second);
|
||||
|
||||
@@ -488,7 +488,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
|
||||
@Override
|
||||
protected void refresh() {
|
||||
for (LocalBluetoothProfile profile : getProfiles()) {
|
||||
if (!profile.isProfileReady()) {
|
||||
if (profile == null || !profile.isProfileReady()) {
|
||||
continue;
|
||||
}
|
||||
SwitchPreference pref = mProfilesContainer.findPreference(
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.AccessibilityShortcutInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.settings.accessibility.RestrictedPreferenceHelper;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This class adds related tools preference.
|
||||
*/
|
||||
public class BluetoothDetailsRelatedToolsController extends BluetoothDetailsController{
|
||||
private static final String KEY_RELATED_TOOLS_GROUP = "bluetooth_related_tools";
|
||||
private static final String KEY_LIVE_CAPTION = "live_caption";
|
||||
private static final int ORDINAL = 99;
|
||||
|
||||
private PreferenceCategory mPreferenceCategory;
|
||||
|
||||
public BluetoothDetailsRelatedToolsController(Context context,
|
||||
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
|
||||
super(context, fragment, device, lifecycle);
|
||||
lifecycle.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return mCachedDevice.isHearingAidDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(PreferenceScreen screen) {
|
||||
if (!mCachedDevice.isHearingAidDevice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPreferenceCategory = screen.findPreference(getPreferenceKey());
|
||||
final Preference liveCaptionPreference = screen.findPreference(KEY_LIVE_CAPTION);
|
||||
if (!liveCaptionPreference.isVisible()) {
|
||||
mPreferenceCategory.removePreference(liveCaptionPreference);
|
||||
}
|
||||
|
||||
final List<ComponentName> relatedToolsList = FeatureFactory.getFactory(
|
||||
mContext).getBluetoothFeatureProvider().getRelatedTools();
|
||||
if (!CollectionUtils.isEmpty(relatedToolsList)) {
|
||||
addAccessibilityInstalledRelatedPreference(relatedToolsList);
|
||||
}
|
||||
|
||||
if (mPreferenceCategory.getPreferenceCount() == 0) {
|
||||
screen.removePreference(mPreferenceCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh() {}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return KEY_RELATED_TOOLS_GROUP;
|
||||
}
|
||||
|
||||
private void addAccessibilityInstalledRelatedPreference(
|
||||
@NonNull List<ComponentName> componentNameList) {
|
||||
final AccessibilityManager a11yManager = AccessibilityManager.getInstance(mContext);
|
||||
final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(
|
||||
mContext);
|
||||
|
||||
final List<AccessibilityServiceInfo> a11yServiceInfoList =
|
||||
a11yManager.getInstalledAccessibilityServiceList().stream()
|
||||
.filter(info -> componentNameList.contains(info.getComponentName()))
|
||||
.collect(Collectors.toList());
|
||||
final List<AccessibilityShortcutInfo> a11yShortcutInfoList =
|
||||
a11yManager.getInstalledAccessibilityShortcutListAsUser(mContext,
|
||||
UserHandle.myUserId()).stream()
|
||||
.filter(info -> componentNameList.contains(info.getComponentName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<RestrictedPreference> preferences = Stream.of(
|
||||
preferenceHelper.createAccessibilityServicePreferenceList(a11yServiceInfoList),
|
||||
preferenceHelper.createAccessibilityActivityPreferenceList(a11yShortcutInfoList))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (RestrictedPreference preference : preferences) {
|
||||
preference.setOrder(ORDINAL);
|
||||
mPreferenceCategory.addPreference(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.content.Context;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
import android.media.Spatializer;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
/**
|
||||
* The controller of the Spatial audio setting in the bluetooth detail settings.
|
||||
*/
|
||||
public class BluetoothDetailsSpatialAudioController extends BluetoothDetailsController
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private static final String TAG = "BluetoothSpatialAudioController";
|
||||
private static final String KEY_SPATIAL_AUDIO_GROUP = "spatial_audio_group";
|
||||
private static final String KEY_SPATIAL_AUDIO = "spatial_audio";
|
||||
private static final String KEY_HEAD_TRACKING = "head_tracking";
|
||||
|
||||
private final Spatializer mSpatializer;
|
||||
|
||||
@VisibleForTesting
|
||||
PreferenceCategory mProfilesContainer;
|
||||
@VisibleForTesting
|
||||
AudioDeviceAttributes mAudioDevice;
|
||||
|
||||
public BluetoothDetailsSpatialAudioController(
|
||||
Context context,
|
||||
PreferenceFragmentCompat fragment,
|
||||
CachedBluetoothDevice device,
|
||||
Lifecycle lifecycle) {
|
||||
super(context, fragment, device, lifecycle);
|
||||
AudioManager audioManager = context.getSystemService(AudioManager.class);
|
||||
mSpatializer = audioManager.getSpatializer();
|
||||
mAudioDevice = new AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT,
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
|
||||
mCachedDevice.getAddress());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return mSpatializer.isAvailableForDevice(mAudioDevice) ? true : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
SwitchPreference switchPreference = (SwitchPreference) preference;
|
||||
String key = switchPreference.getKey();
|
||||
if (TextUtils.equals(key, KEY_SPATIAL_AUDIO)) {
|
||||
if (switchPreference.isChecked()) {
|
||||
mSpatializer.addCompatibleAudioDevice(mAudioDevice);
|
||||
} else {
|
||||
mSpatializer.removeCompatibleAudioDevice(mAudioDevice);
|
||||
}
|
||||
refresh();
|
||||
return true;
|
||||
} else if (TextUtils.equals(key, KEY_HEAD_TRACKING)) {
|
||||
mSpatializer.setHeadTrackerEnabled(switchPreference.isChecked(), mAudioDevice);
|
||||
return true;
|
||||
} else {
|
||||
Log.w(TAG, "invalid key name.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return KEY_SPATIAL_AUDIO_GROUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(PreferenceScreen screen) {
|
||||
mProfilesContainer = screen.findPreference(getPreferenceKey());
|
||||
mProfilesContainer.setLayoutResource(R.layout.preference_bluetooth_profile_category);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh() {
|
||||
SwitchPreference spatialAudioPref = mProfilesContainer.findPreference(KEY_SPATIAL_AUDIO);
|
||||
if (spatialAudioPref == null) {
|
||||
spatialAudioPref = createSpatialAudioPreference(mProfilesContainer.getContext());
|
||||
mProfilesContainer.addPreference(spatialAudioPref);
|
||||
}
|
||||
|
||||
boolean isSpatialAudioOn = mSpatializer.getCompatibleAudioDevices().contains(mAudioDevice);
|
||||
Log.d(TAG, "refresh() isSpatialAudioOn : " + isSpatialAudioOn);
|
||||
spatialAudioPref.setChecked(isSpatialAudioOn);
|
||||
|
||||
SwitchPreference headTrackingPref = mProfilesContainer.findPreference(KEY_HEAD_TRACKING);
|
||||
if (headTrackingPref == null) {
|
||||
headTrackingPref = createHeadTrackingPreference(mProfilesContainer.getContext());
|
||||
mProfilesContainer.addPreference(headTrackingPref);
|
||||
}
|
||||
|
||||
boolean isHeadTrackingAvailable =
|
||||
isSpatialAudioOn && mSpatializer.hasHeadTracker(mAudioDevice);
|
||||
Log.d(TAG, "refresh() has head tracker : " + mSpatializer.hasHeadTracker(mAudioDevice));
|
||||
headTrackingPref.setVisible(isHeadTrackingAvailable);
|
||||
if (isHeadTrackingAvailable) {
|
||||
headTrackingPref.setChecked(mSpatializer.isHeadTrackerEnabled(mAudioDevice));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SwitchPreference createSpatialAudioPreference(Context context) {
|
||||
SwitchPreference pref = new SwitchPreference(context);
|
||||
pref.setKey(KEY_SPATIAL_AUDIO);
|
||||
pref.setTitle(context.getString(R.string.bluetooth_details_spatial_audio_title));
|
||||
pref.setSummary(context.getString(R.string.bluetooth_details_spatial_audio_summary));
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
return pref;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SwitchPreference createHeadTrackingPreference(Context context) {
|
||||
SwitchPreference pref = new SwitchPreference(context);
|
||||
pref.setKey(KEY_HEAD_TRACKING);
|
||||
pref.setTitle(context.getString(R.string.bluetooth_details_head_tracking_title));
|
||||
pref.setSummary(context.getString(R.string.bluetooth_details_head_tracking_summary));
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,19 @@ import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
@@ -36,6 +43,7 @@ import com.android.settings.core.SettingsUIDeviceConfig;
|
||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.slices.BlockingSlicePrefController;
|
||||
import com.android.settings.slices.SlicePreferenceController;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
@@ -61,6 +69,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
|
||||
@VisibleForTesting
|
||||
interface TestDataFactory {
|
||||
CachedBluetoothDevice getDevice(String deviceAddress);
|
||||
|
||||
LocalBluetoothManager getManager(Context context);
|
||||
}
|
||||
|
||||
@@ -120,7 +129,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
|
||||
use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager);
|
||||
|
||||
final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
|
||||
context).getBluetoothFeatureProvider(context);
|
||||
context).getBluetoothFeatureProvider();
|
||||
final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
|
||||
SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
|
||||
|
||||
@@ -129,6 +138,52 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
|
||||
: null);
|
||||
}
|
||||
|
||||
private void updateExtraControlUri(int viewWidth) {
|
||||
BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
|
||||
getContext()).getBluetoothFeatureProvider();
|
||||
boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
|
||||
SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
|
||||
Uri controlUri = null;
|
||||
String uri = featureProvider.getBluetoothDeviceControlUri(mCachedDevice.getDevice());
|
||||
if (!TextUtils.isEmpty(uri)) {
|
||||
try {
|
||||
controlUri = Uri.parse(uri + viewWidth);
|
||||
} catch (NullPointerException exception) {
|
||||
Log.d(TAG, "unable to parse uri");
|
||||
controlUri = null;
|
||||
}
|
||||
}
|
||||
use(SlicePreferenceController.class).setSliceUri(sliceEnabled ? controlUri : null);
|
||||
use(SlicePreferenceController.class).onStart();
|
||||
}
|
||||
|
||||
private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener =
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
View view = getView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
if (view.getWidth() <= 0) {
|
||||
return;
|
||||
}
|
||||
updateExtraControlUri(view.getWidth() - getPaddingSize());
|
||||
view.getViewTreeObserver().removeOnGlobalLayoutListener(
|
||||
mOnGlobalLayoutListener);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
if (view != null) {
|
||||
view.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@@ -188,11 +243,28 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
|
||||
lifecycle));
|
||||
controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
|
||||
mCachedDevice, lifecycle));
|
||||
controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
|
||||
lifecycle));
|
||||
controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
|
||||
mCachedDevice, lifecycle));
|
||||
controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
|
||||
lifecycle));
|
||||
controllers.add(new BluetoothDetailsRelatedToolsController(context, this, mCachedDevice,
|
||||
lifecycle));
|
||||
}
|
||||
return controllers;
|
||||
}
|
||||
|
||||
private int getPaddingSize() {
|
||||
TypedArray resolvedAttributes =
|
||||
getContext().obtainStyledAttributes(
|
||||
new int[]{
|
||||
android.R.attr.listPreferredItemPaddingStart,
|
||||
android.R.attr.listPreferredItemPaddingEnd
|
||||
});
|
||||
int width = resolvedAttributes.getDimensionPixelSize(0, 0)
|
||||
+ resolvedAttributes.getDimensionPixelSize(1, 0);
|
||||
resolvedAttributes.recycle();
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import androidx.preference.Preference;
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothDiscoverableTimeoutReceiver;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
|
||||
* checkbox. It sets/unsets discoverability and keeps track of how much time
|
||||
@@ -136,9 +138,8 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClick
|
||||
int timeout = getDiscoverableTimeout();
|
||||
long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
|
||||
LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);
|
||||
|
||||
mBluetoothAdapter
|
||||
.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
|
||||
mBluetoothAdapter.setDiscoverableTimeout(Duration.ofSeconds(timeout));
|
||||
mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
|
||||
updateCountdownSummary();
|
||||
|
||||
Log.d(TAG, "setEnabled(): enabled = " + enable + "timeout = " + timeout);
|
||||
|
||||
@@ -115,7 +115,8 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh
|
||||
}
|
||||
|
||||
mSwitchController.startListening();
|
||||
mContext.registerReceiver(mReceiver, mIntentFilter);
|
||||
mContext.registerReceiver(mReceiver, mIntentFilter,
|
||||
Context.RECEIVER_EXPORTED_UNAUDITED);
|
||||
mValidListener = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,37 @@
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ComponentName;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provider for bluetooth related feature
|
||||
* Provider for bluetooth related features.
|
||||
*/
|
||||
public interface BluetoothFeatureProvider {
|
||||
|
||||
/**
|
||||
* Get the {@link Uri} that represents extra settings for a specific bluetooth device
|
||||
* Gets the {@link Uri} that represents extra settings for a specific bluetooth device
|
||||
*
|
||||
* @param bluetoothDevice bluetooth device
|
||||
* @return {@link Uri} for extra settings
|
||||
*/
|
||||
Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice);
|
||||
|
||||
/**
|
||||
* Gets the {@link Uri} that represents extra control for a specific bluetooth device
|
||||
*
|
||||
* @param bluetoothDevice bluetooth device
|
||||
* @return {@link String} uri string for extra control
|
||||
*/
|
||||
String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice);
|
||||
|
||||
/**
|
||||
* Gets the {@link ComponentName} of services or activities that need to be shown in related
|
||||
* tools.
|
||||
*
|
||||
* @return list of {@link ComponentName}
|
||||
*/
|
||||
List<ComponentName> getRelatedTools();
|
||||
}
|
||||
|
||||
@@ -17,19 +17,20 @@
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Impl of {@link BluetoothFeatureProvider}
|
||||
*/
|
||||
public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public BluetoothFeatureProviderImpl(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
public BluetoothFeatureProviderImpl(Context context) {}
|
||||
|
||||
@Override
|
||||
public Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice) {
|
||||
@@ -37,4 +38,14 @@ public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
|
||||
BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
|
||||
return uriByte == null ? null : Uri.parse(new String(uriByte));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) {
|
||||
return BluetoothUtils.getControlUriMetaData(bluetoothDevice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ComponentName> getRelatedTools() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,33 +19,53 @@ package com.android.settings.bluetooth;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_NONE;
|
||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastAssistant;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
||||
/**
|
||||
* This fragment allowed users to find the nearby broadcast sources.
|
||||
*/
|
||||
public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment {
|
||||
|
||||
private static final String TAG = "BTFindBroadcastsFrg";
|
||||
private static final String TAG = "BtFindBroadcastsFrg";
|
||||
|
||||
public static final String KEY_DEVICE_ADDRESS = "device_address";
|
||||
|
||||
public static final String PREF_KEY_BROADCAST_SOURCE = "broadcast_source";
|
||||
public static final String PREF_KEY_BROADCAST_SOURCE_LIST = "broadcast_source_list";
|
||||
|
||||
@VisibleForTesting
|
||||
String mDeviceAddress;
|
||||
@@ -53,6 +73,91 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment
|
||||
LocalBluetoothManager mManager;
|
||||
@VisibleForTesting
|
||||
CachedBluetoothDevice mCachedDevice;
|
||||
@VisibleForTesting
|
||||
PreferenceCategory mBroadcastSourceListCategory;
|
||||
BluetoothFindBroadcastsHeaderController mBluetoothFindBroadcastsHeaderController;
|
||||
private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
|
||||
private BluetoothBroadcastSourcePreference mSelectedPreference;
|
||||
private Executor mExecutor;
|
||||
private int mSourceId;
|
||||
|
||||
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
|
||||
new BluetoothLeBroadcastAssistant.Callback() {
|
||||
@Override
|
||||
public void onSearchStarted(int reason) {
|
||||
Log.d(TAG, "onSearchStarted: " + reason);
|
||||
getActivity().runOnUiThread(() -> handleSearchStarted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchStartFailed(int reason) {
|
||||
Log.d(TAG, "onSearchStartFailed: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchStopped(int reason) {
|
||||
Log.d(TAG, "onSearchStopped: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchStopFailed(int reason) {
|
||||
Log.d(TAG, "onSearchStopFailed: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
|
||||
Log.d(TAG, "onSourceFound:");
|
||||
getActivity().runOnUiThread(
|
||||
() -> updateListCategoryFromBroadcastMetadata(source, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
|
||||
setSourceId(sourceId);
|
||||
if (mSelectedPreference == null) {
|
||||
Log.w(TAG, "onSourceAdded: mSelectedPreference == null!");
|
||||
return;
|
||||
}
|
||||
getActivity().runOnUiThread(() -> updateListCategoryFromBroadcastMetadata(
|
||||
mSelectedPreference.getBluetoothLeBroadcastMetadata(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceAddFailed(@NonNull BluetoothDevice sink,
|
||||
@NonNull BluetoothLeBroadcastMetadata source, int reason) {
|
||||
mSelectedPreference = null;
|
||||
Log.d(TAG, "onSourceAddFailed: clear the mSelectedPreference.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
|
||||
int reason) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
|
||||
int reason) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
|
||||
int reason) {
|
||||
Log.d(TAG, "onSourceRemoved:");
|
||||
getActivity().runOnUiThread(() -> handleSourceRemoved());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
|
||||
int reason) {
|
||||
Log.d(TAG, "onSourceRemoveFailed:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
|
||||
@NonNull BluetoothLeBroadcastReceiveState state) {
|
||||
Log.d(TAG, "onReceiveStateChanged:");
|
||||
}
|
||||
};
|
||||
|
||||
public BluetoothFindBroadcastsFragment() {
|
||||
super(DISALLOW_CONFIG_BLUETOOTH);
|
||||
@@ -75,19 +180,52 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment
|
||||
mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
|
||||
mManager = getLocalBluetoothManager(context);
|
||||
mCachedDevice = getCachedDevice(mDeviceAddress);
|
||||
mLeBroadcastAssistant = getLeBroadcastAssistant();
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
super.onAttach(context);
|
||||
if (mCachedDevice == null) {
|
||||
if (mCachedDevice == null || mLeBroadcastAssistant == null) {
|
||||
//Close this page if device is null with invalid device mac address
|
||||
Log.w(TAG, "onAttach() CachedDevice is null!");
|
||||
//or if the device does not have LeBroadcastAssistant profile
|
||||
Log.w(TAG, "onAttach() CachedDevice or LeBroadcastAssistant is null!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
mBroadcastSourceListCategory = findPreference(PREF_KEY_BROADCAST_SOURCE_LIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (mLeBroadcastAssistant != null) {
|
||||
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
finishFragmentIfNecessary();
|
||||
//check assistant status. Start searching...
|
||||
if (mLeBroadcastAssistant != null && !mLeBroadcastAssistant.isSearchInProgress()) {
|
||||
mLeBroadcastAssistant.startSearchingForSources(getScanFilter());
|
||||
} else {
|
||||
addConnectedSourcePreference();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (mLeBroadcastAssistant != null) {
|
||||
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -104,6 +242,28 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment
|
||||
return SettingsEnums.PAGE_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts to scan broadcast source by the BluetoothLeBroadcastAssistant.
|
||||
*/
|
||||
public void scanBroadcastSource() {
|
||||
if (mLeBroadcastAssistant == null) {
|
||||
Log.w(TAG, "scanBroadcastSource: LeBroadcastAssistant is null!");
|
||||
return;
|
||||
}
|
||||
mLeBroadcastAssistant.startSearchingForSources(getScanFilter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the broadcast source by the BluetoothLeBroadcastAssistant.
|
||||
*/
|
||||
public void leaveBroadcastSession() {
|
||||
if (mLeBroadcastAssistant == null || mCachedDevice == null) {
|
||||
Log.w(TAG, "leaveBroadcastSession: LeBroadcastAssistant or CachedDevice is null!");
|
||||
return;
|
||||
}
|
||||
mLeBroadcastAssistant.removeSource(mCachedDevice.getDevice(), getSourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return TAG;
|
||||
@@ -120,9 +280,178 @@ public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment
|
||||
|
||||
if (mCachedDevice != null) {
|
||||
Lifecycle lifecycle = getSettingsLifecycle();
|
||||
controllers.add(new BluetoothFindBroadcastsHeaderController(context, this,
|
||||
mCachedDevice, lifecycle, mManager));
|
||||
mBluetoothFindBroadcastsHeaderController = new BluetoothFindBroadcastsHeaderController(
|
||||
context, this, mCachedDevice, lifecycle, mManager);
|
||||
controllers.add(mBluetoothFindBroadcastsHeaderController);
|
||||
}
|
||||
return controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the LocalBluetoothLeBroadcastAssistant
|
||||
* @return the LocalBluetoothLeBroadcastAssistant
|
||||
*/
|
||||
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
|
||||
if (mManager == null) {
|
||||
Log.w(TAG, "getLeBroadcastAssistant: LocalBluetoothManager is null!");
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalBluetoothProfileManager profileManager = mManager.getProfileManager();
|
||||
if (profileManager == null) {
|
||||
Log.w(TAG, "getLeBroadcastAssistant: LocalBluetoothProfileManager is null!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return profileManager.getLeAudioBroadcastAssistantProfile();
|
||||
}
|
||||
|
||||
private List<ScanFilter> getScanFilter() {
|
||||
// Currently there is no function for setting the ScanFilter. It may have this function
|
||||
// in the further.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private void updateListCategoryFromBroadcastMetadata(BluetoothLeBroadcastMetadata source,
|
||||
boolean isConnected) {
|
||||
BluetoothBroadcastSourcePreference item = mBroadcastSourceListCategory.findPreference(
|
||||
Integer.toString(source.getBroadcastId()));
|
||||
if (item == null) {
|
||||
item = createBluetoothBroadcastSourcePreference();
|
||||
item.setKey(Integer.toString(source.getBroadcastId()));
|
||||
mBroadcastSourceListCategory.addPreference(item);
|
||||
}
|
||||
item.updateMetadataAndRefreshUi(source, isConnected);
|
||||
item.setOrder(isConnected ? 0 : 1);
|
||||
|
||||
//refresh the header
|
||||
if (mBluetoothFindBroadcastsHeaderController != null) {
|
||||
mBluetoothFindBroadcastsHeaderController.refreshUi();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateListCategoryFromBroadcastReceiveState(
|
||||
BluetoothLeBroadcastReceiveState receiveState) {
|
||||
BluetoothBroadcastSourcePreference item = mBroadcastSourceListCategory.findPreference(
|
||||
Integer.toString(receiveState.getBroadcastId()));
|
||||
if (item == null) {
|
||||
item = createBluetoothBroadcastSourcePreference();
|
||||
item.setKey(Integer.toString(receiveState.getBroadcastId()));
|
||||
mBroadcastSourceListCategory.addPreference(item);
|
||||
}
|
||||
item.updateReceiveStateAndRefreshUi(receiveState);
|
||||
item.setOrder(0);
|
||||
|
||||
setSourceId(receiveState.getSourceId());
|
||||
mSelectedPreference = item;
|
||||
|
||||
//refresh the header
|
||||
if (mBluetoothFindBroadcastsHeaderController != null) {
|
||||
mBluetoothFindBroadcastsHeaderController.refreshUi();
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothBroadcastSourcePreference createBluetoothBroadcastSourcePreference() {
|
||||
BluetoothBroadcastSourcePreference pref = new BluetoothBroadcastSourcePreference(
|
||||
getContext());
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
if (pref.getBluetoothLeBroadcastMetadata() == null) {
|
||||
Log.d(TAG, "BluetoothLeBroadcastMetadata is null, do nothing.");
|
||||
return false;
|
||||
}
|
||||
if (pref.isEncrypted()) {
|
||||
launchBroadcastCodeDialog(pref);
|
||||
} else {
|
||||
addSource(pref);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return pref;
|
||||
}
|
||||
|
||||
private void addSource(BluetoothBroadcastSourcePreference pref) {
|
||||
if (mLeBroadcastAssistant == null || mCachedDevice == null) {
|
||||
Log.w(TAG, "addSource: LeBroadcastAssistant or CachedDevice is null!");
|
||||
return;
|
||||
}
|
||||
if (mSelectedPreference != null) {
|
||||
// The previous preference status set false after user selects the new Preference.
|
||||
getActivity().runOnUiThread(
|
||||
() -> {
|
||||
mSelectedPreference.updateMetadataAndRefreshUi(
|
||||
mSelectedPreference.getBluetoothLeBroadcastMetadata(), false);
|
||||
mSelectedPreference.setOrder(1);
|
||||
});
|
||||
}
|
||||
mSelectedPreference = pref;
|
||||
mLeBroadcastAssistant.addSource(mCachedDevice.getDevice(),
|
||||
pref.getBluetoothLeBroadcastMetadata(), true);
|
||||
}
|
||||
|
||||
private void addBroadcastCodeIntoPreference(BluetoothBroadcastSourcePreference pref,
|
||||
String broadcastCode) {
|
||||
BluetoothLeBroadcastMetadata metadata =
|
||||
new BluetoothLeBroadcastMetadata.Builder(pref.getBluetoothLeBroadcastMetadata())
|
||||
.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8))
|
||||
.build();
|
||||
pref.updateMetadataAndRefreshUi(metadata, false);
|
||||
}
|
||||
|
||||
private void launchBroadcastCodeDialog(BluetoothBroadcastSourcePreference pref) {
|
||||
final View layout = LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.bluetooth_find_broadcast_password_dialog, null);
|
||||
final TextView broadcastName = layout.requireViewById(R.id.broadcast_name_text);
|
||||
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
|
||||
broadcastName.setText(pref.getTitle());
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(getContext())
|
||||
.setTitle(R.string.find_broadcast_password_dialog_title)
|
||||
.setView(layout)
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.bluetooth_connect_access_dialog_positive,
|
||||
(d, w) -> {
|
||||
Log.d(TAG, "setPositiveButton: clicked");
|
||||
if (pref.getBluetoothLeBroadcastMetadata() == null) {
|
||||
Log.d(TAG, "BluetoothLeBroadcastMetadata is null, do nothing.");
|
||||
return;
|
||||
}
|
||||
addBroadcastCodeIntoPreference(pref, editText.getText().toString());
|
||||
addSource(pref);
|
||||
})
|
||||
.create();
|
||||
|
||||
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private void handleSearchStarted() {
|
||||
cacheRemoveAllPrefs(mBroadcastSourceListCategory);
|
||||
addConnectedSourcePreference();
|
||||
}
|
||||
|
||||
private void handleSourceRemoved() {
|
||||
if (mSelectedPreference != null) {
|
||||
if (mSelectedPreference.getBluetoothLeBroadcastMetadata() == null) {
|
||||
mBroadcastSourceListCategory.removePreference(mSelectedPreference);
|
||||
} else {
|
||||
mSelectedPreference.clearReceiveState();
|
||||
}
|
||||
}
|
||||
mSelectedPreference = null;
|
||||
}
|
||||
|
||||
private void addConnectedSourcePreference() {
|
||||
List<BluetoothLeBroadcastReceiveState> receiveStateList =
|
||||
mLeBroadcastAssistant.getAllSources(mCachedDevice.getDevice());
|
||||
if (!receiveStateList.isEmpty()) {
|
||||
updateListCategoryFromBroadcastReceiveState(receiveStateList.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public int getSourceId() {
|
||||
return mSourceId;
|
||||
}
|
||||
|
||||
public void setSourceId(int sourceId) {
|
||||
mSourceId = sourceId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,22 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
@@ -52,11 +53,12 @@ public class BluetoothFindBroadcastsHeaderController extends BluetoothDetailsCon
|
||||
LinearLayout mBtnBroadcastLayout;
|
||||
Button mBtnLeaveBroadcast;
|
||||
Button mBtnScanQrCode;
|
||||
|
||||
BluetoothFindBroadcastsFragment mBluetoothFindBroadcastsFragment;
|
||||
public BluetoothFindBroadcastsHeaderController(Context context,
|
||||
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle,
|
||||
LocalBluetoothManager bluetoothManager) {
|
||||
BluetoothFindBroadcastsFragment fragment, CachedBluetoothDevice device,
|
||||
Lifecycle lifecycle, LocalBluetoothManager bluetoothManager) {
|
||||
super(context, fragment, device, lifecycle);
|
||||
mBluetoothFindBroadcastsFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,20 +103,41 @@ public class BluetoothFindBroadcastsHeaderController extends BluetoothDetailsCon
|
||||
mBtnFindBroadcast.setVisibility(View.VISIBLE);
|
||||
mBtnBroadcastLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mBtnLeaveBroadcast.setEnabled(false);
|
||||
if (mBluetoothFindBroadcastsFragment != null && mCachedDevice != null) {
|
||||
LocalBluetoothLeBroadcastAssistant broadcastAssistant =
|
||||
mBluetoothFindBroadcastsFragment.getLeBroadcastAssistant();
|
||||
if (broadcastAssistant != null
|
||||
&& broadcastAssistant.getConnectionStatus(mCachedDevice.getDevice())
|
||||
== BluetoothProfile.STATE_CONNECTED) {
|
||||
mBtnLeaveBroadcast.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanBroadcastSource() {
|
||||
// TODO(b/228258236) : Call the LocalBluetoothLeBroadcastAssistant
|
||||
// to start searching for source
|
||||
// TODO(b/231543455) : Using the BluetoothDeviceUpdater to refactor it.
|
||||
if (mBluetoothFindBroadcastsFragment == null) {
|
||||
return;
|
||||
}
|
||||
mBluetoothFindBroadcastsFragment.scanBroadcastSource();
|
||||
}
|
||||
|
||||
private void leaveBroadcastSession() {
|
||||
// TODO(b/228258236) : Call the LocalBluetoothLeBroadcastAssistant
|
||||
// to leave the broadcast session
|
||||
if (mBluetoothFindBroadcastsFragment == null) {
|
||||
return;
|
||||
}
|
||||
mBluetoothFindBroadcastsFragment.leaveBroadcastSession();
|
||||
}
|
||||
|
||||
private void launchQrCodeScanner() {
|
||||
// TODO(b/228259065) : Launch the QR code scanner page by intent
|
||||
final Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
|
||||
intent.setAction(BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER)
|
||||
.putExtra(BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP, true)
|
||||
.putExtra(BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK,
|
||||
mCachedDevice.getDevice());
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,4 +151,11 @@ public class BluetoothFindBroadcastsHeaderController extends BluetoothDetailsCon
|
||||
public String getPreferenceKey() {
|
||||
return KEY_BROADCAST_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI
|
||||
*/
|
||||
public void refreshUi() {
|
||||
updateHeaderLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,15 +147,13 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver {
|
||||
title = context.getString(
|
||||
R.string.bluetooth_sim_card_access_notification_title);
|
||||
message = context.getString(
|
||||
R.string.bluetooth_sim_card_access_notification_content,
|
||||
deviceAlias, deviceAlias);
|
||||
R.string.bluetooth_sim_card_access_notification_content);
|
||||
break;
|
||||
default:
|
||||
title = context.getString(
|
||||
R.string.bluetooth_connect_access_notification_title);
|
||||
message = context.getString(
|
||||
R.string.bluetooth_connect_access_notification_content,
|
||||
deviceAlias, deviceAlias);
|
||||
R.string.bluetooth_connect_access_notification_content);
|
||||
break;
|
||||
}
|
||||
NotificationManager notificationManager =
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.HearingAidProfile;
|
||||
|
||||
/**
|
||||
* Provides a dialog to pair another side of hearing aid device.
|
||||
*/
|
||||
public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment {
|
||||
public static final String TAG = "HearingAidPairingDialogFragment";
|
||||
private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side";
|
||||
|
||||
/**
|
||||
* Creates a new {@link HearingAidPairingDialogFragment} and shows pair another side of hearing
|
||||
* aid device according to {@code CachedBluetoothDevice} side.
|
||||
*
|
||||
* @param device The remote Bluetooth device, that needs to be hearing aid device.
|
||||
* @return a DialogFragment
|
||||
*/
|
||||
public static HearingAidPairingDialogFragment newInstance(CachedBluetoothDevice device) {
|
||||
Bundle args = new Bundle(1);
|
||||
args.putInt(KEY_CACHED_DEVICE_SIDE, device.getDeviceSide());
|
||||
final HearingAidPairingDialogFragment fragment = new HearingAidPairingDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
// TODO(b/225117454): Need to update SettingsEnums later
|
||||
return SettingsEnums.ACCESSIBILITY;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
final int deviceSide = getArguments().getInt(KEY_CACHED_DEVICE_SIDE);
|
||||
final int titleId = R.string.bluetooth_pair_other_ear_dialog_title;
|
||||
final int messageId = (deviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT)
|
||||
? R.string.bluetooth_pair_other_ear_dialog_left_ear_message
|
||||
: R.string.bluetooth_pair_other_ear_dialog_right_ear_message;
|
||||
final int pairBtnId = (deviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT)
|
||||
? R.string.bluetooth_pair_other_ear_dialog_right_ear_positive_button
|
||||
: R.string.bluetooth_pair_other_ear_dialog_left_ear_positive_button;
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setNegativeButton(
|
||||
android.R.string.cancel, /* listener= */ null)
|
||||
.setPositiveButton(pairBtnId, (dialog, which) -> positiveButtonListener())
|
||||
.create();
|
||||
}
|
||||
|
||||
private void positiveButtonListener() {
|
||||
new SubSettingLauncher(getActivity())
|
||||
.setDestination(BluetoothPairingDetail.class.getName())
|
||||
.setSourceMetricsCategory(SettingsEnums.ACCESSIBILITY)
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
@@ -198,38 +198,25 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private int getBatteryTitleResource(int deviceId) {
|
||||
if (deviceId == LEFT_DEVICE_ID) {
|
||||
return R.id.bt_battery_left_title;
|
||||
}
|
||||
if (deviceId == RIGHT_DEVICE_ID) {
|
||||
return R.id.bt_battery_right_title;
|
||||
}
|
||||
Log.d(TAG, "No resource id. The deviceId is " + deviceId);
|
||||
return INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
private int getBatterySummaryResource(int deviceId) {
|
||||
if (deviceId == LEFT_DEVICE_ID) {
|
||||
private int getBatterySummaryResource(int containerId) {
|
||||
if (containerId == R.id.bt_battery_case) {
|
||||
return R.id.bt_battery_case_summary;
|
||||
} else if (containerId == R.id.bt_battery_left) {
|
||||
return R.id.bt_battery_left_summary;
|
||||
}
|
||||
if (deviceId == RIGHT_DEVICE_ID) {
|
||||
} else if (containerId == R.id.bt_battery_right) {
|
||||
return R.id.bt_battery_right_summary;
|
||||
}
|
||||
Log.d(TAG, "No resource id. The deviceId is " + deviceId);
|
||||
Log.d(TAG, "No summary resource id. The containerId is " + containerId);
|
||||
return INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
private void hideAllOfBatteryLayouts() {
|
||||
// hide the case
|
||||
updateBatteryLayout(R.id.bt_battery_case_title, R.id.bt_battery_case_summary,
|
||||
BluetoothUtils.META_INT_ERROR);
|
||||
updateBatteryLayout(R.id.bt_battery_case, BluetoothUtils.META_INT_ERROR);
|
||||
// hide the left
|
||||
updateBatteryLayout(R.id.bt_battery_left_title, R.id.bt_battery_left_summary,
|
||||
BluetoothUtils.META_INT_ERROR);
|
||||
updateBatteryLayout(R.id.bt_battery_left, BluetoothUtils.META_INT_ERROR);
|
||||
// hide the right
|
||||
updateBatteryLayout(R.id.bt_battery_right_title, R.id.bt_battery_right_summary,
|
||||
BluetoothUtils.META_INT_ERROR);
|
||||
updateBatteryLayout(R.id.bt_battery_right, BluetoothUtils.META_INT_ERROR);
|
||||
}
|
||||
|
||||
private List<CachedBluetoothDevice> getAllOfLeAudioDevices() {
|
||||
@@ -285,36 +272,36 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr
|
||||
summary.setText(mCachedDevice.getConnectionSummary());
|
||||
}
|
||||
} else if (isLeft) {
|
||||
updateBatteryLayout(getBatteryTitleResource(LEFT_DEVICE_ID),
|
||||
getBatterySummaryResource(LEFT_DEVICE_ID), cachedDevice.getBatteryLevel());
|
||||
updateBatteryLayout(R.id.bt_battery_left, cachedDevice.getBatteryLevel());
|
||||
} else if (isRight) {
|
||||
updateBatteryLayout(getBatteryTitleResource(RIGHT_DEVICE_ID),
|
||||
getBatterySummaryResource(RIGHT_DEVICE_ID), cachedDevice.getBatteryLevel());
|
||||
updateBatteryLayout(R.id.bt_battery_right, cachedDevice.getBatteryLevel());
|
||||
} else {
|
||||
Log.d(TAG, "The device id is other Audio Location. Do nothing.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBatteryLayout(int titleResId, int summaryResId, int batteryLevel) {
|
||||
final TextView batteryTitleView = mLayoutPreference.findViewById(titleResId);
|
||||
final TextView batterySummaryView = mLayoutPreference.findViewById(summaryResId);
|
||||
if (batteryTitleView == null || batterySummaryView == null) {
|
||||
Log.e(TAG, "updateBatteryLayout: No TextView");
|
||||
private void updateBatteryLayout(int resId, int batteryLevel) {
|
||||
final View batteryView = mLayoutPreference.findViewById(resId);
|
||||
if (batteryView == null) {
|
||||
Log.e(TAG, "updateBatteryLayout: No View");
|
||||
return;
|
||||
}
|
||||
if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
|
||||
batteryTitleView.setVisibility(View.VISIBLE);
|
||||
batterySummaryView.setVisibility(View.VISIBLE);
|
||||
batterySummaryView.setText(
|
||||
com.android.settings.Utils.formatPercentage(batteryLevel));
|
||||
batteryView.setVisibility(View.VISIBLE);
|
||||
final TextView batterySummaryView =
|
||||
batteryView.requireViewById(getBatterySummaryResource(resId));
|
||||
final String batteryLevelPercentageString =
|
||||
com.android.settings.Utils.formatPercentage(batteryLevel);
|
||||
batterySummaryView.setText(batteryLevelPercentageString);
|
||||
batterySummaryView.setContentDescription(mContext.getString(
|
||||
R.string.bluetooth_battery_level, batteryLevelPercentageString));
|
||||
batterySummaryView.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
createBtBatteryIcon(mContext, batteryLevel), /* top */ null,
|
||||
/* end */ null, /* bottom */ null);
|
||||
} else {
|
||||
Log.d(TAG, "updateBatteryLayout: Hide it if it doesn't have battery information.");
|
||||
batteryTitleView.setVisibility(View.GONE);
|
||||
batterySummaryView.setVisibility(View.GONE);
|
||||
batteryView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
src/com/android/settings/bluetooth/QrCodeScanModeActivity.java
Normal file
111
src/com/android/settings/bluetooth/QrCodeScanModeActivity.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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 static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
|
||||
import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
|
||||
//TODO (b/232365943): Add test case for tthe QrCode UI.
|
||||
public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity {
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final String TAG = "QrCodeScanModeActivity";
|
||||
|
||||
private boolean mIsGroupOp;
|
||||
private BluetoothDevice mSink;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIntent(Intent intent) {
|
||||
String action = intent != null ? intent.getAction() : null;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "handleIntent(), action = " + action);
|
||||
}
|
||||
|
||||
if (action == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER:
|
||||
showQrCodeScannerFragment(intent);
|
||||
break;
|
||||
default:
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "Launch with an invalid action");
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected void showQrCodeScannerFragment(Intent intent) {
|
||||
if (intent == null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "intent is null, can not get bluetooth information from intent.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "showQrCodeScannerFragment");
|
||||
}
|
||||
|
||||
mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK);
|
||||
mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "get extra from intent");
|
||||
}
|
||||
|
||||
QrCodeScanModeFragment fragment =
|
||||
(QrCodeScanModeFragment) mFragmentManager.findFragmentByTag(
|
||||
BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
|
||||
|
||||
if (fragment == null) {
|
||||
fragment = new QrCodeScanModeFragment(mIsGroupOp, mSink);
|
||||
} else {
|
||||
if (fragment.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the fragment in back stack but not on top of the stack, we can simply pop
|
||||
// stack because current fragment transactions are arranged in an order
|
||||
mFragmentManager.popBackStackImmediate();
|
||||
return;
|
||||
}
|
||||
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
|
||||
|
||||
fragmentTransaction.replace(R.id.fragment_container, fragment,
|
||||
BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.core.lifecycle.ObservableActivity;
|
||||
|
||||
public abstract class QrCodeScanModeBaseActivity extends ObservableActivity {
|
||||
|
||||
protected FragmentManager mFragmentManager;
|
||||
|
||||
protected abstract void handleIntent(Intent intent);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTheme(R.style.SudThemeGlifV3_DayNight);
|
||||
|
||||
setContentView(R.layout.qrcode_scan_mode_activity);
|
||||
mFragmentManager = getSupportFragmentManager();
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
|
||||
public class QrCodeScanModeController {
|
||||
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final String TAG = "QrCodeScanModeController";
|
||||
|
||||
private LocalBluetoothLeBroadcastMetadata mLocalBroadcastMetadata;
|
||||
private LocalBluetoothLeBroadcastAssistant mLocalBroadcastAssistant;
|
||||
private LocalBluetoothManager mLocalBluetoothManager;
|
||||
private LocalBluetoothProfileManager mProfileManager;
|
||||
|
||||
public QrCodeScanModeController(Context context) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "QrCodeScanModeController constructor.");
|
||||
}
|
||||
mLocalBluetoothManager = Utils.getLocalBtManager(context);
|
||||
mProfileManager = mLocalBluetoothManager.getProfileManager();
|
||||
mLocalBroadcastMetadata = new LocalBluetoothLeBroadcastMetadata();
|
||||
CachedBluetoothDeviceManager cachedDeviceManager = new CachedBluetoothDeviceManager(context,
|
||||
mLocalBluetoothManager);
|
||||
mLocalBroadcastAssistant = new LocalBluetoothLeBroadcastAssistant(context,
|
||||
cachedDeviceManager, mProfileManager);
|
||||
}
|
||||
|
||||
private BluetoothLeBroadcastMetadata convertToBroadcastMetadata(String qrCodeString) {
|
||||
return mLocalBroadcastMetadata.convertToBroadcastMetadata(qrCodeString);
|
||||
}
|
||||
|
||||
public void addSource(BluetoothDevice sink, String sourceMetadata,
|
||||
boolean isGroupOp) {
|
||||
mLocalBroadcastAssistant.addSource(sink,
|
||||
convertToBroadcastMetadata(sourceMetadata), isGroupOp);
|
||||
}
|
||||
}
|
||||
243
src/com/android/settings/bluetooth/QrCodeScanModeFragment.java
Normal file
243
src/com/android/settings/bluetooth/QrCodeScanModeFragment.java
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.core.lifecycle.ObservableFragment;
|
||||
import com.android.settingslib.qrcode.QrCamera;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class QrCodeScanModeFragment extends InstrumentedFragment implements
|
||||
TextureView.SurfaceTextureListener,
|
||||
QrCamera.ScannerCallback {
|
||||
private static final boolean DEBUG = BluetoothUtils.D;
|
||||
private static final String TAG = "QrCodeScanModeFragment";
|
||||
|
||||
/** Message sent to hide error message */
|
||||
private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
|
||||
/** Message sent to show error message */
|
||||
private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
|
||||
/** Message sent to broadcast QR code */
|
||||
private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
|
||||
|
||||
private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
|
||||
private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
|
||||
|
||||
private boolean mIsGroupOp;
|
||||
private int mCornerRadius;
|
||||
private BluetoothDevice mSink;
|
||||
private String mBroadcastMetadata;
|
||||
private Context mContext;
|
||||
private QrCamera mCamera;
|
||||
private QrCodeScanModeController mController;
|
||||
private TextureView mTextureView;
|
||||
private TextView mSummary;
|
||||
private TextView mErrorMessage;
|
||||
|
||||
public QrCodeScanModeFragment(boolean isGroupOp, BluetoothDevice sink) {
|
||||
mIsGroupOp = isGroupOp;
|
||||
mSink = sink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mContext = getContext();
|
||||
mController = new QrCodeScanModeController(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.qrcode_scanner_fragment, container,
|
||||
/* attachToRoot */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
mTextureView = view.findViewById(R.id.preview_view);
|
||||
mCornerRadius = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.qrcode_preview_radius);
|
||||
mTextureView.setSurfaceTextureListener(this);
|
||||
mTextureView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(0,0, view.getWidth(), view.getHeight(), mCornerRadius);
|
||||
}
|
||||
});
|
||||
mTextureView.setClipToOutline(true);
|
||||
mErrorMessage = view.findViewById(R.id.error_message);
|
||||
}
|
||||
|
||||
private void initCamera(SurfaceTexture surface) {
|
||||
// Check if the camera has already created.
|
||||
if (mCamera == null) {
|
||||
mCamera = new QrCamera(mContext, this);
|
||||
mCamera.start(surface);
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyCamera() {
|
||||
if (mCamera != null) {
|
||||
mCamera.stop();
|
||||
mCamera = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
|
||||
initCamera(surface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width,
|
||||
int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
|
||||
destroyCamera();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccessfulResult(String qrCode) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "handleSuccessfulResult(), get the qr code string.");
|
||||
}
|
||||
mBroadcastMetadata = qrCode;
|
||||
handleBtLeAudioScanner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCameraFailure() {
|
||||
destroyCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size getViewSize() {
|
||||
return new Size(mTextureView.getWidth(), mTextureView.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getFramePosition(Size previewSize, int cameraOrientation) {
|
||||
return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransform(Matrix transform) {
|
||||
mTextureView.setTransform(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String qrCode) {
|
||||
if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) {
|
||||
return true;
|
||||
} else {
|
||||
showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDecodeTaskAlive() {
|
||||
return mCamera != null && mCamera.isDecodeTaskAlive();
|
||||
}
|
||||
|
||||
private final Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_HIDE_ERROR_MESSAGE:
|
||||
mErrorMessage.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
|
||||
case MESSAGE_SHOW_ERROR_MESSAGE:
|
||||
final String errorMessage = (String) msg.obj;
|
||||
|
||||
mErrorMessage.setVisibility(View.VISIBLE);
|
||||
mErrorMessage.setText(errorMessage);
|
||||
mErrorMessage.sendAccessibilityEvent(
|
||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
||||
|
||||
// Cancel any pending messages to hide error view and requeue the message so
|
||||
// user has time to see error
|
||||
removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
|
||||
sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
|
||||
SHOW_ERROR_MESSAGE_INTERVAL);
|
||||
break;
|
||||
|
||||
case MESSAGE_SCAN_BROADCAST_SUCCESS:
|
||||
mController.addSource(mSink, mBroadcastMetadata, mIsGroupOp);
|
||||
updateSummary();
|
||||
mSummary.sendAccessibilityEvent(
|
||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void showErrorMessage(@StringRes int messageResId) {
|
||||
final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
|
||||
getString(messageResId));
|
||||
message.sendToTarget();
|
||||
}
|
||||
|
||||
private void handleBtLeAudioScanner() {
|
||||
Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
|
||||
mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
|
||||
}
|
||||
|
||||
private void updateSummary() {
|
||||
mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner,
|
||||
null /* broadcast_name*/));;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothStatusCodes;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -39,7 +42,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.bluetooth.BluetoothDiscoverableTimeoutReceiver;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RequestPermissionActivity asks the user whether to enable discovery. This is
|
||||
@@ -261,22 +264,26 @@ public class RequestPermissionActivity extends Activity implements
|
||||
if (mRequest == REQUEST_ENABLE || mRequest == REQUEST_DISABLE) {
|
||||
// BT toggled. Done
|
||||
returnCode = RESULT_OK;
|
||||
} else if (mBluetoothAdapter.setScanMode(
|
||||
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) {
|
||||
// If already in discoverable mode, this will extend the timeout.
|
||||
long endTime = System.currentTimeMillis() + (long) mTimeout * 1000;
|
||||
LocalBluetoothPreferences.persistDiscoverableEndTimestamp(
|
||||
this, endTime);
|
||||
if (0 < mTimeout) {
|
||||
BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(this, endTime);
|
||||
}
|
||||
returnCode = mTimeout;
|
||||
// Activity.RESULT_FIRST_USER should be 1
|
||||
if (returnCode < RESULT_FIRST_USER) {
|
||||
returnCode = RESULT_FIRST_USER;
|
||||
}
|
||||
} else {
|
||||
returnCode = RESULT_CANCELED;
|
||||
mBluetoothAdapter.setDiscoverableTimeout(Duration.ofSeconds(mTimeout));
|
||||
if (mBluetoothAdapter.setScanMode(
|
||||
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||
== BluetoothStatusCodes.SUCCESS) {
|
||||
// If already in discoverable mode, this will extend the timeout.
|
||||
long endTime = System.currentTimeMillis() + (long) mTimeout * 1000;
|
||||
LocalBluetoothPreferences.persistDiscoverableEndTimestamp(
|
||||
this, endTime);
|
||||
if (0 < mTimeout) {
|
||||
BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(this, endTime);
|
||||
}
|
||||
returnCode = mTimeout;
|
||||
// Activity.RESULT_FIRST_USER should be 1
|
||||
if (returnCode < RESULT_FIRST_USER) {
|
||||
returnCode = RESULT_FIRST_USER;
|
||||
}
|
||||
} else {
|
||||
returnCode = RESULT_CANCELED;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDialog != null) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
@@ -117,6 +118,8 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
|
||||
if (device.isConnected()) {
|
||||
return device.setActive();
|
||||
}
|
||||
mMetricsFeatureProvider.action(mPrefContext,
|
||||
SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT);
|
||||
device.connect();
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user