Loading services/core/java/com/android/server/am/ActiveServices.java +4 −0 Original line number Diff line number Diff line Loading @@ -5933,6 +5933,10 @@ public final class ActiveServices { * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state. */ private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mDefaultFgsAtomSampleRate)) { return; } FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, Loading services/core/java/com/android/server/am/ActivityManagerConstants.java +21 −0 Original line number Diff line number Diff line Loading @@ -97,6 +97,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration"; static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration"; static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout"; static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; Loading Loading @@ -137,6 +138,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000; private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000; private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000; private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 % // Flag stored in the DeviceConfig API. /** Loading Loading @@ -430,6 +432,13 @@ final class ActivityManagerConstants extends ContentObserver { */ volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS; /** * Sample rate for the FGS westworld atom. * * If the value is 0.1, 10% of the installed packages would be sampled. */ volatile float mDefaultFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE; private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -629,6 +638,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_FGS_START_FOREGROUND_TIMEOUT: updateFgsStartForegroundTimeout(); break; case KEY_FGS_ATOM_SAMPLE_RATE: updateFgsAtomSamplePercent(); break; default: break; } Loading Loading @@ -933,6 +945,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS); } private void updateFgsAtomSamplePercent() { mDefaultFgsAtomSampleRate = DeviceConfig.getFloat( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_FGS_ATOM_SAMPLE_RATE, DEFAULT_FGS_ATOM_SAMPLE_RATE); } private void updateImperceptibleKillExemptions() { IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear(); IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); Loading Loading @@ -1145,6 +1164,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(mFlagFgsStartRestrictionEnabled); pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK); pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk); pw.print(" "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE); pw.print("="); pw.println(mDefaultFgsAtomSampleRate); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { Loading services/core/java/com/android/server/am/ActivityManagerUtils.java 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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.server.am; import android.app.ActivityThread; import android.provider.Settings; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * To store random utility methods... */ public class ActivityManagerUtils { private ActivityManagerUtils() { } private static Integer sAndroidIdHash; @GuardedBy("sHashCache") private static final ArrayMap<String, Integer> sHashCache = new ArrayMap<>(); private static String sInjectedAndroidId; /** Used by the unit tests to inject an android ID. Do not set in the prod code. */ @VisibleForTesting static void injectAndroidIdForTest(String androidId) { sInjectedAndroidId = androidId; sAndroidIdHash = null; } /** * Return a hash between [0, MAX_VALUE] generated from the android ID. */ @VisibleForTesting static int getAndroidIdHash() { // No synchronization is required. Double-initialization is fine here. if (sAndroidIdHash == null) { final String androidId = Settings.Secure.getString( ActivityThread.currentApplication().getContentResolver(), Settings.Secure.ANDROID_ID); sAndroidIdHash = getUnsignedHashUnCached( sInjectedAndroidId != null ? sInjectedAndroidId : androidId); } return sAndroidIdHash; } /** * Return a hash between [0, MAX_VALUE] generated from a package name, using a cache. * * Because all the results are cached, do not use it for dynamically generated strings. */ @VisibleForTesting static int getUnsignedHashCached(String s) { synchronized (sHashCache) { final Integer cached = sHashCache.get(s); if (cached != null) { return cached; } final int hash = getUnsignedHashUnCached(s); sHashCache.put(s.intern(), hash); return hash; } } /** * Return a hash between [0, MAX_VALUE] generated from a package name. */ private static int getUnsignedHashUnCached(String s) { try { final MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(s.getBytes()); return unsignedIntFromBytes(digest.digest()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @VisibleForTesting static int unsignedIntFromBytes(byte[] longEnoughBytes) { return (extractByte(longEnoughBytes, 0) | extractByte(longEnoughBytes, 1) | extractByte(longEnoughBytes, 2) | extractByte(longEnoughBytes, 3)) & 0x7FFF_FFFF; } private static int extractByte(byte[] bytes, int index) { return (((int) bytes[index]) & 0xFF) << (index * 8); } /** * @return whether a package should be logged, using a random value based on the ANDROID_ID, * with a given sampling rate. */ public static boolean shouldSamplePackageForAtom(String packageName, float rate) { if (rate <= 0) { return false; } if (rate >= 1) { return true; } final int hash = getUnsignedHashCached(packageName) ^ getAndroidIdHash(); return (((double) hash) / Integer.MAX_VALUE) <= rate; } } services/tests/servicestests/src/com/android/server/am/ActivityManagerUtilsTest.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * 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.server.am; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import androidx.test.filters.SmallTest; import org.junit.Test; @SmallTest public class ActivityManagerUtilsTest { @Test public void getAndroidIdHash() { // getAndroidIdHash() essentially returns a random a value. Just make sure it's // non-negative. assertThat(ActivityManagerUtils.getAndroidIdHash()).isAtLeast(0); } @Test public void getUnsignedHashCached() { assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isEqualTo( ActivityManagerUtils.getUnsignedHashCached("x")); assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isNotEqualTo( ActivityManagerUtils.getUnsignedHashCached("y")); } @Test public void shouldSamplePackage_sampleNone() { final int numTests = 100000; for (int i = 0; i < numTests; i++) { assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 0)) .isFalse(); } } @Test public void shouldSamplePackage_sampleAll() { final int numTests = 100000; for (int i = 0; i < numTests; i++) { assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 1)) .isTrue(); } } /** * Make sure, with the same android ID, an expected rate of the packages are selected. */ @Test public void shouldSamplePackage_sampleSome_fixedAndroidId() { checkShouldSamplePackage_fixedAndroidId(0.1f); checkShouldSamplePackage_fixedAndroidId(0.5f); checkShouldSamplePackage_fixedAndroidId(0.9f); } /** * Make sure, the same package is selected on an expected rate of the devices. */ @Test public void shouldSamplePackage_sampleSome_fixedPackage() { checkShouldSamplePackage_fixedPackage(0.1f); checkShouldSamplePackage_fixedPackage(0.5f); checkShouldSamplePackage_fixedPackage(0.9f); } private void checkShouldSamplePackage_fixedPackage(float sampleRate) { checkShouldSamplePackage(sampleRate, sampleRate, true, false); } private void checkShouldSamplePackage_fixedAndroidId(float sampleRate) { checkShouldSamplePackage(sampleRate, sampleRate, false, true); } @Test public void testSheckShouldSamplePackage() { // Just make sure checkShouldSamplePackage is actually working... try { checkShouldSamplePackage(0.3f, 0.6f, false, true); fail(); } catch (AssertionError expected) { } try { checkShouldSamplePackage(0.6f, 0.3f, true, false); fail(); } catch (AssertionError expected) { } } private void checkShouldSamplePackage(float inputSampleRate, float expectedRate, boolean fixedPackage, boolean fixedAndroidId) { final int numTests = 100000; try { int numSampled = 0; for (int i = 0; i < numTests; i++) { final String pkg = fixedPackage ? "fixed-package" : "" + i; ActivityManagerUtils.injectAndroidIdForTest( fixedAndroidId ? "fixed-android-id" : "" + i); if (ActivityManagerUtils.shouldSamplePackageForAtom(pkg, inputSampleRate)) { numSampled++; } assertThat(ActivityManagerUtils.getUnsignedHashCached(pkg)).isEqualTo( ActivityManagerUtils.getUnsignedHashCached(pkg)); } final double actualSampleRate = ((double) numSampled) / numTests; assertThat(actualSampleRate).isWithin(0.05).of(expectedRate); } finally { ActivityManagerUtils.injectAndroidIdForTest(null); } } } Loading
services/core/java/com/android/server/am/ActiveServices.java +4 −0 Original line number Diff line number Diff line Loading @@ -5933,6 +5933,10 @@ public final class ActiveServices { * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state. */ private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mDefaultFgsAtomSampleRate)) { return; } FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, Loading
services/core/java/com/android/server/am/ActivityManagerConstants.java +21 −0 Original line number Diff line number Diff line Loading @@ -97,6 +97,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration"; static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration"; static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout"; static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; Loading Loading @@ -137,6 +138,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000; private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000; private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000; private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 % // Flag stored in the DeviceConfig API. /** Loading Loading @@ -430,6 +432,13 @@ final class ActivityManagerConstants extends ContentObserver { */ volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS; /** * Sample rate for the FGS westworld atom. * * If the value is 0.1, 10% of the installed packages would be sampled. */ volatile float mDefaultFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE; private final ActivityManagerService mService; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -629,6 +638,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_FGS_START_FOREGROUND_TIMEOUT: updateFgsStartForegroundTimeout(); break; case KEY_FGS_ATOM_SAMPLE_RATE: updateFgsAtomSamplePercent(); break; default: break; } Loading Loading @@ -933,6 +945,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS); } private void updateFgsAtomSamplePercent() { mDefaultFgsAtomSampleRate = DeviceConfig.getFloat( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_FGS_ATOM_SAMPLE_RATE, DEFAULT_FGS_ATOM_SAMPLE_RATE); } private void updateImperceptibleKillExemptions() { IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear(); IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages); Loading Loading @@ -1145,6 +1164,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(mFlagFgsStartRestrictionEnabled); pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK); pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk); pw.print(" "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE); pw.print("="); pw.println(mDefaultFgsAtomSampleRate); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { Loading
services/core/java/com/android/server/am/ActivityManagerUtils.java 0 → 100644 +124 −0 Original line number Diff line number Diff line /* * 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.server.am; import android.app.ActivityThread; import android.provider.Settings; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * To store random utility methods... */ public class ActivityManagerUtils { private ActivityManagerUtils() { } private static Integer sAndroidIdHash; @GuardedBy("sHashCache") private static final ArrayMap<String, Integer> sHashCache = new ArrayMap<>(); private static String sInjectedAndroidId; /** Used by the unit tests to inject an android ID. Do not set in the prod code. */ @VisibleForTesting static void injectAndroidIdForTest(String androidId) { sInjectedAndroidId = androidId; sAndroidIdHash = null; } /** * Return a hash between [0, MAX_VALUE] generated from the android ID. */ @VisibleForTesting static int getAndroidIdHash() { // No synchronization is required. Double-initialization is fine here. if (sAndroidIdHash == null) { final String androidId = Settings.Secure.getString( ActivityThread.currentApplication().getContentResolver(), Settings.Secure.ANDROID_ID); sAndroidIdHash = getUnsignedHashUnCached( sInjectedAndroidId != null ? sInjectedAndroidId : androidId); } return sAndroidIdHash; } /** * Return a hash between [0, MAX_VALUE] generated from a package name, using a cache. * * Because all the results are cached, do not use it for dynamically generated strings. */ @VisibleForTesting static int getUnsignedHashCached(String s) { synchronized (sHashCache) { final Integer cached = sHashCache.get(s); if (cached != null) { return cached; } final int hash = getUnsignedHashUnCached(s); sHashCache.put(s.intern(), hash); return hash; } } /** * Return a hash between [0, MAX_VALUE] generated from a package name. */ private static int getUnsignedHashUnCached(String s) { try { final MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(s.getBytes()); return unsignedIntFromBytes(digest.digest()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } @VisibleForTesting static int unsignedIntFromBytes(byte[] longEnoughBytes) { return (extractByte(longEnoughBytes, 0) | extractByte(longEnoughBytes, 1) | extractByte(longEnoughBytes, 2) | extractByte(longEnoughBytes, 3)) & 0x7FFF_FFFF; } private static int extractByte(byte[] bytes, int index) { return (((int) bytes[index]) & 0xFF) << (index * 8); } /** * @return whether a package should be logged, using a random value based on the ANDROID_ID, * with a given sampling rate. */ public static boolean shouldSamplePackageForAtom(String packageName, float rate) { if (rate <= 0) { return false; } if (rate >= 1) { return true; } final int hash = getUnsignedHashCached(packageName) ^ getAndroidIdHash(); return (((double) hash) / Integer.MAX_VALUE) <= rate; } }
services/tests/servicestests/src/com/android/server/am/ActivityManagerUtilsTest.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * 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.server.am; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import androidx.test.filters.SmallTest; import org.junit.Test; @SmallTest public class ActivityManagerUtilsTest { @Test public void getAndroidIdHash() { // getAndroidIdHash() essentially returns a random a value. Just make sure it's // non-negative. assertThat(ActivityManagerUtils.getAndroidIdHash()).isAtLeast(0); } @Test public void getUnsignedHashCached() { assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isEqualTo( ActivityManagerUtils.getUnsignedHashCached("x")); assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isNotEqualTo( ActivityManagerUtils.getUnsignedHashCached("y")); } @Test public void shouldSamplePackage_sampleNone() { final int numTests = 100000; for (int i = 0; i < numTests; i++) { assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 0)) .isFalse(); } } @Test public void shouldSamplePackage_sampleAll() { final int numTests = 100000; for (int i = 0; i < numTests; i++) { assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 1)) .isTrue(); } } /** * Make sure, with the same android ID, an expected rate of the packages are selected. */ @Test public void shouldSamplePackage_sampleSome_fixedAndroidId() { checkShouldSamplePackage_fixedAndroidId(0.1f); checkShouldSamplePackage_fixedAndroidId(0.5f); checkShouldSamplePackage_fixedAndroidId(0.9f); } /** * Make sure, the same package is selected on an expected rate of the devices. */ @Test public void shouldSamplePackage_sampleSome_fixedPackage() { checkShouldSamplePackage_fixedPackage(0.1f); checkShouldSamplePackage_fixedPackage(0.5f); checkShouldSamplePackage_fixedPackage(0.9f); } private void checkShouldSamplePackage_fixedPackage(float sampleRate) { checkShouldSamplePackage(sampleRate, sampleRate, true, false); } private void checkShouldSamplePackage_fixedAndroidId(float sampleRate) { checkShouldSamplePackage(sampleRate, sampleRate, false, true); } @Test public void testSheckShouldSamplePackage() { // Just make sure checkShouldSamplePackage is actually working... try { checkShouldSamplePackage(0.3f, 0.6f, false, true); fail(); } catch (AssertionError expected) { } try { checkShouldSamplePackage(0.6f, 0.3f, true, false); fail(); } catch (AssertionError expected) { } } private void checkShouldSamplePackage(float inputSampleRate, float expectedRate, boolean fixedPackage, boolean fixedAndroidId) { final int numTests = 100000; try { int numSampled = 0; for (int i = 0; i < numTests; i++) { final String pkg = fixedPackage ? "fixed-package" : "" + i; ActivityManagerUtils.injectAndroidIdForTest( fixedAndroidId ? "fixed-android-id" : "" + i); if (ActivityManagerUtils.shouldSamplePackageForAtom(pkg, inputSampleRate)) { numSampled++; } assertThat(ActivityManagerUtils.getUnsignedHashCached(pkg)).isEqualTo( ActivityManagerUtils.getUnsignedHashCached(pkg)); } final double actualSampleRate = ((double) numSampled) / numTests; assertThat(actualSampleRate).isWithin(0.05).of(expectedRate); } finally { ActivityManagerUtils.injectAndroidIdForTest(null); } } }