Merge "Apps can opt in to be stoppable in the task manager" into main
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 1adefe5..1b2c575 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -18,13 +18,6 @@
}
flag {
- name: "safe_mode_timeout_config"
- namespace: "vcn"
- description: "Feature flag for adjustable safe mode timeout"
- bug: "317406085"
-}
-
-flag {
name: "fix_config_garbage_collection"
namespace: "vcn"
description: "Handle race condition in subscription change"
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 4f315a2..76aa5bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -75,6 +75,24 @@
@Override
public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
applyDefaultSelectedTypeToAllPresets();
+
+ // Activate the last hot plugged valid input device, to match the output device
+ // behavior.
+ @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
+ for (AudioDeviceInfo info : addedDevices) {
+ if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
+ deviceTypeToActivate = info.getType();
+ }
+ }
+
+ // Only activate if we find a different valid input device. e.g. if none of the
+ // addedDevices is supported input device, we don't need to activate anything.
+ if (mSelectedInputDeviceType != deviceTypeToActivate) {
+ mSelectedInputDeviceType = deviceTypeToActivate;
+ AudioDeviceAttributes deviceAttributes =
+ createInputDeviceAttributes(mSelectedInputDeviceType);
+ setPreferredDeviceForAllPresets(deviceAttributes);
+ }
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 782cee2..d808a25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -138,6 +139,18 @@
/* address= */ "");
}
+ private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ /* address= */ "");
+ }
+
+ private AudioDeviceAttributes getHdmiDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
+ }
+
private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
final List<AudioDeviceAttributes> audioDeviceAttributesList =
new ArrayList<AudioDeviceAttributes>();
@@ -303,21 +316,47 @@
}
@Test
- public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+ public void onAudioDevicesAdded_shouldActivateAddedDevice() {
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
- when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
- .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
-
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
- // Called twice, one after initiation, the other after onAudioDevicesAdded call.
- verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+ // The only added wired headset will be activated.
for (@MediaRecorder.Source int preset : PRESETS) {
- verify(audioManager, atLeast(2))
- .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+ verify(audioManager, atLeast(1))
+ .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // When adding multiple valid input devices, the last added device (usb headset in this
+ // case) will be activated.
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, never())
+ .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+ verify(audioManager, atLeast(1))
+ .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockHdmiInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // Do not activate since HDMI is not a valid input device.
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, never())
+ .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3d9eb53..a39ca5d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -103,6 +103,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
import android.service.trust.TrustAgentService;
@@ -129,6 +130,7 @@
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.keyguard.logging.SimLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -190,6 +192,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
+@EnableFlags(Flags.FLAG_USER_ENCRYPTED_SOURCE)
public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
"test_app_fp_listen_on_occluding_activity";
@@ -1292,12 +1295,15 @@
@Test
public void testIsUserUnlocked() {
+ when(mUserManager.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+ true);
// mUserManager will report the user as unlocked on @Before
assertThat(
mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
.isTrue();
// Invalid user should not be unlocked.
int randomUser = 99;
+ when(mUserManager.isUserUnlocked(randomUser)).thenReturn(false);
assertThat(mKeyguardUpdateMonitor.isUserUnlocked(randomUser)).isFalse();
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 4731cfb..0c2ce8d 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -376,6 +376,7 @@
":ravenwood-empty-res",
":framework-platform-compat-config",
":services-platform-compat-config",
+ "texts/ravenwood-build.prop",
],
device_first_srcs: [
":apex_icu.dat",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index e61a054..678a97b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -22,7 +22,6 @@
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
@@ -95,8 +94,6 @@
private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
- private static final String RAVENWOOD_BUILD_PROP =
- getRavenwoodRuntimePath() + "ravenwood-data/build.prop";
/**
* When enabled, attempt to dump all thread stacks just before we hit the
@@ -209,7 +206,7 @@
System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
// Do the basic set up for the android sysprops.
- RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
+ RavenwoodSystemProperties.initialize();
setSystemProperties(null);
// Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 9bc45be..3e4619f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -16,21 +16,30 @@
package android.platform.test.ravenwood;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
+import android.util.Log;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class RavenwoodSystemProperties {
private static final String TAG = "RavenwoodSystemProperties";
+ /** We pull in propeties from this file. */
+ private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
+
+ /** This is the actual build.prop we use to build the device (contents depends on lunch). */
+ private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
+
+ /** The default values. */
private static final Map<String, String> sDefaultValues = new HashMap<>();
private static final String[] PARTITIONS = {
@@ -43,52 +52,54 @@
"vendor_dlkm",
};
- /**
- * More info about property file loading: system/core/init/property_service.cpp
- * In the following logic, the only partition we would need to consider is "system",
- * since we only read from system-build.prop
- */
- static void initialize(String propFile) {
- // Load all properties from build.prop
+ private static Map<String, String> readProperties(String propFile) {
+ // Use an ordered map just for cleaner dump log.
+ final Map<String, String> ret = new LinkedHashMap<>();
try {
Files.readAllLines(Path.of(propFile)).stream()
.map(String::trim)
.filter(s -> !s.startsWith("#"))
.map(s -> s.split("\\s*=\\s*", 2))
.filter(a -> a.length == 2)
- .forEach(a -> sDefaultValues.put(a[0], a[1]));
+ .forEach(a -> ret.put(a[0], a[1]));
} catch (IOException e) {
throw new RuntimeException(e);
}
+ return ret;
+ }
- // If ro.product.${name} is not set, derive from ro.product.${partition}.${name}
- // If ro.product.cpu.abilist* is not set, derive from ro.${partition}.product.cpu.abilist*
- for (var entry : Set.copyOf(sDefaultValues.entrySet())) {
- final String key;
- if (entry.getKey().startsWith("ro.product.system.")) {
- var name = entry.getKey().substring(18);
- key = "ro.product." + name;
+ /**
+ * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in
+ * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too.
+ *
+ * More info about property file loading: system/core/init/property_service.cpp
+ * In the following logic, the only partition we would need to consider is "system",
+ * since we only read from system-build.prop
+ */
+ static void initialize() {
+ var path = getRavenwoodRuntimePath();
+ var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
+ var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
- } else if (entry.getKey().startsWith("ro.system.product.cpu.abilist")) {
- var name = entry.getKey().substring(22);
- key = "ro.product.cpu." + name;
+ Log.i(TAG, "Default system properties:");
+ ravenwoodProps.forEach((key, origValue) -> {
+ final String value;
+
+ // If a value starts with "$$$", then this is a reference to the device-side value.
+ if (origValue.startsWith("$$$")) {
+ var deviceKey = origValue.substring(3);
+ var deviceValue = deviceProps.get(deviceKey);
+ if (deviceValue == null) {
+ throw new RuntimeException("Failed to initialize system properties. Key '"
+ + deviceKey + "' doesn't exist in the device side build.prop");
+ }
+ value = deviceValue;
} else {
- continue;
+ value = origValue;
}
- if (!sDefaultValues.containsKey(key)) {
- sDefaultValues.put(key, entry.getValue());
- }
- }
-
- // Some other custom values
- sDefaultValues.put("ro.board.first_api_level", "1");
- sDefaultValues.put("ro.product.first_api_level", "1");
- sDefaultValues.put("ro.soc.manufacturer", "Android");
- sDefaultValues.put("ro.soc.model", "Ravenwood");
- sDefaultValues.put(RAVENWOOD_SYSPROP, "1");
-
- // Log all values
- sDefaultValues.forEach((key, value) -> RavenwoodCommonUtils.log(TAG, key + "=" + value));
+ Log.i(TAG, key + "=" + value);
+ sDefaultValues.put(key, value);
+ });
// Copy ro.product.* and ro.build.* to all partitions, just in case
// We don't want to log these because these are just a lot of duplicate values
@@ -104,6 +115,13 @@
}
}
}
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ // Dump all properties for local debugging.
+ Log.v(TAG, "All system properties:");
+ for (var key : sDefaultValues.keySet().stream().sorted().toList()) {
+ Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
+ }
+ }
}
private volatile boolean mIsImmutable;
diff --git a/ravenwood/texts/build.prop-sample-cuttlefish b/ravenwood/texts/build.prop-sample-cuttlefish
new file mode 100644
index 0000000..f78b727
--- /dev/null
+++ b/ravenwood/texts/build.prop-sample-cuttlefish
@@ -0,0 +1,132 @@
+# This is file is generated with `aosp_cf_x86_64_phone-trunk_staging-eng` on 2024-11-06.
+# We have this file here only as a reference. We don't actually use this file anywhere.
+
+####################################
+# from generate_common_build_props
+# These properties identify this partition image.
+####################################
+ro.product.system.brand=Android
+ro.product.system.device=generic
+ro.product.system.manufacturer=Android
+ro.product.system.model=mainline
+ro.product.system.name=mainline
+ro.system.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist32=x86,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist64=x86_64,arm64-v8a
+ro.system.build.date=Tue Nov 5 13:25:43 PST 2024
+ro.system.build.date.utc=1730841943
+ro.system.build.fingerprint=generic/aosp_cf_x86_64_phone/vsoc_x86_64:Baklava/MAIN/eng.omakot:eng/test-keys
+ro.system.build.id=MAIN
+ro.system.build.tags=test-keys
+ro.system.build.type=eng
+ro.system.build.version.incremental=eng.omakot
+ro.system.build.version.release=15
+ro.system.build.version.release_or_codename=Baklava
+ro.system.build.version.sdk=35
+####################################
+# from gen_build_prop.py:generate_build_info
+####################################
+# begin build properties
+ro.build.legacy.id=MAIN
+ro.build.display.id=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+ro.build.version.incremental=eng.omakot
+ro.build.version.sdk=35
+ro.build.version.preview_sdk=1
+ro.build.version.preview_sdk_fingerprint=2ef06129940d459014cf4dede3950d71
+ro.build.version.codename=Baklava
+ro.build.version.all_codenames=Baklava
+ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava
+ro.build.version.release=15
+ro.build.version.release_or_codename=Baklava
+ro.build.version.release_or_preview_display=Baklava
+ro.build.version.security_patch=2024-08-05
+ro.build.version.base_os=
+ro.build.version.min_supported_target_sdk=28
+ro.build.date=Tue Nov 5 13:25:43 PST 2024
+ro.build.date.utc=1730841943
+ro.build.type=eng
+ro.build.user=omakoto
+ro.build.host=omakoto-ct1.c.googlers.com
+ro.build.tags=test-keys
+ro.build.flavor=aosp_cf_x86_64_phone-eng
+# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
+# use ro.product.cpu.abilist instead.
+ro.product.cpu.abi=x86_64
+ro.product.locale=en-US
+ro.wifi.channels=
+# ro.build.product is obsolete; use ro.product.device
+ro.build.product=vsoc_x86_64
+# Do not try to parse description or thumbprint
+ro.build.description=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+# end build properties
+####################################
+# from variable ADDITIONAL_SYSTEM_PROPERTIES
+####################################
+ro.treble.enabled=true
+ro.llndk.api_level=202504
+ro.actionable_compatible_property.enabled=true
+persist.debug.dalvik.vm.core_platform_api_policy=just-warn
+ro.postinstall.fstab.prefix=/system
+ro.kernel.android.checkjni=1
+ro.secure=0
+ro.allow.mock.location=1
+dalvik.vm.lockprof.threshold=500
+ro.debuggable=1
+dalvik.vm.image-dex2oat-filter=extract
+init.svc_debug.no_fatal.zygote=true
+net.bt.name=Android
+ro.force.debuggable=0
+####################################
+# from variable PRODUCT_SYSTEM_PROPERTIES
+####################################
+debug.atrace.tags.enableflags=0
+persist.traced.enable=1
+dalvik.vm.image-dex2oat-Xms=64m
+dalvik.vm.image-dex2oat-Xmx=64m
+dalvik.vm.dex2oat-Xms=64m
+dalvik.vm.dex2oat-Xmx=512m
+dalvik.vm.usejit=true
+dalvik.vm.dexopt.secondary=true
+dalvik.vm.dexopt.thermal-cutoff=2
+dalvik.vm.appimageformat=lz4
+ro.dalvik.vm.native.bridge=0
+pm.dexopt.post-boot=verify
+pm.dexopt.first-boot=verify
+pm.dexopt.boot-after-ota=verify
+pm.dexopt.boot-after-mainline-update=verify
+pm.dexopt.install=speed-profile
+pm.dexopt.install-fast=skip
+pm.dexopt.install-bulk=speed-profile
+pm.dexopt.install-bulk-secondary=verify
+pm.dexopt.install-bulk-downgraded=verify
+pm.dexopt.install-bulk-secondary-downgraded=verify
+pm.dexopt.bg-dexopt=speed-profile
+pm.dexopt.ab-ota=speed-profile
+pm.dexopt.inactive=verify
+pm.dexopt.cmdline=verify
+pm.dexopt.shared=speed
+dalvik.vm.disable-art-service-dexopt=true
+dalvik.vm.disable-odrefresh=true
+dalvik.vm.dex2oat-resolve-startup-strings=true
+dalvik.vm.dex2oat-max-image-block-size=524288
+dalvik.vm.minidebuginfo=true
+dalvik.vm.dex2oat-minidebuginfo=true
+dalvik.vm.madvise.vdexfile.size=104857600
+dalvik.vm.madvise.odexfile.size=104857600
+dalvik.vm.madvise.artfile.size=4294967295
+dalvik.vm.usap_pool_enabled=false
+dalvik.vm.usap_refill_threshold=1
+dalvik.vm.usap_pool_size_max=3
+dalvik.vm.usap_pool_size_min=1
+dalvik.vm.usap_pool_refill_delay_ms=3000
+dalvik.vm.useartservice=true
+dalvik.vm.enable_pr_dexopt=true
+ro.cp_system_other_odex=1
+ro.apex.updatable=true
+ro.launcher.depth.widget=0
+####################################
+# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES
+####################################
+# Auto-added by post_process_props.py
+persist.sys.usb.config=adb
+# end of file
diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop
new file mode 100644
index 0000000..93a18cf
--- /dev/null
+++ b/ravenwood/texts/ravenwood-build.prop
@@ -0,0 +1,44 @@
+# This file contains system properties used on ravenwood.
+
+ro.is_on_ravenwood=1
+
+ro.board.first_api_level=1
+ro.product.first_api_level=1
+ro.soc.manufacturer=Android
+ro.soc.model=Ravenwood
+ro.debuggable=1
+
+# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too.
+# See RavenwoodSystemProperties.
+ro.product.brand=Android
+ro.product.device=Ravenwood
+ro.product.manufacturer=Android
+ro.product.model=Ravenwood
+ro.product.name=Ravenwood
+ro.product.cpu.abilist=x86_64
+ro.product.cpu.abilist32=
+ro.product.cpu.abilist64=x86_64
+
+ro.build.date=Thu Jan 01 00:00:00 GMT 2024
+ro.build.date.utc=1704092400
+ro.build.id=MAIN
+ro.build.tags=dev-keys
+ro.build.type=userdebug
+ro.build.version.incremental=userdebug.ravenwood.20240101
+
+# These are what we used to use on Ravenwood, copied here as a reference.
+#ro.build.version.codename=REL
+#ro.build.version.all_codenames=REL
+#ro.build.version.known_codenames=REL
+#ro.build.version.release=14
+#ro.build.version.release_or_codename=VanillaIceCream
+#ro.build.version.sdk=34
+
+# We pull in the following values from the real build.prop file.
+ro.build.version.codename=$$$ro.build.version.codename
+ro.build.version.all_codenames=$$$ro.build.version.codename
+ro.build.version.known_codenames=$$$ro.build.version.codename
+ro.build.version.release=$$$ro.build.version.release
+ro.build.version.release_or_codename=$$$ro.build.version.release_or_codename
+ro.build.version.release_or_preview_display=$$$ro.build.version.release_or_preview_display
+ro.build.version.sdk=$$$ro.build.version.sdk
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 78bc658..3dcca14 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,6 +22,9 @@
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
+import static java.lang.Math.abs;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -48,6 +51,7 @@
import android.os.Handler;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
+import android.os.Looper;
import android.os.OsProtoEnums;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -67,6 +71,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.SomeArgs;
@@ -84,6 +89,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
@@ -149,19 +155,112 @@
private HealthInfo mHealthInfo;
private final HealthInfo mLastHealthInfo = new HealthInfo();
private boolean mBatteryLevelCritical;
- private int mLastBatteryStatus;
- private int mLastBatteryHealth;
- private boolean mLastBatteryPresent;
- private int mLastBatteryLevel;
- private int mLastBatteryVoltage;
- private int mLastBatteryTemperature;
- private boolean mLastBatteryLevelCritical;
- private int mLastMaxChargingCurrent;
- private int mLastMaxChargingVoltage;
- private int mLastChargeCounter;
- private int mLastBatteryCycleCount;
- private int mLastChargingState;
- private int mLastBatteryCapacityLevel;
+
+ /**
+ * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryStatus;
+ /**
+ * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryHealth;
+ /**
+ * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private boolean mLastBroadcastBatteryPresent;
+ /**
+ * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryLevel;
+ /**
+ * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryVoltage;
+ /**
+ * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryTemperature;
+ /**
+ * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private boolean mLastBroadcastBatteryLevelCritical;
+ /**
+ * {@link HealthInfo#maxChargingCurrentMicroamps} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastMaxChargingCurrent;
+ /**
+ * {@link HealthInfo#maxChargingVoltageMicrovolts} value when
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastMaxChargingVoltage;
+ /**
+ * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastChargeCounter;
+ /**
+ * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryCycleCount;
+ /**
+ * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastChargingState;
+ /**
+ * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: This value may be used for internal operations and/or to determine whether to trigger
+ * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+ */
+ private int mLastBroadcastBatteryCapacityLevel;
+ /**
+ * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run
+ /**
+ * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED}
+ * broadcast was sent last.
+ * Note: These values may be used for internal operations and/or to determine whether to trigger
+ * the broadcast or not.
+ */
+ private int mLastBroadcastInvalidCharger;
/**
* The last seen charging policy. This requires the
* {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -172,7 +271,6 @@
private int mSequence = 1;
private int mInvalidCharger;
- private int mLastInvalidCharger;
private int mLowBatteryWarningLevel;
private int mLastLowBatteryWarningLevel;
@@ -184,7 +282,6 @@
private static String sSystemUiPackage;
private int mPlugType;
- private int mLastPlugType = -1; // Extra state so we can detect first run
private boolean mBatteryLevelLow;
@@ -197,6 +294,16 @@
private boolean mUpdatesStopped;
private boolean mBatteryInputSuspended;
+ /**
+ * Time when the voltage was updated last by HAL and we sent the
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+ * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+ * so it is possible that voltage was updated but we did not send the broadcast so in that
+ * case we do not update the time.
+ */
+ @VisibleForTesting
+ public long mLastBroadcastVoltageUpdateTime;
+
private Led mLed;
private boolean mSentLowBatteryBroadcast = false;
@@ -211,7 +318,8 @@
private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
- private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
+ @VisibleForTesting
+ public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
@@ -234,6 +342,25 @@
private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2;
private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast and update the temperature value when the temp change is greater or
+ * equals to 1 degree celsius.
+ */
+ private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * fluctuation of at least 1%.
+ */
+ private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
+ /**
+ * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * fluctuation of at least 1%.
+ */
+ private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+
private final Handler.Callback mLocalCallback = msg -> {
switch (msg.what) {
case MSG_BROADCAST_BATTERY_CHANGED: {
@@ -283,10 +410,19 @@
};
public BatteryService(Context context) {
+ this(context, Objects.requireNonNull(Looper.myLooper(),
+ "BatteryService uses handler!! Can't create handler inside thread that has not "
+ + "called Looper.prepare()"));
+ }
+
+ @VisibleForTesting
+ public BatteryService(Context context, @NonNull Looper looper) {
super(context);
+ Objects.requireNonNull(looper);
+
mContext = context;
- mHandler = new Handler(mLocalCallback, true /*async*/);
+ mHandler = new Handler(looper, mLocalCallback, true /*async*/);
mLed = new Led(context, getLocalService(LightsManager.class));
mBatteryStats = BatteryStatsService.getService();
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -436,7 +572,7 @@
private boolean shouldSendBatteryLowLocked() {
final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
- final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+ final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE;
/* The ACTION_BATTERY_LOW broadcast is sent in these situations:
* - is just un-plugged (previously was plugged) and battery level is
@@ -447,7 +583,7 @@
return !plugged
&& mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
- && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel
+ && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel
|| mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel);
}
@@ -515,7 +651,13 @@
}
}
- private void update(android.hardware.health.HealthInfo info) {
+ /**
+ * Updates the healthInfo and triggers the broadcast.
+ *
+ * @param info the new health info
+ */
+ @VisibleForTesting
+ public void update(android.hardware.health.HealthInfo info) {
traceBegin("HealthInfoUpdate");
Trace.traceCounter(
@@ -556,8 +698,8 @@
long dischargeDuration = 0;
mBatteryLevelCritical =
- mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
+ mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
mPlugType = plugType(mHealthInfo);
if (DEBUG) {
@@ -591,24 +733,28 @@
mHandler.post(this::notifyChargingPolicyChanged);
}
- if (force
- || (mHealthInfo.batteryStatus != mLastBatteryStatus
- || mHealthInfo.batteryHealth != mLastBatteryHealth
- || mHealthInfo.batteryPresent != mLastBatteryPresent
- || mHealthInfo.batteryLevel != mLastBatteryLevel
- || mPlugType != mLastPlugType
- || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage
- || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature
- || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
- || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
- || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
- || mInvalidCharger != mLastInvalidCharger
- || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
- || mHealthInfo.chargingState != mLastChargingState
- || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
+ final boolean includeChargeCounter =
+ !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()
+ && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter;
- if (mPlugType != mLastPlugType) {
- if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+ if (force
+ || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+ || mPlugType != mLastBroadcastPlugType
+ || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage
+ || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature
+ || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+ || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+ || includeChargeCounter
+ || mInvalidCharger != mLastBroadcastInvalidCharger
+ || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+ || mHealthInfo.chargingState != mLastBroadcastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) {
+
+ if (mPlugType != mLastBroadcastPlugType) {
+ if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) {
// discharging -> charging
mChargeStartLevel = mHealthInfo.batteryLevel;
mChargeStartTime = SystemClock.elapsedRealtime();
@@ -622,7 +768,8 @@
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
+ if (mDischargeStartTime != 0
+ && mDischargeStartLevel != mHealthInfo.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
@@ -639,7 +786,7 @@
if (mChargeStartTime != 0 && chargeDuration != 0) {
final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE);
builder.setType(MetricsEvent.TYPE_DISMISS);
- builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType);
+ builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType);
builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS,
chargeDuration);
builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START,
@@ -651,19 +798,20 @@
mChargeStartTime = 0;
}
}
- if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.batteryPresent != mLastBatteryPresent ||
- mPlugType != mLastPlugType) {
+ if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mPlugType != mLastBroadcastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+ mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+ mHealthInfo.batteryPresent ? 1 : 0,
mPlugType, mHealthInfo.batteryTechnology);
SystemProperties.set(
"debug.tracing.battery_status",
Integer.toString(mHealthInfo.batteryStatus));
SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
}
- if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(
@@ -672,8 +820,8 @@
mHealthInfo.batteryVoltageMillivolts,
mHealthInfo.batteryTemperatureTenthsCelsius);
}
- if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
- mPlugType == BATTERY_PLUGGED_NONE) {
+ if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical
+ && mPlugType == BATTERY_PLUGGED_NONE) {
// We want to make sure we log discharge cycle outliers
// if the battery is about to die.
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
@@ -684,7 +832,7 @@
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
&& mHealthInfo.batteryStatus !=
- BatteryManager.BATTERY_STATUS_UNKNOWN
+ BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
@@ -692,7 +840,7 @@
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
} else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
@@ -706,7 +854,7 @@
// Separate broadcast is sent for power connected / not connected
// since the standard intent will not wake any applications and some
// applications may want to have smart behavior based on this.
- if (mPlugType != 0 && mLastPlugType == 0) {
+ if (mPlugType != 0 && mLastBroadcastPlugType == 0) {
final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -726,8 +874,7 @@
}
});
}
- }
- else if (mPlugType == 0 && mLastPlugType != 0) {
+ } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) {
final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -797,8 +944,14 @@
// We are doing this after sending the above broadcasts, so anything processing
// them will get the new sequence number at that point. (See for example how testing
// of JobScheduler's BatteryController works.)
- sendBatteryChangedIntentLocked(force);
- if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) {
+
+ boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force);
+
+ if (!rateLimitBatteryChangedBroadcast) {
+ sendBatteryChangedIntentLocked(force);
+ }
+ if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel
+ || mLastBroadcastPlugType != mPlugType) {
sendBatteryLevelChangedIntentLocked();
}
@@ -811,21 +964,24 @@
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mHealthInfo.batteryStatus;
- mLastBatteryHealth = mHealthInfo.batteryHealth;
- mLastBatteryPresent = mHealthInfo.batteryPresent;
- mLastBatteryLevel = mHealthInfo.batteryLevel;
- mLastPlugType = mPlugType;
- mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
- mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
- mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
- mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
- mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
- mLastBatteryLevelCritical = mBatteryLevelCritical;
- mLastInvalidCharger = mInvalidCharger;
- mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
- mLastChargingState = mHealthInfo.chargingState;
- mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+ // Only update the values when we send the broadcast
+ if (!rateLimitBatteryChangedBroadcast) {
+ mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus;
+ mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth;
+ mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent;
+ mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel;
+ mLastBroadcastPlugType = mPlugType;
+ mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
+ mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
+ mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
+ mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
+ mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah;
+ mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical;
+ mLastBroadcastInvalidCharger = mInvalidCharger;
+ mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+ mLastBroadcastChargingState = mHealthInfo.chargingState;
+ mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+ }
}
}
@@ -1089,6 +1245,74 @@
}
}
+ /**
+ * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter.
+ */
+ private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) {
+ if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
+ return false;
+ }
+ if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ return false;
+ }
+
+ final boolean voltageUpdated =
+ mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
+ final boolean temperatureUpdated =
+ mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+ final boolean otherStatesUpdated = forceUpdate
+ || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+ || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+ || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+ || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+ || mPlugType != mLastBroadcastPlugType
+ || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+ || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+ || mInvalidCharger != mLastBroadcastInvalidCharger
+ || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+ || mHealthInfo.chargingState != mLastBroadcastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel;
+
+ // We only rate limit based on changes in the temp, voltage.
+ if (otherStatesUpdated) {
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ final float basePointDiff =
+ (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts)
+ / mLastBroadcastBatteryVoltage;
+
+ // We only send the broadcast if voltage change is greater than 1% and last voltage
+ // update was sent at least 20 seconds back.
+ if (voltageUpdated
+ && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE
+ && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime
+ >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+
+ return false;
+ }
+
+ // Only send the broadcast if the temperature update is greater than 1 degree celsius.
+ if (temperatureUpdated
+ && abs(
+ mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius)
+ >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) {
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ return true;
+ }
+
class Shell extends ShellCommand {
@Override
public int onCommand(String cmd) {
@@ -1399,6 +1623,10 @@
pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
+ pw.println(" Time when the latest updated value of the voltage was sent via "
+ + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+ pw.println(" The last voltage value sent via the battery changed broadcast: "
+ + mLastBroadcastBatteryVoltage);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
pw.println(" Charging state: " + mHealthInfo.chargingState);
@@ -1457,6 +1685,11 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ @VisibleForTesting
+ public Handler getHandlerForTest() {
+ return mHandler;
+ }
+
@SuppressLint("AndroidFrameworkRequiresPermission")
private static void sendBroadcastToAllUsers(Context context, Intent intent,
Bundle options) {
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 69ba785..eea5c98 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -67,3 +67,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "backstage_power"
+ name: "rate_limit_battery_changed_broadcast"
+ description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates"
+ bug: "362337621"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5fc3e33..05bc69a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@
permission, attributionSource, message, forDataDelivery, startDataDelivery,
fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
- if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+ && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
if (attributedOp == AppOpsManager.OP_NONE) {
finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@
final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
AttributionSource current = attributionSource;
AttributionSource next = null;
+ AttributionSource prev = null;
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,6 +1312,22 @@
selfAccess, singleReceiverFromDatasource, attributedOp,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
+ if (startDataDelivery && opMode != AppOpsManager.MODE_ALLOWED) {
+ // Current failed the perm check, so if we are part-way through an attr chain,
+ // we need to clean up the already started proxy op higher up the chain. Note,
+ // proxy ops are verified two by two, which means we have to clear the 2nd next
+ // from the previous iteration (since it is actually curr.next which failed
+ // to pass the perm check).
+ if (prev != null) {
+ final var cutAttrSourceState = prev.asState();
+ if (cutAttrSourceState.next.length > 0) {
+ cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+ }
+ finishDataDelivery(context, attributedOp,
+ cutAttrSourceState, fromDatasource);
+ }
+ }
+
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
@@ -1335,6 +1353,8 @@
return PermissionChecker.PERMISSION_GRANTED;
}
+ // an attribution we have already possibly started an op for
+ prev = current;
current = next;
}
}
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 6ce8685..9213d96 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -74,10 +74,6 @@
return mFeatureFlags;
}
- public boolean isFlagSafeModeTimeoutConfigEnabled() {
- return mFeatureFlags.safeModeTimeoutConfig();
- }
-
/**
* Verifies that the caller is running on the VcnContext Thread.
*
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2d3bc84..2325f35 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1263,7 +1263,7 @@
final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
int resultSeconds = defaultSeconds;
- if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+ if (carrierConfig != null) {
resultSeconds =
carrierConfig.getInt(
VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
new file mode 100644
index 0000000..5e2f80b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.hardware.health.HealthInfo;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.flags.Flags;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+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 java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryServiceTest {
+
+ private static final int CURRENT_BATTERY_VOLTAGE = 3000;
+ private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029;
+ private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030;
+ private static final int CURRENT_BATTERY_TEMP = 300;
+ private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305;
+ private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310;
+ private static final int CURRENT_BATTERY_HEALTH = 2;
+ private static final int UPDATED_BATTERY_HEALTH = 3;
+ private static final int CURRENT_CHARGE_COUNTER = 4680000;
+ private static final int UPDATED_CHARGE_COUNTER = 4218000;
+ private static final int HANDLER_IDLE_TIME_MS = 5000;
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(ActivityManager.class)
+ .mockStatic(BatteryStatsService.class)
+ .build();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private LightsManager mLightsManagerMock;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternalMock;
+ @Mock
+ private IBatteryStats mIBatteryStatsMock;
+
+ private BatteryService mBatteryService;
+ private String mSystemUiPackage;
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getResources().getString(R.string.config_systemUi);
+
+ when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
+ when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true);
+ when(mContextMock.getResources()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
+ ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock);
+
+ doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(),
+ anyInt(), anyInt(), anyInt(), anyInt(), anyLong());
+ doNothing().when(() -> SystemProperties.set(anyString(), anyString()));
+ doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(),
+ eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+ eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)));
+
+ addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+ addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+
+ createBatteryService();
+ }
+
+ @Test
+ public void createBatteryService_withNullLooper_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
+ mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyVoltageUpdated_broadcastSent() {
+ mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+ mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void tempUpdated_broadcastSent() {
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
+ UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ UPDATED_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+
+ // updating counter just after the health update does not triggers broadcast.
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ UPDATED_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
+ mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ private HealthInfo createHealthInfo(
+ int batteryVoltage,
+ int batteryTemperature,
+ int batteryChargeCounter,
+ int batteryHealth) {
+ HealthInfo h = new HealthInfo();
+ h.batteryVoltageMillivolts = batteryVoltage;
+ h.batteryTemperatureTenthsCelsius = batteryTemperature;
+ h.batteryChargeCounterUah = batteryChargeCounter;
+ h.batteryStatus = 5;
+ h.batteryHealth = batteryHealth;
+ h.batteryPresent = true;
+ h.batteryLevel = 100;
+ h.maxChargingCurrentMicroamps = 298125;
+ h.batteryCurrentAverageMicroamps = -2812;
+ h.batteryCurrentMicroamps = 298125;
+ h.maxChargingVoltageMicrovolts = 3000;
+ h.batteryCycleCount = 50;
+ h.chargingState = 4;
+ h.batteryCapacityLevel = 100;
+ return h;
+ }
+
+ // Creates a new battery service objects and sets the initial values.
+ private void createBatteryService() throws InterruptedException {
+ final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest");
+ handlerThread.start();
+
+ mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper());
+
+ // trigger the update to set the initial values.
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+ }
+
+ private void waitForHandlerToExecute() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mBatteryService.getHandlerForTest().post(latch::countDown);
+ boolean isExecutionComplete = false;
+
+ try {
+ isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Handler interrupted before executing the message " + e);
+ }
+
+ assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete);
+ }
+
+ private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) {
+ // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test
+ // setUp.
+ verify(() -> ActivityManager.broadcastStickyIntent(any(),
+ eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+ eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)),
+ times(++numberOfTimes));
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 49665f7..613b926 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -360,12 +360,10 @@
private void verifyGetSafeModeTimeoutMs(
boolean isInTestMode,
- boolean isConfigTimeoutSupported,
PersistableBundleWrapper carrierConfig,
long expectedTimeoutMs)
throws Exception {
doReturn(isInTestMode).when(mVcnContext).isInTestMode();
- doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
@@ -377,16 +375,7 @@
}
@Test
- public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
- verifyGetSafeModeTimeoutMs(
- false /* isInTestMode */,
- false /* isConfigTimeoutSupported */,
- null /* carrierConfig */,
- TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
- }
-
- @Test
- public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+ public void testGetSafeModeTimeoutMs() throws Exception {
final int carrierConfigTimeoutSeconds = 20;
final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
doReturn(carrierConfigTimeoutSeconds)
@@ -395,17 +384,14 @@
verifyGetSafeModeTimeoutMs(
false /* isInTestMode */,
- true /* isConfigTimeoutSupported */,
carrierConfig,
TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
}
@Test
- public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
- throws Exception {
+ public void testGetSafeModeTimeoutMs_carrierConfigNull() throws Exception {
verifyGetSafeModeTimeoutMs(
false /* isInTestMode */,
- true /* isConfigTimeoutSupported */,
null /* carrierConfig */,
TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
}
@@ -420,7 +406,6 @@
verifyGetSafeModeTimeoutMs(
true /* isInTestMode */,
- true /* isConfigTimeoutSupported */,
carrierConfig,
TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 4c7b25a..8374fd9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -222,7 +222,6 @@
doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
- doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
doReturn(mUnderlyingNetworkController)
.when(mDeps)