Merge "Add SfpsRestToUnlockFeature to FingerprintFeatureProvider" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4230b6a..2ba87d7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4692,20 +4692,6 @@
android:permission="android.permission.MANAGE_SLICE_PERMISSIONS"
android:exported="true" />
- <!-- Couldn't be triggered from outside of settings. Statsd can trigger it because we send
- PendingIntent to it-->
- <receiver android:name=".fuelgauge.batterytip.AnomalyDetectionReceiver"
- android:exported="false" />
-
- <service android:name=".fuelgauge.batterytip.AnomalyCleanupJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
-
- <service android:name=".fuelgauge.batterytip.AnomalyConfigJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
-
- <service android:name=".fuelgauge.batterytip.AnomalyDetectionJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
-
<provider
android:name=".homepage.contextualcards.CardContentProvider"
android:authorities="${applicationId}.homepage.CardContentProvider"
diff --git a/aconfig/settings_accessibility_flag_declarations_legacy.aconfig b/aconfig/settings_accessibility_flag_declarations_legacy.aconfig
index acdce96..5a464b5 100644
--- a/aconfig/settings_accessibility_flag_declarations_legacy.aconfig
+++ b/aconfig/settings_accessibility_flag_declarations_legacy.aconfig
@@ -32,7 +32,7 @@
flag {
name: "new_hearing_device_pairing_page"
namespace: "accessibility"
- description: "New hearing device pairing page with deny list method"
+ description: "New hearing device pairing page with extra MFi+ASHA filtering"
bug: "307473972"
}
diff --git a/res/layout/arrow_preference.xml b/res/layout/arrow_preference.xml
new file mode 100644
index 0000000..0924a44
--- /dev/null
+++ b/res/layout/arrow_preference.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingVertical="@dimen/settingslib_switchbar_margin"
+ android:background="@android:color/transparent">
+
+ <LinearLayout
+ android:id="@+id/background"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+ android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_gravity="start|center_vertical"
+ android:layout_weight="1"
+ android:paddingVertical="@dimen/settingslib_switch_title_margin"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/MainSwitchText.Settingslib"/>
+
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_arrow_forward"/>
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5246618..ff59f6a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10815,9 +10815,37 @@
]]>
</string>
+ <!-- Message of the warning dialog for disabling the credential provider (new strings for 24Q3). [CHAR_LIMIT=NONE] -->
+ <string name="credman_confirmation_message_new_ui">
+ <![CDATA[
+ <b>Turn off all services?</b>
+ <br/>
+ <br/>
+ Passwords, passkeys, and other saved info won\'t be available for autofill when you sign in
+ ]]>
+ </string>
+
+ <!-- Message of the warning dialog for setting a new preferred credential provider (new strings for 24Q3). [CHAR_LIMIT=NONE] -->
+ <string name="credman_autofill_confirmation_message_new_ui">
+ <![CDATA[
+ <b>Change your preferred service to <xliff:g id="app_name" example="Provider">%1$s</xliff:g>\?</b>
+ <br/>
+ <br/>
+ New passwords, passkeys, and other info will be saved here from now on.
+ <xliff:g id="app_name" example="Provider">%1$s</xliff:g> may use what\'s on your
+ screen to determine what can be autofilled
+ ]]>
+ </string>
+
<!-- Title of the warning dialog for enabling the credential provider. [CHAR_LIMIT=NONE] -->
<string name="credman_enable_confirmation_message_title">Use %1$s\?</string>
+ <!-- Title of the error dialog when too many credential providers are selected (new strings for 24Q3). [CHAR_LIMIT=NONE] -->
+ <string name="credman_limit_error_msg_title">You can only have 5 services on</string>
+
+ <!-- Message of the error dialog when too many credential providers are selected (new strings for 24Q3). [CHAR_LIMIT=NONE] -->
+ <string name="credman_limit_error_msg">Turn off at least 1 service to add another</string>
+
<!-- Message of the warning dialog for enabling the credential provider. [CHAR_LIMIT=NONE] -->
<string name="credman_enable_confirmation_message">%1$s uses what\'s on your screen to determine what can be autofilled.</string>
diff --git a/res/xml/accessibility_hearing_aids.xml b/res/xml/accessibility_hearing_aids.xml
index 20c8e29..57a0fe2 100644
--- a/res/xml/accessibility_hearing_aids.xml
+++ b/res/xml/accessibility_hearing_aids.xml
@@ -28,11 +28,10 @@
settings:controller="com.android.settings.accessibility.AvailableHearingDevicePreferenceController"/>
<com.android.settingslib.RestrictedPreference
- android:key="add_bt_devices"
+ android:key="hearing_device_add_bt_devices"
android:title="@string/bluetooth_pairing_pref_title"
android:icon="@drawable/ic_add_24dp"
android:summary="@string/connected_device_add_device_summary"
- android:fragment="com.android.settings.accessibility.HearingDevicePairingDetail"
settings:userRestriction="no_config_bluetooth"
settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.connecteddevice.AddDevicePreferenceController"/>
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index 9ffa2b2..dc577f6 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -38,11 +38,17 @@
android:title="Play a test sound"
settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPlaySoundPreferenceController" />
- <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
- android:key="audio_sharing_stream_name"
- android:summary="********"
- android:title="Stream name"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
+ <PreferenceCategory
+ android:key="audio_sharing_stream_settings_category"
+ android:title="Stream settings"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.StreamSettingsCategoryController">
+
+ <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
+ android:key="audio_sharing_stream_name"
+ android:summary="********"
+ android:title="Stream name"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
+ </PreferenceCategory>
<PreferenceCategory
android:key="audio_streams_settings_category"
diff --git a/res/xml/hearing_device_pairing_fragment.xml b/res/xml/hearing_device_pairing_fragment.xml
new file mode 100644
index 0000000..1ccc1dd
--- /dev/null
+++ b/res/xml/hearing_device_pairing_fragment.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/bluetooth_pairing_pref_title">
+
+ <com.android.settings.bluetooth.BluetoothProgressCategory
+ android:key="available_hearing_devices"
+ android:title="@string/accessibility_found_hearing_devices" />
+
+ <PreferenceCategory
+ android:key="more_devices_category"
+ android:title="@string/accessibility_found_all_devices">
+ <com.android.settings.accessibility.ArrowPreference
+ android:key="more_devices"
+ android:title="@string/accessibility_list_all_devices_title"
+ settings:searchable="false"
+ settings:userRestriction="no_config_bluetooth"
+ settings:useAdminDisabledSummary="true"
+ settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
+ </PreferenceCategory>
+
+ <com.android.settings.accessibility.AccessibilityFooterPreference
+ android:key="hearing_device_footer"
+ android:title="@string/accessibility_hearing_device_footer_summary"
+ android:selectable="false"
+ settings:searchable="false"
+ settings:controller="com.android.settings.accessibility.PairHearingDeviceFooterPreferenceController"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/SettingsDumpService.java b/src/com/android/settings/SettingsDumpService.java
index 4feb6b2..7e84691 100644
--- a/src/com/android/settings/SettingsDumpService.java
+++ b/src/com/android/settings/SettingsDumpService.java
@@ -38,7 +38,6 @@
import com.android.settings.applications.ProcStatsData;
import com.android.settings.datausage.lib.DataUsageLib;
-import com.android.settings.fuelgauge.batterytip.AnomalyConfigJobService;
import com.android.settings.network.MobileNetworkRepository;
import com.android.settingslib.net.DataUsageController;
@@ -99,7 +98,6 @@
dump.put(KEY_DATAUSAGE, dumpDataUsage());
dump.put(KEY_MEMORY, dumpMemory());
dump.put(KEY_DEFAULT_BROWSER_APP, dumpDefaultBrowser());
- dump.put(KEY_ANOMALY_DETECTION, dumpAnomalyDetection());
} catch (Exception e) {
Log.w(TAG, "exception in dump: ", e);
}
@@ -197,20 +195,6 @@
}
}
- @VisibleForTesting
- JSONObject dumpAnomalyDetection() throws JSONException {
- final JSONObject obj = new JSONObject();
- final SharedPreferences sharedPreferences = getSharedPreferences(
- AnomalyConfigJobService.PREF_DB,
- Context.MODE_PRIVATE);
- final int currentVersion = sharedPreferences.getInt(
- AnomalyConfigJobService.KEY_ANOMALY_CONFIG_VERSION,
- 0 /* defValue */);
- obj.put("anomaly_config_version", String.valueOf(currentVersion));
-
- return obj;
- }
-
private void dumpMobileNetworkSettings(IndentingPrintWriter writer) {
MobileNetworkRepository.getInstance(this).dump(writer);
}
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
index 33fef62..80a03c6 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
@@ -36,9 +36,9 @@
/** Accessibility settings for hearing aids. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPreferenceFragment {
-
private static final String TAG = "AccessibilityHearingAidsFragment";
private static final String KEY_HEARING_OPTIONS_CATEGORY = "hearing_options_category";
+ public static final String KEY_HEARING_DEVICE_ADD_BT_DEVICES = "hearing_device_add_bt_devices";
private static final int SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX = 20;
private String mFeatureName;
diff --git a/src/com/android/settings/accessibility/ArrowPreference.java b/src/com/android/settings/accessibility/ArrowPreference.java
new file mode 100644
index 0000000..32e2bcb
--- /dev/null
+++ b/src/com/android/settings/accessibility/ArrowPreference.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.accessibility;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+
+/**
+ * A settings preference with colored rounded rectangle background and an arrow icon on the right
+ */
+public class ArrowPreference extends Preference {
+
+ public ArrowPreference(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
+ }
+
+ public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ private void init() {
+ setLayoutResource(R.layout.arrow_preference);
+ }
+}
diff --git a/src/com/android/settings/accessibility/HearingDevicePairingFragment.java b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java
new file mode 100644
index 0000000..fb79ece
--- /dev/null
+++ b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2023 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.accessibility;
+
+import static android.app.Activity.RESULT_OK;
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.BluetoothDevicePreference;
+import com.android.settings.bluetooth.BluetoothProgressCategory;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidInfo;
+import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This fragment shows all scanned hearing devices through BLE scanning. Users can
+ * pair them in this page.
+ */
+public class HearingDevicePairingFragment extends RestrictedDashboardFragment implements
+ BluetoothCallback {
+
+ private static final boolean DEBUG = true;
+ private static final String TAG = "HearingDevicePairingFragment";
+ private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+ "persist.bluetooth.showdeviceswithoutnames";
+ private static final String KEY_AVAILABLE_HEARING_DEVICES = "available_hearing_devices";
+
+ LocalBluetoothManager mLocalManager;
+ @Nullable
+ BluetoothAdapter mBluetoothAdapter;
+ @Nullable
+ CachedBluetoothDeviceManager mCachedDeviceManager;
+
+ private boolean mShowDevicesWithoutNames;
+ @Nullable
+ private BluetoothProgressCategory mAvailableHearingDeviceGroup;
+
+ @Nullable
+ BluetoothDevice mSelectedDevice;
+ final List<BluetoothDevice> mSelectedDeviceList = new ArrayList<>();
+ final List<BluetoothGatt> mConnectingGattList = new ArrayList<>();
+ final Map<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+ new HashMap<>();
+
+ private List<ScanFilter> mLeScanFilters;
+
+ public HearingDevicePairingFragment() {
+ super(DISALLOW_CONFIG_BLUETOOTH);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLocalManager = Utils.getLocalBtManager(getActivity());
+ if (mLocalManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
+ mCachedDeviceManager = mLocalManager.getCachedDeviceManager();
+ mShowDevicesWithoutNames = SystemProperties.getBoolean(
+ BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
+
+ initPreferencesFromPreferenceScreen();
+ initHearingDeviceLeScanFilters();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ use(ViewAllBluetoothDevicesPreferenceController.class).init(this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mLocalManager == null || mBluetoothAdapter == null || isUiRestricted()) {
+ return;
+ }
+ mLocalManager.setForegroundActivity(getActivity());
+ mLocalManager.getEventManager().registerCallback(this);
+ if (mBluetoothAdapter.isEnabled()) {
+ startScanning();
+ } else {
+ // Turn on bluetooth if it is disabled
+ mBluetoothAdapter.enable();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mLocalManager == null || isUiRestricted()) {
+ return;
+ }
+ stopScanning();
+ removeAllDevices();
+ for (BluetoothGatt gatt: mConnectingGattList) {
+ gatt.disconnect();
+ }
+ mLocalManager.setForegroundActivity(null);
+ mLocalManager.getEventManager().unregisterCallback(this);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (preference instanceof BluetoothDevicePreference) {
+ stopScanning();
+ BluetoothDevicePreference devicePreference = (BluetoothDevicePreference) preference;
+ mSelectedDevice = devicePreference.getCachedDevice().getDevice();
+ if (mSelectedDevice != null) {
+ mSelectedDeviceList.add(mSelectedDevice);
+ }
+ devicePreference.onClicked();
+ return true;
+ }
+ return super.onPreferenceTreeClick(preference);
+ }
+
+ @Override
+ public void onDeviceDeleted(@NonNull CachedBluetoothDevice cachedDevice) {
+ removeDevice(cachedDevice);
+ }
+
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ switch (bluetoothState) {
+ case BluetoothAdapter.STATE_ON:
+ startScanning();
+ showBluetoothTurnedOnToast();
+ break;
+ case BluetoothAdapter.STATE_OFF:
+ finish();
+ break;
+ }
+ }
+
+ @Override
+ public void onDeviceBondStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
+ int bondState) {
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceBondStateChanged: " + cachedDevice.getName() + ", state = "
+ + bondState);
+ }
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ // If one device is connected(bonded), then close this fragment.
+ setResult(RESULT_OK);
+ finish();
+ return;
+ } else if (bondState == BluetoothDevice.BOND_BONDING) {
+ // Set the bond entry where binding process starts for logging hearing aid device info
+ final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+ .getAttribution(getActivity());
+ final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
+ pageId);
+ HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
+ }
+ if (mSelectedDevice != null) {
+ BluetoothDevice device = cachedDevice.getDevice();
+ if (mSelectedDevice.equals(device) && bondState == BluetoothDevice.BOND_NONE) {
+ // If current selected device failed to bond, restart scanning
+ startScanning();
+ }
+ }
+ }
+
+ @Override
+ public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
+ int state, int bluetoothProfile) {
+ // This callback is used to handle the case that bonded device is connected in pairing list.
+ // 1. If user selected multiple bonded devices in pairing list, after connected
+ // finish this page.
+ // 2. If the bonded devices auto connected in paring list, after connected it will be
+ // removed from paring list.
+ if (cachedDevice.isConnected()) {
+ final BluetoothDevice device = cachedDevice.getDevice();
+ if (device != null && mSelectedDeviceList.contains(device)) {
+ setResult(RESULT_OK);
+ finish();
+ } else {
+ removeDevice(cachedDevice);
+ }
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.HEARING_AID_PAIRING;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.hearing_device_pairing_fragment;
+ }
+
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ void addDevice(CachedBluetoothDevice cachedDevice) {
+ if (mBluetoothAdapter == null) {
+ return;
+ }
+ // Do not create new preference while the list shows one of the state messages
+ if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) {
+ return;
+ }
+ if (mDevicePreferenceMap.get(cachedDevice) != null) {
+ return;
+ }
+ String key = cachedDevice.getDevice().getAddress();
+ BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key);
+ if (preference == null) {
+ preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice,
+ mShowDevicesWithoutNames, BluetoothDevicePreference.SortType.TYPE_FIFO);
+ preference.setKey(key);
+ preference.hideSecondTarget(true);
+ }
+ if (mAvailableHearingDeviceGroup != null) {
+ mAvailableHearingDeviceGroup.addPreference(preference);
+ }
+ mDevicePreferenceMap.put(cachedDevice, preference);
+ if (DEBUG) {
+ Log.d(TAG, "Add device. device: " + cachedDevice);
+ }
+ }
+
+ void removeDevice(CachedBluetoothDevice cachedDevice) {
+ if (DEBUG) {
+ Log.d(TAG, "removeDevice: " + cachedDevice);
+ }
+ BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
+ if (mAvailableHearingDeviceGroup != null && preference != null) {
+ mAvailableHearingDeviceGroup.removePreference(preference);
+ }
+ }
+
+ void startScanning() {
+ if (mCachedDeviceManager != null) {
+ mCachedDeviceManager.clearNonBondedDevices();
+ }
+ removeAllDevices();
+ startLeScanning();
+ }
+
+ void stopScanning() {
+ stopLeScanning();
+ }
+
+ private final ScanCallback mLeScanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ handleLeScanResult(result);
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ for (ScanResult result: results) {
+ handleLeScanResult(result);
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.w(TAG, "BLE Scan failed with error code " + errorCode);
+ }
+ };
+
+ void handleLeScanResult(ScanResult result) {
+ if (mCachedDeviceManager == null) {
+ return;
+ }
+ final BluetoothDevice device = result.getDevice();
+ CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ cachedDevice = mCachedDeviceManager.addDevice(device);
+ }
+ if (cachedDevice.getHearingAidInfo() == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Set hearing aid info on device: " + cachedDevice);
+ }
+ cachedDevice.setHearingAidInfo(new HearingAidInfo.Builder().build());
+ }
+ // No need to handle the device if the device is already in the list or discovering services
+ if (mDevicePreferenceMap.get(cachedDevice) == null
+ && mConnectingGattList.stream().noneMatch(
+ gatt -> gatt.getDevice().equals(device))) {
+ if (isAndroidCompatibleHearingAid(result)) {
+ addDevice(cachedDevice);
+ } else {
+ discoverServices(cachedDevice);
+ }
+ }
+ }
+
+ void startLeScanning() {
+ if (mBluetoothAdapter == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "startLeScanning");
+ }
+ final BluetoothLeScanner leScanner = mBluetoothAdapter.getBluetoothLeScanner();
+ if (leScanner == null) {
+ Log.w(TAG, "LE scanner not found, cannot start LE scanning");
+ } else {
+ final ScanSettings settings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .setLegacy(false)
+ .build();
+ leScanner.startScan(mLeScanFilters, settings, mLeScanCallback);
+ if (mAvailableHearingDeviceGroup != null) {
+ mAvailableHearingDeviceGroup.setProgress(true);
+ }
+ }
+ }
+
+ void stopLeScanning() {
+ if (mBluetoothAdapter == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "stopLeScanning");
+ }
+ final BluetoothLeScanner leScanner = mBluetoothAdapter.getBluetoothLeScanner();
+ if (leScanner != null) {
+ leScanner.stopScan(mLeScanCallback);
+ if (mAvailableHearingDeviceGroup != null) {
+ mAvailableHearingDeviceGroup.setProgress(false);
+ }
+ }
+ }
+
+ private void removeAllDevices() {
+ mDevicePreferenceMap.clear();
+ if (mAvailableHearingDeviceGroup != null) {
+ mAvailableHearingDeviceGroup.removeAll();
+ }
+ }
+
+ void initPreferencesFromPreferenceScreen() {
+ mAvailableHearingDeviceGroup = findPreference(KEY_AVAILABLE_HEARING_DEVICES);
+ }
+
+ private void initHearingDeviceLeScanFilters() {
+ mLeScanFilters = new ArrayList<>();
+ // Filters for ASHA hearing aids
+ mLeScanFilters.add(
+ new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HEARING_AID).build());
+ mLeScanFilters.add(new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.HEARING_AID, new byte[0]).build());
+ // Filters for LE audio hearing aids
+ mLeScanFilters.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HAS).build());
+ mLeScanFilters.add(new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.HAS, new byte[0]).build());
+ // Filters for MFi hearing aids
+ mLeScanFilters.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.MFI_HAS).build());
+ mLeScanFilters.add(new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.MFI_HAS, new byte[0]).build());
+ }
+
+ boolean isAndroidCompatibleHearingAid(ScanResult scanResult) {
+ ScanRecord scanRecord = scanResult.getScanRecord();
+ if (scanRecord == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Scan record is null, not compatible with Android. device: "
+ + scanResult.getDevice());
+ }
+ return false;
+ }
+ List<ParcelUuid> uuids = scanRecord.getServiceUuids();
+ if (uuids != null) {
+ if (uuids.contains(BluetoothUuid.HEARING_AID) || uuids.contains(BluetoothUuid.HAS)) {
+ if (DEBUG) {
+ Log.d(TAG, "Scan record uuid matched, compatible with Android. device: "
+ + scanResult.getDevice());
+ }
+ return true;
+ }
+ }
+ if (scanRecord.getServiceData(BluetoothUuid.HEARING_AID) != null
+ || scanRecord.getServiceData(BluetoothUuid.HAS) != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Scan record service data matched, compatible with Android. device: "
+ + scanResult.getDevice());
+ }
+ return true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Scan record mismatched, not compatible with Android. device: "
+ + scanResult.getDevice());
+ }
+ return false;
+ }
+
+ void discoverServices(CachedBluetoothDevice cachedDevice) {
+ if (DEBUG) {
+ Log.d(TAG, "connectGattToCheckCompatibility, device: " + cachedDevice);
+ }
+ BluetoothGatt gatt = cachedDevice.getDevice().connectGatt(getContext(), false,
+ new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status,
+ int newState) {
+ super.onConnectionStateChange(gatt, status, newState);
+ if (DEBUG) {
+ Log.d(TAG, "onConnectionStateChange, status: " + status + ", newState: "
+ + newState + ", device: " + cachedDevice);
+ }
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ gatt.discoverServices();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ boolean isCompatible = gatt.getService(BluetoothUuid.HEARING_AID.getUuid())
+ != null
+ || gatt.getService(BluetoothUuid.HAS.getUuid()) != null;
+ if (DEBUG) {
+ Log.d(TAG,
+ "onServicesDiscovered, compatible with Android: " + isCompatible
+ + ", device: " + cachedDevice);
+ }
+ if (isCompatible) {
+ addDevice(cachedDevice);
+ }
+ }
+ });
+ mConnectingGattList.add(gatt);
+ }
+
+ void showBluetoothTurnedOnToast() {
+ Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
+ Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index c843282..c7d5a73 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -899,8 +899,18 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
- .setTitle(getContext().getString(R.string.credman_error_message_title))
- .setMessage(getContext().getString(R.string.credman_error_message))
+ .setTitle(
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_limit_error_msg_title
+ : R.string.credman_error_message_title))
+ .setMessage(
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_limit_error_msg
+ : R.string.credman_error_message))
.setPositiveButton(android.R.string.ok, this)
.create();
}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
index 2637d83..0bffee9 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPicker.java
@@ -25,6 +25,7 @@
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.SetEnabledProvidersException;
+import android.credentials.flags.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -305,14 +306,21 @@
protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
// If we are selecting none then show a warning label.
if (appInfo == null) {
- final String message = getContext().getString(R.string.credman_confirmation_message);
+ final String message =
+ getContext()
+ .getString(
+ Flags.newSettingsUi()
+ ? R.string.credman_confirmation_message_new_ui
+ : R.string.credman_confirmation_message);
return Html.fromHtml(message);
}
final CharSequence appName = appInfo.loadLabel();
final String message =
getContext()
.getString(
- R.string.credman_autofill_confirmation_message,
+ Flags.newSettingsUi()
+ ? R.string.credman_autofill_confirmation_message_new_ui
+ : R.string.credman_autofill_confirmation_message,
Html.escapeHtml(appName));
return Html.fromHtml(message);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 98d78f2..ac0c63b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -156,7 +156,7 @@
return R.layout.preference_widget_gear;
}
- CachedBluetoothDevice getCachedDevice() {
+ public CachedBluetoothDevice getCachedDevice() {
return mCachedDevice;
}
@@ -362,7 +362,11 @@
}
}
- void onClicked() {
+ /**
+ * Performs different actions according to the device connected and bonded state after
+ * clicking on the preference.
+ */
+ public void onClicked() {
Context context = getContext();
int bondState = mCachedDevice.getBondState();
diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
index 12cbd58..3a16e3e 100644
--- a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
@@ -29,8 +29,10 @@
import com.android.settings.R;
import com.android.settings.accessibility.HearingDevicePairingDetail;
+import com.android.settings.accessibility.HearingDevicePairingFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidInfo;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -123,8 +125,11 @@
final int launchPage = getArguments().getInt(KEY_LAUNCH_PAGE);
final boolean launchFromA11y = (launchPage == SettingsEnums.ACCESSIBILITY)
|| (launchPage == SettingsEnums.ACCESSIBILITY_HEARING_AID_SETTINGS);
+ final String a11yDestination = Flags.newHearingDevicePairingPage()
+ ? HearingDevicePairingFragment.class.getName()
+ : HearingDevicePairingDetail.class.getName();
final String destination = launchFromA11y
- ? HearingDevicePairingDetail.class.getName()
+ ? a11yDestination
: BluetoothPairingDetail.class.getName();
new SubSettingLauncher(getActivity())
.setDestination(destination)
diff --git a/src/com/android/settings/connecteddevice/AddDevicePreferenceController.java b/src/com/android/settings/connecteddevice/AddDevicePreferenceController.java
index d2bc319..ef44843 100644
--- a/src/com/android/settings/connecteddevice/AddDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/AddDevicePreferenceController.java
@@ -15,18 +15,25 @@
*/
package com.android.settings.connecteddevice;
+import static com.android.settings.accessibility.AccessibilityHearingAidsFragment.KEY_HEARING_DEVICE_ADD_BT_DEVICES;
+
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.accessibility.HearingDevicePairingDetail;
+import com.android.settings.accessibility.HearingDevicePairingFragment;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.flags.Flags;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -76,6 +83,21 @@
}
@Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(preference.getKey(), KEY_HEARING_DEVICE_ADD_BT_DEVICES)) {
+ String destination = Flags.newHearingDevicePairingPage()
+ ? HearingDevicePairingFragment.class.getName()
+ : HearingDevicePairingDetail.class.getName();
+ new SubSettingLauncher(preference.getContext())
+ .setDestination(destination)
+ .setSourceMetricsCategory(getMetricsCategory())
+ .launch();
+ return true;
+ }
+ return super.handlePreferenceTreeClick(preference);
+ }
+
+ @Override
public int getAvailabilityStatus() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
? AVAILABLE
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index 9105297..7a7f337 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -35,7 +35,6 @@
private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
private CallsAndAlarmsPreferenceController mCallsAndAlarmsPreferenceController;
private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
- private AudioSharingNamePreferenceController mAudioSharingNamePreferenceController;
private AudioStreamsCategoryController mAudioStreamsCategoryController;
public AudioSharingDashboardFragment() {
@@ -77,7 +76,6 @@
mCallsAndAlarmsPreferenceController.init(this);
mAudioSharingPlaySoundPreferenceController =
use(AudioSharingPlaySoundPreferenceController.class);
- mAudioSharingNamePreferenceController = use(AudioSharingNamePreferenceController.class);
mAudioStreamsCategoryController = use(AudioStreamsCategoryController.class);
}
@@ -104,7 +102,6 @@
mAudioSharingDeviceVolumeGroupController.updateVisibility();
mCallsAndAlarmsPreferenceController.updateVisibility();
mAudioSharingPlaySoundPreferenceController.updateVisibility();
- mAudioSharingNamePreferenceController.updateVisibility();
mAudioStreamsCategoryController.updateVisibility();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
index 36f66ff..a3eb188 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -18,13 +18,12 @@
import android.content.Context;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.widget.ValidatedEditTextPreference;
-public class AudioSharingNamePreferenceController extends AudioSharingBasePreferenceController
+public class AudioSharingNamePreferenceController extends BasePreferenceController
implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener {
private static final String TAG = "AudioSharingNamePreferenceController";
@@ -33,12 +32,17 @@
private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
- public AudioSharingNamePreferenceController(Context context) {
- super(context, PREF_KEY);
+ public AudioSharingNamePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
}
@Override
+ public int getAvailabilityStatus() {
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
public String getPreferenceKey() {
return PREF_KEY;
}
@@ -53,16 +57,4 @@
public boolean isTextValid(String value) {
return mAudioSharingNameTextValidator.isTextValid(value);
}
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- super.onStart(owner);
- // TODO
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- super.onStop(owner);
- // TODO
- }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java
new file mode 100644
index 0000000..f62183d
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 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.connecteddevice.audiosharing;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+
+public class StreamSettingsCategoryController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String TAG = "StreamSettingsCategoryController";
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final IntentFilter mIntentFilter;
+ private @Nullable Preference mPreference;
+ private BroadcastReceiver mReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) return;
+ int adapterState =
+ intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
+ mContext.getMainExecutor()
+ .execute(
+ () -> {
+ if (mPreference == null) {
+ Log.w(
+ TAG,
+ "Skip BT state change due to mPreference "
+ + "is null");
+ } else {
+ mPreference.setVisible(
+ adapterState == BluetoothAdapter.STATE_ON);
+ }
+ });
+ }
+ };
+
+ public StreamSettingsCategoryController(Context context, String key) {
+ super(context, key);
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference != null) {
+ mPreference.setVisible(isBluetoothStateOn());
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ private boolean isBluetoothStateOn() {
+ return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index d0b57fd..6a65dc0 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -80,7 +80,8 @@
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_not_charging);
} else if (BatteryUtils.isBatteryDefenderOn(info)) {
- return null;
+ return mContext.getString(
+ com.android.settingslib.R.string.battery_info_status_charging_on_hold);
} else if (info.remainingLabel == null
|| info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
// Present status only if no remaining time or status anomalous
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index f4217b6..f846a6c 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -22,17 +22,14 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Build;
-import android.os.Process;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -47,14 +44,11 @@
import com.android.internal.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
-import com.android.settings.fuelgauge.batterytip.AnomalyInfo;
import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
-import com.android.settings.fuelgauge.batterytip.StatsManagerConfig;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.fuelgauge.EstimateKt;
-import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
import com.android.settingslib.utils.PowerUtil;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.utils.ThreadUtils;
@@ -68,7 +62,6 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
-import java.util.List;
/** Utils for battery operation */
public class BatteryUtils {
@@ -548,74 +541,6 @@
return false;
}
- /** Return {@code true} if we should hide anomaly app represented by {@code uid} */
- public boolean shouldHideAnomaly(
- PowerAllowlistBackend powerAllowlistBackend, int uid, AnomalyInfo anomalyInfo) {
- final String[] packageNames = mPackageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
- // Don't show it if app has been uninstalled
- return true;
- }
-
- return isSystemUid(uid)
- || powerAllowlistBackend.isAllowlisted(packageNames, uid)
- || (isSystemApp(mPackageManager, packageNames) && !hasLauncherEntry(packageNames))
- || (isExcessiveBackgroundAnomaly(anomalyInfo) && !isPreOApp(packageNames));
- }
-
- private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) {
- return anomalyInfo.anomalyType
- == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE;
- }
-
- private boolean isSystemUid(int uid) {
- final int appUid = UserHandle.getAppId(uid);
- return appUid >= Process.ROOT_UID && appUid < Process.FIRST_APPLICATION_UID;
- }
-
- private boolean isSystemApp(PackageManager packageManager, String[] packageNames) {
- for (String packageName : packageNames) {
- try {
- final ApplicationInfo info =
- packageManager.getApplicationInfo(packageName, 0 /* flags */);
- if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Package not found: " + packageName, e);
- }
- }
-
- return false;
- }
-
- private boolean hasLauncherEntry(String[] packageNames) {
- final Intent launchIntent = new Intent(Intent.ACTION_MAIN, null);
- launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- // If we do not specify MATCH_DIRECT_BOOT_AWARE or
- // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
- // according to the user's lock state. When the user is locked,
- // components
- // with ComponentInfo#directBootAware == false will be filtered. We should
- // explicitly include both direct boot aware and unaware components here.
- final List<ResolveInfo> resolveInfos =
- mPackageManager.queryIntentActivities(
- launchIntent,
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_SYSTEM_ONLY);
- for (int i = 0, size = resolveInfos.size(); i < size; i++) {
- final ResolveInfo resolveInfo = resolveInfos.get(i);
- if (ArrayUtils.contains(packageNames, resolveInfo.activityInfo.packageName)) {
- return true;
- }
- }
-
- return false;
- }
-
/**
* Return version number of an app represented by {@code packageName}, and return -1 if not
* found.
diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
index 0f54f3e..08d49f1 100644
--- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
@@ -151,7 +151,13 @@
private CharSequence generateLabel(BatteryInfo info) {
if (Utils.containsIncompatibleChargers(mContext, TAG)) {
return mContext.getString(
- com.android.settingslib.R.string.battery_info_status_not_charging);
+ com.android.settingslib.R.string.power_incompatible_charging_settings_home_page,
+ info.batteryPercentString);
+ }
+ if (BatteryUtils.isBatteryDefenderOn(info)) {
+ return mContext.getString(
+ com.android.settingslib.R.string.power_charging_on_hold_settings_home_page,
+ info.batteryPercentString);
}
if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
// Present status only if no remaining time or status anomalous
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java
deleted file mode 100644
index 0eaed17..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobService.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settings.R;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.concurrent.TimeUnit;
-
-/** A JobService to clean up obsolete data in anomaly database */
-public class AnomalyCleanupJobService extends JobService {
- private static final String TAG = "AnomalyCleanUpJobService";
-
- @VisibleForTesting static final long CLEAN_UP_FREQUENCY_MS = TimeUnit.DAYS.toMillis(1);
-
- public static void scheduleCleanUp(Context context) {
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-
- final ComponentName component = new ComponentName(context, AnomalyCleanupJobService.class);
- final JobInfo.Builder jobBuilder =
- new JobInfo.Builder(R.integer.job_anomaly_clean_up, component)
- .setPeriodic(CLEAN_UP_FREQUENCY_MS)
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setPersisted(true);
- final JobInfo pending = jobScheduler.getPendingJob(R.integer.job_anomaly_clean_up);
-
- // Don't schedule it if it already exists, to make sure it runs periodically even after
- // reboot
- if (pending == null
- && jobScheduler.schedule(jobBuilder.build()) != JobScheduler.RESULT_SUCCESS) {
- Log.i(TAG, "Anomaly clean up job service schedule failed.");
- }
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- final BatteryDatabaseManager batteryDatabaseManager =
- BatteryDatabaseManager.getInstance(this);
- final BatteryTipPolicy policy = new BatteryTipPolicy(this);
- ThreadUtils.postOnBackgroundThread(
- () -> {
- batteryDatabaseManager.deleteAllAnomaliesBeforeTimeStamp(
- System.currentTimeMillis()
- - TimeUnit.DAYS.toMillis(policy.dataHistoryRetainDay));
- jobFinished(params, false /* wantsReschedule */);
- });
-
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters jobParameters) {
- return false;
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java
deleted file mode 100644
index fe75c8e..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobService.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import android.app.StatsManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settings.R;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.concurrent.TimeUnit;
-
-/** A JobService check whether to update the anomaly config periodically */
-public class AnomalyConfigJobService extends JobService {
- private static final String TAG = "AnomalyConfigJobService";
-
- public static final String PREF_DB = "anomaly_pref";
- public static final String KEY_ANOMALY_CONFIG_VERSION = "anomaly_config_version";
- private static final int DEFAULT_VERSION = 0;
-
- @VisibleForTesting static final long CONFIG_UPDATE_FREQUENCY_MS = TimeUnit.DAYS.toMillis(1);
-
- public static void scheduleConfigUpdate(Context context) {
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
-
- final ComponentName component = new ComponentName(context, AnomalyConfigJobService.class);
- final JobInfo.Builder jobBuilder =
- new JobInfo.Builder(R.integer.job_anomaly_config_update, component)
- .setPeriodic(CONFIG_UPDATE_FREQUENCY_MS)
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setPersisted(true);
- final JobInfo pending = jobScheduler.getPendingJob(R.integer.job_anomaly_config_update);
-
- // Don't schedule it if it already exists, to make sure it runs periodically even after
- // reboot
- if (pending == null
- && jobScheduler.schedule(jobBuilder.build()) != JobScheduler.RESULT_SUCCESS) {
- Log.i(TAG, "Anomaly config update job service schedule failed.");
- }
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- ThreadUtils.postOnBackgroundThread(
- () -> {
- final StatsManager statsManager = getSystemService(StatsManager.class);
- checkAnomalyConfig(statsManager);
- try {
- BatteryTipUtils.uploadAnomalyPendingIntent(this, statsManager);
- } catch (StatsManager.StatsUnavailableException e) {
- Log.w(TAG, "Failed to uploadAnomalyPendingIntent.", e);
- }
- jobFinished(params, false /* wantsReschedule */);
- });
-
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters jobParameters) {
- return false;
- }
-
- @VisibleForTesting
- synchronized void checkAnomalyConfig(StatsManager statsManager) {
- final SharedPreferences sharedPreferences =
- getSharedPreferences(PREF_DB, Context.MODE_PRIVATE);
- final int currentVersion =
- sharedPreferences.getInt(KEY_ANOMALY_CONFIG_VERSION, DEFAULT_VERSION);
- final int newVersion =
- Settings.Global.getInt(
- getContentResolver(),
- Settings.Global.ANOMALY_CONFIG_VERSION,
- DEFAULT_VERSION);
- final String rawConfig =
- Settings.Global.getString(getContentResolver(), Settings.Global.ANOMALY_CONFIG);
- Log.i(TAG, "CurrentVersion: " + currentVersion + " new version: " + newVersion);
-
- if (newVersion > currentVersion) {
- try {
- statsManager.removeConfig(StatsManagerConfig.ANOMALY_CONFIG_KEY);
- } catch (StatsManager.StatsUnavailableException e) {
- Log.i(
- TAG,
- "When updating anomaly config, failed to first remove the old config "
- + StatsManagerConfig.ANOMALY_CONFIG_KEY,
- e);
- }
- if (!TextUtils.isEmpty(rawConfig)) {
- try {
- final byte[] config = Base64.decode(rawConfig, Base64.DEFAULT);
- statsManager.addConfig(StatsManagerConfig.ANOMALY_CONFIG_KEY, config);
- Log.i(
- TAG,
- "Upload the anomaly config. configKey: "
- + StatsManagerConfig.ANOMALY_CONFIG_KEY);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putInt(KEY_ANOMALY_CONFIG_VERSION, newVersion);
- editor.commit();
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Anomaly raw config is in wrong format", e);
- } catch (StatsManager.StatsUnavailableException e) {
- Log.i(
- TAG,
- "Upload of anomaly config failed for configKey "
- + StatsManagerConfig.ANOMALY_CONFIG_KEY,
- e);
- }
- }
- }
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
deleted file mode 100644
index 538b047..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigReceiver.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import android.app.StatsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * Receive broadcast when {@link StatsManager} restart, then check the anomaly config and prepare
- * info for {@link StatsManager}
- */
-public class AnomalyConfigReceiver extends BroadcastReceiver {
- private static final String TAG = "AnomalyConfigReceiver";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (StatsManager.ACTION_STATSD_STARTED.equals(intent.getAction())
- || Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- final StatsManager statsManager = context.getSystemService(StatsManager.class);
-
- // Check whether to update the config
- AnomalyConfigJobService.scheduleConfigUpdate(context);
-
- try {
- BatteryTipUtils.uploadAnomalyPendingIntent(context, statsManager);
- } catch (StatsManager.StatsUnavailableException e) {
- Log.w(TAG, "Failed to uploadAnomalyPendingIntent.", e);
- }
-
- if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- AnomalyCleanupJobService.scheduleCleanUp(context);
- }
- }
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java
deleted file mode 100644
index a80987d..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobService.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import static android.os.StatsDimensionsValue.INT_VALUE_TYPE;
-import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE;
-
-import android.app.AppOpsManager;
-import android.app.StatsManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.app.job.JobWorkItem;
-import android.app.settings.SettingsEnums;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.StatsDimensionsValue;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/** A JobService to store anomaly data to anomaly database */
-public class AnomalyDetectionJobService extends JobService {
- private static final String TAG = "AnomalyDetectionService";
- private static final int ON = 1;
- @VisibleForTesting static final int UID_NULL = -1;
- @VisibleForTesting static final int STATSD_UID_FILED = 1;
- @VisibleForTesting static final long MAX_DELAY_MS = Duration.ofDays(1).toMillis();
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- @VisibleForTesting
- boolean mIsJobCanceled = false;
-
- public static void scheduleAnomalyDetection(Context context, Intent intent) {
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- final ComponentName component =
- new ComponentName(context, AnomalyDetectionJobService.class);
- final JobInfo.Builder jobBuilder =
- new JobInfo.Builder(R.integer.job_anomaly_detection, component)
- .setOverrideDeadline(MAX_DELAY_MS);
-
- if (jobScheduler.enqueue(jobBuilder.build(), new JobWorkItem(intent))
- != JobScheduler.RESULT_SUCCESS) {
- Log.i(TAG, "Anomaly detection job service enqueue failed.");
- }
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- synchronized (mLock) {
- mIsJobCanceled = false;
- }
- ThreadUtils.postOnBackgroundThread(
- () -> {
- final Context context = AnomalyDetectionJobService.this;
- final BatteryDatabaseManager batteryDatabaseManager =
- BatteryDatabaseManager.getInstance(this);
- final BatteryTipPolicy policy = new BatteryTipPolicy(this);
- final BatteryUtils batteryUtils = BatteryUtils.getInstance(this);
- final ContentResolver contentResolver = getContentResolver();
- final UserManager userManager = getSystemService(UserManager.class);
- final PowerAllowlistBackend powerAllowlistBackend =
- PowerAllowlistBackend.getInstance(context);
- final PowerUsageFeatureProvider powerUsageFeatureProvider =
- FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
- final MetricsFeatureProvider metricsFeatureProvider =
- FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-
- for (JobWorkItem item = dequeueWork(params);
- item != null;
- item = dequeueWork(params)) {
- saveAnomalyToDatabase(
- context,
- userManager,
- batteryDatabaseManager,
- batteryUtils,
- policy,
- powerAllowlistBackend,
- contentResolver,
- powerUsageFeatureProvider,
- metricsFeatureProvider,
- item.getIntent().getExtras());
-
- completeWork(params, item);
- }
- });
-
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters jobParameters) {
- synchronized (mLock) {
- mIsJobCanceled = true;
- }
- return true; // Need to reschedule
- }
-
- @VisibleForTesting
- void saveAnomalyToDatabase(
- Context context,
- UserManager userManager,
- BatteryDatabaseManager databaseManager,
- BatteryUtils batteryUtils,
- BatteryTipPolicy policy,
- PowerAllowlistBackend powerAllowlistBackend,
- ContentResolver contentResolver,
- PowerUsageFeatureProvider powerUsageFeatureProvider,
- MetricsFeatureProvider metricsFeatureProvider,
- Bundle bundle) {
- // The Example of intentDimsValue is: 35:{1:{1:{1:10013|}|}|}
- final StatsDimensionsValue intentDimsValue =
- bundle.getParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE);
- final long timeMs =
- bundle.getLong(
- AnomalyDetectionReceiver.KEY_ANOMALY_TIMESTAMP, System.currentTimeMillis());
- final ArrayList<String> cookies =
- bundle.getStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES);
- final AnomalyInfo anomalyInfo =
- new AnomalyInfo(!ArrayUtils.isEmpty(cookies) ? cookies.get(0) : "");
- Log.i(TAG, "Extra stats value: " + intentDimsValue.toString());
-
- try {
- final int uid = extractUidFromStatsDimensionsValue(intentDimsValue);
- final boolean autoFeatureOn =
- powerUsageFeatureProvider.isSmartBatterySupported()
- ? Settings.Global.getInt(
- contentResolver,
- Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
- ON)
- == ON
- : Settings.Global.getInt(
- contentResolver,
- Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
- ON)
- == ON;
- final String packageName = batteryUtils.getPackageName(uid);
- final long versionCode = batteryUtils.getAppLongVersionCode(packageName);
- final String versionedPackage = packageName + "/" + versionCode;
- if (batteryUtils.shouldHideAnomaly(powerAllowlistBackend, uid, anomalyInfo)) {
- metricsFeatureProvider.action(
- SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_ANOMALY_IGNORED,
- SettingsEnums.PAGE_UNKNOWN,
- versionedPackage,
- anomalyInfo.anomalyType);
- } else {
- if (autoFeatureOn && anomalyInfo.autoRestriction) {
- // Auto restrict this app
- batteryUtils.setForceAppStandby(uid, packageName, AppOpsManager.MODE_IGNORED);
- databaseManager.insertAnomaly(
- uid,
- packageName,
- anomalyInfo.anomalyType,
- AnomalyDatabaseHelper.State.AUTO_HANDLED,
- timeMs);
- } else {
- databaseManager.insertAnomaly(
- uid,
- packageName,
- anomalyInfo.anomalyType,
- AnomalyDatabaseHelper.State.NEW,
- timeMs);
- }
- metricsFeatureProvider.action(
- SettingsEnums.PAGE_UNKNOWN,
- SettingsEnums.ACTION_ANOMALY_TRIGGERED,
- SettingsEnums.PAGE_UNKNOWN,
- versionedPackage,
- anomalyInfo.anomalyType);
- }
-
- } catch (NullPointerException | IndexOutOfBoundsException e) {
- Log.e(TAG, "Parse stats dimensions value error.", e);
- }
- }
-
- /**
- * Extract the uid from {@link StatsDimensionsValue} <br>
- * <br>
- * The uid dimension has the format: {1:int} inside the tuple list. Here are some examples: <br>
- * 1.Excessive bg anomaly: 27:{1:10089|} <br>
- * 2.Wakeup alarm anomaly: 35:{1:{1:{1:10013|}|}|} <br>
- * 3.Bluetooth anomaly: 3:{1:{1:{1:10140|}|}|}
- */
- @VisibleForTesting
- int extractUidFromStatsDimensionsValue(StatsDimensionsValue statsDimensionsValue) {
- if (statsDimensionsValue == null) {
- return UID_NULL;
- }
- if (statsDimensionsValue.isValueType(INT_VALUE_TYPE)
- && statsDimensionsValue.getField() == STATSD_UID_FILED) {
- // Find out the real uid
- return statsDimensionsValue.getIntValue();
- }
- if (statsDimensionsValue.isValueType(TUPLE_VALUE_TYPE)) {
- final List<StatsDimensionsValue> values = statsDimensionsValue.getTupleValueList();
- for (int i = 0, size = values.size(); i < size; i++) {
- int uid = extractUidFromStatsDimensionsValue(values.get(i));
- if (uid != UID_NULL) {
- return uid;
- }
- }
- }
-
- return UID_NULL;
- }
-
- @VisibleForTesting
- JobWorkItem dequeueWork(JobParameters parameters) {
- synchronized (mLock) {
- if (mIsJobCanceled) {
- return null;
- }
-
- return parameters.dequeueWork();
- }
- }
-
- @VisibleForTesting
- void completeWork(JobParameters parameters, JobWorkItem item) {
- synchronized (mLock) {
- if (mIsJobCanceled) {
- return;
- }
-
- parameters.completeWork(item);
- }
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java
deleted file mode 100644
index 0d43add..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionReceiver.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import android.app.StatsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-/** Receive the anomaly info from {@link StatsManager} */
-public class AnomalyDetectionReceiver extends BroadcastReceiver {
- private static final String TAG = "SettingsAnomalyReceiver";
-
- public static final String KEY_ANOMALY_TIMESTAMP = "key_anomaly_timestamp";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final long configUid = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_UID, -1);
- final long configKey = intent.getLongExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, -1);
- final long subscriptionId =
- intent.getLongExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, -1);
- Log.i(
- TAG,
- "Anomaly intent received. configUid = "
- + configUid
- + " configKey = "
- + configKey
- + " subscriptionId = "
- + subscriptionId);
-
- final Bundle bundle = intent.getExtras();
- if (bundle == null) {
- return;
- }
- bundle.putLong(KEY_ANOMALY_TIMESTAMP, System.currentTimeMillis());
-
- AnomalyDetectionJobService.scheduleAnomalyDetection(context, intent);
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyInfo.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyInfo.java
deleted file mode 100644
index da277c6..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/AnomalyInfo.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import android.util.KeyValueListParser;
-import android.util.Log;
-
-/** Model class to parse and store anomaly info from statsd. */
-public class AnomalyInfo {
- private static final String TAG = "AnomalyInfo";
-
- private static final String KEY_ANOMALY_TYPE = "anomaly_type";
- private static final String KEY_AUTO_RESTRICTION = "auto_restriction";
- public final Integer anomalyType;
- public final boolean autoRestriction;
-
- public AnomalyInfo(String info) {
- Log.i(TAG, "anomalyInfo: " + info);
- KeyValueListParser parser = new KeyValueListParser(',');
- parser.setString(info);
- anomalyType = parser.getInt(KEY_ANOMALY_TYPE, -1);
- autoRestriction = parser.getBoolean(KEY_AUTO_RESTRICTION, false);
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
index d65bd26..7dc993c 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
@@ -17,8 +17,6 @@
package com.android.settings.fuelgauge.batterytip;
import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.StatsManager;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
@@ -111,36 +109,8 @@
}
}
- /**
- * Upload the {@link PendingIntent} to {@link StatsManager} for anomaly detection
- *
- * @throws StatsManager.StatsUnavailableException if failed to communicate with stats service
- */
- public static void uploadAnomalyPendingIntent(Context context, StatsManager statsManager)
- throws StatsManager.StatsUnavailableException {
- final Intent extraIntent = new Intent(context, AnomalyDetectionReceiver.class);
- final PendingIntent pendingIntent =
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE,
- extraIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
- statsManager.setBroadcastSubscriber(
- pendingIntent,
- StatsManagerConfig.ANOMALY_CONFIG_KEY,
- StatsManagerConfig.SUBSCRIBER_ID);
- }
-
- /** Detect and return anomaly apps after {@code timeAfterMs} */
+ /** Detect and return anomaly apps after {@code timeAfterMs} */
public static List<AppInfo> detectAnomalies(Context context, long timeAfterMs) {
- final List<AppInfo> highUsageApps =
- BatteryDatabaseManager.getInstance(context)
- .queryAllAnomalies(timeAfterMs, AnomalyDatabaseHelper.State.NEW);
- // Remove it if it doesn't have label or been restricted
- highUsageApps.removeIf(
- AppLabelPredicate.getInstance(context)
- .or(AppRestrictionPredicate.getInstance(context)));
-
- return highUsageApps;
+ return new ArrayList<>();
}
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index 002c807..df9f063 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -479,7 +479,9 @@
}
}
- private static BatteryUsageDiff convertToBatteryUsageDiff(BatteryDiffEntry batteryDiffEntry) {
+
+ @VisibleForTesting
+ static BatteryUsageDiff convertToBatteryUsageDiff(BatteryDiffEntry batteryDiffEntry) {
BatteryUsageDiff.Builder builder =
BatteryUsageDiff.newBuilder()
.setUid(batteryDiffEntry.mUid)
@@ -496,6 +498,8 @@
batteryDiffEntry.mForegroundServiceUsageConsumePower)
.setCachedUsageConsumePower(batteryDiffEntry.mCachedUsageConsumePower)
.setForegroundUsageTime(batteryDiffEntry.mForegroundUsageTimeInMs)
+ .setForegroundServiceUsageTime(
+ batteryDiffEntry.mForegroundServiceUsageTimeInMs)
.setBackgroundUsageTime(batteryDiffEntry.mBackgroundUsageTimeInMs)
.setScreenOnTime(batteryDiffEntry.mScreenOnTimeInMs);
if (batteryDiffEntry.mKey != null) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 8a1cd76..a41e9bd 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -767,7 +767,8 @@
BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA);
writeString(context, writer, "LastLoadFullChargeTime", KEY_LAST_LOAD_FULL_CHARGE_TIME);
writeString(context, writer, "LastUploadFullChargeTime", KEY_LAST_UPLOAD_FULL_CHARGE_TIME);
- writeString(context, writer, "DismissedPowerAnomalyKeys", KEY_DISMISSED_POWER_ANOMALY_KEYS);
+ writeStringSet(
+ context, writer, "DismissedPowerAnomalyKeys", KEY_DISMISSED_POWER_ANOMALY_KEYS);
}
static SharedPreferences getSharedPreferences(Context context) {
@@ -921,9 +922,22 @@
private static void writeString(
Context context, PrintWriter writer, String prefix, String key) {
final SharedPreferences sharedPreferences = getSharedPreferences(context);
- if (sharedPreferences != null) {
- final String content = sharedPreferences.getString(key, "");
- writer.println(String.format("\t\t%s: %s", prefix, content));
+ if (sharedPreferences == null) {
+ return;
+ }
+ final String content = sharedPreferences.getString(key, "");
+ writer.println(String.format("\t\t%s: %s", prefix, content));
+ }
+
+ private static void writeStringSet(
+ Context context, PrintWriter writer, String prefix, String key) {
+ final SharedPreferences sharedPreferences = getSharedPreferences(context);
+ if (sharedPreferences == null) {
+ return;
+ }
+ final Set<String> results = sharedPreferences.getStringSet(key, new ArraySet<>());
+ if (results != null) {
+ writer.println(String.format("\t\t%s: %s", prefix, results.toString()));
}
}
diff --git a/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt
new file mode 100644
index 0000000..b421185
--- /dev/null
+++ b/src/com/android/settings/network/telephony/CarrierConfigManagerExt.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.network.telephony
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import androidx.core.os.persistableBundleOf
+
+/**
+ * Gets the configuration values of the specified config keys applied.
+ */
+fun CarrierConfigManager.safeGetConfig(
+ keys: List<String>,
+ subId: Int = SubscriptionManager.getDefaultSubscriptionId(),
+): PersistableBundle = try {
+ getConfigForSubId(subId, *keys.toTypedArray())
+} catch (e: IllegalStateException) {
+ // The CarrierConfigLoader (the service implemented the CarrierConfigManager) hasn't been
+ // initialized yet. This may occurs during very early phase of phone booting up or when Phone
+ // process has been restarted.
+ // Settings should not assume Carrier config loader (and any other system services as well) are
+ // always available. If not available, use default value instead.
+ persistableBundleOf()
+}
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index 663b4e5..db031e7 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -203,6 +203,7 @@
private boolean mOnlyEnforceDevicePasswordRequirement = false;
private int mExtraLockScreenTitleResId;
private int mExtraLockScreenDescriptionResId;
+ private boolean mWaitingForBiometricEnrollment = false;
@Override
public int getMetricsCategory() {
@@ -250,6 +251,7 @@
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
+ mWaitingForBiometricEnrollment = mForBiometrics || mForFingerprint || mForFace;
mExtraLockScreenTitleResId = intent.getIntExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE, -1);
mExtraLockScreenDescriptionResId =
@@ -440,6 +442,7 @@
return true;
} else if (KEY_SKIP_FINGERPRINT.equals(key) || KEY_SKIP_FACE.equals(key)
|| KEY_SKIP_BIOMETRICS.equals(key)) {
+ mWaitingForBiometricEnrollment = false;
Intent chooseLockGenericIntent = new Intent(getActivity(),
getInternalActivityClass());
chooseLockGenericIntent.setAction(getIntent().getAction());
@@ -493,6 +496,7 @@
finish();
} else if (requestCode == CHOOSE_LOCK_BEFORE_BIOMETRIC_REQUEST
&& resultCode == BiometricEnrollBase.RESULT_FINISHED) {
+ mWaitingForBiometricEnrollment = false;
Intent intent = getBiometricEnrollIntent(getActivity());
if (data != null) {
// ChooseLockGeneric should have requested for a Gatekeeper Password Handle to
@@ -873,7 +877,8 @@
// Otherwise, bugs would be caused. (e.g. b/278488549, b/278530059)
final boolean hasCredential = mLockPatternUtils.isSecure(mUserId);
if (!getActivity().isChangingConfigurations()
- && !mWaitingForConfirmation && !mWaitingForActivityResult && hasCredential) {
+ && !mWaitingForConfirmation && !mWaitingForActivityResult && hasCredential
+ && !mWaitingForBiometricEnrollment) {
getActivity().finish();
}
}
diff --git a/src/com/android/settings/system/ClientInitiatedActionRepository.kt b/src/com/android/settings/system/ClientInitiatedActionRepository.kt
index 24c04b4..715acfa 100644
--- a/src/com/android/settings/system/ClientInitiatedActionRepository.kt
+++ b/src/com/android/settings/system/ClientInitiatedActionRepository.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.telephony.CarrierConfigManager
import android.util.Log
+import com.android.settings.network.telephony.safeGetConfig
class ClientInitiatedActionRepository(private val context: Context) {
private val configManager = context.getSystemService(CarrierConfigManager::class.java)!!
@@ -29,11 +30,13 @@
*/
fun onSystemUpdate() {
val bundle =
- configManager.getConfig(
- CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL,
- CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING,
- CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING,
- CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING,
+ configManager.safeGetConfig(
+ keys = listOf(
+ CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL,
+ CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING,
+ CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING,
+ CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING,
+ ),
)
if (!bundle.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) return
diff --git a/tests/robotests/src/com/android/settings/SettingsDumpServiceTest.java b/tests/robotests/src/com/android/settings/SettingsDumpServiceTest.java
index 9d8841f..e44d528 100644
--- a/tests/robotests/src/com/android/settings/SettingsDumpServiceTest.java
+++ b/tests/robotests/src/com/android/settings/SettingsDumpServiceTest.java
@@ -29,10 +29,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import com.android.settings.fuelgauge.batterytip.AnomalyConfigJobService;
-
-import org.json.JSONException;
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,22 +80,6 @@
}
@Test
- public void testDumpAnomalyDetection_returnAnomalyInfo() throws JSONException {
- final SharedPreferences sharedPreferences =
- RuntimeEnvironment.application.getSharedPreferences(AnomalyConfigJobService.PREF_DB,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putInt(AnomalyConfigJobService.KEY_ANOMALY_CONFIG_VERSION, ANOMALY_VERSION);
- editor.commit();
- doReturn(sharedPreferences).when(mTestService).getSharedPreferences(anyString(), anyInt());
-
- final JSONObject jsonObject = mTestService.dumpAnomalyDetection();
-
- assertThat(jsonObject.getInt(AnomalyConfigJobService.KEY_ANOMALY_CONFIG_VERSION)).isEqualTo(
- ANOMALY_VERSION);
- }
-
- @Test
public void testDump_printServiceAsKey() {
mResolveInfo.activityInfo = new ActivityInfo();
mResolveInfo.activityInfo.packageName = PACKAGE_BROWSER;
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
index 6c1de59..bd57e9d 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
@@ -32,6 +32,10 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
@@ -43,6 +47,7 @@
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.bluetooth.HearingAidPairingDialogFragment;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -77,6 +82,9 @@
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
private static final int TEST_LAUNCH_PAGE = SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
@@ -129,7 +137,22 @@
}
@Test
- public void dialogPositiveButtonClick_intentToA11yPairingPage() {
+ @RequiresFlagsEnabled(Flags.FLAG_NEW_HEARING_DEVICE_PAIRING_PAGE)
+ public void dialogPositiveButtonClick_intentToNewA11yPairingPage() {
+ setupDialog(SettingsEnums.ACCESSIBILITY);
+ final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY);
+ dialog.show();
+
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+ final Intent intent = shadowOf(mActivity).getNextStartedActivity();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(HearingDevicePairingFragment.class.getName());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_NEW_HEARING_DEVICE_PAIRING_PAGE)
+ public void dialogPositiveButtonClick_intentToOldA11yPairingPage() {
setupDialog(SettingsEnums.ACCESSIBILITY);
final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY);
dialog.show();
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingDevicePairingFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingDevicePairingFragmentTest.java
new file mode 100644
index 0000000..e14686e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingDevicePairingFragmentTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 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.accessibility;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.Pair;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.bluetooth.BluetoothDevicePreference;
+import com.android.settings.bluetooth.BluetoothProgressCategory;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidInfo;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+/** Tests for {@link HearingDevicePairingFragment}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HearingDevicePairingFragmentTest {
+
+ private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Spy
+ private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ @Spy
+ private final HearingDevicePairingFragment mFragment = new TestHearingDevicePairingFragment();
+
+ @Mock
+ private LocalBluetoothManager mLocalManager;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ @Mock
+ private CachedBluetoothDevice mCachedDevice;
+ @Mock
+ private BluetoothProgressCategory mAvailableHearingDeviceGroup;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private BluetoothDevice mDevice;
+ private BluetoothDevicePreference mDevicePreference;
+
+
+ @Before
+ public void setUp() {
+ mFragment.mLocalManager = mLocalManager;
+ mFragment.mCachedDeviceManager = mCachedDeviceManager;
+ mFragment.mBluetoothAdapter = mBluetoothAdapter;
+ doReturn(mContext).when(mFragment).getContext();
+ doReturn(mAvailableHearingDeviceGroup).when(mFragment).findPreference(
+ "available_hearing_devices");
+ mFragment.initPreferencesFromPreferenceScreen();
+
+
+ mDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+ doReturn(mDevice).when(mCachedDevice).getDevice();
+ final Pair<Drawable, String> pair = new Pair<>(mock(Drawable.class), "test_device");
+ doReturn(pair).when(mCachedDevice).getDrawableWithDescription();
+
+ mDevicePreference = new BluetoothDevicePreference(mContext, mCachedDevice, true,
+ BluetoothDevicePreference.SortType.TYPE_DEFAULT);
+ }
+
+ @Test
+ public void startAndStopScanning_stateIsCorrect() {
+ mFragment.startScanning();
+
+ verify(mFragment).startLeScanning();
+
+ mFragment.stopScanning();
+
+ verify(mFragment).stopLeScanning();
+ }
+
+ @Test
+ public void onDeviceDeleted_stateIsCorrect() {
+ mFragment.mDevicePreferenceMap.put(mCachedDevice, mDevicePreference);
+
+ assertThat(mFragment.mDevicePreferenceMap).isNotEmpty();
+
+ mFragment.onDeviceDeleted(mCachedDevice);
+
+ assertThat(mFragment.mDevicePreferenceMap).isEmpty();
+ verify(mAvailableHearingDeviceGroup).removePreference(mDevicePreference);
+ }
+
+ @Test
+ public void addDevice_bluetoothOff_doNothing() {
+ doReturn(BluetoothAdapter.STATE_OFF).when(mBluetoothAdapter).getState();
+
+ assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+
+ mFragment.addDevice(mCachedDevice);
+
+ verify(mAvailableHearingDeviceGroup, never()).addPreference(mDevicePreference);
+ assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void addDevice_addToAvailableHearingDeviceGroup() {
+ doReturn(BluetoothAdapter.STATE_ON).when(mBluetoothAdapter).getState();
+
+ assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+
+ mFragment.addDevice(mCachedDevice);
+
+ verify(mAvailableHearingDeviceGroup).addPreference(mDevicePreference);
+ assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(1);
+ }
+
+ @Test
+ public void handleLeScanResult_markDeviceAsHearingAid() {
+ ScanResult scanResult = mock(ScanResult.class);
+ doReturn(mDevice).when(scanResult).getDevice();
+ doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice);
+
+ mFragment.handleLeScanResult(scanResult);
+
+ verify(mCachedDevice).setHearingAidInfo(new HearingAidInfo.Builder().build());
+ }
+
+ @Test
+ public void handleLeScanResult_isAndroidCompatible_addDevice() {
+ ScanResult scanResult = mock(ScanResult.class);
+ doReturn(mDevice).when(scanResult).getDevice();
+ doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice);
+ doReturn(true).when(mFragment).isAndroidCompatibleHearingAid(scanResult);
+
+ mFragment.handleLeScanResult(scanResult);
+
+ verify(mFragment).addDevice(mCachedDevice);
+ }
+
+ @Test
+ public void handleLeScanResult_isNotAndroidCompatible_() {
+ ScanResult scanResult = mock(ScanResult.class);
+ doReturn(mDevice).when(scanResult).getDevice();
+ doReturn(mCachedDevice).when(mCachedDeviceManager).findDevice(mDevice);
+ doReturn(false).when(mFragment).isAndroidCompatibleHearingAid(scanResult);
+
+ mFragment.handleLeScanResult(scanResult);
+
+ verify(mFragment).discoverServices(mCachedDevice);
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_deviceConnected_inSelectedList_finish() {
+ doReturn(true).when(mCachedDevice).isConnected();
+ mFragment.mSelectedDeviceList.add(mDevice);
+
+ mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.A2DP);
+
+ verify(mFragment).finish();
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_deviceConnected_notInSelectedList_deleteDevice() {
+ doReturn(true).when(mCachedDevice).isConnected();
+
+ mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.A2DP);
+
+ verify(mFragment).removeDevice(mCachedDevice);
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_deviceNotConnected_doNothing() {
+ doReturn(false).when(mCachedDevice).isConnected();
+
+ mFragment.onProfileConnectionStateChanged(mCachedDevice, BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.A2DP);
+
+ verify(mFragment, never()).finish();
+ verify(mFragment, never()).removeDevice(mCachedDevice);
+ }
+
+ @Test
+ public void onBluetoothStateChanged_stateOn_startScanningAndShowToast() {
+ mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+ verify(mFragment).startScanning();
+ verify(mFragment).showBluetoothTurnedOnToast();
+ }
+
+ @Test
+ public void onBluetoothStateChanged_stateOff_finish() {
+ mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
+
+ verify(mFragment).finish();
+ }
+
+ @Test
+ public void onDeviceBondStateChanged_bonded_finish() {
+ mFragment.onDeviceBondStateChanged(mCachedDevice, BluetoothDevice.BOND_BONDED);
+
+ verify(mFragment).finish();
+ }
+
+ @Test
+ public void onDeviceBondStateChanged_selectedDeviceNotBonded_startScanning() {
+ mFragment.mSelectedDevice = mDevice;
+
+ mFragment.onDeviceBondStateChanged(mCachedDevice, BluetoothDevice.BOND_NONE);
+
+ verify(mFragment).startScanning();
+ }
+
+ @Test
+ public void isAndroidCompatibleHearingAid_asha_returnTrue() {
+ ScanResult scanResult = createAshaScanResult();
+
+ boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult);
+
+ assertThat(isCompatible).isTrue();
+ }
+
+ @Test
+ public void isAndroidCompatibleHearingAid_has_returnTrue() {
+ ScanResult scanResult = createHasScanResult();
+
+ boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult);
+
+ assertThat(isCompatible).isTrue();
+ }
+
+ @Test
+ public void isAndroidCompatibleHearingAid_mfiHas_returnFalse() {
+ ScanResult scanResult = createMfiHasScanResult();
+
+ boolean isCompatible = mFragment.isAndroidCompatibleHearingAid(scanResult);
+
+ assertThat(isCompatible).isFalse();
+ }
+
+ private ScanResult createAshaScanResult() {
+ ScanResult scanResult = mock(ScanResult.class);
+ ScanRecord scanRecord = mock(ScanRecord.class);
+ byte[] fakeAshaServiceData = new byte[] {
+ 0x09, 0x16, (byte) 0xf0, (byte) 0xfd, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04};
+ doReturn(scanRecord).when(scanResult).getScanRecord();
+ doReturn(fakeAshaServiceData).when(scanRecord).getServiceData(BluetoothUuid.HEARING_AID);
+ return scanResult;
+ }
+
+ private ScanResult createHasScanResult() {
+ ScanResult scanResult = mock(ScanResult.class);
+ ScanRecord scanRecord = mock(ScanRecord.class);
+ doReturn(scanRecord).when(scanResult).getScanRecord();
+ doReturn(List.of(BluetoothUuid.HAS)).when(scanRecord).getServiceUuids();
+ return scanResult;
+ }
+
+ private ScanResult createMfiHasScanResult() {
+ ScanResult scanResult = mock(ScanResult.class);
+ ScanRecord scanRecord = mock(ScanRecord.class);
+ byte[] fakeMfiServiceData = new byte[] {0x00, 0x00, 0x00, 0x00};
+ doReturn(scanRecord).when(scanResult).getScanRecord();
+ doReturn(fakeMfiServiceData).when(scanRecord).getServiceData(BluetoothUuid.MFI_HAS);
+ return scanResult;
+ }
+
+ private class TestHearingDevicePairingFragment extends HearingDevicePairingFragment {
+ @Override
+ protected Preference getCachedPreference(String key) {
+ if (key.equals(TEST_DEVICE_ADDRESS)) {
+ return mDevicePreference;
+ }
+ return super.getCachedPreference(key);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AddDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AddDevicePreferenceControllerTest.java
index 7384d3a..63fa88d 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/AddDevicePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/AddDevicePreferenceControllerTest.java
@@ -15,34 +15,49 @@
*/
package com.android.settings.connecteddevice;
+import static com.android.settings.accessibility.AccessibilityHearingAidsFragment.KEY_HEARING_DEVICE_ADD_BT_DEVICES;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.TextUtils;
import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.accessibility.HearingDevicePairingDetail;
+import com.android.settings.accessibility.HearingDevicePairingFragment;
+import com.android.settings.flags.Flags;
import com.android.settingslib.RestrictedPreference;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
import org.robolectric.util.ReflectionHelpers;
@@ -51,12 +66,16 @@
@Config(shadows = ShadowApplicationPackageManager.class)
public class AddDevicePreferenceControllerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private PreferenceScreen mScreen;
@Mock
private BluetoothAdapter mBluetoothAdapter;
- private Context mContext;
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
private AddDevicePreferenceController mAddDevicePreferenceController;
private RestrictedPreference mAddDevicePreference;
private ShadowApplicationPackageManager mPackageManager;
@@ -66,8 +85,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
+ mPackageManager = (ShadowApplicationPackageManager) shadowOf(
mContext.getPackageManager());
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
@@ -82,6 +100,8 @@
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
when(mScreen.findPreference(key)).thenReturn(mAddDevicePreference);
mAddDevicePreferenceController.displayPreference(mScreen);
+
+ doNothing().when(mContext).startActivity(any(Intent.class));
}
@Test
@@ -137,4 +157,30 @@
assertThat(mAddDevicePreferenceController.getAvailabilityStatus())
.isEqualTo(UNSUPPORTED_ON_DEVICE);
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NEW_HEARING_DEVICE_PAIRING_PAGE)
+ public void handlePreferenceClick_A11yPreference_redirectToNewPairingPage() {
+ mAddDevicePreference.setKey(KEY_HEARING_DEVICE_ADD_BT_DEVICES);
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ mAddDevicePreferenceController.handlePreferenceTreeClick(mAddDevicePreference);
+
+ verify(mContext).startActivity(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(HearingDevicePairingFragment.class.getName());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_NEW_HEARING_DEVICE_PAIRING_PAGE)
+ public void handlePreferenceClick_A11yPreference_redirectToOldPairingPage() {
+ mAddDevicePreference.setKey(KEY_HEARING_DEVICE_ADD_BT_DEVICES);
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ mAddDevicePreferenceController.handlePreferenceTreeClick(mAddDevicePreference);
+
+ verify(mContext).startActivity(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(HearingDevicePairingDetail.class.getName());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index 83ff582..1df8a40 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -272,7 +272,8 @@
mController.updateHeaderPreference(mBatteryInfo);
- verify(mBatteryUsageProgressBarPref).setBottomSummary(null);
+ verify(mBatteryUsageProgressBarPref).setBottomSummary(mContext.getString(
+ com.android.settingslib.R.string.battery_info_status_charging_on_hold));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
index 0d8c669..1a2b246 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
@@ -40,20 +40,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.Build;
-import android.os.Process;
import android.os.SystemClock;
import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
-import com.android.settings.fuelgauge.batterytip.AnomalyInfo;
import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
@@ -70,9 +66,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import java.util.ArrayList;
-import java.util.List;
-
@RunWith(RobolectricTestRunner.class)
public class BatteryUtilsTest {
@@ -122,7 +115,6 @@
@Mock private ApplicationInfo mLowApplicationInfo;
@Mock private PowerAllowlistBackend mPowerAllowlistBackend;
@Mock private BatteryDatabaseManager mBatteryDatabaseManager;
- private AnomalyInfo mAnomalyInfo;
private BatteryUtils mBatteryUtils;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mProvider;
@@ -169,7 +161,6 @@
doReturn(0L)
.when(mBatteryUtils)
.getForegroundServiceTotalTimeUs(any(BatteryStats.Uid.class), anyLong());
- mAnomalyInfo = new AnomalyInfo(INFO_WAKELOCK);
BatteryDatabaseManager.setUpForTest(mBatteryDatabaseManager);
ShadowThreadUtils.setIsMainThread(true);
@@ -391,79 +382,6 @@
}
@Test
- public void testShouldHideAnomaly_systemAppWithLauncher_returnTrue() {
- final List<ResolveInfo> resolveInfos = new ArrayList<>();
- final ResolveInfo resolveInfo = new ResolveInfo();
- resolveInfo.activityInfo = new ActivityInfo();
- resolveInfo.activityInfo.packageName = HIGH_SDK_PACKAGE;
-
- doReturn(resolveInfos).when(mPackageManager).queryIntentActivities(any(), anyInt());
- doReturn(new String[] {HIGH_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
- mHighApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isTrue();
- }
-
- @Test
- public void testShouldHideAnomaly_systemAppWithoutLauncher_returnTrue() {
- doReturn(new ArrayList<>()).when(mPackageManager).queryIntentActivities(any(), anyInt());
- doReturn(new String[] {HIGH_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
- mHighApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isTrue();
- }
-
- @Test
- public void testShouldHideAnomaly_systemUid_returnTrue() {
- final int systemUid = Process.ROOT_UID;
- doReturn(new String[] {HIGH_SDK_PACKAGE})
- .when(mPackageManager)
- .getPackagesForUid(systemUid);
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, systemUid, mAnomalyInfo))
- .isTrue();
- }
-
- @Test
- public void testShouldHideAnomaly_AppInDozeList_returnTrue() {
- doReturn(new String[] {HIGH_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
- doReturn(true)
- .when(mPowerAllowlistBackend)
- .isAllowlisted(new String[] {HIGH_SDK_PACKAGE}, UID);
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isTrue();
- }
-
- @Test
- public void testShouldHideAnomaly_normalApp_returnFalse() {
- doReturn(new String[] {HIGH_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isFalse();
- }
-
- @Test
- public void testShouldHideAnomaly_excessivePriorOApp_returnFalse() {
- doReturn(new String[] {LOW_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
- mAnomalyInfo = new AnomalyInfo(INFO_EXCESSIVE);
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isFalse();
- }
-
- @Test
- public void testShouldHideAnomaly_excessiveOApp_returnTrue() {
- doReturn(new String[] {HIGH_SDK_PACKAGE}).when(mPackageManager).getPackagesForUid(UID);
- mAnomalyInfo = new AnomalyInfo(INFO_EXCESSIVE);
-
- assertThat(mBatteryUtils.shouldHideAnomaly(mPowerAllowlistBackend, UID, mAnomalyInfo))
- .isTrue();
- }
-
- @Test
public void clearForceAppStandby_appRestricted_clearAndReturnTrue() {
when(mBatteryUtils.getPackageUid(HIGH_SDK_PACKAGE)).thenReturn(UID);
when(mAppOpsManager.checkOpNoThrow(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
index 3435987..f7b5049 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
@@ -130,11 +130,14 @@
BatteryTestUtils.setupIncompatibleEvent(mUsbPort, mUsbManager, mUsbPortStatus);
mController.mPreference = new Preference(mContext);
BatteryInfo info = new BatteryInfo();
+ info.batteryPercentString = "66%";
assertThat(mController.getDashboardLabel(mContext, info, true))
.isEqualTo(
mContext.getString(
- com.android.settingslib.R.string.battery_info_status_not_charging));
+ com.android.settingslib.R.string
+ .power_incompatible_charging_settings_home_page,
+ info.batteryPercentString));
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobServiceTest.java
deleted file mode 100644
index 22c59b0..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyCleanupJobServiceTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.JobSchedulerImpl;
-import android.app.job.IJobScheduler;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.content.Context;
-import android.os.Binder;
-
-import com.android.settings.R;
-import com.android.settings.testutils.DatabaseTestUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(RobolectricTestRunner.class)
-public class AnomalyCleanupJobServiceTest {
- private static final int UID = 1234;
- private static final String PACKAGE_NAME = "com.android.package";
- private static final String PACKAGE_NAME_OLD = "com.android.package.old";
- private static final int ANOMALY_TYPE = 1;
- private static final long TIMESTAMP_NOW = System.currentTimeMillis();
- private static final long TIMESTAMP_31_DAYS_BEFORE = TIMESTAMP_NOW - TimeUnit.DAYS.toMillis(31);
-
- private Context mContext;
- private JobScheduler mJobScheduler;
- @Mock private JobParameters mParams;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = spy(RuntimeEnvironment.application);
- mJobScheduler =
- spy(new JobSchedulerImpl(mContext, IJobScheduler.Stub.asInterface(new Binder())));
- when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler);
- }
-
- @After
- public void cleanUp() {
- DatabaseTestUtils.clearDb(mContext);
- }
-
- @Test
- public void scheduleCleanUp() {
- AnomalyCleanupJobService.scheduleCleanUp(mContext);
-
- JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
- List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs();
- assertEquals(1, pendingJobs.size());
- JobInfo pendingJob = pendingJobs.get(0);
- assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_clean_up);
- assertThat(pendingJob.getIntervalMillis()).isEqualTo(TimeUnit.DAYS.toMillis(1));
- assertThat(pendingJob.isRequireDeviceIdle()).isTrue();
- assertThat(pendingJob.isRequireCharging()).isTrue();
- assertThat(pendingJob.isPersisted()).isTrue();
- }
-
- @Test
- public void scheduleCleanUp_invokeTwice_onlyScheduleOnce() {
- AnomalyCleanupJobService.scheduleCleanUp(mContext);
- AnomalyCleanupJobService.scheduleCleanUp(mContext);
-
- verify(mJobScheduler, times(1)).schedule(any());
- }
-
- @Test
- @Ignore
- public void onStartJob_cleanUpDataBefore30days() {
- final BatteryDatabaseManager databaseManager = BatteryDatabaseManager.getInstance(mContext);
- final AnomalyCleanupJobService service =
- spy(Robolectric.setupService(AnomalyCleanupJobService.class));
- doNothing().when(service).jobFinished(any(), anyBoolean());
-
- // Insert two records, one is current and the other one is 31 days before
- databaseManager.insertAnomaly(
- UID, PACKAGE_NAME, ANOMALY_TYPE, AnomalyDatabaseHelper.State.NEW, TIMESTAMP_NOW);
- databaseManager.insertAnomaly(
- UID,
- PACKAGE_NAME_OLD,
- ANOMALY_TYPE,
- AnomalyDatabaseHelper.State.NEW,
- TIMESTAMP_31_DAYS_BEFORE);
-
- service.onStartJob(mParams);
-
- // In database, it only contains the current record
- final List<AppInfo> appInfos =
- databaseManager.queryAllAnomalies(0, AnomalyDatabaseHelper.State.NEW);
- assertThat(appInfos)
- .containsExactly(
- new AppInfo.Builder()
- .setUid(UID)
- .setPackageName(PACKAGE_NAME)
- .addAnomalyType(ANOMALY_TYPE)
- .build());
- }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java
deleted file mode 100644
index 345b8a1..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyConfigJobServiceTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.robolectric.RuntimeEnvironment.application;
-
-import android.app.JobSchedulerImpl;
-import android.app.StatsManager;
-import android.app.job.IJobScheduler;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.Context;
-import android.os.Binder;
-import android.provider.Settings;
-
-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 java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(RobolectricTestRunner.class)
-public class AnomalyConfigJobServiceTest {
-
- private static final int ANOMALY_CONFIG_VERSION = 1;
- private static final String ANOMALY_CONFIG = "X64s";
- @Mock private StatsManager mStatsManager;
-
- private Context mContext;
- private JobScheduler mJobScheduler;
- private AnomalyConfigJobService mJobService;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = spy(RuntimeEnvironment.application);
- mJobScheduler =
- spy(new JobSchedulerImpl(mContext, IJobScheduler.Stub.asInterface(new Binder())));
- when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler);
-
- mJobService = spy(new AnomalyConfigJobService());
- doReturn(
- application.getSharedPreferences(
- AnomalyConfigJobService.PREF_DB, Context.MODE_PRIVATE))
- .when(mJobService)
- .getSharedPreferences(anyString(), anyInt());
- doReturn(application.getContentResolver()).when(mJobService).getContentResolver();
- }
-
- @Test
- public void testScheduleConfigUpdate() {
- AnomalyConfigJobService.scheduleConfigUpdate(mContext);
-
- JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
- List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs();
- assertEquals(1, pendingJobs.size());
- JobInfo pendingJob = pendingJobs.get(0);
- assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_config_update);
- assertThat(pendingJob.getIntervalMillis()).isEqualTo(TimeUnit.DAYS.toMillis(1));
- assertThat(pendingJob.isRequireDeviceIdle()).isTrue();
- assertThat(pendingJob.isRequireCharging()).isTrue();
- assertThat(pendingJob.isPersisted()).isTrue();
- }
-
- @Test
- public void testScheduleConfigUpdate_invokeTwice_onlyScheduleOnce() {
- AnomalyConfigJobService.scheduleConfigUpdate(mContext);
- AnomalyConfigJobService.scheduleConfigUpdate(mContext);
-
- verify(mJobScheduler, times(1)).schedule(any());
- }
-
- @Test
- public void checkAnomalyConfig_newConfigExist_removeOldConfig()
- throws StatsManager.StatsUnavailableException {
- Settings.Global.putInt(
- application.getContentResolver(),
- Settings.Global.ANOMALY_CONFIG_VERSION,
- ANOMALY_CONFIG_VERSION);
- Settings.Global.putString(
- application.getContentResolver(), Settings.Global.ANOMALY_CONFIG, ANOMALY_CONFIG);
-
- mJobService.checkAnomalyConfig(mStatsManager);
-
- verify(mStatsManager).removeConfig(StatsManagerConfig.ANOMALY_CONFIG_KEY);
- }
-
- @Test
- public void checkAnomalyConfig_newConfigExist_uploadNewConfig()
- throws StatsManager.StatsUnavailableException {
- Settings.Global.putInt(
- application.getContentResolver(),
- Settings.Global.ANOMALY_CONFIG_VERSION,
- ANOMALY_CONFIG_VERSION);
- Settings.Global.putString(
- application.getContentResolver(), Settings.Global.ANOMALY_CONFIG, ANOMALY_CONFIG);
-
- mJobService.checkAnomalyConfig(mStatsManager);
-
- verify(mStatsManager).addConfig(eq(StatsManagerConfig.ANOMALY_CONFIG_KEY), any());
- }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java
deleted file mode 100644
index 482f0d0..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AnomalyDetectionJobServiceTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2018 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.fuelgauge.batterytip;
-
-import static android.os.StatsDimensionsValue.FLOAT_VALUE_TYPE;
-import static android.os.StatsDimensionsValue.INT_VALUE_TYPE;
-import static android.os.StatsDimensionsValue.TUPLE_VALUE_TYPE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.JobSchedulerImpl;
-import android.app.StatsManager;
-import android.app.job.IJobScheduler;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobWorkItem;
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.StatsDimensionsValue;
-import android.os.UserManager;
-
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.testutils.shadow.ShadowConnectivityManager;
-import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.android.controller.ServiceController;
-import org.robolectric.annotation.Config;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowConnectivityManager.class})
-public class AnomalyDetectionJobServiceTest {
- private static final int UID = 12345;
- private static final String SYSTEM_PACKAGE = "com.android.system";
- private static final String SUBSCRIBER_COOKIES_AUTO_RESTRICTION =
- "anomaly_type=6,auto_restriction=true";
- private static final String SUBSCRIBER_COOKIES_NOT_AUTO_RESTRICTION =
- "anomaly_type=6,auto_restriction=false";
- private static final int ANOMALY_TYPE = 6;
- private static final long VERSION_CODE = 15;
- @Mock private UserManager mUserManager;
- @Mock private BatteryDatabaseManager mBatteryDatabaseManager;
- @Mock private BatteryUtils mBatteryUtils;
- @Mock private PowerAllowlistBackend mPowerAllowlistBackend;
- @Mock private StatsDimensionsValue mStatsDimensionsValue;
- @Mock private JobParameters mJobParameters;
- @Mock private JobWorkItem mJobWorkItem;
-
- private BatteryTipPolicy mPolicy;
- private Bundle mBundle;
- private AnomalyDetectionJobService mAnomalyDetectionJobService;
- private FakeFeatureFactory mFeatureFactory;
- private Context mContext;
- private JobScheduler mJobScheduler;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = spy(RuntimeEnvironment.application);
- mJobScheduler =
- spy(new JobSchedulerImpl(mContext, IJobScheduler.Stub.asInterface(new Binder())));
- when(mContext.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler);
-
- mPolicy = new BatteryTipPolicy(mContext);
- mBundle = new Bundle();
- mBundle.putParcelable(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, mStatsDimensionsValue);
- mFeatureFactory = FakeFeatureFactory.setupForTest();
- when(mBatteryUtils.getAppLongVersionCode(any())).thenReturn(VERSION_CODE);
-
- final ServiceController<AnomalyDetectionJobService> controller =
- Robolectric.buildService(AnomalyDetectionJobService.class);
- mAnomalyDetectionJobService = spy(controller.get());
- doNothing().when(mAnomalyDetectionJobService).jobFinished(any(), anyBoolean());
- }
-
- @Test
- public void scheduleCleanUp() {
- AnomalyDetectionJobService.scheduleAnomalyDetection(mContext, new Intent());
-
- JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
- List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs();
- assertThat(pendingJobs).hasSize(1);
-
- JobInfo pendingJob = pendingJobs.get(0);
- assertThat(pendingJob.getId()).isEqualTo(R.integer.job_anomaly_detection);
- assertThat(pendingJob.getMaxExecutionDelayMillis())
- .isEqualTo(Duration.ofDays(1).toMillis());
- }
-
- @Test
- public void saveAnomalyToDatabase_systemAllowlisted_doNotSave() {
- doReturn(UID).when(mAnomalyDetectionJobService).extractUidFromStatsDimensionsValue(any());
- doReturn(true)
- .when(mPowerAllowlistBackend)
- .isAllowlisted(any(String[].class), any(Integer.class));
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager, never())
- .insertAnomaly(anyInt(), anyString(), anyInt(), anyInt(), anyLong());
- }
-
- @Test
- public void saveAnomalyToDatabase_systemApp_doNotSaveButLog() {
- final ArrayList<String> cookies = new ArrayList<>();
- cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION);
- mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies);
- doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt());
- doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE);
- doReturn(Process.FIRST_APPLICATION_UID)
- .when(mAnomalyDetectionJobService)
- .extractUidFromStatsDimensionsValue(any());
- doReturn(true).when(mBatteryUtils).shouldHideAnomaly(any(), anyInt(), any());
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager, never())
- .insertAnomaly(anyInt(), anyString(), anyInt(), anyInt(), anyLong());
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(
- SettingsEnums.PAGE_UNKNOWN,
- MetricsProto.MetricsEvent.ACTION_ANOMALY_IGNORED,
- SettingsEnums.PAGE_UNKNOWN,
- SYSTEM_PACKAGE + "/" + VERSION_CODE,
- ANOMALY_TYPE);
- }
-
- @Test
- public void saveAnomalyToDatabase_systemUid_doNotSave() {
- doReturn(Process.SYSTEM_UID)
- .when(mAnomalyDetectionJobService)
- .extractUidFromStatsDimensionsValue(any());
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager, never())
- .insertAnomaly(anyInt(), anyString(), anyInt(), anyInt(), anyLong());
- }
-
- @Test
- public void saveAnomalyToDatabase_uidNull_doNotSave() {
- doReturn(AnomalyDetectionJobService.UID_NULL)
- .when(mAnomalyDetectionJobService)
- .extractUidFromStatsDimensionsValue(any());
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager, never())
- .insertAnomaly(anyInt(), anyString(), anyInt(), anyInt(), anyLong());
- }
-
- @Test
- public void saveAnomalyToDatabase_normalAppWithAutoRestriction_save() {
- final ArrayList<String> cookies = new ArrayList<>();
- cookies.add(SUBSCRIBER_COOKIES_AUTO_RESTRICTION);
- mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies);
- doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt());
- doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE);
- doReturn(Process.FIRST_APPLICATION_UID)
- .when(mAnomalyDetectionJobService)
- .extractUidFromStatsDimensionsValue(any());
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager)
- .insertAnomaly(
- anyInt(),
- anyString(),
- eq(6),
- eq(AnomalyDatabaseHelper.State.AUTO_HANDLED),
- anyLong());
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(
- SettingsEnums.PAGE_UNKNOWN,
- MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
- SettingsEnums.PAGE_UNKNOWN,
- SYSTEM_PACKAGE + "/" + VERSION_CODE,
- ANOMALY_TYPE);
- }
-
- @Test
- public void saveAnomalyToDatabase_normalAppWithoutAutoRestriction_save() {
- final ArrayList<String> cookies = new ArrayList<>();
- cookies.add(SUBSCRIBER_COOKIES_NOT_AUTO_RESTRICTION);
- mBundle.putStringArrayList(StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookies);
- doReturn(SYSTEM_PACKAGE).when(mBatteryUtils).getPackageName(anyInt());
- doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(SYSTEM_PACKAGE);
- doReturn(Process.FIRST_APPLICATION_UID)
- .when(mAnomalyDetectionJobService)
- .extractUidFromStatsDimensionsValue(any());
-
- mAnomalyDetectionJobService.saveAnomalyToDatabase(
- mContext,
- mUserManager,
- mBatteryDatabaseManager,
- mBatteryUtils,
- mPolicy,
- mPowerAllowlistBackend,
- mContext.getContentResolver(),
- mFeatureFactory.powerUsageFeatureProvider,
- mFeatureFactory.metricsFeatureProvider,
- mBundle);
-
- verify(mBatteryDatabaseManager)
- .insertAnomaly(
- anyInt(),
- anyString(),
- eq(6),
- eq(AnomalyDatabaseHelper.State.NEW),
- anyLong());
- verify(mFeatureFactory.metricsFeatureProvider)
- .action(
- SettingsEnums.PAGE_UNKNOWN,
- MetricsProto.MetricsEvent.ACTION_ANOMALY_TRIGGERED,
- SettingsEnums.PAGE_UNKNOWN,
- SYSTEM_PACKAGE + "/" + VERSION_CODE,
- ANOMALY_TYPE);
- }
-
- @Test
- public void extractUidFromStatsDimensionsValue_extractCorrectUid() {
- // Build an integer dimensions value.
- final StatsDimensionsValue intValue = mock(StatsDimensionsValue.class);
- when(intValue.isValueType(INT_VALUE_TYPE)).thenReturn(true);
- when(intValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED);
- when(intValue.getIntValue()).thenReturn(UID);
-
- // Build a tuple dimensions value and put the previous integer dimensions value inside.
- final StatsDimensionsValue tupleValue = mock(StatsDimensionsValue.class);
- when(tupleValue.isValueType(TUPLE_VALUE_TYPE)).thenReturn(true);
- final List<StatsDimensionsValue> statsDimensionsValues = new ArrayList<>();
- statsDimensionsValues.add(intValue);
- when(tupleValue.getTupleValueList()).thenReturn(statsDimensionsValues);
-
- assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue(tupleValue))
- .isEqualTo(UID);
- }
-
- @Test
- public void extractUidFromStatsDimensionsValue_wrongFormat_returnNull() {
- // Build a float dimensions value
- final StatsDimensionsValue floatValue = mock(StatsDimensionsValue.class);
- when(floatValue.isValueType(FLOAT_VALUE_TYPE)).thenReturn(true);
- when(floatValue.getField()).thenReturn(AnomalyDetectionJobService.STATSD_UID_FILED);
- when(floatValue.getFloatValue()).thenReturn(0f);
-
- assertThat(mAnomalyDetectionJobService.extractUidFromStatsDimensionsValue(floatValue))
- .isEqualTo(AnomalyDetectionJobService.UID_NULL);
- }
-
- @Test
- public void stopJobWhileDequeuingWork_shouldNotCrash() {
- when(mJobParameters.dequeueWork()).thenThrow(new SecurityException());
-
- mAnomalyDetectionJobService.onStopJob(mJobParameters);
-
- // Should not crash even job is stopped
- mAnomalyDetectionJobService.dequeueWork(mJobParameters);
- }
-
- @Test
- public void stopJobWhileCompletingWork_shouldNotCrash() {
- doThrow(new SecurityException()).when(mJobParameters).completeWork(any());
-
- mAnomalyDetectionJobService.onStopJob(mJobParameters);
-
- // Should not crash even job is stopped
- mAnomalyDetectionJobService.completeWork(mJobParameters, mJobWorkItem);
- }
-
- @Test
- public void restartWorkAfterBeenStopped_jobStarted() {
- mAnomalyDetectionJobService.onStopJob(mJobParameters);
- mAnomalyDetectionJobService.onStartJob(mJobParameters);
-
- assertThat(mAnomalyDetectionJobService.mIsJobCanceled).isFalse();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index e68b892..5ce449b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -361,6 +361,52 @@
}
@Test
+ public void convertToBatteryUsageDiff_returnsExpectedResult() {
+ final BatteryDiffEntry batteryDiffEntry =
+ new BatteryDiffEntry(
+ mContext,
+ /* uid= */ 101L,
+ /* userId= */ 1001L,
+ /* key= */ "key",
+ /* isHidden= */ false,
+ /* componentId= */ -1,
+ /* legacyPackageName= */ null,
+ /* legacyLabel= */ null,
+ /* consumerType= */ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
+ /* foregroundUsageTimeInMs= */ 1234L,
+ /* foregroundServiceUsageTimeInMs= */ 3456L,
+ /* backgroundUsageTimeInMs= */ 5678L,
+ /* screenOnTimeInMs= */ 123L,
+ /* consumePower= */ 1.1,
+ /* foregroundUsageConsumePower= */ 1.2,
+ /* foregroundServiceUsageConsumePower= */ 1.3,
+ /* backgroundUsageConsumePower= */ 1.4,
+ /* cachedUsageConsumePower= */ 1.5);
+
+ final BatteryUsageDiff batteryUsageDiff =
+ ConvertUtils.convertToBatteryUsageDiff(batteryDiffEntry);
+
+ assertThat(batteryUsageDiff.getUid()).isEqualTo(101L);
+ assertThat(batteryUsageDiff.getUserId()).isEqualTo(1001L);
+ assertThat(batteryUsageDiff.getIsHidden()).isFalse();
+ assertThat(batteryUsageDiff.getComponentId()).isEqualTo(-1);
+ assertThat(batteryUsageDiff.getConsumerType())
+ .isEqualTo(ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
+ assertThat(batteryUsageDiff.getConsumePower()).isEqualTo(1.1);
+ assertThat(batteryUsageDiff.getForegroundUsageConsumePower()).isEqualTo(1.2);
+ assertThat(batteryUsageDiff.getForegroundServiceUsageConsumePower()).isEqualTo(1.3);
+ assertThat(batteryUsageDiff.getBackgroundUsageConsumePower()).isEqualTo(1.4);
+ assertThat(batteryUsageDiff.getCachedUsageConsumePower()).isEqualTo(1.5);
+ assertThat(batteryUsageDiff.getForegroundUsageTime()).isEqualTo(1234L);
+ assertThat(batteryUsageDiff.getForegroundServiceUsageTime()).isEqualTo(3456L);
+ assertThat(batteryUsageDiff.getBackgroundUsageTime()).isEqualTo(5678L);
+ assertThat(batteryUsageDiff.getScreenOnTime()).isEqualTo(123L);
+ assertThat(batteryUsageDiff.getKey()).isEqualTo("key");
+ assertThat(batteryUsageDiff.hasPackageName()).isFalse();
+ assertThat(batteryUsageDiff.hasLabel()).isFalse();
+ }
+
+ @Test
public void convertToAppUsageEvent_returnsExpectedResult()
throws PackageManager.NameNotFoundException {
final Event event = new Event();
diff --git a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
index 0f647aa..a399e17 100644
--- a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
@@ -74,6 +74,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
@@ -709,6 +710,7 @@
verify(mUserManager).getAliveUsers();
}
+ @Ignore
@Test
public void updateUserList_userIconMissing_shouldLoadIcon() {
UserInfo currentUser = getAdminUser(true);
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigManagerExtTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigManagerExtTest.kt
new file mode 100644
index 0000000..5a82f99
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigManagerExtTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.network.telephony
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import androidx.core.os.persistableBundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class CarrierConfigManagerExtTest {
+
+ private val mockCarrierConfigManager = mock<CarrierConfigManager>()
+
+ private val context = mock<Context> {
+ on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
+ }
+
+ @Test
+ fun safeGetConfig_managerReturnKeyValue_returnNonEmptyBundle() {
+ mockCarrierConfigManager.stub {
+ on { getConfigForSubId(any(), eq(KEY)) } doReturn persistableBundleOf(KEY to VALUE)
+ }
+ val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+ val bundle = carrierConfigManager.safeGetConfig(listOf(KEY))
+
+ assertThat(bundle.getString(KEY)).isEqualTo(VALUE)
+ }
+
+ @Test
+ fun safeGetConfig_managerThrowIllegalStateException_returnEmptyBundle() {
+ mockCarrierConfigManager.stub {
+ on { getConfigForSubId(any(), eq(KEY)) } doThrow IllegalStateException()
+ }
+ val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+ val bundle = carrierConfigManager.safeGetConfig(listOf(KEY))
+
+ assertThat(bundle.containsKey(KEY)).isFalse()
+ }
+
+ private companion object {
+ const val KEY = "key"
+ const val VALUE = "value"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
index f202668..2f52031 100644
--- a/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/system/ClientInitiatedActionRepositoryTest.kt
@@ -25,7 +25,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
-import org.mockito.kotlin.anyVararg
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
@@ -47,7 +46,7 @@
@Test
fun onSystemUpdate_notEnabled() {
mockCarrierConfigManager.stub {
- on { getConfig(anyVararg()) } doReturn persistableBundleOf()
+ on { getConfigForSubId(any(), any()) } doReturn persistableBundleOf()
}
repository.onSystemUpdate()
@@ -58,7 +57,7 @@
@Test
fun onSystemUpdate_enabled() {
mockCarrierConfigManager.stub {
- on { getConfig(anyVararg()) } doReturn persistableBundleOf(
+ on { getConfigForSubId(any(), any()) } doReturn persistableBundleOf(
CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL to true,
CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING to ACTION,
)