Merge "[LE]Gray out the a2dp and hfp when LeAudio is enabled"
diff --git a/res/layout/confirm_convert_fbe.xml b/res/layout/confirm_convert_fbe.xml
deleted file mode 100644
index 537c368..0000000
--- a/res/layout/confirm_convert_fbe.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_marginBottom="12dp" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:layout_marginTop="12dp"
- android:textSize="18sp"
- android:text="@string/confirm_convert_to_fbe_warning" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_gravity="center"
- android:orientation="horizontal">
- <Button
- android:id="@+id/button_confirm_convert_fbe"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginBottom="12dp"
- android:text="@string/button_confirm_convert_fbe" />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/convert_fbe.xml b/res/layout/convert_fbe.xml
deleted file mode 100644
index d1e0cea..0000000
--- a/res/layout/convert_fbe.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_marginBottom="12dp" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/preference_no_icon_padding_start"
- android:layout_marginEnd="12dp"
- android:layout_marginTop="12dp"
- android:textSize="18sp"
- android:text="@string/convert_to_fbe_warning" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_gravity="center"
- android:orientation="horizontal">
- <Button
- android:id="@+id/button_convert_fbe"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginBottom="12dp"
- android:text="@string/button_convert_fbe" />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/le_audio_bt_entity_header.xml b/res/layout/le_audio_bt_entity_header.xml
new file mode 100644
index 0000000..6e2a1e8
--- /dev/null
+++ b/res/layout/le_audio_bt_entity_header.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/entity_header"
+ style="@style/EntityHeader"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:gravity="center_horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/entity_header_title"
+ style="@style/TextAppearance.EntityHeaderTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:singleLine="false"
+ android:ellipsize="marquee"
+ android:textDirection="locale"/>
+
+ <TextView
+ android:id="@+id/entity_header_summary"
+ style="@style/TextAppearance.EntityHeaderSummary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="4dp"
+ android:singleLine="false"
+ android:ellipsize="marquee"
+ android:textDirection="locale"/>
+
+ <ImageView
+ android:id="@+id/entity_header_icon"
+ android:layout_width="72dp"
+ android:layout_height="72dp"
+ android:layout_marginTop="24dp"
+ android:scaleType="fitCenter"
+ android:antialias="true"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/le_bluetooth_battery_start_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/bt_battery_case_title"
+ style="@style/TextAppearance.EntityHeaderTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:gravity="start|center_vertical"
+ android:ellipsize="end"
+ android:textDirection="locale"
+ android:text="@string/bluetooth_middle_name"
+ android:textSize="@dimen/advanced_bluetooth_header_title_text_size"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/bt_battery_left_title"
+ style="@style/TextAppearance.EntityHeaderTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:gravity="start|center_vertical"
+ android:ellipsize="end"
+ android:textDirection="locale"
+ android:text="@string/bluetooth_left_name"
+ android:textSize="@dimen/advanced_bluetooth_header_title_text_size"/>
+ <TextView
+ android:id="@+id/bt_battery_right_title"
+ style="@style/TextAppearance.EntityHeaderTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:gravity="start|center_vertical"
+ android:ellipsize="end"
+ android:textDirection="locale"
+ android:text="@string/bluetooth_right_name"
+ android:textSize="@dimen/advanced_bluetooth_header_title_text_size"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/le_bluetooth_summary_start_margin"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/bt_battery_case_summary"
+ style="@style/TextAppearance.EntityHeaderSummary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:padding="@dimen/le_bluetooth_summary_padding"
+ android:drawablePadding="@dimen/le_bluetooth_summary_drawable_padding"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/bt_battery_left_summary"
+ style="@style/TextAppearance.EntityHeaderSummary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:padding="@dimen/le_bluetooth_summary_padding"
+ android:drawablePadding="@dimen/le_bluetooth_summary_drawable_padding"/>
+ <TextView
+ android:id="@+id/bt_battery_right_summary"
+ style="@style/TextAppearance.EntityHeaderSummary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/le_bluetooth_battery_top_margin"
+ android:padding="@dimen/le_bluetooth_summary_padding"
+ android:drawablePadding="@dimen/le_bluetooth_summary_drawable_padding"/>
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5fabd09..f225150 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -392,6 +392,13 @@
<dimen name="advanced_bluetooth_battery_height">27.5dp</dimen>
<dimen name="advanced_bluetooth_battery_right_margin">-4dp</dimen>
+ <!-- Header layout of LE audio bluetooth device at bluretooth device detalis -->
+ <dimen name="le_bluetooth_battery_top_margin">5dp</dimen>
+ <dimen name="le_bluetooth_battery_start_margin">10dp</dimen>
+ <dimen name="le_bluetooth_summary_drawable_padding">6dp</dimen>
+ <dimen name="le_bluetooth_summary_start_margin">20dp</dimen>
+ <dimen name="le_bluetooth_summary_padding">1.5dp</dimen>
+
<!-- Developer option bluetooth settings dialog -->
<dimen name="developer_option_dialog_margin_start">8dp</dimen>
<dimen name="developer_option_dialog_margin_top">8dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5042302..0c3b8fd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11396,11 +11396,6 @@
<!-- Title for the See more preference item in Special app access settings [CHAR LIMIT=30] -->
<string name="special_access_more">See more</string>
- <!-- Developer option to convert to file encryption - final warning -->
- <string name="confirm_convert_to_fbe_warning">Really wipe user data and convert to file encryption?</string>
- <!-- Developer option to convert to file encryption - final button -->
- <string name="button_confirm_convert_fbe">Wipe and convert</string>
-
<!-- Reset rate-limiting in the system service ShortcutManager. "ShortcutManager" is the name of a system service and not translatable.
If the word "rate-limit" is hard to translate, use "Reset ShortcutManager API call limit" as the source text, which means
the same thing in this context.
diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml
index 9df1955..34599f7 100644
--- a/res/xml/bluetooth_device_details_fragment.xml
+++ b/res/xml/bluetooth_device_details_fragment.xml
@@ -34,6 +34,14 @@
settings:searchable="false"
settings:controller="com.android.settings.bluetooth.AdvancedBluetoothDetailsHeaderController"/>
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="le_audio_bluetooth_device_header"
+ android:layout="@layout/le_audio_bt_entity_header"
+ android:selectable="false"
+ settings:allowDividerBelow="true"
+ settings:searchable="false"
+ settings:controller="com.android.settings.bluetooth.LeAudioBluetoothDetailsHeaderController"/>
+
<com.android.settingslib.widget.ActionButtonsPreference
android:key="action_buttons"
settings:allowDividerBelow="true"/>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index cd37f4c..a0f0151 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -88,12 +88,6 @@
android:summary="@string/runningservices_settings_summary"
android:fragment="com.android.settings.applications.RunningServices" />
- <Preference
- android:key="convert_to_file_encryption"
- android:title="@string/convert_to_file_encryption"
- android:summary="@string/convert_to_file_encryption_enabled"
- android:fragment="com.android.settings.applications.ConvertToFbe" />
-
<com.android.settings.development.ColorModePreference
android:key="picture_color_mode"
android:title="@string/picture_color_mode"
diff --git a/src/com/android/settings/applications/ConfirmConvertToFbe.java b/src/com/android/settings/applications/ConfirmConvertToFbe.java
deleted file mode 100644
index 35ddc6b..0000000
--- a/src/com/android/settings/applications/ConfirmConvertToFbe.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 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.applications;
-
-import android.app.settings.SettingsEnums;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-public class ConfirmConvertToFbe extends SettingsPreferenceFragment {
- static final String TAG = "ConfirmConvertToFBE";
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.confirm_convert_fbe, null);
-
- final Button button = (Button) rootView.findViewById(R.id.button_confirm_convert_fbe);
- button.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.setPackage("android");
- intent.putExtra(Intent.EXTRA_REASON, "convert_fbe");
- getActivity().sendBroadcast(intent);
- }
- });
-
- return rootView;
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.CONVERT_FBE_CONFIRM;
- }
-}
diff --git a/src/com/android/settings/applications/ConvertToFbe.java b/src/com/android/settings/applications/ConvertToFbe.java
deleted file mode 100644
index d470011..0000000
--- a/src/com/android/settings/applications/ConvertToFbe.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.settings.SettingsEnums;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import com.android.settings.R;
-import com.android.settings.core.InstrumentedFragment;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.password.ChooseLockSettingsHelper;
-
-/* Class to prompt for conversion of userdata to file based encryption
- */
-public class ConvertToFbe extends InstrumentedFragment {
- static final String TAG = "ConvertToFBE";
- private static final int KEYGUARD_REQUEST = 55;
-
- private boolean runKeyguardConfirmation(int request) {
- Resources res = getActivity().getResources();
- final ChooseLockSettingsHelper.Builder builder =
- new ChooseLockSettingsHelper.Builder(getActivity(), this);
- return builder.setRequestCode(request)
- .setTitle(res.getText(R.string.convert_to_file_encryption))
- .show();
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getActivity().setTitle(R.string.convert_to_file_encryption);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.convert_fbe, null);
-
- final Button button = rootView.findViewById(R.id.button_convert_fbe);
- button.setOnClickListener(v -> {
- if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
- convert();
- }
- });
-
- return rootView;
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (requestCode != KEYGUARD_REQUEST) {
- return;
- }
-
- // If the user entered a valid keyguard credential, start the conversion
- // process
- if (resultCode == Activity.RESULT_OK) {
- convert();
- }
- }
-
- private void convert() {
- new SubSettingLauncher(getContext())
- .setDestination(ConfirmConvertToFbe.class.getName())
- .setTitleRes(R.string.convert_to_file_encryption)
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.CONVERT_FBE;
- }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
index 9f5e78e..9c7aa58 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
@@ -16,6 +16,7 @@
package com.android.settings.bluetooth;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -53,7 +54,10 @@
@Override
public boolean isAvailable() {
- return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice());
+ boolean hasLeAudio = mCachedDevice.getConnectableProfiles()
+ .stream()
+ .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO);
+ return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && !hasLeAudio;
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 4980ba3..425d1c4 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -117,6 +117,7 @@
return;
}
use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice);
+ use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager);
final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
context).getBluetoothFeatureProvider(context);
diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
new file mode 100644
index 0000000..06cee85
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
@@ -0,0 +1,323 @@
+/*
+ * 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.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.fuelgauge.BatteryMeterView;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LeAudioProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class adds a header with device name and status (connected/disconnected, etc.).
+ */
+public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceController implements
+ LifecycleObserver, OnStart, OnStop, OnDestroy, CachedBluetoothDevice.Callback {
+ private static final String TAG = "LeAudioBtHeaderCtrl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ @VisibleForTesting
+ static final int LEFT_DEVICE_ID =
+ BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_BACK_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_OF_CENTER
+ | BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_LEFT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_WIDE
+ | BluetoothLeAudio.AUDIO_LOCATION_LEFT_SURROUND;
+
+ @VisibleForTesting
+ static final int RIGHT_DEVICE_ID =
+ BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_BACK_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER
+ | BluetoothLeAudio.AUDIO_LOCATION_SIDE_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_RIGHT
+ | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_WIDE
+ | BluetoothLeAudio.AUDIO_LOCATION_RIGHT_SURROUND;
+
+ @VisibleForTesting
+ static final int INVALID_RESOURCE_ID = -1;
+
+ @VisibleForTesting
+ LayoutPreference mLayoutPreference;
+ private CachedBluetoothDevice mCachedDevice;
+ @VisibleForTesting
+ Handler mHandler = new Handler(Looper.getMainLooper());
+ @VisibleForTesting
+ boolean mIsRegisterCallback = false;
+
+ private LocalBluetoothProfileManager mProfileManager;
+
+ public LeAudioBluetoothDetailsHeaderController(Context context, String prefKey) {
+ super(context, prefKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mCachedDevice == null || mProfileManager == null) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ boolean hasLeAudio = mCachedDevice.getConnectableProfiles()
+ .stream()
+ .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO);
+
+ return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && hasLeAudio
+ ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mLayoutPreference = screen.findPreference(getPreferenceKey());
+ mLayoutPreference.setVisible(isAvailable());
+ }
+
+ @Override
+ public void onStart() {
+ if (!isAvailable()) {
+ return;
+ }
+ mIsRegisterCallback = true;
+ mCachedDevice.registerCallback(this);
+ refresh();
+ }
+
+ @Override
+ public void onStop() {
+ if (!mIsRegisterCallback) {
+ return;
+ }
+ mCachedDevice.unregisterCallback(this);
+ mIsRegisterCallback = false;
+ }
+
+ @Override
+ public void onDestroy() {
+ }
+
+ public void init(CachedBluetoothDevice cachedBluetoothDevice,
+ LocalBluetoothManager bluetoothManager) {
+ mCachedDevice = cachedBluetoothDevice;
+ mProfileManager = bluetoothManager.getProfileManager();
+ }
+
+ @VisibleForTesting
+ void refresh() {
+ if (mLayoutPreference == null || mCachedDevice == null) {
+ return;
+ }
+ final ImageView imageView = mLayoutPreference.findViewById(R.id.entity_header_icon);
+ if (imageView != null) {
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, mCachedDevice);
+ imageView.setImageDrawable(pair.first);
+ imageView.setContentDescription(pair.second);
+ }
+
+ final TextView title = mLayoutPreference.findViewById(R.id.entity_header_title);
+ if (title != null) {
+ title.setText(mCachedDevice.getName());
+ }
+ final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary);
+ if (summary != null) {
+ summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */));
+ }
+
+ if (!mCachedDevice.isConnected() || mCachedDevice.isBusy()) {
+ hideAllOfBatteryLayouts();
+ return;
+ }
+
+ updateBatteryLayout();
+ }
+
+ @VisibleForTesting
+ Drawable createBtBatteryIcon(Context context, int level) {
+ final BatteryMeterView.BatteryMeterDrawable drawable =
+ new BatteryMeterView.BatteryMeterDrawable(context,
+ context.getColor(R.color.meter_background_color),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.advanced_bluetooth_battery_meter_width),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.advanced_bluetooth_battery_meter_height));
+ drawable.setBatteryLevel(level);
+ drawable.setColorFilter(new PorterDuffColorFilter(
+ com.android.settings.Utils.getColorAttrDefaultColor(context,
+ android.R.attr.colorControlNormal),
+ PorterDuff.Mode.SRC));
+ 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) {
+ return R.id.bt_battery_left_summary;
+ }
+ if (deviceId == RIGHT_DEVICE_ID) {
+ return R.id.bt_battery_right_summary;
+ }
+ Log.d(TAG, "No resource id. The deviceId is " + deviceId);
+ 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);
+ // hide the left
+ updateBatteryLayout(R.id.bt_battery_left_title, R.id.bt_battery_left_summary,
+ BluetoothUtils.META_INT_ERROR);
+ // hide the right
+ updateBatteryLayout(R.id.bt_battery_right_title, R.id.bt_battery_right_summary,
+ BluetoothUtils.META_INT_ERROR);
+ }
+
+ private List<CachedBluetoothDevice> getAllOfLeAudioDevices() {
+ if (mCachedDevice == null) {
+ return null;
+ }
+ List<CachedBluetoothDevice> leAudioDevices = new ArrayList<>();
+ leAudioDevices.add(mCachedDevice);
+ if (mCachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ for (CachedBluetoothDevice member : mCachedDevice.getMemberDevice()) {
+ leAudioDevices.add(member);
+ }
+ }
+ return leAudioDevices;
+ }
+
+ private void updateBatteryLayout() {
+ // Init the battery layouts.
+ hideAllOfBatteryLayouts();
+ final List<CachedBluetoothDevice> leAudioDevices = getAllOfLeAudioDevices();
+ LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile();
+ if (leAudioDevices == null || leAudioDevices.isEmpty()) {
+ Log.e(TAG, "There is no LeAudioProfile.");
+ return;
+ }
+
+ if (!leAudioProfile.isEnabled(mCachedDevice.getDevice())) {
+ Log.d(TAG, "Show the legacy battery style if the LeAudio is not enabled.");
+ final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary);
+ if (summary != null) {
+ summary.setText(mCachedDevice.getConnectionSummary());
+ }
+ return;
+ }
+
+ for (CachedBluetoothDevice cachedDevice : leAudioDevices) {
+ int deviceId = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
+ Log.d(TAG, "LeAudioDevices:" + cachedDevice.getDevice().getAnonymizedAddress()
+ + ", deviceId:" + deviceId);
+
+ if (deviceId == BluetoothLeAudio.AUDIO_LOCATION_INVALID) {
+ Log.d(TAG, "The device does not support the AUDIO_LOCATION.");
+ return;
+ }
+ boolean isLeft = (deviceId & LEFT_DEVICE_ID) != 0;
+ boolean isRight = (deviceId & LEFT_DEVICE_ID) != 0;
+ boolean isLeftRight = isLeft && isRight;
+ // The LE device updates the BatteryLayout
+ if (isLeftRight) {
+ Log.d(TAG, "The device id is left+right. Do nothing.");
+ } else if (isLeft) {
+ updateBatteryLayout(getBatteryTitleResource(LEFT_DEVICE_ID),
+ getBatterySummaryResource(LEFT_DEVICE_ID), cachedDevice.getBatteryLevel());
+ } else if (isRight) {
+ updateBatteryLayout(getBatteryTitleResource(RIGHT_DEVICE_ID),
+ getBatterySummaryResource(RIGHT_DEVICE_ID), 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");
+ return;
+ }
+ if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
+ batteryTitleView.setVisibility(View.VISIBLE);
+ batterySummaryView.setVisibility(View.VISIBLE);
+ batterySummaryView.setText(
+ com.android.settings.Utils.formatPercentage(batteryLevel));
+ 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);
+ }
+ }
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ if (mCachedDevice != null) {
+ refresh();
+ }
+ }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 7556d23..47f47ce 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -477,7 +477,6 @@
controllers.add(new HdcpCheckingPreferenceController(context));
controllers.add(new BluetoothSnoopLogPreferenceController(context));
controllers.add(new OemUnlockPreferenceController(context, activity, fragment));
- controllers.add(new FileEncryptionPreferenceController(context));
controllers.add(new PictureColorModePreferenceController(context, lifecycle));
controllers.add(new WebViewAppPreferenceController(context));
controllers.add(new CoolColorTemperaturePreferenceController(context));
diff --git a/src/com/android/settings/development/FileEncryptionPreferenceController.java b/src/com/android/settings/development/FileEncryptionPreferenceController.java
deleted file mode 100644
index 82a58ba..0000000
--- a/src/com/android/settings/development/FileEncryptionPreferenceController.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 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.development;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.storage.IStorageManager;
-import android.text.TextUtils;
-import android.sysprop.CryptoProperties;
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.development.DeveloperOptionsPreferenceController;
-
-public class FileEncryptionPreferenceController extends DeveloperOptionsPreferenceController
- implements PreferenceControllerMixin {
-
- private static final String KEY_CONVERT_FBE = "convert_to_file_encryption";
- private static final String KEY_STORAGE_MANAGER = "mount";
-
- private final IStorageManager mStorageManager;
-
- public FileEncryptionPreferenceController(Context context) {
- super(context);
-
- mStorageManager = getStorageManager();
- }
-
- @Override
- public boolean isAvailable() {
- if (mStorageManager == null) {
- return false;
- }
-
- try {
- return mStorageManager.isConvertibleToFBE();
- } catch (RemoteException e) {
- return false;
- }
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_CONVERT_FBE;
- }
-
- @Override
- public void updateState(Preference preference) {
- if (CryptoProperties.type().orElse(CryptoProperties.type_values.NONE) !=
- CryptoProperties.type_values.FILE) {
- return;
- }
-
- mPreference.setEnabled(false);
- mPreference.setSummary(
- mContext.getResources().getString(R.string.convert_to_file_encryption_done));
- }
-
- private IStorageManager getStorageManager() {
- try {
- return IStorageManager.Stub.asInterface(
- ServiceManager.getService(KEY_STORAGE_MANAGER));
- } catch (VerifyError e) {
- // Used for tests since Robolectric cannot initialize this class.
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
index d8bdf32..528fbc3 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
@@ -168,10 +168,18 @@
break;
case MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS:
+ final Context context = getContext();
+ if (context == null) {
+ // Context may be null if the message is received after the Activity has
+ // been destroyed
+ Log.d(TAG, "Scan success but context is null");
+ return;
+ }
+
// We may get 2 WifiConfiguration if the QR code has no password in it,
// one for open network and one for enhanced open network.
final WifiManager wifiManager =
- getContext().getSystemService(WifiManager.class);
+ context.getSystemService(WifiManager.class);
final WifiNetworkConfig qrCodeWifiNetworkConfig =
(WifiNetworkConfig)msg.obj;
final List<WifiConfiguration> qrCodeWifiConfigurations =
diff --git a/src/com/android/settings/wifi/qrcode/QrCamera.java b/src/com/android/settings/wifi/qrcode/QrCamera.java
index 3865eb1..d07c543 100644
--- a/src/com/android/settings/wifi/qrcode/QrCamera.java
+++ b/src/com/android/settings/wifi/qrcode/QrCamera.java
@@ -124,6 +124,7 @@
}
if (mCamera != null) {
mCamera.stopPreview();
+ releaseCamera();
}
}
diff --git a/tests/robotests/assets/exempt_not_implementing_index_provider b/tests/robotests/assets/exempt_not_implementing_index_provider
index d4a1c2e..6a1a1ff 100644
--- a/tests/robotests/assets/exempt_not_implementing_index_provider
+++ b/tests/robotests/assets/exempt_not_implementing_index_provider
@@ -15,7 +15,6 @@
com.android.settings.applications.appinfo.WriteSettingsDetails
com.android.settings.applications.AppLaunchSettings
com.android.settings.applications.AppStorageSettings
-com.android.settings.applications.ConfirmConvertToFbe
com.android.settings.applications.ProcessStatsDetail
com.android.settings.applications.ProcessStatsSummary
com.android.settings.applications.ProcessStatsUi
diff --git a/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java
deleted file mode 100644
index 0784a61..0000000
--- a/tests/robotests/src/com/android/settings/development/FileEncryptionPreferenceControllerTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 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.development;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.storage.IStorageManager;
-import android.sysprop.CryptoProperties;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.util.ReflectionHelpers;
-
-@RunWith(RobolectricTestRunner.class)
-public class FileEncryptionPreferenceControllerTest {
-
- @Mock
- private Preference mPreference;
- @Mock
- private PreferenceScreen mPreferenceScreen;
- @Mock
- private IStorageManager mStorageManager;
-
- private Context mContext;
- private FileEncryptionPreferenceController mController;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mController = new FileEncryptionPreferenceController(mContext);
- when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
- .thenReturn(mPreference);
- }
-
- @Test
- public void isAvailable_storageManagerNull_shouldBeFalse() {
- ReflectionHelpers.setField(mController, "mStorageManager", null);
-
- assertThat(mController.isAvailable()).isFalse();
- }
-
- @Test
- public void isAvailable_notConvertibleToFBE_shouldBeFalse() throws RemoteException {
- ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager);
- when(mStorageManager.isConvertibleToFBE()).thenReturn(false);
-
- assertThat(mController.isAvailable()).isFalse();
- }
-
- @Test
- public void isAvailable_convertibleToFBE_shouldBeTrue() throws RemoteException {
- ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager);
- when(mStorageManager.isConvertibleToFBE()).thenReturn(true);
-
- assertThat(mController.isAvailable()).isTrue();
- }
-
- @Test
- public void updateState_settingIsNotFile_shouldDoNothing() throws RemoteException {
- ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager);
- when(mStorageManager.isConvertibleToFBE()).thenReturn(true);
- mController.displayPreference(mPreferenceScreen);
- CryptoProperties.type(CryptoProperties.type_values.NONE);
-
- mController.updateState(mPreference);
-
- verify(mPreference, never()).setEnabled(anyBoolean());
- verify(mPreference, never()).setSummary(anyString());
- }
-
- @Test
- public void updateState_settingIsFile_shouldSetSummaryAndDisablePreference()
- throws RemoteException {
- ReflectionHelpers.setField(mController, "mStorageManager", mStorageManager);
- when(mStorageManager.isConvertibleToFBE()).thenReturn(true);
- mController.displayPreference(mPreferenceScreen);
- CryptoProperties.type(CryptoProperties.type_values.FILE);
-
- mController.updateState(mPreference);
-
- verify(mPreference).setEnabled(false);
- verify(mPreference).setSummary(mContext.getString(R.string.convert_to_file_encryption_done));
- }
-}