From 71e11ae9ba8e1f5716b7d1a5c77c1fea9a9442b7 Mon Sep 17 00:00:00 2001 From: Juan Yescas Date: Tue, 23 Aug 2022 04:56:59 +0000 Subject: [PATCH 1/2] Add method to parse the size in FileUtils The method parseSize() will parse the strings that match the regular expression: "^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$" For example: 12GB, +100MB, 23MiB. It will return the size in bytes. Test: atest Bug: 231718727 Change-Id: Id4b9084d2ff8a0a8bd23ddb8a7e10dcf17391c49 --- core/java/android/os/FileUtils.java | 81 +++++++++++++++++++ .../src/android/os/FileUtilsTest.java | 54 ++++++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index edfcb3d6f12a7..d5c3de1460154 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -54,6 +54,7 @@ import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; import android.text.TextUtils; +import android.util.DataUnit; import android.util.Log; import android.util.Slog; import android.webkit.MimeTypeMap; @@ -83,6 +84,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -1309,6 +1311,85 @@ public final class FileUtils { return val * pow; } + private static long toBytes(long value, String unit) { + unit = unit.toUpperCase(); + + if (List.of("B").contains(unit)) { + return value; + } + + if (List.of("K", "KB").contains(unit)) { + return DataUnit.KILOBYTES.toBytes(value); + } + + if (List.of("M", "MB").contains(unit)) { + return DataUnit.MEGABYTES.toBytes(value); + } + + if (List.of("G", "GB").contains(unit)) { + return DataUnit.GIGABYTES.toBytes(value); + } + + if (List.of("KI", "KIB").contains(unit)) { + return DataUnit.KIBIBYTES.toBytes(value); + } + + if (List.of("MI", "MIB").contains(unit)) { + return DataUnit.MEBIBYTES.toBytes(value); + } + + if (List.of("GI", "GIB").contains(unit)) { + return DataUnit.GIBIBYTES.toBytes(value); + } + + return Long.MIN_VALUE; + } + + /** + * @param fmtSize The string that contains the size to be parsed. The + * expected format is: + * + *

"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$" + * + *

For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive. + * + * @return the size in bytes. If {@code fmtSize} has invalid format, it + * returns {@link Long#MIN_VALUE}. + * @hide + */ + public static long parseSize(@Nullable String fmtSize) { + if (fmtSize == null || fmtSize.isBlank()) { + return Long.MIN_VALUE; + } + + int sign = 1; + fmtSize = fmtSize.trim(); + char first = fmtSize.charAt(0); + if (first == '-' || first == '+') { + if (first == '-') { + sign = -1; + } + + fmtSize = fmtSize.replace(first + "", ""); + } + + int index = 0; + // Find the last index of the value in fmtSize. + while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) { + index++; + } + + // Check if number and units are present. + if (index == 0 || index == fmtSize.length()) { + return Long.MIN_VALUE; + } + + long value = sign * Long.valueOf(fmtSize.substring(0, index)); + String unit = fmtSize.substring(index).trim(); + + return toBytes(value, unit); + } + /** * Closes the given object quietly, ignoring any checked exceptions. Does * nothing if the given object is {@code null}. diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index c1e72fe756667..5f731e2018662 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -58,10 +58,10 @@ import android.provider.DocumentsContract.Document; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import libcore.io.Streams; - import com.google.android.collect.Sets; +import libcore.io.Streams; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -523,6 +523,56 @@ public class FileUtilsTest { assertEquals(G64, roundStorageSize(G32 + 1)); } + @Test + public void testParseSize() { + assertEquals(0L, FileUtils.parseSize("0MB")); + assertEquals(1_024L, FileUtils.parseSize("1024b")); + assertEquals(-1L, FileUtils.parseSize(" -1 b ")); + assertEquals(0L, FileUtils.parseSize(" -0 gib ")); + assertEquals(1_000L, FileUtils.parseSize("1K")); + assertEquals(1_000L, FileUtils.parseSize("1KB")); + assertEquals(10_000L, FileUtils.parseSize("10KB")); + assertEquals(100_000L, FileUtils.parseSize("100KB")); + assertEquals(1_000_000L, FileUtils.parseSize("1000KB")); + assertEquals(1_024_000L, FileUtils.parseSize("1000KiB")); + assertEquals(70_000_000L, FileUtils.parseSize("070M")); + assertEquals(70_000_000L, FileUtils.parseSize("070MB")); + assertEquals(73_400_320L, FileUtils.parseSize("70MiB")); + assertEquals(700_000_000L, FileUtils.parseSize("700000KB")); + assertEquals(200_000_000L, FileUtils.parseSize("+200MB")); + assertEquals(1_000_000_000L, FileUtils.parseSize("1000MB")); + assertEquals(1_000_000_000L, FileUtils.parseSize("+1000 mb")); + assertEquals(644_245_094_400L, FileUtils.parseSize("600GiB")); + assertEquals(999_000_000_000L, FileUtils.parseSize("999GB")); + assertEquals(999_000_000_000L, FileUtils.parseSize("999 gB")); + assertEquals(9_999_000_000_000L, FileUtils.parseSize("9999GB")); + assertEquals(9_000_000_000_000L, FileUtils.parseSize(" 9000 GB ")); + assertEquals(1_234_000_000_000L, FileUtils.parseSize(" 1234 GB ")); + assertEquals(1_234_567_890_000L, FileUtils.parseSize(" 1234567890 KB ")); + } + + @Test + public void testParseSize_invalidArguments() { + assertEquals(Long.MIN_VALUE, FileUtils.parseSize(null)); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("null")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("KB")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123 dd")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("Invalid")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ABC890 KB ")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-=+90 KB ")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("--123")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-KB")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("++123")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+ 1 +")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+--+ 1 +")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize("1GB+")); + assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" + 1234567890 KB ")); + } + @Test public void testTranslateMode() throws Exception { assertTranslate("r", O_RDONLY, MODE_READ_ONLY); From 7afbbec42f4d30be77a818552aa2201e54c8b798 Mon Sep 17 00:00:00 2001 From: Juan Yescas Date: Tue, 23 Aug 2022 07:09:21 +0000 Subject: [PATCH 2/2] Add new field in MemoryInfo to get advertised memory size. This API will get the advertised memory size from the bootloader parameter passed to the kernel. The advertised memory size might be different from the MemTotal value in /proc/meminfo. This could be due the original design manufacturer (ODM) reserved part of the memory for the Trusted Execution Environment. Test: Run test caes Bug: 231718727 Change-Id: I06494cb9013048d73a83f2180a9241c2259d46eb --- core/api/current.txt | 1 + core/java/android/app/ActivityManager.java | 12 ++++++++++++ core/java/android/os/Process.java | 19 +++++++++++++++++++ .../coretests/src/android/os/ProcessTest.java | 3 +++ .../com/android/server/am/ProcessList.java | 2 ++ 5 files changed, 37 insertions(+) diff --git a/core/api/current.txt b/core/api/current.txt index 644a194f6ed1e..05e2a9aa72860 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4415,6 +4415,7 @@ package android.app { method public void readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public long advertisedMem; field public long availMem; field public boolean lowMemory; field public long threshold; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 5d1d225f4d2d9..6f9a98d8313fd 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -29,6 +29,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -2812,6 +2813,15 @@ public class ActivityManager { * {@link ActivityManager#getMemoryInfo}. */ public static class MemoryInfo implements Parcelable { + /** + * The advertised memory of the system, as the end user would encounter in a retail display + * environment. This value might be different from {@code totalMem}. This could be due to + * many reasons. For example, the ODM could reserve part of the memory for the Trusted + * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it. + */ + @SuppressLint("MutableBareField") + public long advertisedMem; + /** * The available memory on the system. This number should not * be considered absolute: due to the nature of the kernel, a significant @@ -2861,6 +2871,7 @@ public class ActivityManager { } public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(advertisedMem); dest.writeLong(availMem); dest.writeLong(totalMem); dest.writeLong(threshold); @@ -2872,6 +2883,7 @@ public class ActivityManager { } public void readFromParcel(Parcel source) { + advertisedMem = source.readLong(); availMem = source.readLong(); totalMem = source.readLong(); threshold = source.readLong(); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index e06e7326a8603..b3afaecca8496 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -26,6 +26,7 @@ import android.annotation.TestApi; import android.annotation.UptimeMillisLong; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build.VERSION_CODES; +import android.sysprop.MemoryProperties; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -1336,6 +1337,24 @@ public class Process { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public static final native void sendSignalQuiet(int pid, int signal); + /** + * @return The advertised memory of the system, as the end user would encounter in a retail + * display environment. If the advertised memory is not defined, it returns + * {@code getTotalMemory()} rounded. + * + * @hide + */ + public static final long getAdvertisedMem() { + String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB"); + long memSize = FileUtils.parseSize(formatSize); + + if (memSize == Long.MIN_VALUE) { + return FileUtils.roundStorageSize(getTotalMemory()); + } + + return memSize; + } + /** @hide */ @UnsupportedAppUsage public static final native long getFreeMemory(); diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java index ae4edb97aab00..52846dfbb14b8 100644 --- a/core/tests/coretests/src/android/os/ProcessTest.java +++ b/core/tests/coretests/src/android/os/ProcessTest.java @@ -72,4 +72,7 @@ public class ProcessTest extends TestCase { assertEquals(-1, Process.getThreadGroupLeader(BAD_PID)); } + public void testGetAdvertisedMem() { + assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem()); + } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 178b6bbe755ed..eb6a3b3a3cea0 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -27,6 +27,7 @@ import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; +import static android.os.Process.getAdvertisedMem; import static android.os.Process.getFreeMemory; import static android.os.Process.getTotalMemory; import static android.os.Process.killProcessQuiet; @@ -1524,6 +1525,7 @@ public final class ProcessList { void getMemoryInfo(ActivityManager.MemoryInfo outInfo) { final long homeAppMem = getMemLevel(HOME_APP_ADJ); final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ); + outInfo.advertisedMem = getAdvertisedMem(); outInfo.availMem = getFreeMemory(); outInfo.totalMem = getTotalMemory(); outInfo.threshold = homeAppMem;