Merge "[PK Setting] add keyboard review under keyboard selection page" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2b49148..19927a2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -138,6 +138,7 @@
<uses-permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI" />
<uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
<uses-permission android:name="android.permission.ACCESS_GPU_SERVICE" />
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".SettingsApplication"
@@ -491,6 +492,17 @@
android:value="@string/menu_key_display"/>
</activity>
+ <activity android:name=".Settings$ScreenTimeoutActivity"
+ android:label="@string/screen_timeout"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.settings.SCREEN_TIMEOUT_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.display.ScreenTimeoutSettings"/>
+ </activity>
+
<activity
android:name="Settings$ConfigureWifiSettingsActivity"
android:label="@string/wifi_configure_settings_preference_title"
diff --git a/aconfig/settings_telephony_flag_declarations.aconfig b/aconfig/settings_telephony_flag_declarations.aconfig
index 68e313b..0999a7b 100644
--- a/aconfig/settings_telephony_flag_declarations.aconfig
+++ b/aconfig/settings_telephony_flag_declarations.aconfig
@@ -6,3 +6,10 @@
description: "Stop honoring CarrierConfigManager.KEY_HIDE_ENABLE_2G. Allow 2G toggle cannot be hidden from users by carriers."
bug: "300248708"
}
+
+flag {
+ name: "is_dual_sim_onboarding_enabled"
+ namespace: "settings_experience"
+ description: "Control the Dual SIM onobarding feature"
+ bug: "298898436"
+}
diff --git a/res/drawable/ic_calls_sms.xml b/res/drawable/ic_calls_sms.xml
new file mode 100644
index 0000000..2033e8f
--- /dev/null
+++ b/res/drawable/ic_calls_sms.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2020 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal"
+ >
+
+ <path
+ android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20.17,14.85l-3.26-0.65c-0.33-0.07-0.67,0.04-0.9,0.27l-2.62,2.62c-2.75-1.49-5.01-3.75-6.5-6.5l2.62-2.62 c0.24-0.24,0.34-0.58,0.27-0.9L9.13,3.82c-0.09-0.47-0.5-0.8-0.98-0.8H4c-0.56,0-1.03,0.47-1,1.03c0.17,2.91,1.04,5.63,2.43,8.01 c1.57,2.69,3.81,4.93,6.5,6.5c2.38,1.39,5.1,2.26,8.01,2.43c0.56,0.03,1.03-0.44,1.03-1v-4.15C20.97,15.36,20.64,14.95,20.17,14.85 L20.17,14.85z M12,3v10l3-3h6V3H12z M19,8h-5V5h5V8z" />
+</vector>
diff --git a/res/layout/audio_sharing_device_item.xml b/res/layout/audio_sharing_device_item.xml
index f8e7454..04ecdd7 100644
--- a/res/layout/audio_sharing_device_item.xml
+++ b/res/layout/audio_sharing_device_item.xml
@@ -17,16 +17,17 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
<Button
android:id="@+id/device_button"
- android:overScrollMode="never"
+ style="@style/SettingsLibActionButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAlignment="center"
- android:text=""/>
+ android:layout_marginTop="4dp"
+ android:background="@drawable/audio_sharing_rounded_bg_ripple"
+ android:textAlignment="center" />
</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_audio_sharing.xml b/res/layout/dialog_audio_sharing.xml
index 3ea2c01..aace5ab 100644
--- a/res/layout/dialog_audio_sharing.xml
+++ b/res/layout/dialog_audio_sharing.xml
@@ -50,7 +50,7 @@
android:src="@drawable/audio_sharing_guidance"
android:visibility="gone" />
- <com.android.internal.widget.RecyclerView
+ <androidx.recyclerview.widget.RecyclerView
android:id="@+id/btn_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/res/layout/dialog_audio_sharing_disconnect.xml b/res/layout/dialog_audio_sharing_disconnect.xml
index 54dee40..592b41b 100644
--- a/res/layout/dialog_audio_sharing_disconnect.xml
+++ b/res/layout/dialog_audio_sharing_disconnect.xml
@@ -31,11 +31,13 @@
android:layout_gravity="center"
android:paddingBottom="24dp" />
- <com.android.internal.widget.RecyclerView
+ <androidx.recyclerview.widget.RecyclerView
android:id="@+id/device_btn_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center" />
+ android:layout_gravity="center"
+ android:nestedScrollingEnabled="false"
+ android:overScrollMode="never" />
<Button
android:id="@+id/cancel_btn"
diff --git a/res/values/config.xml b/res/values/config.xml
index c30a047..d084ff9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -258,9 +258,6 @@
com.android.settings.intelligence
</string>
- <!-- Whether the confirmation for sim deletion is defaulted to be on or off-->
- <bool name="config_sim_deletion_confirmation_default_on">false</bool>
-
<!-- Package Installer package name -->
<string name="config_package_installer_package_name" translatable="false">
com.android.packageinstaller
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 33cae6d..01f2525 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3898,6 +3898,8 @@
<string name="force_stop">Force stop</string>
<!-- Manage applications, text label for button to archive an application. Archiving means uninstalling the app without deleting user's personal data and replacing the app with a stub app with minimum size. So, the user can unarchive the app later and not lose any personal data. -->
<string name="archive">Archive</string>
+ <!-- Manage applications, text label for button to restore an application. Restoring means installing the archived app. -->
+ <string name="restore">Restore</string>
<!-- Manage applications, individual application info screen,label under Storage heading. The total storage space taken up by this app. -->
<string name="total_size_label">Total</string>
<!-- Manage applications, individual application info screen, label under Storage heading. The amount of space taken up by the application itself (for example, the java compield files and things like that) -->
@@ -4012,6 +4014,12 @@
<string name="archiving_failed">Archiving failed</string>
<!-- Toast message when archiving an app succeeded. -->
<string name="archiving_succeeded">Archived <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+ <!-- Toast message when restoring an app failed. -->
+ <string name="restoring_failed">Restoring failed</string>
+ <!-- Toast message when restoring an app succeeded. -->
+ <string name="restoring_succeeded">Restored <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+ <!-- Toast message when restoring an app has started. -->
+ <string name="restoring_in_progress">Restoring <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
<!-- Text of pop up message if the request for a "migrate primary storage" operation
(see storage_menu_migrate) is denied as another is already in progress. [CHAR LIMIT=75] -->
@@ -9107,6 +9115,12 @@
<!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for months [CHAR LIMIT=NONE]-->
<string name="unused_apps_switch_summary">Remove permissions, delete temporary files, and stop notifications</string>
+ <!-- Label of a switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=40]-->
+ <string name="unused_apps_switch_v2">Manage app if unused</string>
+
+ <!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=NONE]-->
+ <string name="unused_apps_switch_summary_v2">Remove permissions, delete temporary files, stop notifications, and archive the app</string>
+
<!-- Label for showing all apps in list [CHAR LIMIT=30] -->
<string name="filter_all_apps">All apps</string>
<!-- Label for showing enabled apps in list [CHAR LIMIT=30] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2c928ff..8df990b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -438,11 +438,10 @@
</style>
<style name="DeviceAudioSharingText">
- <item name="android:textAlignment">viewStart</item>
+ <item name="android:textAlignment">center</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:paddingBottom">24dp</item>
</style>
<style name="ContextualCardStyle">
diff --git a/res/xml/audio_stream_details_fragment.xml b/res/xml/audio_stream_details_fragment.xml
new file mode 100644
index 0000000..2a84939
--- /dev/null
+++ b/res/xml/audio_stream_details_fragment.xml
@@ -0,0 +1,36 @@
+<?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="Audio stream details">
+
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="audio_stream_header"
+ android:layout="@layout/settings_entity_header"
+ android:selectable="false"
+ settings:allowDividerBelow="true"
+ settings:searchable="false"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController" />
+
+ <com.android.settingslib.widget.ActionButtonsPreference
+ android:key="audio_stream_button"
+ settings:allowDividerBelow="true"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamButtonController" />
+
+</PreferenceScreen>
diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml
index 822b03d..5cd60f0 100644
--- a/res/xml/more_security_privacy_settings.xml
+++ b/res/xml/more_security_privacy_settings.xml
@@ -204,14 +204,6 @@
settings:keywords="@string/keywords_app_pinning"
settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
- <SwitchPreferenceCompat
- android:order="290"
- android:key="confirm_sim_deletion"
- android:title="@string/confirm_sim_deletion_title"
- android:summary="@string/confirm_sim_deletion_description"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
<Preference
android:order="300"
android:id="@+id/memtag_page"
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index cd44ab1..1a8ee08 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -31,10 +31,16 @@
settings:keywords="@string/keywords_internet"
settings:useAdminDisabledSummary="true" />
- <com.android.settings.spa.preference.ComposePreference
+ <com.android.settingslib.RestrictedPreference
android:key="calls_and_sms"
android:title="@string/calls_and_sms"
+ android:icon="@drawable/ic_calls_sms"
android:order="-20"
+ android:summary="@string/summary_placeholder"
+ android:fragment="com.android.settings.network.NetworkProviderCallsSmsFragment"
+ settings:userRestriction="no_config_mobile_networks"
+ settings:allowDividerBelow="true"
+ settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.network.NetworkProviderCallsSmsController" />
<com.android.settingslib.RestrictedPreference
diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml
index 89834b4..c220e99 100644
--- a/res/xml/security_advanced_settings.xml
+++ b/res/xml/security_advanced_settings.xml
@@ -106,14 +106,6 @@
settings:keywords="@string/keywords_app_pinning"
settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
- <SwitchPreferenceCompat
- android:order="290"
- android:key="confirm_sim_deletion"
- android:title="@string/confirm_sim_deletion_title"
- android:summary="@string/confirm_sim_deletion_description"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
<com.android.settingslib.RestrictedPreference
android:order="300"
android:id="@+id/memtag_page"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 68ae0ae..86baba4 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -478,4 +478,6 @@
public static class OneHandedSettingsActivity extends SettingsActivity { /* empty */ }
public static class PreviouslyConnectedDeviceActivity extends SettingsActivity { /* empty */ }
+
+ public static class ScreenTimeoutActivity extends SettingsActivity { /* empty */ }
}
diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
index dfa2f33..6f21fb8 100644
--- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
+++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
@@ -234,6 +234,32 @@
// permittedServices null means all accessibility services are allowed.
boolean serviceAllowed = permittedServices == null || permittedServices.contains(
preference.getPackageName());
+
+ if (android.security.Flags.extendEcmToAllSettings()) {
+ preference.checkEcmRestrictionAndSetDisabled(
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
+ preference.getPackageName(), preference.getUid());
+ if (preference.isDisabledByEcm()) {
+ serviceAllowed = false;
+ }
+
+ if (serviceAllowed || serviceEnabled) {
+ preference.setEnabled(true);
+ } else {
+ // Disable accessibility service that are not permitted.
+ final RestrictedLockUtils.EnforcedAdmin admin =
+ RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
+ mContext, preference.getPackageName(), UserHandle.myUserId());
+
+ if (admin != null) {
+ preference.setDisabledByAdmin(admin);
+ } else if (!preference.isDisabledByEcm()) {
+ preference.setEnabled(false);
+ }
+ }
+ return;
+ }
+
boolean appOpsAllowed;
if (serviceAllowed) {
try {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
index e3816bf..fb78e3e 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
@@ -41,6 +41,8 @@
private PreferenceFragmentCompat mParent;
private NotificationManager mNm;
private PackageManager mPm;
+ // The appOp representing this preference
+ private String mAppOpStr;
public ApprovalPreferenceController(Context context, String key) {
super(context, key);
@@ -71,6 +73,14 @@
return this;
}
+ /**
+ * Set the associated appOp for the Setting
+ */
+ public ApprovalPreferenceController setAppOpStr(String appOpStr) {
+ mAppOpStr = appOpStr;
+ return this;
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
@@ -107,8 +117,20 @@
return false;
}
});
- preference.updateState(
- mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+
+ if (android.security.Flags.extendEcmToAllSettings()) {
+ if (!isAllowedCn && !isEnabled) {
+ preference.setEnabled(false);
+ } else if (isEnabled) {
+ preference.setEnabled(true);
+ } else {
+ preference.checkEcmRestrictionAndSetDisabled(mAppOpStr,
+ mCn.getPackageName(), mPkgInfo.applicationInfo.uid);
+ }
+ } else {
+ preference.updateState(
+ mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+ }
}
public void disable(final ComponentName cn) {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index 17dabe4..89767dd 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -21,6 +21,7 @@
import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;
import android.Manifest;
+import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.companion.ICompanionDeviceManager;
@@ -102,6 +103,7 @@
.setCn(mComponentName)
.setNm(context.getSystemService(NotificationManager.class))
.setPm(mPm)
+ .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
.setParent(this);
use(HeaderPreferenceController.class)
.setFragment(this)
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index b71330a..059173c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -290,6 +290,17 @@
}
@Override
+ protected Intent getFingerprintEnrollingIntent() {
+ final Intent ret = super.getFingerprintEnrollingIntent();
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ ret.putExtras(mCalibrator.getExtrasForNextIntent(true));
+ }
+ }
+ return ret;
+ }
+
+ @Override
public void onBackPressed() {
stopLookingForFingerprint();
super.onBackPressed();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index bd52b64..aef3c06 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -385,7 +385,7 @@
}
if (Flags.udfpsEnrollCalibration()) {
if (mCalibrator != null) {
- intent.putExtras(mCalibrator.getExtrasForNextIntent());
+ intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
}
}
return intent;
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
index 9809bcc..c54c6b5 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -6,7 +6,7 @@
interface UdfpsEnrollCalibrator {
- val extrasForNextIntent: Bundle
+ fun getExtrasForNextIntent(isEnrolling: Boolean): Bundle
fun onSaveInstanceState(outState: Bundle)
diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
index c095fee..fc3493c 100644
--- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
@@ -17,11 +17,16 @@
import static com.android.settingslib.Utils.isAudioModeOngoingCall;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
@@ -33,36 +38,94 @@
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
/**
- * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all
- * available media devices. It uses {@link DevicePreferenceCallback}
- * to add/remove {@link Preference}
+ * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
+ * devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
*/
public class AvailableMediaDeviceGroupController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback, BluetoothCallback {
+ private static final boolean DEBUG = BluetoothUtils.D;
private static final String TAG = "AvailableMediaDeviceGroupController";
private static final String KEY = "available_device_list";
- @VisibleForTesting
- PreferenceGroup mPreferenceGroup;
- @VisibleForTesting
- LocalBluetoothManager mLocalBluetoothManager;
+ @VisibleForTesting PreferenceGroup mPreferenceGroup;
+ @VisibleForTesting LocalBluetoothManager mLocalBluetoothManager;
+ private final Executor mExecutor;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private FragmentManager mFragmentManager;
+ private BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSearchStarted(int reason) {}
+
+ @Override
+ public void onSearchStartFailed(int reason) {}
+
+ @Override
+ public void onSearchStopped(int reason) {}
+
+ @Override
+ public void onSearchStopFailed(int reason) {}
+
+ @Override
+ public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+ @Override
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+ mBluetoothDeviceUpdater.forceUpdate();
+ }
+
+ @Override
+ public void onSourceAddFailed(
+ @NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source,
+ int reason) {}
+
+ @Override
+ public void onSourceModified(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceModifyFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoved(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {
+ mBluetoothDeviceUpdater.forceUpdate();
+ }
+
+ @Override
+ public void onSourceRemoveFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink,
+ int sourceId,
+ BluetoothLeBroadcastReceiveState state) {}
+ };
public AvailableMediaDeviceGroupController(Context context) {
super(context, KEY);
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ mExecutor = Executors.newSingleThreadExecutor();
}
@Override
@@ -71,6 +134,18 @@
Log.e(TAG, "onStart() Bluetooth is not supported on this device");
return;
}
+ if (AudioSharingUtils.isFeatureEnabled()) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager
+ .getProfileManager()
+ .getLeAudioBroadcastAssistantProfile();
+ if (assistant != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onStart() Register callbacks for assistant.");
+ }
+ assistant.registerServiceCallBack(mExecutor, mAssistantCallback);
+ }
+ }
mBluetoothDeviceUpdater.registerCallback();
mLocalBluetoothManager.getEventManager().registerCallback(this);
mBluetoothDeviceUpdater.refreshPreference();
@@ -82,6 +157,18 @@
Log.e(TAG, "onStop() Bluetooth is not supported on this device");
return;
}
+ if (AudioSharingUtils.isFeatureEnabled()) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager
+ .getProfileManager()
+ .getLeAudioBroadcastAssistantProfile();
+ if (assistant != null) {
+ if (DEBUG) {
+ Log.d(TAG, "onStop() Register callbacks for assistant.");
+ }
+ assistant.unregisterServiceCallBack(mAssistantCallback);
+ }
+ }
mBluetoothDeviceUpdater.unregisterCallback();
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
@@ -130,8 +217,11 @@
public void init(DashboardFragment fragment) {
mFragmentManager = fragment.getParentFragmentManager();
- mBluetoothDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(fragment.getContext(),
- AvailableMediaDeviceGroupController.this, fragment.getMetricsCategory());
+ mBluetoothDeviceUpdater =
+ new AvailableMediaBluetoothDeviceUpdater(
+ fragment.getContext(),
+ AvailableMediaDeviceGroupController.this,
+ fragment.getMetricsCategory());
}
@VisibleForTesting
@@ -157,20 +247,20 @@
}
if (bluetoothProfile == BluetoothProfile.HEARING_AID) {
- HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice,
- getMetricsCategory());
+ HearingAidUtils.launchHearingAidPairingDialog(
+ mFragmentManager, activeDevice, getMetricsCategory());
}
}
private void updateTitle() {
if (isAudioModeOngoingCall(mContext)) {
// in phone call
- mPreferenceGroup.
- setTitle(mContext.getString(R.string.connected_device_call_device_title));
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_call_device_title));
} else {
// without phone call
- mPreferenceGroup.
- setTitle(mContext.getString(R.string.connected_device_media_device_title));
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_media_device_title));
}
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java
index bc8ff21..a5f5adb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java
@@ -22,7 +22,8 @@
import android.view.ViewGroup;
import android.widget.Button;
-import com.android.internal.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.settings.R;
import java.util.ArrayList;
@@ -32,11 +33,13 @@
private static final String TAG = "AudioSharingDeviceAdapter";
private final ArrayList<AudioSharingDeviceItem> mDevices;
private final OnClickListener mOnClickListener;
+ private final String mPrefix;
public AudioSharingDeviceAdapter(
- ArrayList<AudioSharingDeviceItem> devices, OnClickListener listener) {
+ ArrayList<AudioSharingDeviceItem> devices, OnClickListener listener, String prefix) {
mDevices = devices;
mOnClickListener = listener;
+ mPrefix = prefix;
}
private class AudioSharingDeviceViewHolder extends RecyclerView.ViewHolder {
@@ -49,7 +52,7 @@
public void bindView(int position) {
if (mButtonView != null) {
- mButtonView.setText(mDevices.get(position).getName());
+ mButtonView.setText(mPrefix + mDevices.get(position).getName());
mButtonView.setOnClickListener(
v -> mOnClickListener.onClick(mDevices.get(position)));
} else {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index 9329cc29..70859c2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -47,6 +47,9 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
@@ -260,6 +263,9 @@
return;
}
mLocalBtManager.getEventManager().registerCallback(this);
+ if (DEBUG) {
+ Log.d(TAG, "onStart() Register callbacks for broadcast and assistant.");
+ }
mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mBluetoothDeviceUpdater.registerCallback();
@@ -281,15 +287,11 @@
return;
}
mLocalBtManager.getEventManager().unregisterCallback(this);
- // TODO: verify the reason for failing to unregister
- try {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- } catch (IllegalArgumentException e) {
- Log.e(
- TAG,
- "Fail to unregister broadcast or assistant callback due to " + e.getMessage());
+ if (DEBUG) {
+ Log.d(TAG, "onStop() Unregister callbacks for broadcast and assistant.");
}
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+ mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
mBluetoothDeviceUpdater.unregisterCallback();
}
@@ -358,6 +360,28 @@
"Ignore onProfileConnectionStateChanged, no broadcast or assistant supported");
return;
}
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> handleOnProfileStateChanged(cachedDevice, bluetoothProfile));
+ }
+
+ /**
+ * Initialize the controller.
+ *
+ * @param fragment The fragment to provide the context and metrics category for {@link
+ * AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
+ */
+ public void init(DashboardFragment fragment) {
+ mFragment = fragment;
+ mBluetoothDeviceUpdater =
+ new AudioSharingBluetoothDeviceUpdater(
+ fragment.getContext(),
+ AudioSharingDevicePreferenceController.this,
+ fragment.getMetricsCategory());
+ }
+
+ private void handleOnProfileStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
boolean isLeAudioSupported = isLeAudioSupported(cachedDevice);
// For eligible (LE audio) remote device, we only check its connected LE audio profile.
if (isLeAudioSupported && bluetoothProfile != BluetoothProfile.LE_AUDIO) {
@@ -384,120 +408,143 @@
}
if (!isLeAudioSupported) {
// Handle connected ineligible (non LE audio) remote device
- if (isBroadcasting()) {
- // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
- // connected during a sharing session.
- closeOpeningDialogs();
- AudioSharingStopDialogFragment.show(
- mFragment,
- cachedDevice.getName(),
- () -> mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()));
- } else {
- // Do nothing for ineligible (non LE audio) remote device when no sharing session.
- if (DEBUG) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged for non LE audio without"
- + " sharing session");
- }
- }
+ handleOnProfileStateChangedForNonLeAudioDevice(cachedDevice);
} else {
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
- AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
// Handle connected eligible (LE audio) remote device
- if (isBroadcasting()) {
- // Show audio sharing switch or join dialog according to device count in the sharing
- // session.
- ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
- AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
- mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
- // Show audio sharing switch dialog when the third eligible (LE audio) remote device
- // connected during a sharing session.
- if (deviceItemsInSharingSession.size() >= 2) {
- closeOpeningDialogs();
- AudioSharingDisconnectDialogFragment.show(
- mFragment,
- deviceItemsInSharingSession,
- cachedDevice.getName(),
- (AudioSharingDeviceItem item) -> {
- // Remove all sources from the device user clicked
- for (CachedBluetoothDevice device :
- groupedDevices.get(item.getGroupId())) {
- for (BluetoothLeBroadcastReceiveState source :
- mAssistant.getAllSources(device.getDevice())) {
- mAssistant.removeSource(
- device.getDevice(), source.getSourceId());
- }
- }
- // Add current broadcast to the latest connected device
- mAssistant.addSource(
- cachedDevice.getDevice(),
- mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
- /* isGroupOp= */ true);
- });
- } else {
- // Show audio sharing join dialog when the first or second eligible (LE audio)
- // remote device connected during a sharing session.
- closeOpeningDialogs();
- AudioSharingJoinDialogFragment.show(
- mFragment,
- deviceItemsInSharingSession,
- cachedDevice.getName(),
- () -> {
- // Add current broadcast to the latest connected device
- mAssistant.addSource(
- cachedDevice.getDevice(),
- mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
- /* isGroupOp= */ true);
- });
- }
- } else {
- ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
- // Use random device in the group within the sharing session to
- // represent the group.
- CachedBluetoothDevice device = devices.get(0);
- if (device.getGroupId() == cachedDevice.getGroupId()) {
- continue;
- }
- deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
- }
- // Show audio sharing join dialog when the second eligible (LE audio) remote device
- // connect and no sharing session.
- if (deviceItems.size() == 1) {
- closeOpeningDialogs();
- AudioSharingJoinDialogFragment.show(
- mFragment,
- deviceItems,
- cachedDevice.getName(),
- () -> {
- mTargetSinks = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices :
- groupedDevices.values()) {
- for (CachedBluetoothDevice device : devices) {
- mTargetSinks.add(device.getDevice());
- }
- }
- mBroadcast.startBroadcast("test", null);
- });
- }
+ handleOnProfileStateChangedForLeAudioDevice(cachedDevice);
+ }
+ }
+
+ private void handleOnProfileStateChangedForNonLeAudioDevice(
+ @NonNull CachedBluetoothDevice cachedDevice) {
+ if (isBroadcasting()) {
+ // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
+ // connected during a sharing session.
+ ThreadUtils.postOnMainThread(
+ () -> {
+ closeOpeningDialogs();
+ AudioSharingStopDialogFragment.show(
+ mFragment,
+ cachedDevice.getName(),
+ () -> mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()));
+ });
+ } else {
+ // Do nothing for ineligible (non LE audio) remote device when no sharing session.
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Ignore onProfileConnectionStateChanged for non LE audio without"
+ + " sharing session");
}
}
}
- /**
- * Initialize the controller.
- *
- * @param fragment The fragment to provide the context and metrics category for {@link
- * AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
- */
- public void init(DashboardFragment fragment) {
- mFragment = fragment;
- mBluetoothDeviceUpdater =
- new AudioSharingBluetoothDeviceUpdater(
- fragment.getContext(),
- AudioSharingDevicePreferenceController.this,
- fragment.getMetricsCategory());
+ private void handleOnProfileStateChangedForLeAudioDevice(
+ @NonNull CachedBluetoothDevice cachedDevice) {
+ Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
+ AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
+ if (isBroadcasting()) {
+ if (groupedDevices.containsKey(cachedDevice.getGroupId())
+ && groupedDevices.get(cachedDevice.getGroupId()).stream()
+ .anyMatch(
+ device ->
+ AudioSharingUtils.hasBroadcastSource(
+ device, mLocalBtManager))) {
+ Log.d(
+ TAG,
+ "Automatically add another device within the same group to the sharing: "
+ + cachedDevice.getDevice().getAnonymizedAddress());
+ addSourceToTargetDevices(ImmutableList.of(cachedDevice.getDevice()));
+ return;
+ }
+ // Show audio sharing switch or join dialog according to device count in the sharing
+ // session.
+ ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
+ AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
+ mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
+ // Show audio sharing switch dialog when the third eligible (LE audio) remote device
+ // connected during a sharing session.
+ if (deviceItemsInSharingSession.size() >= 2) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ closeOpeningDialogs();
+ AudioSharingDisconnectDialogFragment.show(
+ mFragment,
+ deviceItemsInSharingSession,
+ cachedDevice.getName(),
+ (AudioSharingDeviceItem item) -> {
+ // Remove all sources from the device user clicked
+ if (groupedDevices.containsKey(item.getGroupId())) {
+ for (CachedBluetoothDevice device :
+ groupedDevices.get(item.getGroupId())) {
+ for (BluetoothLeBroadcastReceiveState source :
+ mAssistant.getAllSources(
+ device.getDevice())) {
+ mAssistant.removeSource(
+ device.getDevice(),
+ source.getSourceId());
+ }
+ }
+ }
+ // Add current broadcast to the latest connected device
+ mAssistant.addSource(
+ cachedDevice.getDevice(),
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+ /* isGroupOp= */ true);
+ });
+ });
+ } else {
+ // Show audio sharing join dialog when the first or second eligible (LE audio)
+ // remote device connected during a sharing session.
+ ThreadUtils.postOnMainThread(
+ () -> {
+ closeOpeningDialogs();
+ AudioSharingJoinDialogFragment.show(
+ mFragment,
+ deviceItemsInSharingSession,
+ cachedDevice.getName(),
+ () -> {
+ // Add current broadcast to the latest connected device
+ mAssistant.addSource(
+ cachedDevice.getDevice(),
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+ /* isGroupOp= */ true);
+ });
+ });
+ }
+ } else {
+ ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
+ // Use random device in the group within the sharing session to represent the group.
+ CachedBluetoothDevice device = devices.get(0);
+ if (device.getGroupId() == cachedDevice.getGroupId()) {
+ continue;
+ }
+ deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
+ }
+ // Show audio sharing join dialog when the second eligible (LE audio) remote
+ // device connect and no sharing session.
+ if (deviceItems.size() == 1) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ closeOpeningDialogs();
+ AudioSharingJoinDialogFragment.show(
+ mFragment,
+ deviceItems,
+ cachedDevice.getName(),
+ () -> {
+ mTargetSinks = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices :
+ groupedDevices.values()) {
+ for (CachedBluetoothDevice device : devices) {
+ mTargetSinks.add(device.getDevice());
+ }
+ }
+ mBroadcast.startBroadcast("test", null);
+ });
+ });
+ }
+ }
}
private boolean isLeAudioSupported(CachedBluetoothDevice cachedDevice) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
index e60eabd..5c0a90a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
@@ -51,9 +51,10 @@
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
boolean isFilterMatched = false;
if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
- // If device is LE audio device and has a broadcast source,
- // it would show in audio sharing devices group.
+ // If device is LE audio device and in a sharing session on current sharing device,
+ // it would show in volume control group.
if (cachedDevice.isConnectedLeAudioDevice()
+ && AudioSharingUtils.isBroadcasting(mLocalBtManager)
&& AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBtManager)) {
isFilterMatched = true;
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index e44939d..32cd2f8 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -28,9 +28,9 @@
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
@@ -139,7 +139,8 @@
(AudioSharingDeviceItem item) -> {
sListener.onItemClick(item);
dismiss();
- }));
+ },
+ "Connect "));
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setVisibility(View.VISIBLE);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
index 7eedb9a..a2b1824 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
@@ -28,9 +28,9 @@
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
@@ -110,7 +110,8 @@
(AudioSharingDeviceItem item) -> {
sListener.onItemClick(item);
dismiss();
- }));
+ },
+ "Disconnect "));
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
Button cancelBtn = rootView.findViewById(R.id.cancel_btn);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index b5361f2..3d4ef82 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -41,6 +41,8 @@
import java.util.Optional;
import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
public class AudioSharingUtils {
private static final String TAG = "AudioSharingUtils";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -237,7 +239,7 @@
* @return An Optional containing the active LE Audio device, or an empty Optional if not found.
*/
public static Optional<CachedBluetoothDevice> getActiveSinkOnAssistant(
- LocalBluetoothManager manager) {
+ @Nullable LocalBluetoothManager manager) {
if (manager == null) {
Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
return Optional.empty();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
new file mode 100644
index 0000000..bb729d6
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -0,0 +1,66 @@
+/*
+ * 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.connecteddevice.audiosharing.audiostreams;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+public class AudioStreamButtonController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String KEY = "audio_stream_button";
+ private @Nullable ActionButtonsPreference mPreference;
+ private int mBroadcastId = -1;
+
+ public AudioStreamButtonController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public final void displayPreference(PreferenceScreen screen) {
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ // TODO(chelseahao): update this based on stream connection state
+ mPreference
+ .setButton1Text(R.string.bluetooth_device_context_disconnect)
+ .setButton1Icon(R.drawable.ic_settings_close);
+ }
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /** Initialize with broadcast id */
+ void init(int broadcastId) {
+ mBroadcastId = broadcastId;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
new file mode 100644
index 0000000..e1dc228
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
@@ -0,0 +1,59 @@
+/*
+ * 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.connecteddevice.audiosharing.audiostreams;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+
+public class AudioStreamDetailsFragment extends DashboardFragment {
+ static final String BROADCAST_NAME_ARG = "broadcast_name";
+ static final String BROADCAST_ID_ARG = "broadcast_id";
+ private static final String TAG = "AudioStreamDetailsFragment";
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ Bundle arguments = getArguments();
+ if (arguments != null) {
+ use(AudioStreamHeaderController.class)
+ .init(
+ this,
+ arguments.getString(BROADCAST_NAME_ARG),
+ arguments.getInt(BROADCAST_ID_ARG));
+ use(AudioStreamButtonController.class).init(arguments.getInt(BROADCAST_ID_ARG));
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(chelseahao): update metrics id
+ return 0;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.audio_stream_details_fragment;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
new file mode 100644
index 0000000..89f24bc
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.connecteddevice.audiosharing.audiostreams;
+
+import android.content.Context;
+
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import javax.annotation.Nullable;
+
+public class AudioStreamHeaderController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String KEY = "audio_stream_header";
+ private @Nullable EntityHeaderController mHeaderController;
+ private @Nullable DashboardFragment mFragment;
+ private String mBroadcastName = "";
+ private int mBroadcastId = -1;
+
+ public AudioStreamHeaderController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public final void displayPreference(PreferenceScreen screen) {
+ LayoutPreference headerPreference = screen.findPreference(KEY);
+ if (headerPreference != null && mFragment != null) {
+ mHeaderController =
+ EntityHeaderController.newInstance(
+ mFragment.getActivity(),
+ mFragment,
+ headerPreference.findViewById(R.id.entity_header));
+ if (mBroadcastName != null) {
+ mHeaderController.setLabel(mBroadcastName);
+ }
+ mHeaderController.setIcon(
+ screen.getContext().getDrawable(R.drawable.ic_bt_audio_sharing));
+ // TODO(chelseahao): update this based on stream connection state
+ mHeaderController.setSummary("Listening now");
+ mHeaderController.done(true);
+ screen.addPreference(headerPreference);
+ }
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /** Initialize with {@link AudioStreamDetailsFragment} and broadcast name and id */
+ void init(
+ AudioStreamDetailsFragment audioStreamDetailsFragment,
+ String broadcastName,
+ int broadcastId) {
+ mFragment = audioStreamDetailsFragment;
+ mBroadcastName = broadcastName;
+ mBroadcastId = broadcastId;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 5acbc1f..198e8e5 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -80,8 +80,8 @@
});
}
- /** Removes all sources from LE broadcasts associated for all active sinks. */
- void removeSource() {
+ /** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
+ void removeSource(int broadcastId) {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
return;
@@ -93,14 +93,17 @@
if (DEBUG) {
Log.d(
TAG,
- "removeSource(): remove all sources from sink : "
+ "removeSource(): remove all sources with broadcast id :"
+ + broadcastId
+ + " from sink : "
+ sink.getAddress());
}
- var sources = mLeBroadcastAssistant.getAllSources(sink);
- if (!sources.isEmpty()) {
- mLeBroadcastAssistant.removeSource(
- sink, sources.get(0).getSourceId());
- }
+ mLeBroadcastAssistant.getAllSources(sink).stream()
+ .filter(state -> state.getBroadcastId() == broadcastId)
+ .forEach(
+ state ->
+ mLeBroadcastAssistant.removeSource(
+ sink, state.getSourceId()));
}
});
}
@@ -121,6 +124,12 @@
return mLeBroadcastAssistant;
}
+ static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+ return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
+ && state.getBigEncryptionState()
+ == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+ }
+
private static List<BluetoothDevice> getActiveSinksOnAssistant(
@Nullable LocalBluetoothManager manager) {
if (manager == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 6cf69c5..3c005b2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -18,10 +18,18 @@
import static java.util.Collections.emptyList;
+import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.os.Bundle;
import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
@@ -29,13 +37,19 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -46,11 +60,22 @@
implements DefaultLifecycleObserver {
private static final String TAG = "AudioStreamsProgressCategoryController";
private static final boolean DEBUG = BluetoothUtils.D;
+ private final BluetoothCallback mBluetoothCallback =
+ new BluetoothCallback() {
+ @Override
+ public void onActiveDeviceChanged(
+ @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ mExecutor.execute(() -> init(activeDevice != null));
+ }
+ }
+ };
private final Executor mExecutor;
private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>();
private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -58,7 +83,8 @@
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey);
mExecutor = Executors.newSingleThreadExecutor();
- mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(mContext));
+ mBluetoothManager = Utils.getLocalBtManager(mContext);
+ mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this);
}
@@ -76,48 +102,24 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
- return;
- }
- mBroadcastIdToPreferenceMap.clear();
- if (mCategoryPreference != null) {
- mCategoryPreference.removeAll();
+ if (mBluetoothManager != null) {
+ mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
}
mExecutor.execute(
() -> {
- mLeBroadcastAssistant.registerServiceCallBack(
- mExecutor, mBroadcastAssistantCallback);
- if (DEBUG) {
- Log.d(TAG, "scanAudioStreamsStart()");
- }
- mLeBroadcastAssistant.startSearchingForSources(emptyList());
- // Display currently connected streams
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () ->
- mAudioStreamsHelper
- .getAllSources()
- .forEach(this::handleSourceConnected));
+ boolean hasActive =
+ AudioSharingUtils.getActiveSinkOnAssistant(mBluetoothManager)
+ .isPresent();
+ init(hasActive);
});
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
- return;
+ if (mBluetoothManager != null) {
+ mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
}
- mExecutor.execute(
- () -> {
- if (mLeBroadcastAssistant.isSearchInProgress()) {
- if (DEBUG) {
- Log.d(TAG, "scanAudioStreamsStop()");
- }
- mLeBroadcastAssistant.stopSearchingForSources();
- }
- mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- });
+ mExecutor.execute(this::stopScanning);
}
void setScanning(boolean isScanning) {
@@ -131,7 +133,10 @@
Preference.OnPreferenceClickListener addSourceOrShowDialog =
preference -> {
if (DEBUG) {
- Log.d(TAG, "preferenceClicked(): attempt to join broadcast");
+ Log.d(
+ TAG,
+ "preferenceClicked(): attempt to join broadcast id : "
+ + source.getBroadcastId());
}
if (source.isEncrypted()) {
ThreadUtils.postOnMainThread(
@@ -166,11 +171,13 @@
}
});
}
- mAudioStreamsHelper.removeSource();
+ mAudioStreamsHelper.removeSource(broadcastId);
}
void handleSourceConnected(BluetoothLeBroadcastReceiveState state) {
- // TODO(chelseahao): only continue when the state indicates a successful connection
+ if (!AudioStreamsHelper.isConnected(state)) {
+ return;
+ }
mBroadcastIdToPreferenceMap.compute(
state.getBroadcastId(),
(k, v) -> {
@@ -183,7 +190,7 @@
ThreadUtils.postOnMainThread(
() -> {
preference.setIsConnected(
- true, p -> launchDetailFragment((AudioStreamPreference) p));
+ true, p -> launchDetailFragment(state.getBroadcastId()));
if (mCategoryPreference != null && !existed) {
mCategoryPreference.addPreference(preference);
}
@@ -197,12 +204,111 @@
AudioSharingUtils.toastMessage(mContext, msg);
}
- private boolean launchDetailFragment(AudioStreamPreference preference) {
- // TODO(chelseahao): impl
+ private void init(boolean hasActive) {
+ mBroadcastIdToPreferenceMap.clear();
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mCategoryPreference != null) {
+ mCategoryPreference.removeAll();
+ mCategoryPreference.setVisible(hasActive);
+ }
+ });
+ if (hasActive) {
+ startScanning();
+ } else {
+ stopScanning();
+ }
+ }
+
+ private void startScanning() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "startScanning(): LeBroadcastAssistant is null!");
+ return;
+ }
+ if (mLeBroadcastAssistant.isSearchInProgress()) {
+ showToast("Failed to start scanning, please try again.");
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "startScanning()");
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ mLeBroadcastAssistant.startSearchingForSources(emptyList());
+
+ // Display currently connected streams
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () ->
+ mAudioStreamsHelper
+ .getAllSources()
+ .forEach(this::handleSourceConnected));
+ }
+
+ private void stopScanning() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!");
+ return;
+ }
+ if (mLeBroadcastAssistant.isSearchInProgress()) {
+ if (DEBUG) {
+ Log.d(TAG, "stopScanning()");
+ }
+ mLeBroadcastAssistant.stopSearchingForSources();
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+ }
+
+ private boolean launchDetailFragment(int broadcastId) {
+ if (!mBroadcastIdToPreferenceMap.containsKey(broadcastId)) {
+ Log.w(
+ TAG,
+ "launchDetailFragment(): broadcastId not exist in BroadcastIdToPreferenceMap!");
+ return false;
+ }
+ AudioStreamPreference preference = mBroadcastIdToPreferenceMap.get(broadcastId);
+
+ Bundle broadcast = new Bundle();
+ broadcast.putString(
+ AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) preference.getTitle());
+ broadcast.putInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG, broadcastId);
+
+ new SubSettingLauncher(mContext)
+ .setTitleText("Audio stream details")
+ .setDestination(AudioStreamDetailsFragment.class.getName())
+ // TODO(chelseahao): Add logging enum
+ .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
+ .setArguments(broadcast)
+ .launch();
return true;
}
private void launchPasswordDialog(BluetoothLeBroadcastMetadata source, Preference preference) {
- // TODO(chelseahao): impl
+ View layout =
+ LayoutInflater.from(mContext)
+ .inflate(R.layout.bluetooth_find_broadcast_password_dialog, null);
+ ((TextView) layout.requireViewById(R.id.broadcast_name_text))
+ .setText(preference.getTitle());
+ AlertDialog alertDialog =
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.find_broadcast_password_dialog_title)
+ .setView(layout)
+ .setNeutralButton(android.R.string.cancel, null)
+ .setPositiveButton(
+ R.string.bluetooth_connect_access_dialog_positive,
+ (dialog, which) -> {
+ var code =
+ ((EditText)
+ layout.requireViewById(
+ R.id.broadcast_edit_text))
+ .getText()
+ .toString();
+ mAudioStreamsHelper.addSource(
+ new BluetoothLeBroadcastMetadata.Builder(source)
+ .setBroadcastCode(
+ code.getBytes(StandardCharsets.UTF_8))
+ .build());
+ })
+ .create();
+ alertDialog.show();
}
}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 6d1d4e8..d68f2c8 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -100,6 +100,7 @@
import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
import com.android.settings.display.AutoBrightnessSettings;
import com.android.settings.display.NightDisplaySettings;
+import com.android.settings.display.ScreenTimeoutSettings;
import com.android.settings.display.SmartAutoRotatePreferenceFragment;
import com.android.settings.display.darkmode.DarkModeSettingsFragment;
import com.android.settings.dream.DreamSettings;
@@ -369,7 +370,8 @@
LongBackgroundTasksDetails.class.getName(),
RegionalPreferencesEntriesFragment.class.getName(),
BatteryInfoFragment.class.getName(),
- UserAspectRatioDetails.class.getName()
+ UserAspectRatioDetails.class.getName(),
+ ScreenTimeoutSettings.class.getName(),
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
index 3321d50..8279588 100644
--- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
+++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
@@ -41,6 +41,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
@@ -130,7 +132,6 @@
if (titleResId > 0) {
activity.setTitle(titleResId);
}
- final int selectedTab = getTabId(activity, getArguments());
final View tabContainer = mContentView.findViewById(R.id.tab_container);
mViewPager = tabContainer.findViewById(R.id.view_pager);
@@ -149,6 +150,7 @@
}
);
tabContainer.setVisibility(View.VISIBLE);
+ final int selectedTab = getTabId(activity, getArguments());
final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
tab.select();
@@ -228,7 +230,7 @@
if (bundle != null) {
final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1);
if (extraTab != -1) {
- return extraTab;
+ return ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(extraTab);
}
final int userId = bundle.getInt(EXTRA_USER_ID, UserHandle.SYSTEM.getIdentifier());
final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId);
@@ -357,6 +359,11 @@
return fragment;
}
+ @VisibleForTesting
+ void setViewPager(ViewPager2 viewPager) {
+ mViewPager = viewPager;
+ }
+
interface FragmentConstructor {
Fragment constructAndGetFragment();
}
@@ -376,6 +383,15 @@
mChildFragments = fragment.getFragments();
}
+ @VisibleForTesting
+ ViewPagerAdapter(
+ @NonNull FragmentManager fragmentManager,
+ @NonNull Lifecycle lifecycle,
+ ProfileSelectFragment profileSelectFragment) {
+ super(fragmentManager, lifecycle);
+ mChildFragments = profileSelectFragment.getFragments();
+ }
+
@Override
public Fragment createFragment(int position) {
return mChildFragments[position];
@@ -386,7 +402,8 @@
return mChildFragments.length;
}
- private int getTabForPosition(int position) {
+ @VisibleForTesting
+ int getTabForPosition(int position) {
if (position >= mChildFragments.length) {
Log.e(TAG, "tab requested for out of bound position " + position);
return PERSONAL_TAB;
diff --git a/src/com/android/settings/development/BackAnimationPreferenceController.java b/src/com/android/settings/development/BackAnimationPreferenceController.java
index aa4faf5..8c731a4 100644
--- a/src/com/android/settings/development/BackAnimationPreferenceController.java
+++ b/src/com/android/settings/development/BackAnimationPreferenceController.java
@@ -16,6 +16,9 @@
package com.android.settings.development;
+import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+
+import android.annotation.Nullable;
import android.content.Context;
import android.provider.Settings;
@@ -26,8 +29,6 @@
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
-import java.util.Objects;
-
/**
* PreferenceController for enabling/disabling animation related to back button and back gestures.
*/
@@ -49,13 +50,17 @@
public BackAnimationPreferenceController(Context context,
- DevelopmentSettingsDashboardFragment fragment) {
+ @Nullable DevelopmentSettingsDashboardFragment fragment) {
super(context);
- Objects.requireNonNull(fragment);
mFragment = fragment;
}
@Override
+ public boolean isAvailable() {
+ return !predictiveBackSystemAnimations();
+ }
+
+ @Override
public String getPreferenceKey() {
return BACK_NAVIGATION_ANIMATION_KEY;
}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 4c8b2dc..b52409d 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -49,6 +49,7 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -606,8 +607,9 @@
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,
- BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
+ @Nullable Activity activity, @Nullable Lifecycle lifecycle,
+ @Nullable DevelopmentSettingsDashboardFragment fragment,
+ @Nullable BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new MemoryUsagePreferenceController(context));
controllers.add(new BugReportPreferenceController(context));
diff --git a/src/com/android/settings/development/GrammaticalGenderPreferenceController.java b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java
index 7b8ec65..0540974 100644
--- a/src/com/android/settings/development/GrammaticalGenderPreferenceController.java
+++ b/src/com/android/settings/development/GrammaticalGenderPreferenceController.java
@@ -95,4 +95,9 @@
listPreference.setValue(mListValues[index]);
listPreference.setSummary(mListSummaries[index]);
}
+
+ @Override
+ public boolean isAvailable() {
+ return android.app.Flags.systemTermsOfAddressEnabled();
+ }
}
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index 86ef3cf..23680d9 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -148,7 +148,8 @@
launchArgs.mShowTimeInformation = showTimeInformation;
if (launchArgs.mShowTimeInformation) {
launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs;
- launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
+ launchArgs.mBackgroundTimeMs =
+ diffEntry.mBackgroundUsageTimeInMs + diffEntry.mForegroundServiceUsageTimeInMs;
launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
launchArgs.mAnomalyHintPrefKey = anomalyHintPrefKey;
launchArgs.mAnomalyHintText = anomalyHintText;
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index a2ee3e4..0bc6176 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -22,6 +22,7 @@
import android.util.ArrayMap;
import android.util.SparseIntArray;
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
import com.android.settingslib.fuelgauge.Estimate;
@@ -103,11 +104,9 @@
/** Returns {@code true} if delay the hourly job when device is booting */
boolean delayHourlyJobWhenBooting();
- /** Insert settings configuration data for anomaly detection */
- void insertSettingsData(Context context, double displayDrain);
-
/** Returns {@link Bundle} for settings anomaly detection result */
- PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain);
+ PowerAnomalyEventList detectSettingsAnomaly(
+ Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType);
/** Gets an intent for one time bypass charge limited to resume charging. */
Intent getResumeChargeIntent(boolean isDockDefender);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 2e2cf12..a8a2f75 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -27,6 +27,7 @@
import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
import com.android.settingslib.fuelgauge.Estimate;
@@ -168,10 +169,8 @@
}
@Override
- public void insertSettingsData(Context context, double displayDrain) {}
-
- @Override
- public PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain) {
+ public PowerAnomalyEventList detectSettingsAnomaly(
+ Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType) {
return null;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index bad1b76..2c376e5 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -92,6 +92,7 @@
public String mLegacyLabel;
public int mConsumerType;
public long mForegroundUsageTimeInMs;
+ public long mForegroundServiceUsageTimeInMs;
public long mBackgroundUsageTimeInMs;
public long mScreenOnTimeInMs;
public double mConsumePower;
@@ -125,6 +126,7 @@
String legacyLabel,
int consumerType,
long foregroundUsageTimeInMs,
+ long foregroundServiceUsageTimeInMs,
long backgroundUsageTimeInMs,
long screenOnTimeInMs,
double consumePower,
@@ -142,6 +144,7 @@
mLegacyLabel = legacyLabel;
mConsumerType = consumerType;
mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
+ mForegroundServiceUsageTimeInMs = foregroundServiceUsageTimeInMs;
mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
mScreenOnTimeInMs = screenOnTimeInMs;
mConsumePower = consumePower;
@@ -164,6 +167,7 @@
legacyLabel,
consumerType,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0,
/* screenOnTimeInMs= */ 0,
/* consumePower= */ 0,
@@ -232,6 +236,7 @@
this.mLegacyLabel,
this.mConsumerType,
this.mForegroundUsageTimeInMs,
+ this.mForegroundServiceUsageTimeInMs,
this.mBackgroundUsageTimeInMs,
this.mScreenOnTimeInMs,
this.mConsumePower,
@@ -515,48 +520,50 @@
@Override
public String toString() {
- final StringBuilder builder =
- new StringBuilder()
- .append("BatteryDiffEntry{")
- .append(
- String.format(
- "\n\tname=%s restrictable=%b",
- mAppLabel, mValidForRestriction))
- .append(
- String.format(
- "\n\tconsume=%.2f%% %f/%f",
- mPercentage, mConsumePower, mTotalConsumePower))
- .append(
- String.format(
- "\n\tconsume power= foreground:%f foregroundService:%f",
- mForegroundUsageConsumePower,
- mForegroundServiceUsageConsumePower))
- .append(
- String.format(
- "\n\tconsume power= background:%f cached:%f",
- mBackgroundUsageConsumePower, mCachedUsageConsumePower))
- .append(
- String.format(
- "\n\ttime= foreground:%s background:%s screen-on:%s",
- StringUtil.formatElapsedTime(
- mContext,
- (double) mForegroundUsageTimeInMs,
- /* withSeconds= */ true,
- /* collapseTimeUnit= */ false),
- StringUtil.formatElapsedTime(
- mContext,
- (double) mBackgroundUsageTimeInMs,
- /* withSeconds= */ true,
- /* collapseTimeUnit= */ false),
- StringUtil.formatElapsedTime(
- mContext,
- (double) mScreenOnTimeInMs,
- /* withSeconds= */ true,
- /* collapseTimeUnit= */ false)))
- .append(
- String.format(
- "\n\tpackage:%s|%s uid:%d userId:%d",
- mLegacyPackageName, getPackageName(), mUid, mUserId));
+ final StringBuilder builder = new StringBuilder();
+ builder.append("BatteryDiffEntry{");
+ builder.append(
+ String.format("\n\tname=%s restrictable=%b", mAppLabel, mValidForRestriction));
+ builder.append(
+ String.format(
+ "\n\tconsume=%.2f%% %f/%f",
+ mPercentage, mConsumePower, mTotalConsumePower));
+ builder.append(
+ String.format(
+ "\n\tconsume power= foreground:%f foregroundService:%f",
+ mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower));
+ builder.append(
+ String.format(
+ "\n\tconsume power= background:%f cached:%f",
+ mBackgroundUsageConsumePower, mCachedUsageConsumePower));
+ builder.append(
+ String.format(
+ "\n\ttime= foreground:%s foregroundService:%s "
+ + "background:%s screen-on:%s",
+ StringUtil.formatElapsedTime(
+ mContext,
+ (double) mForegroundUsageTimeInMs,
+ /* withSeconds= */ true,
+ /* collapseTimeUnit= */ false),
+ StringUtil.formatElapsedTime(
+ mContext,
+ (double) mForegroundServiceUsageTimeInMs,
+ /* withSeconds= */ true,
+ /* collapseTimeUnit= */ false),
+ StringUtil.formatElapsedTime(
+ mContext,
+ (double) mBackgroundUsageTimeInMs,
+ /* withSeconds= */ true,
+ /* collapseTimeUnit= */ false),
+ StringUtil.formatElapsedTime(
+ mContext,
+ (double) mScreenOnTimeInMs,
+ /* withSeconds= */ true,
+ /* collapseTimeUnit= */ false)));
+ builder.append(
+ String.format(
+ "\n\tpackage:%s|%s uid:%d userId:%d",
+ mLegacyPackageName, getPackageName(), mUid, mUserId));
return builder.toString();
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
index 751e7ad..4b65cc9 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
@@ -111,6 +111,7 @@
@BatteryConsumer.PowerComponent private final int mPowerComponentId;
private long mUsageDurationMs;
private long mTimeInForegroundMs;
+ private long mTimeInForegroundServiceMs;
private long mTimeInBackgroundMs;
public String mName;
@@ -188,9 +189,14 @@
}
}
mTimeInForegroundMs =
- uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND);
+ mTimeInForegroundServiceMs =
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
mTimeInBackgroundMs =
- uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_BACKGROUND);
mConsumedPowerInForeground =
safeGetConsumedPower(
uidBatteryConsumer, BATTERY_DIMENSIONS[BATTERY_USAGE_INDEX_FOREGROUND]);
@@ -433,20 +439,19 @@
/** Returns foreground time/ms that is attributed to this entry. */
public long getTimeInForegroundMs() {
- if (mBatteryConsumer instanceof UidBatteryConsumer) {
- return mTimeInForegroundMs;
- } else {
- return mUsageDurationMs;
- }
+ return (mBatteryConsumer instanceof UidBatteryConsumer)
+ ? mTimeInForegroundMs
+ : mUsageDurationMs;
+ }
+
+ /** Returns foreground service time/ms that is attributed to this entry. */
+ public long getTimeInForegroundServiceMs() {
+ return (mBatteryConsumer instanceof UidBatteryConsumer) ? mTimeInForegroundServiceMs : 0;
}
/** Returns background activity time/ms that is attributed to this entry. */
public long getTimeInBackgroundMs() {
- if (mBatteryConsumer instanceof UidBatteryConsumer) {
- return mTimeInBackgroundMs;
- } else {
- return 0;
- }
+ return (mBatteryConsumer instanceof UidBatteryConsumer) ? mTimeInBackgroundMs : 0;
}
/** Returns total amount of power (in milli-amp-hours) that is attributed to this entry. */
@@ -510,9 +515,14 @@
if (batteryConsumer instanceof UidBatteryConsumer) {
UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer;
mTimeInForegroundMs +=
- uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND);
+ mTimeInForegroundServiceMs +=
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
mTimeInBackgroundMs +=
- uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
+ uidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_BACKGROUND);
mConsumedPowerInForeground +=
safeGetConsumedPower(
uidBatteryConsumer, BATTERY_DIMENSIONS[BATTERY_USAGE_INDEX_FOREGROUND]);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
index 97cdc34..b42d373 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
@@ -20,8 +20,6 @@
import android.os.BatteryConsumer;
import android.util.Log;
-import java.time.Duration;
-
/** A container class to carry data from {@link ContentValues}. */
public class BatteryHistEntry {
private static final boolean DEBUG = false;
@@ -57,6 +55,7 @@
public final double mCachedUsageConsumePower;
public final double mPercentOfTotal;
public final long mForegroundUsageTimeInMs;
+ public final long mForegroundServiceUsageTimeInMs;
public final long mBackgroundUsageTimeInMs;
@BatteryConsumer.PowerComponent public final int mDrainType;
@ConvertUtils.ConsumerType public final int mConsumerType;
@@ -89,6 +88,7 @@
mCachedUsageConsumePower = batteryInformation.getCachedUsageConsumePower();
mPercentOfTotal = batteryInformation.getPercentOfTotal();
mForegroundUsageTimeInMs = batteryInformation.getForegroundUsageTimeInMs();
+ mForegroundServiceUsageTimeInMs = batteryInformation.getForegroundServiceUsageTimeInMs();
mBackgroundUsageTimeInMs = batteryInformation.getBackgroundUsageTimeInMs();
mDrainType = batteryInformation.getDrainType();
final DeviceBatteryState deviceBatteryState = batteryInformation.getDeviceBatteryState();
@@ -118,6 +118,7 @@
mCachedUsageConsumePower = batteryInformation.getCachedUsageConsumePower();
mPercentOfTotal = batteryInformation.getPercentOfTotal();
mForegroundUsageTimeInMs = batteryInformation.getForegroundUsageTimeInMs();
+ mForegroundServiceUsageTimeInMs = batteryInformation.getForegroundServiceUsageTimeInMs();
mBackgroundUsageTimeInMs = batteryInformation.getBackgroundUsageTimeInMs();
mDrainType = batteryInformation.getDrainType();
final DeviceBatteryState deviceBatteryState = batteryInformation.getDeviceBatteryState();
@@ -137,6 +138,7 @@
double backgroundUsageConsumePower,
double cachedUsageConsumePower,
long foregroundUsageTimeInMs,
+ long foregroundServiceUsageTimeInMs,
long backgroundUsageTimeInMs,
int batteryLevel) {
mUid = fromEntry.mUid;
@@ -155,6 +157,7 @@
mCachedUsageConsumePower = cachedUsageConsumePower;
mPercentOfTotal = fromEntry.mPercentOfTotal;
mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
+ mForegroundServiceUsageTimeInMs = foregroundServiceUsageTimeInMs;
mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
mDrainType = fromEntry.mDrainType;
mConsumerType = fromEntry.mConsumerType;
@@ -189,45 +192,40 @@
@Override
public String toString() {
final String recordAtDateTime = ConvertUtils.utcToLocalTimeForLogging(mTimestamp);
- final StringBuilder builder =
- new StringBuilder()
- .append("\nBatteryHistEntry{")
- .append(
- String.format(
- "\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b",
- mPackageName, mAppLabel, mUid, mUserId, mIsHidden))
- .append(
- String.format(
- "\n\ttimestamp=%s|zoneId=%s|bootTimestamp=%d",
- recordAtDateTime,
- mZoneId,
- Duration.ofMillis(mBootTimestamp).getSeconds()))
- .append(
- String.format(
- "\n\tusage=%f|total=%f|consume=%f",
- mPercentOfTotal, mTotalPower, mConsumePower))
- .append(
- String.format(
- "\n\tforeground=%f|foregroundService=%f",
- mForegroundUsageConsumePower,
- mForegroundServiceUsageConsumePower))
- .append(
- String.format(
- "\n\tbackground=%f|cached=%f",
- mBackgroundUsageConsumePower, mCachedUsageConsumePower))
- .append(
- String.format(
- "\n\telapsedTime=%d|%d",
- Duration.ofMillis(mForegroundUsageTimeInMs).getSeconds(),
- Duration.ofMillis(mBackgroundUsageTimeInMs).getSeconds()))
- .append(
- String.format(
- "\n\tdrainType=%d|consumerType=%d",
- mDrainType, mConsumerType))
- .append(
- String.format(
- "\n\tbattery=%d|status=%d|health=%d\n}",
- mBatteryLevel, mBatteryStatus, mBatteryHealth));
+ final StringBuilder builder = new StringBuilder();
+ builder.append("\nBatteryHistEntry{");
+ builder.append(
+ String.format(
+ "\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b",
+ mPackageName, mAppLabel, mUid, mUserId, mIsHidden));
+ builder.append(
+ String.format(
+ "\n\ttimestamp=%s|zoneId=%s|bootTimestamp=%d",
+ recordAtDateTime, mZoneId, TimestampUtils.getSeconds(mBootTimestamp)));
+ builder.append(
+ String.format(
+ "\n\tusage=%f|total=%f|consume=%f",
+ mPercentOfTotal, mTotalPower, mConsumePower));
+ builder.append(
+ String.format(
+ "\n\tforeground=%f|foregroundService=%f",
+ mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower));
+ builder.append(
+ String.format(
+ "\n\tbackground=%f|cached=%f",
+ mBackgroundUsageConsumePower, mCachedUsageConsumePower));
+ builder.append(
+ String.format(
+ "\n\telapsedTime,fg=%d|fgs=%d|bg=%d",
+ TimestampUtils.getSeconds(mBackgroundUsageTimeInMs),
+ TimestampUtils.getSeconds(mForegroundServiceUsageTimeInMs),
+ TimestampUtils.getSeconds(mBackgroundUsageTimeInMs)));
+ builder.append(
+ String.format("\n\tdrainType=%d|consumerType=%d", mDrainType, mConsumerType));
+ builder.append(
+ String.format(
+ "\n\tbattery=%d|status=%d|health=%d\n}",
+ mBatteryLevel, mBatteryStatus, mBatteryHealth));
return builder.toString();
}
@@ -323,19 +321,20 @@
ratio);
final double foregroundUsageTimeInMs =
interpolate(
- (double)
- (lowerHistEntry == null
- ? 0
- : lowerHistEntry.mForegroundUsageTimeInMs),
- (double) upperHistEntry.mForegroundUsageTimeInMs,
+ (lowerHistEntry == null ? 0 : lowerHistEntry.mForegroundUsageTimeInMs),
+ upperHistEntry.mForegroundUsageTimeInMs,
+ ratio);
+ final double foregroundServiceUsageTimeInMs =
+ interpolate(
+ (lowerHistEntry == null
+ ? 0
+ : lowerHistEntry.mForegroundServiceUsageTimeInMs),
+ upperHistEntry.mForegroundServiceUsageTimeInMs,
ratio);
final double backgroundUsageTimeInMs =
interpolate(
- (double)
- (lowerHistEntry == null
- ? 0
- : lowerHistEntry.mBackgroundUsageTimeInMs),
- (double) upperHistEntry.mBackgroundUsageTimeInMs,
+ (lowerHistEntry == null ? 0 : lowerHistEntry.mBackgroundUsageTimeInMs),
+ upperHistEntry.mBackgroundUsageTimeInMs,
ratio);
// Checks whether there is any abnormal cases!
if (upperHistEntry.mConsumePower < consumePower
@@ -345,6 +344,7 @@
|| upperHistEntry.mBackgroundUsageConsumePower < backgroundUsageConsumePower
|| upperHistEntry.mCachedUsageConsumePower < cachedUsageConsumePower
|| upperHistEntry.mForegroundUsageTimeInMs < foregroundUsageTimeInMs
+ || upperHistEntry.mForegroundServiceUsageTimeInMs < foregroundServiceUsageTimeInMs
|| upperHistEntry.mBackgroundUsageTimeInMs < backgroundUsageTimeInMs) {
if (DEBUG) {
Log.w(
@@ -371,6 +371,7 @@
backgroundUsageConsumePower,
cachedUsageConsumePower,
Math.round(foregroundUsageTimeInMs),
+ Math.round(foregroundServiceUsageTimeInMs),
Math.round(backgroundUsageTimeInMs),
(int) Math.round(batteryLevel));
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index d6f8709..0ffd090 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -400,7 +400,7 @@
mPrefContext,
entry.isSystemEntry(),
entry.mForegroundUsageTimeInMs,
- entry.mBackgroundUsageTimeInMs,
+ entry.mBackgroundUsageTimeInMs + entry.mForegroundServiceUsageTimeInMs,
entry.mScreenOnTimeInMs));
}
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index ead580b..fb5b9a1 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -135,7 +135,10 @@
.isEmpty()))) {
FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
- .detectSettingsAnomaly(context, /* displayDrain= */ 0);
+ .detectSettingsAnomaly(
+ context,
+ /* displayDrain= */ 0,
+ DetectRequestSourceType.TYPE_DATA_LOADER);
}
});
if (batteryLevelData == null) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index ae93734..e23e219 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -544,6 +544,7 @@
batteryUsageDiff.getLabel(),
batteryUsageDiff.getConsumerType(),
batteryUsageDiff.getForegroundUsageTime(),
+ batteryUsageDiff.getForegroundServiceUsageTime(),
batteryUsageDiff.getBackgroundUsageTime(),
batteryUsageDiff.getScreenOnTime(),
batteryUsageDiff.getConsumePower(),
@@ -612,6 +613,7 @@
.setPercentOfTotal(entry.mPercent)
.setDrainType(entry.getPowerComponentId())
.setForegroundUsageTimeInMs(entry.getTimeInForegroundMs())
+ .setForegroundServiceUsageTimeInMs(entry.getTimeInForegroundServiceMs())
.setBackgroundUsageTimeInMs(entry.getTimeInBackgroundMs());
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index d8f0a77..2ef12f1 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -675,6 +675,7 @@
entry.mAppLabel,
entry.mConsumerType,
entry.mForegroundUsageTimeInMs,
+ entry.mForegroundServiceUsageTimeInMs,
entry.mBackgroundUsageTimeInMs,
/* screenOnTimeInMs= */ 0,
entry.mConsumePower,
@@ -1412,6 +1413,7 @@
// Cumulative values is a specific time slot for a specific app.
long foregroundUsageTimeInMs = 0;
+ long foregroundServiceUsageTimeInMs = 0;
long backgroundUsageTimeInMs = 0;
double consumePower = 0;
double foregroundUsageConsumePower = 0;
@@ -1425,6 +1427,10 @@
getDiffValue(
currentEntry.mForegroundUsageTimeInMs,
nextEntry.mForegroundUsageTimeInMs);
+ foregroundServiceUsageTimeInMs +=
+ getDiffValue(
+ currentEntry.mForegroundServiceUsageTimeInMs,
+ nextEntry.mForegroundServiceUsageTimeInMs);
backgroundUsageTimeInMs +=
getDiffValue(
currentEntry.mBackgroundUsageTimeInMs,
@@ -1453,24 +1459,32 @@
foregroundUsageTimeInMs = slotScreenOnTime;
}
// Excludes entry since we don't have enough data to calculate.
- if (foregroundUsageTimeInMs == 0 && backgroundUsageTimeInMs == 0 && consumePower == 0) {
+ if (foregroundUsageTimeInMs == 0
+ && foregroundServiceUsageTimeInMs == 0
+ && backgroundUsageTimeInMs == 0
+ && consumePower == 0) {
continue;
}
// Forces refine the cumulative value since it may introduce deviation error since we
// will apply the interpolation arithmetic.
- final float totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
+ final float totalUsageTimeInMs =
+ foregroundUsageTimeInMs
+ + backgroundUsageTimeInMs
+ + foregroundServiceUsageTimeInMs;
if (totalUsageTimeInMs > slotDuration) {
final float ratio = slotDuration / totalUsageTimeInMs;
if (sDebug) {
Log.w(
TAG,
String.format(
- "abnormal usage time %d|%d for:\n%s",
+ "abnormal usage time %d|%d|%d for:\n%s",
Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
+ Duration.ofMillis(foregroundServiceUsageTimeInMs).getSeconds(),
Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
selectedBatteryEntry));
}
foregroundUsageTimeInMs = Math.round(foregroundUsageTimeInMs * ratio);
+ foregroundServiceUsageTimeInMs = Math.round(foregroundServiceUsageTimeInMs * ratio);
backgroundUsageTimeInMs = Math.round(backgroundUsageTimeInMs * ratio);
consumePower = consumePower * ratio;
foregroundUsageConsumePower = foregroundUsageConsumePower * ratio;
@@ -1487,9 +1501,14 @@
appUsageMap,
selectedBatteryEntry.mUserId,
selectedBatteryEntry.mPackageName));
- // Make sure the background + screen-on time will not exceed the threshold.
+ // Ensure the following value will not exceed the threshold.
+ // value = background + foregroundService + screen-on
backgroundUsageTimeInMs =
Math.min(backgroundUsageTimeInMs, (long) slotDuration - screenOnTime);
+ foregroundServiceUsageTimeInMs =
+ Math.min(
+ foregroundServiceUsageTimeInMs,
+ (long) slotDuration - screenOnTime - backgroundUsageTimeInMs);
final BatteryDiffEntry currentBatteryDiffEntry =
new BatteryDiffEntry(
context,
@@ -1502,6 +1521,7 @@
selectedBatteryEntry.mAppLabel,
selectedBatteryEntry.mConsumerType,
foregroundUsageTimeInMs,
+ foregroundServiceUsageTimeInMs,
backgroundUsageTimeInMs,
screenOnTime,
consumePower,
@@ -1647,6 +1667,8 @@
} else {
// Sums up some field data into the existing one.
oldBatteryDiffEntry.mForegroundUsageTimeInMs += entry.mForegroundUsageTimeInMs;
+ oldBatteryDiffEntry.mForegroundServiceUsageTimeInMs +=
+ entry.mForegroundServiceUsageTimeInMs;
oldBatteryDiffEntry.mBackgroundUsageTimeInMs += entry.mBackgroundUsageTimeInMs;
oldBatteryDiffEntry.mScreenOnTimeInMs += entry.mScreenOnTimeInMs;
oldBatteryDiffEntry.mConsumePower += entry.mConsumePower;
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 7160da4..ee0e449 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -660,36 +660,39 @@
// Creates the ContentValues list to insert them into provider.
final List<ContentValues> valuesList = new ArrayList<>();
if (batteryEntryList != null) {
- batteryEntryList.stream()
- .filter(
- entry -> {
- final long foregroundMs = entry.getTimeInForegroundMs();
- final long backgroundMs = entry.getTimeInBackgroundMs();
- if (entry.getConsumedPower() == 0
- && (foregroundMs != 0 || backgroundMs != 0)) {
- Log.w(
- TAG,
- String.format(
- "no consumed power but has running time for %s"
- + " time=%d|%d",
- entry.getLabel(), foregroundMs, backgroundMs));
- }
- return entry.getConsumedPower() != 0
- || foregroundMs != 0
- || backgroundMs != 0;
- })
- .forEach(
- entry ->
- valuesList.add(
- ConvertUtils.convertBatteryEntryToContentValues(
- entry,
- batteryUsageStats,
- batteryLevel,
- batteryStatus,
- batteryHealth,
- snapshotBootTimestamp,
- snapshotTimestamp,
- isFullChargeStart)));
+ for (BatteryEntry entry : batteryEntryList) {
+ final long foregroundMs = entry.getTimeInForegroundMs();
+ final long foregroundServiceMs = entry.getTimeInForegroundServiceMs();
+ final long backgroundMs = entry.getTimeInBackgroundMs();
+ if (entry.getConsumedPower() == 0
+ && (foregroundMs != 0 || foregroundServiceMs != 0 || backgroundMs != 0)) {
+ Log.w(
+ TAG,
+ String.format(
+ "no consumed power but has running time for %s"
+ + " time=%d|%d|%d",
+ entry.getLabel(),
+ foregroundMs,
+ foregroundServiceMs,
+ backgroundMs));
+ }
+ if (entry.getConsumedPower() == 0
+ && foregroundMs == 0
+ && foregroundServiceMs == 0
+ && backgroundMs == 0) {
+ continue;
+ }
+ valuesList.add(
+ ConvertUtils.convertBatteryEntryToContentValues(
+ entry,
+ batteryUsageStats,
+ batteryLevel,
+ batteryStatus,
+ batteryHealth,
+ snapshotBootTimestamp,
+ snapshotTimestamp,
+ isFullChargeStart));
+ }
}
int size = 1;
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 072040d..1482117 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -263,7 +263,9 @@
FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
final PowerAnomalyEventList anomalyEventList =
powerUsageFeatureProvider.detectSettingsAnomaly(
- getContext(), /* displayDrain= */ 0);
+ getContext(),
+ /* displayDrain= */ 0,
+ DetectRequestSourceType.TYPE_USAGE_UI);
mHandler.post(() -> onAnomalyDetected(anomalyEventList));
});
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java b/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java
index 594a0ef..41a2254 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java
@@ -16,6 +16,7 @@
package com.android.settings.fuelgauge.batteryusage;
+import java.time.Duration;
import java.util.Calendar;
/** A utility class for timestamp operations. */
@@ -48,6 +49,10 @@
return calendar.getTimeInMillis();
}
+ static long getSeconds(final long timeInMs) {
+ return Duration.ofMillis(timeInMs).getSeconds();
+ }
+
static boolean isMidnight(final long timestamp) {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
diff --git a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto
index 5bc1a3e..7f67770 100644
--- a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto
+++ b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto
@@ -31,4 +31,5 @@
optional int64 foreground_usage_time = 14;
optional int64 background_usage_time = 15;
optional int64 screen_on_time = 16;
+ optional int64 foreground_service_usage_time = 17;
}
diff --git a/src/com/android/settings/fuelgauge/protos/fuelgauge_usage_state.proto b/src/com/android/settings/fuelgauge/protos/fuelgauge_usage_state.proto
index b9b05a3..d53b814 100644
--- a/src/com/android/settings/fuelgauge/protos/fuelgauge_usage_state.proto
+++ b/src/com/android/settings/fuelgauge/protos/fuelgauge_usage_state.proto
@@ -36,4 +36,5 @@
optional double foreground_service_usage_consume_power = 17;
optional double background_usage_consume_power = 18;
optional double cached_usage_consume_power = 19;
+ optional int64 foreground_service_usage_time_in_ms = 20;
}
\ No newline at end of file
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
index 930a21b..3c0705f 100644
--- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -21,18 +21,12 @@
optional string dismiss_record_key = 8;
}
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide the tips card style like icons and colors.
-//
// Next id: 2
enum PowerAnomalyType{
TYPE_SETTINGS_BANNER = 0;
TYPE_APPS_ITEM = 1;
}
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide pre-defined title and button labels.
-//
// Next id: 8
enum PowerAnomalyKey{
KEY_BRIGHTNESS = 0;
@@ -45,6 +39,13 @@
KEY_APP_FOREGROUND_HIGHER_THAN_USUAL = 7;
}
+// Next id: 3
+enum DetectRequestSourceType{
+ TYPE_UNKNOWN_SOURCE = 0;
+ TYPE_USAGE_UI = 1;
+ TYPE_DATA_LOADER = 2;
+}
+
message WarningBannerInfo {
optional string title_string = 1;
optional string description_string = 2;
diff --git a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
index 0200e52..3bf9db3 100644
--- a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
+++ b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
@@ -32,7 +32,6 @@
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
import com.android.settings.system.ResetDashboardFragment;
import com.android.settings.wifi.dpp.WifiDppUtils;
@@ -76,14 +75,7 @@
if (which == DialogInterface.BUTTON_POSITIVE) {
Context context = getContext();
- if (ConfirmationSimDeletionPredicate.getSingleton().test(context)) {
- // Create a "verify it's you" verification over keyguard
- // when "erase" button been pressed.
- // This might protect from erasing by some automation process.
- WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
- } else {
- runAsyncWipe(context);
- }
+ WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
}
}
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index e5d9242..323d935 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -59,6 +59,7 @@
super.onAttach(context);
use(AirplaneModePreferenceController.class).setFragment(this);
+ use(NetworkProviderCallsSmsController.class).init(this);
}
@Override
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
index a265041..7346e23 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
@@ -16,35 +16,23 @@
package com.android.settings.network
-import android.app.settings.SettingsEnums
import android.content.Context
import android.content.IntentFilter
-import android.os.UserManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.PermPhoneMsg
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.PreferenceScreen
import com.android.settings.R
-import com.android.settings.core.SubSettingLauncher
-import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.RestrictedPreference
import com.android.settingslib.Utils
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
import com.android.settingslib.spaprivileged.framework.common.userManager
-import com.android.settingslib.spaprivileged.framework.compose.placeholder
-import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -64,7 +52,14 @@
SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context)
},
private val isInService: (Int) -> Boolean = IsInServiceImpl(context)::isInService,
-) : ComposePreferenceController(context, preferenceKey) {
+) : BasePreferenceController(context, preferenceKey) {
+
+ private lateinit var lazyViewModel: Lazy<SubscriptionInfoListViewModel>
+ private lateinit var preference: RestrictedPreference
+
+ fun init(fragment: Fragment) {
+ lazyViewModel = fragment.viewModels()
+ }
override fun getAvailabilityStatus() = when {
!SubscriptionUtil.isSimHardwareVisible(mContext) -> UNSUPPORTED_ON_DEVICE
@@ -72,35 +67,23 @@
else -> AVAILABLE
}
- @Composable
- override fun Content() {
- Column {
- CallsAndSms()
- HorizontalDivider()
- }
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)!!
}
- @Composable
- private fun CallsAndSms() {
- val viewModel: SubscriptionInfoListViewModel = viewModel()
- val subscriptionInfos by viewModel.subscriptionInfoListFlow.collectAsStateWithLifecycle()
- val summary by remember { summaryFlow(viewModel.subscriptionInfoListFlow) }
- .collectAsStateWithLifecycle(initialValue = placeholder())
- RestrictedPreference(
- model = object : PreferenceModel {
- override val title = stringResource(R.string.calls_and_sms)
- override val icon = @Composable { SettingsIcon(Icons.Outlined.PermPhoneMsg) }
- override val summary = { summary }
- override val enabled = { subscriptionInfos.isNotEmpty() }
- override val onClick = {
- SubSettingLauncher(mContext).apply {
- setDestination(NetworkProviderCallsSmsFragment::class.qualifiedName)
- setSourceMetricsCategory(SettingsEnums.SETTINGS_NETWORK_CATEGORY)
- }.launch()
+ override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+ val viewModel by lazyViewModel
+
+ summaryFlow(viewModel.subscriptionInfoListFlow)
+ .collectLatestWithLifecycle(viewLifecycleOwner) { preference.summary = it }
+
+ viewModel.subscriptionInfoListFlow
+ .collectLatestWithLifecycle(viewLifecycleOwner) { subscriptionInfoList ->
+ if (!preference.isDisabledByAdmin) {
+ preference.isEnabled = subscriptionInfoList.isNotEmpty()
}
- },
- restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
- )
+ }
}
private fun summaryFlow(subscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) = combine(
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
index d30b21d..ee88177 100644
--- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -19,8 +19,10 @@
import android.app.Application
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
+
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
@@ -32,13 +34,12 @@
class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel(application) {
private val scope = viewModelScope + Dispatchers.Default
-
val subscriptionInfoListFlow = callbackFlow<List<SubscriptionInfo>> {
val subscriptionManager = application.getSystemService(SubscriptionManager::class.java)!!
val listener = object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
- trySend(subscriptionManager.activeSubscriptionInfoList ?: emptyList())
+ trySend(SubscriptionUtil.getActiveSubscriptions(subscriptionManager))
}
}
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 9974ba2..e416760 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -18,6 +18,8 @@
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+
import static com.android.internal.util.CollectionUtils.emptyIfNull;
import android.annotation.Nullable;
@@ -36,9 +38,11 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
@@ -84,6 +88,8 @@
}
public static List<SubscriptionInfo> getActiveSubscriptions(SubscriptionManager manager) {
+ //TODO (b/315499317) : Refactor the subscription utils.
+
if (sActiveResultsForTesting != null) {
return sActiveResultsForTesting;
}
@@ -94,7 +100,12 @@
if (subscriptions == null) {
return new ArrayList<>();
}
- return subscriptions;
+ // Since the SubscriptionManager.getActiveSubscriptionInfoList() has checked whether the
+ // sim visible by the SubscriptionManager.isSubscriptionVisible(), here only checks whether
+ // the esim visible here.
+ return subscriptions.stream()
+ .filter(subInfo -> subInfo != null && isEmbeddedSubscriptionVisible(subInfo))
+ .collect(Collectors.toList());
}
/**
@@ -128,7 +139,7 @@
}
/**
- * Get subscription which is available to be displayed to the user
+ * Get subscriptionInfo which is available to be displayed to the user
* per subscription id.
*
* @param context {@code Context}
@@ -138,13 +149,20 @@
* @return {@code SubscriptionInfo} based on the given subscription id. Null of subscription
* is invalid or not allowed to be displayed to the user.
*/
- public static SubscriptionInfo getAvailableSubscription(Context context,
+ public static SubscriptionInfo getAvailableSubscriptionBySubIdAndShowingForUser(Context context,
ProxySubscriptionManager subscriptionManager, int subId) {
+ //TODO (b/315499317) : Refactor the subscription utils.
final SubscriptionInfo subInfo = subscriptionManager.getAccessibleSubscriptionInfo(subId);
if (subInfo == null) {
return null;
}
+ // hide provisioning/bootstrap and satellite profiles for user
+ if (!isEmbeddedSubscriptionVisible(subInfo)) {
+ Log.d(TAG, "Do not insert the provision eSIM or NTN eSim");
+ return null;
+ }
+
final ParcelUuid groupUuid = subInfo.getGroupUuid();
if (groupUuid != null) {
@@ -567,6 +585,12 @@
public static boolean isSubscriptionVisible(
SubscriptionManager subscriptionManager, Context context, SubscriptionInfo info) {
if (info == null) return false;
+
+ // hide provisioning/bootstrap and satellite profiles for user
+ if (!isEmbeddedSubscriptionVisible(info)) {
+ return false;
+ }
+
// If subscription is NOT grouped opportunistic subscription, it's visible.
if (info.getGroupUuid() == null || !info.isOpportunistic()) return true;
@@ -786,4 +810,14 @@
}
return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo();
}
+
+ private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded()
+ && (subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING
+ || (Flags.oemEnabledSatelliteFlag()
+ && subInfo.isOnlyNonTerrestrialNetwork()))) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java
index 0c3e6bd..6601828 100644
--- a/src/com/android/settings/network/SubscriptionsPreferenceController.java
+++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java
@@ -521,7 +521,7 @@
* Uses to inject function and value for class and test class.
*/
public boolean canSubscriptionBeDisplayed(Context context, int subId) {
- return (SubscriptionUtil.getAvailableSubscription(context,
+ return (SubscriptionUtil.getAvailableSubscriptionBySubIdAndShowingForUser(context,
ProxySubscriptionManager.getInstance(context), subId) != null);
}
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index bb19c59..52066a1 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -39,7 +39,6 @@
import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LocalNavController
-import com.android.settingslib.spa.framework.compose.OnBackEffect
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
@@ -98,29 +97,35 @@
val networkTypeSelectedOptionsState = remember {
getNetworkTypeSelectedOptionsState(apnData.networkType)
}
- OnBackEffect{
- validateAndSaveApnData(
- apnDataInit,
- apnData,
- context,
- uriInit,
- networkTypeSelectedOptionsState
- )
- }
val navController = LocalNavController.current
RegularScaffold(
- title = if(apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
+ title = if (apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
+ actions = {
+ IconButton(onClick = {
+ if (!apnData.validEnabled) apnData = apnData.copy(validEnabled = true)
+ val valid = validateAndSaveApnData(
+ apnDataInit,
+ apnData,
+ context,
+ uriInit,
+ networkTypeSelectedOptionsState
+ )
+ if (valid) navController.navigateBack()
+ }) { Icon(imageVector = Icons.Outlined.Done, contentDescription = null) }
+ },
) {
Column {
SettingsOutlinedTextField(
value = apnData.name,
label = stringResource(R.string.apn_name),
- enabled = apnData.nameEnabled
+ enabled = apnData.nameEnabled,
+ errorMessage = validateName(apnData.validEnabled, apnData.name, context)
) { apnData = apnData.copy(name = it) }
SettingsOutlinedTextField(
value = apnData.apn,
label = stringResource(R.string.apn_apn),
- enabled = apnData.apnEnabled
+ enabled = apnData.apnEnabled,
+ errorMessage = validateAPN(apnData.validEnabled, apnData.apn, context)
) { apnData = apnData.copy(apn = it) }
SettingsOutlinedTextField(
value = apnData.proxy,
@@ -150,7 +155,7 @@
SettingsOutlinedTextField(
value = apnData.mmsc,
label = stringResource(R.string.apn_mmsc),
- errorMessage = validateMMSC(apnData.mmsc, context),
+ errorMessage = validateMMSC(apnData.validEnabled, apnData.mmsc, context),
enabled = apnData.mmscEnabled
) { apnData = apnData.copy(mmsc = it) }
SettingsOutlinedTextField(
@@ -172,7 +177,11 @@
SettingsOutlinedTextField(
value = apnData.apnType,
label = stringResource(R.string.apn_type),
- enabled = apnData.apnTypeEnabled
+ enabled = apnData.apnTypeEnabled,
+ errorMessage = validateAPNType(
+ apnData.validEnabled, apnData.apnType,
+ apnData.customizedConfig.readOnlyApnTypes, context
+ )
) { apnData = apnData.copy(apnType = updateApnType(apnData.copy(apnType = it))) }
SettingsExposedDropdownMenuBox(
label = stringResource(R.string.apn_protocol),
@@ -209,7 +218,6 @@
override val title = stringResource(R.string.menu_delete)
override val onClick = {
deleteApn(uriInit, context)
- apnData = apnData.copy(saveEnabled = false)
navController.navigateBack()
}
}
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index 668ea9b..38c6684 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -69,7 +69,7 @@
val networkTypeEnabled: Boolean = true,
val newApn: Boolean = false,
val subId: Int = -1,
- val saveEnabled: Boolean = true,
+ val validEnabled: Boolean = false,
val customizedConfig: CustomizedConfig = CustomizedConfig()
) {
fun getContentValues(context: Context): ContentValues {
@@ -96,7 +96,8 @@
values.put(Telephony.Carriers.EDITED_STATUS, Telephony.Carriers.USER_EDITED)
if (newApn) {
val simCarrierId =
- context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+ context.getSystemService(TelephonyManager::class.java)!!
+ .createForSubscriptionId(subId)
.getSimCarrierId()
values.put(Telephony.Carriers.CARRIER_ID, simCarrierId)
}
@@ -231,22 +232,12 @@
uriInit: Uri,
networkTypeSelectedOptionsState: SnapshotStateList<Int>
): Boolean {
- // Can not be saved
- if (!apnData.saveEnabled) {
- return false
- }
// Nothing to do if it's a read only APN
if (apnData.customizedConfig.readOnlyApn) {
return true
}
- var errorMsg = validateApnData(apnData, context)
+ val errorMsg = validateApnData(apnData, context)
if (errorMsg != null) {
- //TODO: showError(this)
- return false
- }
- errorMsg = validateMMSC(apnData.mmsc, context)
- if (errorMsg != null) {
- //TODO: showError(this)
return false
}
val newApnData = apnData.copy(networkType = getNetworkType(networkTypeSelectedOptionsState))
@@ -268,37 +259,23 @@
* @return An error message if the apn data is invalid, otherwise return null.
*/
fun validateApnData(apnData: ApnData, context: Context): String? {
- var errorMsg: String? = null
+ var errorMsg: String?
val name = apnData.name
val apn = apnData.apn
- if (name == "") {
- errorMsg = context.resources.getString(R.string.error_name_empty)
+ errorMsg = if (name == "") {
+ context.resources.getString(R.string.error_name_empty)
} else if (apn == "") {
- errorMsg = context.resources.getString(R.string.error_apn_empty)
+ context.resources.getString(R.string.error_apn_empty)
+ } else {
+ validateMMSC(apnData.validEnabled, apnData.mmsc, context)
}
if (errorMsg == null) {
- // if carrier does not allow editing certain apn types, make sure type does not include
- // those
- if (!ArrayUtils.isEmpty(apnData.customizedConfig.readOnlyApnTypes)
- && apnTypesMatch(
- apnData.customizedConfig.readOnlyApnTypes,
- getUserEnteredApnType(apnData.apnType, apnData.customizedConfig.readOnlyApnTypes)
- )
- ) {
- val stringBuilder = StringBuilder()
- for (type in apnData.customizedConfig.readOnlyApnTypes) {
- stringBuilder.append(type).append(", ")
- Log.d(TAG, "validateApnData: appending type: $type")
- }
- // remove last ", "
- if (stringBuilder.length >= 2) {
- stringBuilder.delete(stringBuilder.length - 2, stringBuilder.length)
- }
- errorMsg = String.format(
- context.resources.getString(R.string.error_adding_apn_type),
- stringBuilder
- )
- }
+ errorMsg = validateAPNType(
+ apnData.validEnabled,
+ apnData.apnType,
+ apnData.customizedConfig.readOnlyApnTypes,
+ context
+ )
}
return errorMsg
}
@@ -536,7 +513,39 @@
contentResolver.delete(uri, null, null)
}
-fun validateMMSC(mmsc: String, context: Context): String? {
- return if (mmsc.matches(Regex("^https?:\\/\\/.+"))) null
- else context.resources.getString(R.string.error_mmsc_valid)
+fun validateMMSC(validEnabled: Boolean, mmsc: String, context: Context): String? {
+ return if (validEnabled && !mmsc.matches(Regex("^https?:\\/\\/.+")))
+ context.resources.getString(R.string.error_mmsc_valid)
+ else null
+}
+
+fun validateName(validEnabled: Boolean, name: String, context: Context): String? {
+ return if (validEnabled && (name == "")) context.resources.getString(R.string.error_name_empty)
+ else null
+}
+
+fun validateAPN(validEnabled: Boolean, apn: String, context: Context): String? {
+ return if (validEnabled && (apn == "")) context.resources.getString(R.string.error_apn_empty)
+ else null
+}
+
+fun validateAPNType(
+ validEnabled: Boolean,
+ apnType: String,
+ readOnlyApnTypes: List<String>,
+ context: Context
+): String? {
+ // if carrier does not allow editing certain apn types, make sure type does not include those
+ if (validEnabled && !ArrayUtils.isEmpty(readOnlyApnTypes)
+ && apnTypesMatch(
+ readOnlyApnTypes,
+ getUserEnteredApnType(apnType, readOnlyApnTypes)
+ )
+ ) {
+ return String.format(
+ context.resources.getString(R.string.error_adding_apn_type),
+ readOnlyApnTypes.joinToString(", ")
+ )
+ }
+ return null
}
\ No newline at end of file
diff --git a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java b/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
deleted file mode 100644
index 420f6db..0000000
--- a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 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.helper;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.provider.Settings;
-
-import com.android.settings.R;
-
-import java.util.function.Predicate;
-
-/**
- * {@link Predicate} for detecting the configuration of confirm SIM deletion.
- */
-public class ConfirmationSimDeletionPredicate implements Predicate<Context> {
-
- public static final String KEY_CONFIRM_SIM_DELETION = "confirm_sim_deletion";
-
- private static final ConfirmationSimDeletionPredicate sSingleton =
- new ConfirmationSimDeletionPredicate();
-
- // Get singleton of this predicate
- public static final ConfirmationSimDeletionPredicate getSingleton() {
- return sSingleton;
- }
-
- /**
- * Get default configuration of confirm SIM deletion.
- *
- * @param Context context
- * @return the configuration of confirm SIM deletion
- */
- private static boolean getDefaultValue(Context context) {
- return context.getResources()
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
- }
-
- /**
- * Get the configuration of confirm SIM deletion.
- *
- * @param Context context
- * @return the configuration of confirm SIM deletion
- */
- public boolean test(Context context) {
- final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
- if ((keyguardManager != null) && !keyguardManager.isKeyguardSecure()) {
- return false;
- }
- return Settings.Global.getInt(context.getContentResolver(), KEY_CONFIRM_SIM_DELETION,
- getDefaultValue(context) ? 1 : 0) == 1;
- }
-}
diff --git a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
index a17144f..799543f 100644
--- a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
+++ b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
@@ -43,6 +43,7 @@
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.settings.network.MobileNetworkRepository;
+import com.android.settings.network.SubscriptionUtil;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
@@ -98,10 +99,21 @@
@Override
public int getAvailabilityStatus(int subId) {
- // TODO(b/262195754): Need the intent to enabled the feature.
+ // TODO(b/315073761) : Add a new API to set whether the profile has been
+ // converted/transferred. Remove any confusion to the user according to the set value.
+
+ /*
+ * If pSIM is set to preferred SIM and there is an active eSIM, convert the pSIM to eSIM
+ * and then disable the pSIM.
+ * This causes a dialog to switch the preferred SIM to downloaded new eSIM.
+ * This may cause confusion for the user about the seamless conversion.
+ * To avoid showing users dialogs that can cause confusion,
+ * add conditions to allow conversion in the absence of active eSIM.
+ */
if (findConversionSupportComponent()) {
return mSubscriptionInfoEntity != null && mSubscriptionInfoEntity.isActiveSubscriptionId
&& !mSubscriptionInfoEntity.isEmbedded && isActiveSubscription(subId)
+ && !hasActiveEsimProfiles()
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@@ -135,7 +147,6 @@
@Override
public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
- // TODO(b/262195754): Need the intent to enabled the feature.
mSubscriptionInfoEntityList = subInfoEntityList;
mSubscriptionInfoEntityList.forEach(entity -> {
if (Integer.parseInt(entity.subId) == mSubId) {
@@ -155,6 +166,24 @@
return true;
}
+ private boolean hasActiveEsimProfiles() {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ List<SubscriptionInfo> subscriptionInfoList =
+ SubscriptionUtil.getActiveSubscriptions(subscriptionManager);
+ if (subscriptionInfoList == null || subscriptionInfoList.isEmpty()) {
+ return false;
+ }
+ int activatedEsimCount = (int) subscriptionInfoList
+ .stream()
+ .filter(SubscriptionInfo::isEmbedded)
+ .count();
+ if (activatedEsimCount > 0) {
+ return true;
+ }
+ return false;
+ }
+
private boolean findConversionSupportComponent() {
Intent intent = new Intent(EuiccService.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION);
PackageManager packageManager = mContext.getPackageManager();
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 093c4bf..64f9730 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -26,10 +26,8 @@
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION
import com.android.settings.wifi.dpp.WifiDppUtils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
/** This controls a preference allowing the user to delete the profile for an eSIM. */
class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
@@ -63,16 +61,8 @@
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
if (preference.key != preferenceKey) return false
- val confirmDeletion by mContext.settingsGlobalBoolean(
- name = KEY_CONFIRM_SIM_DELETION,
- defaultValue = mContext.resources
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on),
- )
- if (confirmDeletion) {
- WifiDppUtils.showLockScreen(mContext) { deleteSim() }
- } else {
- deleteSim()
- }
+ WifiDppUtils.showLockScreen(mContext) { deleteSim() }
+
return true
}
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index a0db4ce..2a299c5 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -43,6 +43,7 @@
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.util.Log;
import android.view.WindowManager;
@@ -292,7 +293,7 @@
// use, which optionally accepts a challenge.
mForceVerifyPath = true;
if (isBiometricAllowed(effectiveUserId, mUserId)) {
- showBiometricPrompt(promptInfo);
+ showBiometricPrompt(promptInfo, mUserId);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -302,19 +303,25 @@
&& userProperties != null
&& userProperties.isAuthAlwaysRequiredToDisableQuietMode()
&& isInternalActivity()) {
- // Force verification path is required to be invoked as we might need to verify the tied
- // profile challenge if the profile is using the unified challenge mode. This would
- // result in ConfirmLockPassword.startVerifyPassword/
+ // Force verification path is required to be invoked as we might need to verify the
+ // tied profile challenge if the profile is using the unified challenge mode. This
+ // would result in ConfirmLockPassword.startVerifyPassword/
// ConfirmLockPattern.startVerifyPattern being called instead of the
// startCheckPassword/startCheckPattern
mForceVerifyPath = userProperties.isCredentialShareableWithParent();
- showConfirmCredentials();
- launchedCDC = true;
+ if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && isBiometricAllowed(effectiveUserId, mUserId)) {
+ showBiometricPrompt(promptInfo, effectiveUserId);
+ launchedBiometric = true;
+ } else {
+ showConfirmCredentials();
+ launchedCDC = true;
+ }
} else {
if (isBiometricAllowed(effectiveUserId, mUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
- showBiometricPrompt(promptInfo);
+ showBiometricPrompt(promptInfo, mUserId);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -400,7 +407,19 @@
// biometric is disabled due to device restart.
private boolean isStrongAuthRequired(int effectiveUserId) {
return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
- || !mUserManager.isUserUnlocked(mUserId);
+ || doesUserStateEnforceStrongAuth(mUserId);
+ }
+
+ private boolean doesUserStateEnforceStrongAuth(int userId) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ // Check if CE storage for user is locked since biometrics can't unlock fbe/keystore of
+ // the profile user using verifyTiedProfileChallenge. Biometrics can still be used if
+ // the user is stopped with delayed locking (i.e., with storage unlocked), so the user
+ // state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon.
+ return !StorageManager.isUserKeyUnlocked(userId);
+ }
+ return !mUserManager.isUserUnlocked(userId);
}
private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
@@ -408,7 +427,7 @@
.hasPendingEscrowToken(realUserId);
}
- private void showBiometricPrompt(PromptInfo promptInfo) {
+ private void showBiometricPrompt(PromptInfo promptInfo, int userId) {
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
boolean newFragment = false;
@@ -418,7 +437,9 @@
newFragment = true;
}
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
- mBiometricFragment.setUser(mUserId);
+ // TODO(b/315864564): Move the logic of choosing the user id against which the
+ // authentication needs to happen to the BiometricPrompt API
+ mBiometricFragment.setUser(userId);
if (newFragment) {
getSupportFragmentManager().beginTransaction()
diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
index 3ec7c92..b8f140f 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
@@ -18,6 +18,7 @@
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -34,6 +35,7 @@
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.GuardedBy;
@@ -95,6 +97,7 @@
IActivityManager am = ActivityManager.getService();
try {
+ //TODO(b/313926659): To check and handle failure of startProfile
am.startProfile(mUserHandle.getIdentifier());
} catch (RemoteException e) {
Log.e(TAG, "Failed to start private profile");
@@ -103,6 +106,7 @@
Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
resetPrivateSpaceSettings();
+ setUserSetupComplete();
}
return true;
}
@@ -226,7 +230,37 @@
HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
}
+ /**
+ * Returns true if private space exists and quiet mode is successfully enabled, otherwise
+ * returns false
+ */
+ public synchronized boolean lockPrivateSpace() {
+ if (isPrivateProfileRunning()) {
+ return mUserManager.requestQuietModeEnabled(true, mUserHandle);
+ }
+ return false;
+ }
+
+ /** Returns true if private space exists and is running, otherwise returns false */
+ @VisibleForTesting
+ synchronized boolean isPrivateProfileRunning() {
+ if (doesPrivateSpaceExist() && mUserHandle != null) {
+ return mUserManager.isUserRunning(mUserHandle);
+ }
+ return false;
+ }
+
private void resetPrivateSpaceSettings() {
setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
}
+
+ /**
+ * Sets the USER_SETUP_COMPLETE for private profile on which device theme is applied to the
+ * profile.
+ */
+ @GuardedBy("this")
+ private void setUserSetupComplete() {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(), USER_SETUP_COMPLETE,
+ 1, mUserHandle.getIdentifier());
+ }
}
diff --git a/src/com/android/settings/privatespace/SetupSuccessFragment.java b/src/com/android/settings/privatespace/SetupSuccessFragment.java
index 0b1b9d9..ebeae7a 100644
--- a/src/com/android/settings/privatespace/SetupSuccessFragment.java
+++ b/src/com/android/settings/privatespace/SetupSuccessFragment.java
@@ -84,6 +84,8 @@
if (activity != null) {
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_DONE);
+ //TODO(b/307729746): Add a test to verify PS is locked after setup completion.
+ PrivateSpaceMaintainer.getInstance(activity).lockPrivateSpace();
Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
ResolveInfo resolveInfo =
activity.getPackageManager()
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
index 581eb2e..d21d983 100644
--- a/src/com/android/settings/search/BaseSearchIndexProvider.java
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -136,7 +136,7 @@
try {
controllersFromCode = createPreferenceControllers(context);
} catch (Exception e) {
- Log.w(TAG, "Error initial controller");
+ Log.w(TAG, "Error initializing controller in fragment: " + this + ", e: " + e);
}
final List<SearchIndexableResource> res = getXmlResourcesToIndex(context, true);
diff --git a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java b/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
deleted file mode 100644
index b6b3608..0000000
--- a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2020 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.security;
-
-import android.app.KeyguardManager;
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.wifi.dpp.WifiDppUtils;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-/** Enable/disable user confirmation before deleting an eSim */
-public class ConfirmSimDeletionPreferenceController extends BasePreferenceController implements
- Preference.OnPreferenceChangeListener{
- public static final String KEY_CONFIRM_SIM_DELETION =
- ConfirmationSimDeletionPredicate.KEY_CONFIRM_SIM_DELETION;
- private boolean mConfirmationDefaultOn;
- private MetricsFeatureProvider mMetricsFeatureProvider;
- private UserManager mUserManager;
- private KeyguardManager mKeyguardManager;
-
- public ConfirmSimDeletionPreferenceController(Context context, String key) {
- super(context, key);
- mConfirmationDefaultOn =
- context.getResources()
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
- mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-
- mUserManager = context.getSystemService(UserManager.class);
- mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
- }
-
- @Override
- public int getAvailabilityStatus() {
- // hide if eSim is not supported on the device
- return (!MobileNetworkUtils.isMobileNetworkUserRestricted(mContext)) &&
- MobileNetworkUtils.showEuiccSettings(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- private boolean getGlobalState() {
- return Settings.Global.getInt(
- mContext.getContentResolver(),
- KEY_CONFIRM_SIM_DELETION,
- mConfirmationDefaultOn ? 1 : 0)
- == 1;
- }
-
- public boolean isChecked() {
- return getGlobalState();
- }
-
- public boolean setChecked(boolean isChecked) {
- Settings.Global.putInt(
- mContext.getContentResolver(), KEY_CONFIRM_SIM_DELETION, isChecked ? 1 : 0);
- return true;
- }
-
- // handle UI change
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (!preference.getKey().equals(getPreferenceKey())) {
- return false;
- }
- if (!isChecked()) {
- mMetricsFeatureProvider.action(mContext,
- SettingsEnums.ACTION_CONFIRM_SIM_DELETION_ON);
- setChecked(true);
- return true;
- } else {
- // prevent disabling the feature until authorized
- WifiDppUtils.showLockScreen(mContext, () -> {
- mMetricsFeatureProvider.action(mContext,
- SettingsEnums.ACTION_CONFIRM_SIM_DELETION_OFF);
- // set data
- setChecked(false);
- // set UI
- ((TwoStatePreference) preference).setChecked(false);
- });
- return false;
- }
- }
-
- @Override
- public void updateState(Preference preference) {
- if (!mKeyguardManager.isKeyguardSecure() && mUserManager.isGuestUser()) {
- preference.setEnabled(false);
- if (preference instanceof TwoStatePreference) {
- ((TwoStatePreference) preference).setChecked(false);
- }
- preference.setSummary(R.string.disabled_because_no_backup_security);
- } else {
- preference.setEnabled(true);
- if (preference instanceof TwoStatePreference) {
- ((TwoStatePreference) preference).setChecked(getGlobalState());
- }
- preference.setSummary(R.string.confirm_sim_deletion_description);
- }
- }
-}
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 2b02999..5145f18 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -29,6 +29,8 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -172,7 +174,8 @@
/**
* Notify that data was updated and attempt to sync changes to the Slice.
*/
- protected final void notifySliceChange() {
+ @VisibleForTesting
+ public final void notifySliceChange() {
NotifySliceChangeHandler.getInstance().updateSlice(this);
}
diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt
index e5bee8b..5eade81 100644
--- a/src/com/android/settings/spa/SpaActivity.kt
+++ b/src/com/android/settings/spa/SpaActivity.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.content.Intent
+import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin
import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.util.SESSION_BROWSE
@@ -31,6 +33,11 @@
override fun isPageEnabled(page: SettingsPage) =
super.isPageEnabled(page) && !isSuwAndPageBlocked(page.sppName)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ lifecycle.addObserver(HideNonSystemOverlayMixin(this))
+ }
+
companion object {
private const val TAG = "SpaActivity"
diff --git a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
index c707b44b..af4fc17 100644
--- a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
@@ -49,7 +49,7 @@
@Composable
fun AppBatteryPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { AppBatteryPresenter(context, app) }
+ val presenter = remember(app) { AppBatteryPresenter(context, app) }
if (!presenter.isAvailable()) return
Preference(object : PreferenceModel {
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index f6fafd7..403263c 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -53,6 +53,7 @@
private val appClearButton = AppClearButton(packageInfoPresenter)
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
private val appArchiveButton = AppArchiveButton(packageInfoPresenter)
+ private val appRestoreButton = AppRestoreButton(packageInfoPresenter)
@Composable
fun getActionButtons() =
@@ -63,7 +64,11 @@
@Composable
private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
if (featureFlags.archiving()) {
- appArchiveButton.getActionButton(app)
+ if (app.isArchived) {
+ appRestoreButton.getActionButton(app)
+ } else {
+ appArchiveButton.getActionButton(app)
+ }
} else {
appLaunchButton.getActionButton(app)
},
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
index 057f911..7e6e726 100644
--- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -59,7 +59,7 @@
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
- val presenter = remember {
+ val presenter = remember(app) {
AppDataUsagePresenter(context, app, coroutineScope, networkTemplates, repositoryFactory)
}
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
index de6bd10..91c3887 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
@@ -39,7 +39,7 @@
@Composable
fun rememberAppPermissionSummary(app: ApplicationInfo): AppPermissionSummaryLiveData {
val context = LocalContext.current
- return remember { AppPermissionSummaryLiveData(context, app) }
+ return remember(app) { AppPermissionSummaryLiveData(context, app) }
}
class AppPermissionSummaryLiveData(
@@ -55,7 +55,11 @@
override fun onActive() {
userPackageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
- update()
+ if (app.isArchived) {
+ postValue(noPermissionRequestedState())
+ } else {
+ update()
+ }
}
override fun onInactive() {
diff --git a/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
new file mode 100644
index 0000000..c47fdac
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.os.UserHandle
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.runtime.Composable
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+
+class AppRestoreButton(packageInfoPresenter: PackageInfoPresenter) {
+ private companion object {
+ private const val LOG_TAG = "AppRestoreButton"
+ private const val INTENT_ACTION = "com.android.settings.unarchive.action"
+ }
+
+ private val context = packageInfoPresenter.context
+ private val userPackageManager = packageInfoPresenter.userPackageManager
+ private val packageInstaller = userPackageManager.packageInstaller
+ private val packageName = packageInfoPresenter.packageName
+ private val userHandle = UserHandle.of(packageInfoPresenter.userId)
+ private var broadcastReceiverIsCreated = false
+
+ @Composable
+ fun getActionButton(app: ApplicationInfo): ActionButton {
+ if (!broadcastReceiverIsCreated) {
+ val intentFilter = IntentFilter(INTENT_ACTION)
+ DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
+ if (app.packageName == intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)) {
+ onReceive(intent, app)
+ }
+ }
+ broadcastReceiverIsCreated = true
+ }
+ return ActionButton(
+ text = context.getString(R.string.restore),
+ imageVector = Icons.Outlined.CloudDownload,
+ enabled = app.isArchived
+ ) { onRestoreClicked(app) }
+ }
+
+ private fun onRestoreClicked(app: ApplicationInfo) {
+ val intent = Intent(INTENT_ACTION)
+ intent.setPackage(context.packageName)
+ val pendingIntent = PendingIntent.getBroadcastAsUser(
+ context, 0, intent,
+ // FLAG_MUTABLE is required by PackageInstaller#requestUnarchive
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE,
+ userHandle
+ )
+ try {
+ packageInstaller.requestUnarchive(app.packageName, pendingIntent.intentSender)
+ val appLabel = userPackageManager.getApplicationLabel(app)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_in_progress, appLabel),
+ Toast.LENGTH_SHORT
+ ).show()
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Request unarchive failed", e)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ private fun onReceive(intent: Intent, app: ApplicationInfo) {
+ when (val unarchiveStatus =
+ intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, Int.MIN_VALUE)) {
+ PackageInstaller.STATUS_PENDING_USER_ACTION -> {
+ Log.e(
+ LOG_TAG,
+ "Request unarchiving failed for $packageName with code $unarchiveStatus"
+ )
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ PackageInstaller.STATUS_SUCCESS -> {
+ val appLabel = userPackageManager.getApplicationLabel(app)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_succeeded, appLabel),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ else -> {
+ Log.e(
+ LOG_TAG,
+ "Request unarchiving failed for $packageName with code $unarchiveStatus"
+ )
+ val errorDialogIntent =
+ intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ if (errorDialogIntent != null) {
+ context.startActivityAsUser(errorDialogIntent, userHandle)
+ } else {
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
index 7ba61dc..837df67 100644
--- a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
@@ -40,7 +40,7 @@
@Composable
fun AppTimeSpentPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { AppTimeSpentPresenter(context, app) }
+ val presenter = remember(app) { AppTimeSpentPresenter(context, app) }
if (!presenter.isAvailable()) return
val summary by presenter.summaryLiveData.observeAsState(
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index 78ca15b..324fa06 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -22,6 +22,7 @@
import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.os.Build
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN
@@ -53,15 +54,24 @@
@Composable
fun HibernationSwitchPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { HibernationSwitchPresenter(context, app) }
+ val presenter = remember(app) { HibernationSwitchPresenter(context, app) }
if (!presenter.isAvailable()) return
val isEligibleState by presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null)
SwitchPreference(remember {
object : SwitchPreferenceModel {
- override val title = context.getString(R.string.unused_apps_switch)
- override val summary = { context.getString(R.string.unused_apps_switch_summary) }
+ override val title =
+ if (isArchivingEnabled())
+ context.getString(R.string.unused_apps_switch_v2)
+ else
+ context.getString(R.string.unused_apps_switch)
+ override val summary = {
+ if (isArchivingEnabled())
+ context.getString(R.string.unused_apps_switch_summary_v2)
+ else
+ context.getString(R.string.unused_apps_switch_summary)
+ }
override val changeable = { isEligibleState }
override val checked = { if (changeable()) isCheckedState.value else false }
override val onCheckedChange = presenter::onCheckedChange
@@ -69,6 +79,9 @@
})
}
+private fun isArchivingEnabled() =
+ Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
private class HibernationSwitchPresenter(context: Context, private val app: ApplicationInfo) {
private val appOpsManager = context.appOpsManager
private val permissionControllerManager =
@@ -80,6 +93,10 @@
DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true)
val isEligibleFlow = flow {
+ if (app.isArchived) {
+ emit(false)
+ return@flow
+ }
val eligibility = getEligibility()
emit(
eligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM &&
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
index 06dd42b..e5964d0 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
@@ -17,20 +17,29 @@
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
+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;
@@ -42,6 +51,7 @@
import com.android.settings.R;
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
@@ -51,9 +61,12 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidInfo;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -63,42 +76,46 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.concurrent.Executor;
/** Tests for {@link AvailableMediaDeviceGroupController}. */
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
- ShadowAudioManager.class,
- ShadowBluetoothAdapter.class,
- ShadowBluetoothUtils.class,
- ShadowAlertDialogCompat.class,
-})
+@Config(
+ shadows = {
+ ShadowAudioManager.class,
+ ShadowBluetoothAdapter.class,
+ ShadowBluetoothUtils.class,
+ ShadowAlertDialogCompat.class,
+ })
public class AvailableMediaDeviceGroupControllerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
private static final String PREFERENCE_KEY_1 = "pref_key_1";
- @Mock
- private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater;
- @Mock
- private PreferenceScreen mPreferenceScreen;
+ @Mock private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater;
+ @Mock private PreferenceScreen mPreferenceScreen;
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceManager mPreferenceManager;
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private BluetoothEventManager mEventManager;
- @Mock
- private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
- private CachedBluetoothDeviceManager mCachedDeviceManager;
- @Mock
- private CachedBluetoothDevice mCachedBluetoothDevice;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private BluetoothEventManager mEventManager;
+ @Mock private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
+ @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
+ @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
private PreferenceGroup mPreferenceGroup;
private Context mContext;
private Preference mPreference;
private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController;
private AudioManager mAudioManager;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@Before
public void setUp() {
@@ -113,19 +130,26 @@
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
+ mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ mShadowBluetoothAdapter.setEnabled(true);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
mAudioManager = mContext.getSystemService(AudioManager.class);
doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
- when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn(
- mCachedBluetoothDevice);
+ when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class)))
+ .thenReturn(mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
- mAvailableMediaDeviceGroupController = spy(
- new AvailableMediaDeviceGroupController(mContext));
- mAvailableMediaDeviceGroupController.
- setBluetoothDeviceUpdater(mAvailableMediaBluetoothDeviceUpdater);
+ mAvailableMediaDeviceGroupController =
+ spy(new AvailableMediaDeviceGroupController(mContext));
+ mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater(
+ mAvailableMediaBluetoothDeviceUpdater);
mAvailableMediaDeviceGroupController.setFragmentManager(
mActivity.getSupportFragmentManager());
mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
@@ -176,34 +200,55 @@
mAvailableMediaDeviceGroupController.onStart();
verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback();
- verify(mLocalBluetoothManager.getEventManager()).registerCallback(
- any(BluetoothCallback.class));
+ verify(mLocalBluetoothManager.getEventManager())
+ .registerCallback(any(BluetoothCallback.class));
verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference();
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ public void testRegister_audioSharingOn() {
+ setUpBroadcast();
+ // register the callback in onStart()
+ mAvailableMediaDeviceGroupController.onStart();
+ verify(mAssistant)
+ .registerServiceCallBack(
+ any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+ }
+
+ @Test
public void testUnregister() {
// unregister the callback in onStop()
mAvailableMediaDeviceGroupController.onStop();
verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback();
- verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
- any(BluetoothCallback.class));
+ verify(mLocalBluetoothManager.getEventManager())
+ .unregisterCallback(any(BluetoothCallback.class));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ public void testUnregister_audioSharingOn() {
+ setUpBroadcast();
+ // unregister the callback in onStop()
+ mAvailableMediaDeviceGroupController.onStop();
+ verify(mAssistant)
+ .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
}
@Test
public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
- assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo(
- UNSUPPORTED_ON_DEVICE);
+ assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
+ .isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void testGetAvailabilityStatus_BluetoothFeature_returnSupported() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
- assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo(
- AVAILABLE_UNSEARCHABLE);
+ assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus())
+ .isEqualTo(AVAILABLE_UNSEARCHABLE);
}
@Test
@@ -211,8 +256,8 @@
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mAvailableMediaDeviceGroupController.onAudioModeChanged();
- assertThat(mPreferenceGroup.getTitle()).isEqualTo(
- mContext.getText(R.string.connected_device_call_device_title));
+ assertThat(mPreferenceGroup.getTitle())
+ .isEqualTo(mContext.getText(R.string.connected_device_call_device_title));
}
@Test
@@ -220,8 +265,8 @@
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAvailableMediaDeviceGroupController.onAudioModeChanged();
- assertThat(mPreferenceGroup.getTitle()).isEqualTo(
- mContext.getText(R.string.connected_device_media_device_title));
+ assertThat(mPreferenceGroup.getTitle())
+ .isEqualTo(mContext.getText(R.string.connected_device_media_device_title));
}
@Test
@@ -243,16 +288,31 @@
@Test
public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() {
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
- when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
- HearingAidInfo.DeviceMode.MODE_BINAURAL);
- when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
- HearingAidInfo.DeviceSide.SIDE_LEFT);
+ when(mCachedBluetoothDevice.getDeviceMode())
+ .thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ when(mCachedBluetoothDevice.getDeviceSide())
+ .thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT);
- mAvailableMediaDeviceGroupController.onActiveDeviceChanged(mCachedBluetoothDevice,
- BluetoothProfile.HEARING_AID);
+ mAvailableMediaDeviceGroupController.onActiveDeviceChanged(
+ mCachedBluetoothDevice, BluetoothProfile.HEARING_AID);
shadowMainLooper().idle();
final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue();
}
+
+ private void setUpBroadcast() {
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+ doNothing()
+ .when(mAssistant)
+ .registerServiceCallBack(
+ any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
+ doNothing()
+ .when(mAssistant)
+ .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
index 86c724c..fa6cc6c 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
@@ -34,13 +34,13 @@
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -85,6 +85,7 @@
@Before
public void setUp() {
+ ShadowAlertDialogCompat.reset();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -97,11 +98,6 @@
mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
}
- @After
- public void tearDown() {
- ShadowAlertDialogCompat.reset();
- }
-
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void onCreateDialog_flagOff_dialogNotExist() {
@@ -154,7 +150,7 @@
Button shareBtn = rootView.findViewById(R.id.share_btn);
assertThat(dialog.isShowing()).isTrue();
assertThat(subtitle1.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(TEST_DEVICE_NAME1).isEqualTo(subtitle1.getText());
+ assertThat(subtitle1.getText().toString()).isEqualTo(TEST_DEVICE_NAME1);
assertThat(guidance.getVisibility()).isEqualTo(View.GONE);
assertThat(shareBtn.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -205,10 +201,13 @@
TextView subtitle1 = rootView.findViewById(R.id.share_audio_subtitle1);
ImageView guidance = rootView.findViewById(R.id.share_audio_guidance);
Button shareBtn = rootView.findViewById(R.id.share_btn);
+ RecyclerView recyclerView = rootView.findViewById(R.id.btn_list);
assertThat(dialog.isShowing()).isTrue();
assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
assertThat(guidance.getVisibility()).isEqualTo(View.GONE);
assertThat(shareBtn.getVisibility()).isEqualTo(View.GONE);
+ assertThat(recyclerView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(recyclerView.getAdapter().getItemCount()).isEqualTo(3);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
index 976e164..335bbe3 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
@@ -31,14 +31,13 @@
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.recyclerview.widget.RecyclerView;
-import com.android.internal.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -79,6 +78,7 @@
@Before
public void setUp() {
+ ShadowAlertDialogCompat.reset();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -96,16 +96,11 @@
shadowMainLooper().idle();
}
- @After
- public void tearDown() {
- ShadowAlertDialogCompat.reset();
- }
-
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void onCreateDialog_flagOff_dialogNotExist() {
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
+ assertThat(dialog).isNull();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
index 5eb0e8a..38f80e0 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
@@ -38,7 +38,6 @@
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -77,6 +76,7 @@
@Before
public void setUp() {
+ ShadowAlertDialogCompat.reset();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -89,11 +89,6 @@
mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
}
- @After
- public void tearDown() {
- ShadowAlertDialogCompat.reset();
- }
-
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void onCreateDialog_flagOff_dialogNotExist() {
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
index 1de7b2d..61bc88a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
@@ -35,7 +35,6 @@
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -70,6 +69,7 @@
@Before
public void setUp() {
+ ShadowAlertDialogCompat.reset();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -82,11 +82,6 @@
mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
}
- @After
- public void tearDown() {
- ShadowAlertDialogCompat.reset();
- }
-
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void onCreateDialog_flagOff_dialogNotExist() {
diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java
index 3df6449..17e0d1c 100644
--- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java
@@ -39,17 +39,24 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager2.widget.ViewPager2;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragmentTest;
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ViewPagerAdapter;
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.Before;
import org.junit.Rule;
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;
@@ -75,6 +82,9 @@
private TestProfileSelectFragment mFragment;
private FragmentActivity mActivity;
private ShadowUserManager mUserManager;
+ @Mock private FragmentManager mFragmentManager;
+ @Mock private Lifecycle mLifecycle;
+ @Mock private FragmentTransaction mFragmentTransaction;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
@@ -97,6 +107,16 @@
public void getTabId_setArgumentWork_setCorrectTab() {
final Bundle bundle = new Bundle();
bundle.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, WORK_TAB);
+ ViewPager2 viewPager = new ViewPager2(mContext);
+ TestProfileSelectFragment profileSelectFragment = new TestProfileSelectFragment();
+ ViewPagerAdapter viewPagerAdapter =
+ new TestViewPagerAdapter(mFragmentManager, mLifecycle, profileSelectFragment);
+
+ when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+ viewPager.setAdapter(viewPagerAdapter);
+ mFragment.setViewPager(viewPager);
+ profileSelectFragment.setViewPager(viewPager);
+ mFragmentManager.beginTransaction().add(profileSelectFragment, "tag");
assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(WORK_TAB);
}
@@ -105,6 +125,16 @@
public void getTabId_setArgumentPrivate_setCorrectTab() {
final Bundle bundle = new Bundle();
bundle.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, PRIVATE_TAB);
+ ViewPager2 viewPager = new ViewPager2(mContext);
+ TestProfileSelectFragment profileSelectFragment = new TestProfileSelectFragment();
+ ViewPagerAdapter viewPagerAdapter =
+ new TestViewPagerAdapter(mFragmentManager, mLifecycle, profileSelectFragment);
+
+ when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+ viewPager.setAdapter(viewPagerAdapter);
+ mFragment.setViewPager(viewPager);
+ profileSelectFragment.setViewPager(viewPager);
+ mFragmentManager.beginTransaction().add(profileSelectFragment, "tag");
assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(PRIVATE_TAB);
}
@@ -113,6 +143,16 @@
public void getTabId_setArgumentPersonal_setCorrectTab() {
final Bundle bundle = new Bundle();
bundle.putInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, PERSONAL_TAB);
+ ViewPager2 viewPager = new ViewPager2(mContext);
+ TestProfileSelectFragment profileSelectFragment = new TestProfileSelectFragment();
+ ViewPagerAdapter viewPagerAdapter =
+ new TestViewPagerAdapter(mFragmentManager, mLifecycle, profileSelectFragment);
+
+ when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+ viewPager.setAdapter(viewPagerAdapter);
+ mFragment.setViewPager(viewPager);
+ profileSelectFragment.setViewPager(viewPager);
+ mFragmentManager.beginTransaction().add(profileSelectFragment, "tag");
assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(PERSONAL_TAB);
}
@@ -310,4 +350,18 @@
};
}
}
+
+ static class TestViewPagerAdapter extends ViewPagerAdapter {
+ TestViewPagerAdapter(
+ @NonNull FragmentManager fragmentManager,
+ @NonNull Lifecycle lifecycle,
+ ProfileSelectFragment profileSelectFragment) {
+ super(fragmentManager, lifecycle, profileSelectFragment);
+ }
+
+ @Override
+ int getTabForPosition(int position) {
+ return position;
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
index d01d7e0..2f83da6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
@@ -49,7 +49,9 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
+import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
+import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowActivityManager;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
@@ -91,11 +93,10 @@
private static final String USAGE_PERCENT = "16%";
private static final int ICON_ID = 123;
private static final int UID = 1;
+ private static final long FOREGROUND_TIME_MS = 444;
+ private static final long FOREGROUND_SERVICE_TIME_MS = 123;
private static final long BACKGROUND_TIME_MS = 100;
- private static final long FOREGROUND_ACTIVITY_TIME_MS = 123;
- private static final long FOREGROUND_SERVICE_TIME_MS = 444;
- private static final long FOREGROUND_TIME_MS =
- FOREGROUND_ACTIVITY_TIME_MS + FOREGROUND_SERVICE_TIME_MS;
+ private static final long SCREEN_ON_TIME_MS = 321;
private static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -105,7 +106,6 @@
@Mock private LayoutPreference mHeaderPreference;
@Mock private ApplicationsState mState;
@Mock private ApplicationsState.AppEntry mAppEntry;
- @Mock private Bundle mBundle;
@Mock private BatteryEntry mBatteryEntry;
@Mock private PackageManager mPackageManager;
@Mock private InstallSourceInfo mInstallSourceInfo;
@@ -120,6 +120,8 @@
private SettingsActivity mTestActivity;
private FakeFeatureFactory mFeatureFactory;
private MetricsFeatureProvider mMetricsFeatureProvider;
+ private BatteryDiffEntry mBatteryDiffEntry;
+ private Bundle mBundle;
@Before
public void setUp() {
@@ -131,6 +133,7 @@
mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
mFragment = spy(new AdvancedPowerUsageDetail());
+ mBundle = spy(new Bundle());
doReturn(mContext).when(mFragment).getContext();
doReturn(mActivity).when(mFragment).getActivity();
doReturn(SUMMARY).when(mFragment).getString(anyInt());
@@ -163,10 +166,35 @@
when(mBatteryEntry.getUid()).thenReturn(UID);
when(mBatteryEntry.getLabel()).thenReturn(APP_LABEL);
- when(mBatteryEntry.getTimeInBackgroundMs()).thenReturn(BACKGROUND_TIME_MS);
when(mBatteryEntry.getTimeInForegroundMs()).thenReturn(FOREGROUND_TIME_MS);
+ when(mBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(FOREGROUND_SERVICE_TIME_MS);
+ when(mBatteryEntry.getTimeInBackgroundMs()).thenReturn(BACKGROUND_TIME_MS);
mBatteryEntry.mIconId = ICON_ID;
+ mBatteryDiffEntry =
+ spy(
+ new BatteryDiffEntry(
+ mContext,
+ /* uid= */ UID,
+ /* userId= */ 0,
+ /* key= */ "key",
+ /* isHidden= */ false,
+ /* componentId= */ -1,
+ /* legacyPackageName= */ null,
+ /* legacyLabel= */ null,
+ /*consumerType*/ ConvertUtils.CONSUMER_TYPE_USER_BATTERY,
+ /* foregroundUsageTimeInMs= */ FOREGROUND_TIME_MS,
+ /* foregroundSerUsageTimeInMs= */ FOREGROUND_SERVICE_TIME_MS,
+ /* backgroundUsageTimeInMs= */ BACKGROUND_TIME_MS,
+ /* screenOnTimeInMs= */ SCREEN_ON_TIME_MS,
+ /* consumePower= */ 0,
+ /* foregroundUsageConsumePower= */ 0,
+ /* foregroundServiceUsageConsumePower= */ 0,
+ /* backgroundUsageConsumePower= */ 0,
+ /* cachedUsageConsumePower= */ 0));
+ when(mBatteryDiffEntry.getAppLabel()).thenReturn(APP_LABEL);
+ when(mBatteryDiffEntry.getAppIconId()).thenReturn(ICON_ID);
+
mFragment.mHeaderPreference = mHeaderPreference;
mFragment.mState = mState;
mFragment.mBatteryOptimizeUtils = mBatteryOptimizeUtils;
@@ -191,6 +219,7 @@
.when(mActivity)
.startActivityAsUser(captor.capture(), nullable(UserHandle.class));
doAnswer(callable).when(mActivity).startActivity(captor.capture());
+ doAnswer(callable).when(mContext).startActivity(captor.capture());
mAllowBackgroundUsagePreference = new PrimarySwitchPreference(mContext);
mAllowBackgroundUsagePreference.setKey(KEY_ALLOW_BACKGROUND_USAGE);
@@ -256,6 +285,7 @@
@Test
public void startBatteryDetailPage_invalidToShowSummary_noFGBDData() {
+ mBundle.clear();
AdvancedPowerUsageDetail.startBatteryDetailPage(
mActivity, mFragment, mBatteryEntry, USAGE_PERCENT);
@@ -268,6 +298,35 @@
}
@Test
+ public void startBatteryDetailPage_showSummary_hasFGBDData() {
+ final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ mBundle.clear();
+ AdvancedPowerUsageDetail.startBatteryDetailPage(
+ mContext,
+ mFragment.getMetricsCategory(),
+ mBatteryDiffEntry,
+ USAGE_PERCENT,
+ /* slotInformation= */ null,
+ /* showTimeInformation= */ true,
+ /* anomalyHintPrefKey= */ null,
+ /* anomalyHintText= */ null);
+
+ verify(mContext).startActivity(captor.capture());
+ assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID);
+ assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME))
+ .isEqualTo(BACKGROUND_TIME_MS + FOREGROUND_SERVICE_TIME_MS);
+ assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME))
+ .isEqualTo(FOREGROUND_TIME_MS);
+ assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_SCREEN_ON_TIME))
+ .isEqualTo(SCREEN_ON_TIME_MS);
+ assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
+ .isEqualTo(USAGE_PERCENT);
+ assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_SLOT_TIME))
+ .isEqualTo(null);
+ }
+
+
+ @Test
public void startBatteryDetailPage_noBatteryUsage_hasBasicData() {
final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -292,6 +351,7 @@
@Test
public void startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName()
throws PackageManager.NameNotFoundException {
+ mBundle.clear();
doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */);
AdvancedPowerUsageDetail.startBatteryDetailPage(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
index ffe3c44..d351ca3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
@@ -207,6 +207,7 @@
batteryHistEntry.mAppLabel,
batteryHistEntry.mConsumerType,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0,
/* screenOnTimeInMs= */ 0,
consumePower,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
index d8b733c..6f1dce6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
@@ -111,8 +111,9 @@
/* legacyLabel= */ null,
/*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/* foregroundUsageTimeInMs= */ 10001L,
- /* backgroundUsageTimeInMs= */ 20002L,
- /* screenOnTimeInMs= */ 30003L,
+ /* foregroundServiceUsageTimeInMs= */ 20002L,
+ /* backgroundUsageTimeInMs= */ 30003L,
+ /* screenOnTimeInMs= */ 40004L,
/* consumePower= */ 22.0,
/* foregroundUsageConsumePower= */ 10.0,
/* foregroundServiceUsageConsumePower= */ 10.0,
@@ -137,8 +138,9 @@
/* legacyLabel= */ null,
/*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/* foregroundUsageTimeInMs= */ 10001L,
- /* backgroundUsageTimeInMs= */ 20002L,
- /* screenOnTimeInMs= */ 30003L,
+ /* foregroundServiceUsageTimeInMs= */ 20002L,
+ /* backgroundUsageTimeInMs= */ 30003L,
+ /* screenOnTimeInMs= */ 40004L,
/* consumePower= */ 22.0,
/* foregroundUsageConsumePower= */ 10.0,
/* foregroundServiceUsageConsumePower= */ 10.0,
@@ -165,6 +167,7 @@
/* legacyLabel= */ BatteryDiffEntry.SYSTEM_APPS_KEY,
/*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0,
/* screenOnTimeInMs= */ 0,
/* consumePower= */ 0,
@@ -552,6 +555,7 @@
/* legacyLabel= */ null,
/*consumerType*/ consumerType,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0,
/* screenOnTimeInMs= */ 0,
/* consumePower= */ 0,
@@ -576,6 +580,7 @@
batteryHistEntry.mConsumerType,
/* foregroundUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* screenOnTimeInMs= */ 0,
consumePower,
/* foregroundUsageConsumePower= */ 0,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
index 83b4458..450d058 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
@@ -195,7 +195,8 @@
@Test
public void getTimeInForegroundMs_app() {
- when(mUidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
+ when(mUidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND))
.thenReturn(100L);
final BatteryEntry entry =
@@ -226,8 +227,9 @@
@Test
public void getTimeInBackgroundMs_app() {
- when(mUidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
- .thenReturn(100L);
+ when(mUidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .thenReturn(30L);
final BatteryEntry entry =
new BatteryEntry(
@@ -239,7 +241,26 @@
null,
null);
- assertThat(entry.getTimeInBackgroundMs()).isEqualTo(100L);
+ assertThat(entry.getTimeInBackgroundMs()).isEqualTo(30L);
+ }
+
+ @Test
+ public void getTimeInForegroundServiceMs_app() {
+ when(mUidBatteryConsumer.getTimeInProcessStateMs(
+ UidBatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+ .thenReturn(70L);
+
+ final BatteryEntry entry =
+ new BatteryEntry(
+ RuntimeEnvironment.application,
+ mMockUserManager,
+ mUidBatteryConsumer,
+ false,
+ 0,
+ null,
+ null);
+
+ assertThat(entry.getTimeInForegroundServiceMs()).isEqualTo(70L);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
index 02800f7..dc868c8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
@@ -65,6 +65,7 @@
when(mMockBatteryEntry.getConsumedPowerInCached()).thenReturn(1.5);
mMockBatteryEntry.mPercent = 0.3;
when(mMockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
+ when(mMockBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(3456L);
when(mMockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
when(mMockBatteryEntry.getPowerComponentId()).thenReturn(expectedType);
when(mMockBatteryEntry.getConsumerType())
@@ -105,6 +106,7 @@
/* backgroundUsageConsumePower= */ 1.4,
/* cachedUsageConsumePower= */ 1.5,
/* foregroundUsageTimeInMs= */ 1234L,
+ /* foregroundServiceUsageTimeInMs= */ 3456L,
/* backgroundUsageTimeInMs= */ 5689L,
/* batteryLevel= */ 12),
/* drainType= */ 3,
@@ -211,6 +213,7 @@
/* backgroundUsageConsumePower= */ 3,
/* cachedUsageConsumePower= */ 4,
/* foregroundUsageTimeInMs= */ 100,
+ /* foregroundServiceUsageTimeInMs= */ 150,
/* backgroundUsageTimeInMs= */ 200,
/* batteryLevel= */ 90);
final BatteryHistEntry upperHistEntry =
@@ -224,6 +227,7 @@
/* backgroundUsageConsumePower= */ 6,
/* cachedUsageConsumePower= */ 5,
/* foregroundUsageTimeInMs= */ 200,
+ /* foregroundServiceUsageTimeInMs= */ 250,
/* backgroundUsageTimeInMs= */ 300,
/* batteryLevel= */ 80);
@@ -244,6 +248,7 @@
/* backgroundUsageConsumePower= */ 3 + 0.5 * (6 - 3),
/* cachedUsageConsumePower= */ 4 + 0.5 * (5 - 4),
/* foregroundUsageTimeInMs= */ Math.round(100 + 0.5 * (200 - 100)),
+ /* foregroundServiceUsageTimeInMs= */ Math.round(150 + 0.5 * (250 - 150)),
/* backgroundUsageTimeInMs= */ Math.round(200 + 0.5 * (300 - 200)),
/* batteryLevel= */ (int) Math.round(90 + 0.5 * (80 - 90)));
}
@@ -264,6 +269,7 @@
/* backgroundUsageConsumePower= */ 6,
/* cachedUsageConsumePower= */ 5,
/* foregroundUsageTimeInMs= */ 200,
+ /* foregroundServiceUsageTimeInMs= */ 250,
/* backgroundUsageTimeInMs= */ 300,
/* batteryLevel= */ 80);
@@ -288,6 +294,7 @@
/* backgroundUsageConsumePower= */ 0.5 * 6,
/* cachedUsageConsumePower= */ 0.5 * 5,
/* foregroundUsageTimeInMs= */ Math.round(0.5 * 200),
+ /* foregroundServiceUsageTimeInMs= */ Math.round(0.5 * 250),
/* backgroundUsageTimeInMs= */ Math.round(0.5 * 300),
/* batteryLevel= */ upperHistEntry.mBatteryLevel);
}
@@ -317,6 +324,7 @@
/* backgroundUsageConsumePower= */ 1.4,
/* cachedUsageConsumePower= */ 1.5,
/* foregroundUsageTimeInMs= */ 1234L,
+ /*foregroundServiceUsageTimeInMs=*/ 3456L,
/* backgroundUsageTimeInMs= */ 5689L,
/* batteryLevel= */ 12);
}
@@ -334,6 +342,7 @@
double backgroundUsageConsumePower,
double cachedUsageConsumePower,
long foregroundUsageTimeInMs,
+ long foregroundServiceUsageTimeInMs,
long backgroundUsageTimeInMs,
int batteryLevel) {
assertThat(entry.isValidEntry()).isTrue();
@@ -354,6 +363,7 @@
assertThat(entry.mCachedUsageConsumePower).isEqualTo(cachedUsageConsumePower);
assertThat(entry.mPercentOfTotal).isEqualTo(percentOfTotal);
assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
+ assertThat(entry.mForegroundServiceUsageTimeInMs).isEqualTo(foregroundServiceUsageTimeInMs);
assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
assertThat(entry.mDrainType).isEqualTo(drainType);
assertThat(entry.mConsumerType).isEqualTo(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
@@ -372,6 +382,7 @@
double backgroundUsageConsumePower,
double cachedUsageConsumePower,
long foregroundUsageTimeInMs,
+ long foregroundServiceUsageTimeInMs,
long backgroundUsageTimeInMs,
int batteryLevel) {
final MatrixCursor cursor =
@@ -406,6 +417,7 @@
.setPercentOfTotal(0.3)
.setDrainType(3)
.setForegroundUsageTimeInMs(foregroundUsageTimeInMs)
+ .setForegroundServiceUsageTimeInMs(foregroundServiceUsageTimeInMs)
.setBackgroundUsageTimeInMs(backgroundUsageTimeInMs)
.build();
cursor.addRow(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index fc30702..5704be9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -100,7 +100,8 @@
/* legacyLabel= */ null,
/* consumerType= */ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
/* foregroundUsageTimeInMs= */ 1,
- /* backgroundUsageTimeInMs= */ 2,
+ /* foregroundServiceUsageTimeInMs= */ 2,
+ /* backgroundUsageTimeInMs= */ 3,
/* screenOnTimeInMs= */ 0,
/* consumePower= */ 3,
/* foregroundUsageConsumePower= */ 0,
@@ -275,6 +276,7 @@
/* isSystem= */ true,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0);
batteryDiffEntry.mConsumePower = 0.8;
batteryDiffEntry.setTotalConsumePower(100);
@@ -293,6 +295,7 @@
/* isSystem= */ true,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0);
batteryDiffEntry.mConsumePower = 16;
batteryDiffEntry.setTotalConsumePower(100);
@@ -314,6 +317,7 @@
/* isSystem= */ true,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0));
assertThat(pref.getSummary().toString().isEmpty()).isTrue();
}
@@ -329,6 +333,7 @@
/* isSystem= */ true,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS - 1,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0));
assertThat(pref.getSummary().toString()).isEqualTo("Total: less than a min");
}
@@ -344,6 +349,7 @@
/* isSystem= */ true,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS * 2,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0));
assertThat(pref.getSummary().toString()).isEqualTo("Total: 2 min");
}
@@ -359,6 +365,7 @@
/* isSystem= */ false,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0));
assertThat(pref.getSummary().toString().isEmpty()).isTrue();
}
@@ -374,11 +381,28 @@
/* isSystem= */ false,
/* screenOnTimeInMs= */ 0,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS));
assertThat(pref.getSummary().toString()).isEqualTo("Background: 1 min");
}
@Test
+ public void setPreferenceSummary_appEntryWithFGSTime_expectedSummary() {
+ final PowerGaugePreference pref = new PowerGaugePreference(mContext);
+ pref.setSummary(PREF_SUMMARY);
+
+ mBatteryUsageBreakdownController.setPreferenceSummary(
+ pref,
+ createBatteryDiffEntry(
+ /* isSystem= */ false,
+ /* screenOnTimeInMs= */ 0,
+ /* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS / 2,
+ /* backgroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS / 2));
+ assertThat(pref.getSummary().toString()).isEqualTo("Background: 1 min");
+ }
+
+ @Test
public void setPreferenceSummary_appEntryScreenOnTimeOnly_expectedSummary() {
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
pref.setSummary(PREF_SUMMARY);
@@ -389,6 +413,7 @@
/* isSystem= */ false,
/* screenOnTimeInMs= */ DateUtils.MINUTE_IN_MILLIS,
/* foregroundUsageTimeInMs= */ 0,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ 0));
assertThat(pref.getSummary().toString()).isEqualTo("Screen time: 1 min");
}
@@ -404,6 +429,7 @@
/* isSystem= */ false,
/* screenOnTimeInMs= */ DateUtils.MINUTE_IN_MILLIS - 1,
/* foregroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS - 1,
+ /* foregroundServiceUsageTimeInMs= */ 0,
/* backgroundUsageTimeInMs= */ DateUtils.MINUTE_IN_MILLIS - 1));
assertThat(pref.getSummary().toString())
.isEqualTo("Screen time: less than a min\nBackground: less than a min");
@@ -413,6 +439,7 @@
boolean isSystem,
long screenOnTimeInMs,
long foregroundUsageTimeInMs,
+ long foregroundServiceUsageTimeInMs,
long backgroundUsageTimeInMs) {
final ContentValues contentValues = new ContentValues();
contentValues.put(
@@ -435,6 +462,7 @@
batteryHistEntry.mConsumerType,
foregroundUsageTimeInMs,
backgroundUsageTimeInMs,
+ foregroundServiceUsageTimeInMs,
screenOnTimeInMs,
/* consumePower= */ 0,
/* foregroundUsageConsumePower= */ 0,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
index 95b950f..950f828 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -290,6 +290,7 @@
.setPercentOfTotal(0.9)
.setForegroundUsageTimeInMs(1000)
.setBackgroundUsageTimeInMs(2000)
+ .setForegroundServiceUsageTimeInMs(1500)
.setDrainType(1)
.build();
final String expectedBatteryInformationString =
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 672bc54..e68b892 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -92,6 +92,7 @@
mMockBatteryEntry.mPercent = 0.3;
when(mMockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
when(mMockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
+ when(mMockBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(3456L);
when(mMockBatteryEntry.getPowerComponentId()).thenReturn(-1);
when(mMockBatteryEntry.getConsumerType())
.thenReturn(ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
@@ -133,6 +134,7 @@
assertThat(batteryInformation.getPercentOfTotal()).isEqualTo(0.3);
assertThat(batteryInformation.getForegroundUsageTimeInMs()).isEqualTo(1234L);
assertThat(batteryInformation.getBackgroundUsageTimeInMs()).isEqualTo(5689L);
+ assertThat(batteryInformation.getForegroundServiceUsageTimeInMs()).isEqualTo(3456L);
assertThat(batteryInformation.getDrainType()).isEqualTo(-1);
assertThat(deviceBatteryState.getBatteryLevel()).isEqualTo(12);
assertThat(deviceBatteryState.getBatteryStatus())
@@ -157,6 +159,7 @@
mMockBatteryEntry.mPercent = 0.3;
when(mMockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
when(mMockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
+ when(mMockBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(3456L);
when(mMockBatteryEntry.getConsumerType())
.thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
@@ -196,6 +199,7 @@
assertThat(batteryInformation.getPercentOfTotal()).isEqualTo(0.3);
assertThat(batteryInformation.getForegroundUsageTimeInMs()).isEqualTo(1234L);
assertThat(batteryInformation.getBackgroundUsageTimeInMs()).isEqualTo(5689L);
+ assertThat(batteryInformation.getForegroundServiceUsageTimeInMs()).isEqualTo(3456L);
assertThat(batteryInformation.getDrainType())
.isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU);
assertThat(deviceBatteryState.getBatteryLevel()).isEqualTo(12);
@@ -307,6 +311,7 @@
mMockBatteryEntry.mPercent = 0.3;
when(mMockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
when(mMockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
+ when(mMockBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(3456L);
when(mMockBatteryEntry.getPowerComponentId()).thenReturn(expectedType);
when(mMockBatteryEntry.getConsumerType())
.thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
@@ -331,6 +336,7 @@
assertThat(batteryHistEntry.mPercentOfTotal).isEqualTo(0.3);
assertThat(batteryHistEntry.mForegroundUsageTimeInMs).isEqualTo(1234L);
assertThat(batteryHistEntry.mBackgroundUsageTimeInMs).isEqualTo(5689L);
+ assertThat(batteryHistEntry.mForegroundServiceUsageTimeInMs).isEqualTo(3456L);
assertThat(batteryHistEntry.mDrainType).isEqualTo(expectedType);
assertThat(batteryHistEntry.mConsumerType)
.isEqualTo(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
index f306693..43630e8 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
@@ -16,12 +16,9 @@
package com.android.settings.homepage.contextualcards.slices;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
@@ -29,7 +26,6 @@
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -43,50 +39,46 @@
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
private BluetoothUpdateWorker mBluetoothUpdateWorker;
- private ContentResolver mResolver;
private Context mContext;
@Before
public void setUp() {
- mContext = spy(RuntimeEnvironment.application);
- mBluetoothUpdateWorker = new BluetoothUpdateWorker(mContext, URI);
- mResolver = mock(ContentResolver.class);
- doReturn(mResolver).when(mContext).getContentResolver();
+ mContext = RuntimeEnvironment.getApplication();
+ mBluetoothUpdateWorker = spy(new BluetoothUpdateWorker(mContext, URI));
}
@Test
public void onAclConnectionStateChanged_shouldNotifyChange() {
mBluetoothUpdateWorker.onAclConnectionStateChanged(null, 0);
- verify(mResolver).notifyChange(URI, null);
+ verify(mBluetoothUpdateWorker).notifySliceChange();
}
- @Ignore("b/315399487")
@Test
public void onActiveDeviceChanged_shouldNotifyChange() {
mBluetoothUpdateWorker.onActiveDeviceChanged(null, 0);
- verify(mResolver).notifyChange(URI, null);
+ verify(mBluetoothUpdateWorker).notifySliceChange();
}
@Test
public void onBluetoothStateChanged_shouldNotifyChange() {
mBluetoothUpdateWorker.onBluetoothStateChanged(0);
- verify(mResolver).notifyChange(URI, null);
+ verify(mBluetoothUpdateWorker).notifySliceChange();
}
@Test
public void onConnectionStateChanged_shouldNotifyChange() {
mBluetoothUpdateWorker.onConnectionStateChanged(null, 0);
- verify(mResolver).notifyChange(URI, null);
+ verify(mBluetoothUpdateWorker).notifySliceChange();
}
@Test
public void onProfileConnectionStateChanged_shouldNotifyChange() {
mBluetoothUpdateWorker.onProfileConnectionStateChanged(null, 0, 0);
- verify(mResolver).notifyChange(URI, null);
+ verify(mBluetoothUpdateWorker).notifySliceChange();
}
}
\ No newline at end of file
diff --git a/tests/screenshot/Android.bp b/tests/screenshot/Android.bp
index e20b5d3..5989381 100644
--- a/tests/screenshot/Android.bp
+++ b/tests/screenshot/Android.bp
@@ -29,42 +29,46 @@
"androidx.fragment_fragment",
"androidx.test.runner",
"androidx.test.core",
- ],
- uses_libs: ["org.apache.http.legacy"],
-
- aaptflags: ["--extra-packages com.android.settings"],
- manifest: "AndroidManifest.xml",
-}
-
-android_test {
- name: "SettingsScreenshotTests",
- platform_apis: true,
- certificate: "platform",
- test_suites: ["device-tests"],
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "androidx.fragment_fragment-testing",
- "androidx.fragment_fragment",
"androidx.test.rules",
"androidx.test.ext.junit",
"platform-screenshot-diff-core",
"Settings-testutils2",
- "androidx.test.core",
"androidx.test.espresso.core",
"kotlinx-coroutines-android",
"androidx.lifecycle_lifecycle-runtime-testing",
"kotlinx_coroutines_test",
- "Settings-core",
- "androidx.test.runner",
],
uses_libs: ["org.apache.http.legacy"],
- compile_multilib: "both",
+ aaptflags: ["--extra-packages com.android.settings"],
manifest: "AndroidManifest.xml",
- test_config: "AndroidTest.xml",
- use_embedded_native_libs: false,
- asset_dirs: ["assets"],
+}
+
+// This is a RNG (Robolectric native graphics) test target.
+android_robolectric_test {
+ name: "SettingsScreenshotRNGTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ test_suites: ["general-tests"],
+
+ // Do not add any libraries here, instead add them to the ScreenshotTestStub
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "uiautomator-helpers",
+
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+
+ upstream: true,
instrumentation_for: "ScreenshotTestStub",
- data: [":ScreenshotTestStub"],
+ java_resource_dirs: ["config"],
}
diff --git a/tests/screenshot/AndroidManifest.xml b/tests/screenshot/AndroidManifest.xml
index 9cbc882..6c8bb88 100644
--- a/tests/screenshot/AndroidManifest.xml
+++ b/tests/screenshot/AndroidManifest.xml
@@ -16,32 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.settings.tests.screenshot"
- >
-
- <application android:debuggable="true">
- <provider
- android:name="com.android.settings.slices.SettingsSliceProvider"
- android:authorities="com.android.settings.tests.screenshot.disabled"
- android:enabled="false"
- tools:node="remove"
- tools:replace="android:authorities" />
-
- </application>
-
- <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <uses-permission android:name="android.permission.READ_LOGS" />
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ coreApp="true"
+ package="com.android.settings">
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:label="Android Settings Screenshot tests"
- android:targetPackage="com.android.settings.tests.screenshot" />
+ <application>
+ <activity android:name="com.android.settings.test.screenshot.ContainerActivity" android:exported="true" />
+ </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/tests/screenshot/AndroidTest.xml b/tests/screenshot/AndroidTest.xml
deleted file mode 100644
index 7496ffd..0000000
--- a/tests/screenshot/AndroidTest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<configuration description="Runs settings screendiff tests.">
- <option name="test-suite-tag" value="apct-instrumentation" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <option name="optimized-property-setting" value="true" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="SettingsScreenshotTests.apk" />
- </target_preparer>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys"
- value="/data/user/0/com.android.settings.tests.screenshot/" />
- <option name="collect-on-run-ended-only" value="true" />
- </metrics_collector>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.settings.tests.screenshot" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- </test>
-</configuration>
diff --git "a/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png" "b/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png"
deleted file mode 100644
index 1129250..0000000
--- "a/tests/screenshot/assets/pixel_4a_\0505g\051/fp_enroll_intro.png"
+++ /dev/null
Binary files differ
diff --git a/tests/screenshot/assets/robolectric/fp_enroll_intro.png b/tests/screenshot/assets/robolectric/fp_enroll_intro.png
new file mode 100644
index 0000000..308ab55
--- /dev/null
+++ b/tests/screenshot/assets/robolectric/fp_enroll_intro.png
Binary files differ
diff --git a/tests/screenshot/config/robolectric.properties b/tests/screenshot/config/robolectric.properties
new file mode 100644
index 0000000..88443e3
--- /dev/null
+++ b/tests/screenshot/config/robolectric.properties
@@ -0,0 +1,16 @@
+# 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.
+#
+sdk=NEWEST_SDK
+graphicsMode=NATIVE
\ No newline at end of file
diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/ContainerActivity.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/ContainerActivity.kt
new file mode 100644
index 0000000..a505ef5
--- /dev/null
+++ b/tests/screenshot/src/com/android/settings/tests/screenshot/ContainerActivity.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.tests.screenshot
+
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentContainerView
+
+/**
+ * This activity is a container for all RNG (Robolectric Native Graphic) Settings screenshot tests.
+ */
+class ContainerActivity : FragmentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val contentView = FragmentContainerView(this)
+ contentView.setId(CONTAINER_VIEW_ID)
+ setContentView(contentView)
+ }
+
+ companion object {
+ const val CONTAINER_VIEW_ID = 1234
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/network/SubscriptionInfoListViewModelTest.kt b/tests/spa_unit/src/com/android/settings/network/SubscriptionInfoListViewModelTest.kt
new file mode 100644
index 0000000..020a470
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/SubscriptionInfoListViewModelTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.network
+
+import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
+
+import android.app.Application
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.telephony.flags.Flags
+import com.android.settings.network.telephony.CallStateFlowTest
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class SubscriptionInfoListViewModelTest {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+ private var subInfoListener: SubscriptionManager.OnSubscriptionsChangedListener? = null
+ private val mockSubscriptionManager = mock<SubscriptionManager> {
+ on { activeSubscriptionInfoList } doAnswer { activeSubscriptionInfoList }
+ on { addOnSubscriptionsChangedListener(any(), any()) } doAnswer {
+ subInfoListener =
+ it.arguments[1] as SubscriptionManager.OnSubscriptionsChangedListener
+ subInfoListener?.onSubscriptionsChanged()
+ }
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
+ }
+
+ private val subscriptionInfoListViewModel: SubscriptionInfoListViewModel =
+ SubscriptionInfoListViewModel(context as Application);
+
+ private var activeSubscriptionInfoList: List<SubscriptionInfo>? = null
+
+ @Test
+ fun onSubscriptionsChanged_noProvisioning_resultSameAsInput() = runBlocking {
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2)
+
+ val listDeferred = async {
+ subscriptionInfoListViewModel.subscriptionInfoListFlow.toListWithTimeout()
+ }
+ delay(100)
+ subInfoListener?.onSubscriptionsChanged()
+
+ assertThat(listDeferred.await()).contains(activeSubscriptionInfoList)
+ }
+
+ @Test
+ fun onSubscriptionsChanged_hasProvisioning_filterProvisioning() = runBlocking {
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3)
+ val expectation = listOf(SUB_INFO_1, SUB_INFO_2)
+
+ val listDeferred = async {
+ subscriptionInfoListViewModel.subscriptionInfoListFlow.toListWithTimeout()
+ }
+ delay(100)
+ subInfoListener?.onSubscriptionsChanged()
+
+ assertThat(listDeferred.await()).contains(expectation)
+ }
+
+ @Test
+ fun onSubscriptionsChanged_flagOffHasNonTerrestrialNetwork_filterNonTerrestrialNetwork() =
+ runBlocking {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_4)
+ val expectation = listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_4)
+
+ val listDeferred = async {
+ subscriptionInfoListViewModel.subscriptionInfoListFlow.toListWithTimeout()
+ }
+ delay(100)
+ subInfoListener?.onSubscriptionsChanged()
+
+ assertThat(listDeferred.await()).contains(expectation)
+ }
+
+ @Test
+ fun onSubscriptionsChanged_flagOnHasNonTerrestrialNetwork_filterNonTerrestrialNetwork() =
+ runBlocking {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ activeSubscriptionInfoList = listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_4)
+ val expectation = listOf(SUB_INFO_1, SUB_INFO_2)
+
+ val listDeferred = async {
+ subscriptionInfoListViewModel.subscriptionInfoListFlow.toListWithTimeout()
+ }
+ delay(100)
+ subInfoListener?.onSubscriptionsChanged()
+
+ assertThat(listDeferred.await()).contains(expectation)
+ }
+
+ private companion object {
+ val SUB_INFO_1: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(1)
+ }.build()
+
+ val SUB_INFO_2: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(2)
+ }.build()
+
+ val SUB_INFO_3: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(3)
+ setEmbedded(true)
+ setProfileClass(PROFILE_CLASS_PROVISIONING)
+ setOnlyNonTerrestrialNetwork(false)
+ }.build()
+
+ val SUB_INFO_4: SubscriptionInfo = SubscriptionInfo.Builder().apply {
+ setId(4)
+ setEmbedded(true)
+ setOnlyNonTerrestrialNetwork(true)
+ }.build()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
index 7285ff8..c132273 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
@@ -16,15 +16,15 @@
package com.android.settings.network.telephony
+import android.app.KeyguardManager
import android.content.Context
+import android.os.UserManager
import android.telephony.SubscriptionInfo
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -46,8 +46,13 @@
on { isEmbedded } doReturn true
}
+ private val mockKeyguardManager = mock<KeyguardManager>() {
+ on { isKeyguardSecure() } doReturn false
+ }
+
private var context: Context = spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any())
+ on { getSystemService(Context.KEYGUARD_SERVICE) } doReturn mockKeyguardManager
}
private val preference = Preference(context).apply { key = PREF_KEY }
@@ -103,11 +108,6 @@
fun onPreferenceClick_startsIntent() {
controller.init(SUB_ID)
controller.displayPreference(preferenceScreen)
- // turn off confirmation before click
- var confirmDeletion by context.settingsGlobalBoolean(
- name = ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION,
- )
- confirmDeletion = false
controller.handlePreferenceTreeClick(preference)
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index 50094f2..6d22c92 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -140,6 +140,38 @@
composeTestRule.onNodeWithText(context.getString(R.string.uninstall_text)).assertIsEnabled()
}
+ @Test
+ fun archiveButton_displayed_whenAppIsNotArchived() {
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+ packageName = PACKAGE_NAME
+ }
+ setContent(packageInfo)
+
+ composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsDisplayed()
+ composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun restoreButton_displayed_whenAppIsArchived() {
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+ packageName = PACKAGE_NAME
+ }
+ setContent(packageInfo)
+
+ composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsDisplayed()
+ composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsNotDisplayed()
+ }
+
private fun setContent(packageInfo: PackageInfo = PACKAGE_INFO) {
whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(packageInfo))
composeTestRule.setContent {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
new file mode 100644
index 0000000..9d30521
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppRestoreButtonTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
+
+ private val packageInfoPresenter = mock<PackageInfoPresenter>()
+
+ private val userPackageManager = mock<PackageManager>()
+
+ private val packageInstaller = mock<PackageInstaller>()
+
+ private lateinit var appRestoreButton: AppRestoreButton
+
+ @Before
+ fun setUp() {
+ whenever(packageInfoPresenter.context).thenReturn(context)
+ whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
+ whenever(userPackageManager.packageInstaller).thenReturn(packageInstaller)
+ whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
+ appRestoreButton = AppRestoreButton(packageInfoPresenter)
+ }
+
+ @Test
+ fun appRestoreButton_whenIsNotArchived_isDisabled() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.enabled).isFalse()
+ }
+
+ @Test
+ fun appRestoreButton_whenIsArchived_isEnabled() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.enabled).isTrue()
+ }
+
+ @Test
+ fun appRestoreButton_displaysRightTextAndIcon() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.text).isEqualTo(context.getString(R.string.restore))
+ assertThat(actionButton.imageVector).isEqualTo(Icons.Outlined.CloudDownload)
+ }
+
+ @Test
+ @UiThreadTest
+ fun appRestoreButton_clicked() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+
+ val actionButton = setContent(app)
+ actionButton.onClick()
+
+ verify(packageInstaller).requestUnarchive(
+ eq(PACKAGE_NAME),
+ any()
+ )
+ }
+
+ private fun setContent(app: ApplicationInfo): ActionButton {
+ lateinit var actionButton: ActionButton
+ composeTestRule.setContent {
+ actionButton = appRestoreButton.getActionButton(app)
+ }
+ return actionButton
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
index 6acdcf5..57e31da 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
@@ -24,8 +24,8 @@
import android.apphibernation.AppHibernationManager
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.os.Build
-import android.os.UserHandle
import android.permission.PermissionControllerManager
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_ELIGIBLE
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
@@ -63,7 +63,6 @@
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -147,12 +146,20 @@
setContent()
- composeTestRule.onNodeWithText(context.getString(R.string.unused_apps_switch))
+ val text = if (isArchivingEnabled()) {
+ context.getString(R.string.unused_apps_switch_v2)
+ } else {
+ context.getString(R.string.unused_apps_switch)
+ }
+ composeTestRule.onNodeWithText(text)
.assertIsDisplayed()
.assertIsNotEnabled()
.assertIsOff()
}
+ private fun isArchivingEnabled() =
+ Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
@Test
fun `An app targets Q with ops mode default when hibernation targets pre S - not exempted`() {
mockOpsMode(MODE_DEFAULT)
diff --git a/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java
index 3669358..dc4f56a 100644
--- a/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java
@@ -29,6 +29,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+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.provider.Settings;
import androidx.preference.PreferenceManager;
@@ -37,8 +41,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.window.flags.Flags;
+
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
@@ -58,6 +65,9 @@
private BackAnimationPreferenceController mController;
private Looper mLooper;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -81,6 +91,18 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMATIONS)
+ public void controllerNotAvailable_whenAconfigFlagEnabled() {
+ assertFalse(mController.isAvailable());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_ANIMATIONS)
+ public void controllerAvailable_whenAconfigFlagDisabled() {
+ assertTrue(mController.isAvailable());
+ }
+
+ @Test
public void onPreferenceChange_switchEnabled_shouldEnableBackAnimations() {
mController.onPreferenceChange(mPreference, true /* new value */);
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
index 0a2f3d1..1605ae6 100644
--- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
@@ -21,8 +21,11 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.os.RemoteException;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
@@ -30,6 +33,8 @@
import com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace;
+import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,6 +42,7 @@
@RunWith(AndroidJUnit4.class)
public class PrivateSpaceMaintainerTest {
+ private static final String TAG = "PSMaintainerTest";
private Context mContext;
private ContentResolver mContentResolver;
@@ -48,6 +54,13 @@
mContentResolver = mContext.getContentResolver();
}
+ @After
+ public void tearDown() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.deletePrivateSpace();
+ }
+
/** Tests that {@link PrivateSpaceMaintainer#deletePrivateSpace()} deletes PS when PS exists. */
@Test
public void deletePrivateSpace_psExists_deletesPS() {
@@ -137,4 +150,88 @@
assertThat(privateSpaceMaintainer.getHidePrivateSpaceEntryPointSetting())
.isEqualTo(HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL);
}
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#lockPrivateSpace()} when PS exists and is running
+ * locks the private profile.
+ */
+ @Test
+ public void lockPrivateSpace_psExistsAndPrivateProfileRunning_locksCreatedPrivateSpace() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue();
+ assertThat(privateSpaceMaintainer.isPrivateProfileRunning()).isTrue();
+ assertThat(privateSpaceMaintainer.isPrivateSpaceLocked()).isFalse();
+ assertThat(privateSpaceMaintainer.lockPrivateSpace()).isTrue();
+ assertThat(privateSpaceMaintainer.isPrivateSpaceLocked()).isTrue();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#lockPrivateSpace()} when PS exist and private
+ * profile not running returns false.
+ */
+ @Test
+ public void lockPrivateSpace_psExistsAndPrivateProfileNotRunning_returnsFalse() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue();
+ assertThat(privateSpaceMaintainer.isPrivateProfileRunning()).isTrue();
+ IActivityManager am = ActivityManager.getService();
+ try {
+ am.stopProfile(privateSpaceMaintainer.getPrivateProfileHandle().getIdentifier());
+ } catch (RemoteException e) {
+ Assert.fail("Stop profile failed with exception " + e.getMessage());
+ }
+ assertThat(privateSpaceMaintainer.isPrivateProfileRunning()).isFalse();
+ assertThat(privateSpaceMaintainer.lockPrivateSpace()).isFalse();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#lockPrivateSpace()} when no PS exists returns false.
+ */
+ @Test
+ public void lockPrivateSpace_psDoesNotExist_returnsFalse() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse();
+ assertThat(privateSpaceMaintainer.lockPrivateSpace()).isFalse();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when no PS exists sets
+ * USER_SETUP_COMPLETE setting.
+ */
+ @Test
+ public void createPrivateSpace_psDoesNotExist_setsUserSetupComplete() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists does not
+ * change USER_SETUP_COMPLETE setting.
+ */
+ @Test
+ public void createPrivateSpace_pSExists_doesNotChangeUserSetupSetting() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ }
+
+ private int getSecureUserSetupComplete() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ return Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0,
+ privateSpaceMaintainer.getPrivateProfileHandle().getIdentifier());
+ }
}