Merge "CryptoAsync: CryptoException when used with cryptoInfos" into main
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 46ae9d8..320641d 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -25,8 +25,6 @@
import dalvik.system.VMRuntime;
-import libcore.io.IoUtils;
-
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -65,7 +63,7 @@
mMemoryRegistration = new MemoryRegistration(mSize);
mCleaner = Cleaner.create(mFileDescriptor,
- new Closer(mFileDescriptor, mMemoryRegistration));
+ new Closer(mFileDescriptor.getInt$(), mMemoryRegistration));
}
/**
@@ -278,6 +276,7 @@
*/
@Override
public void close() {
+ mFileDescriptor.setInt$(-1);
if (mCleaner != null) {
mCleaner.clean();
mCleaner = null;
@@ -327,20 +326,21 @@
* Cleaner that closes the FD
*/
private static final class Closer implements Runnable {
- private FileDescriptor mFd;
+ private int mFd;
private MemoryRegistration mMemoryReference;
- private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
+ private Closer(int fd, MemoryRegistration memoryReference) {
mFd = fd;
- IoUtils.setFdOwner(mFd, this);
mMemoryReference = memoryReference;
}
@Override
public void run() {
- IoUtils.closeQuietly(mFd);
- mFd = null;
-
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(mFd);
+ Os.close(fd);
+ } catch (ErrnoException e) { /* swallow error */ }
mMemoryReference.release();
mMemoryReference = null;
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index a13dd78..104ba01 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -287,6 +287,7 @@
"tests/AttributeResolution_bench.cpp",
"tests/CursorWindow_bench.cpp",
"tests/Generic_bench.cpp",
+ "tests/LocaleDataLookup_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp
index 6e751a7..ea9e9a2 100644
--- a/libs/androidfw/LocaleDataLookup.cpp
+++ b/libs/androidfw/LocaleDataLookup.cpp
@@ -7518,6 +7518,13 @@
}
}
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */
bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {
const uint64_t packed_locale =
((static_cast<uint64_t>(language_and_region)) << 32u) |
diff --git a/libs/androidfw/tests/LocaleDataLookup_bench.cpp b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
new file mode 100644
index 0000000..60ce3b9
--- /dev/null
+++ b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#include "benchmark/benchmark.h"
+
+#include "androidfw/LocaleDataLookup.h"
+
+namespace android {
+
+static void BM_LocaleDataLookupIsLocaleRepresentative(benchmark::State& state) {
+ for (auto&& _ : state) {
+ isLocaleRepresentative(packLocale("en", "US"), "Latn");
+ isLocaleRepresentative(packLocale("es", "ES"), "Latn");
+ isLocaleRepresentative(packLocale("zh", "CN"), "Hans");
+ isLocaleRepresentative(packLocale("pt", "BR"), "Latn");
+ isLocaleRepresentative(packLocale("ar", "EG"), "Arab");
+ isLocaleRepresentative(packLocale("hi", "IN"), "Deva");
+ isLocaleRepresentative(packLocale("jp", "JP"), "Jpan");
+ }
+}
+BENCHMARK(BM_LocaleDataLookupIsLocaleRepresentative);
+
+static void BM_LocaleDataLookupLikelyScript(benchmark::State& state) {
+ for (auto&& _ : state) {
+ lookupLikelyScript(packLocale("en", ""));
+ lookupLikelyScript(packLocale("es", ""));
+ lookupLikelyScript(packLocale("zh", ""));
+ lookupLikelyScript(packLocale("pt", ""));
+ lookupLikelyScript(packLocale("ar", ""));
+ lookupLikelyScript(packLocale("hi", ""));
+ lookupLikelyScript(packLocale("jp", ""));
+ lookupLikelyScript(packLocale("en", "US"));
+ lookupLikelyScript(packLocale("es", "ES"));
+ lookupLikelyScript(packLocale("zh", "CN"));
+ lookupLikelyScript(packLocale("pt", "BR"));
+ lookupLikelyScript(packLocale("ar", "EG"));
+ lookupLikelyScript(packLocale("hi", "IN"));
+ lookupLikelyScript(packLocale("jp", "JP"));
+ }
+}
+BENCHMARK(BM_LocaleDataLookupLikelyScript);
+
+
+} // namespace android
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 54ded0c..eb30bbe 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -198,10 +198,6 @@
bug: "380892385"
}
-flag {
- name: "nfc_hce_latency_events"
- is_exported: true
- namespace: "wallet_integration"
- description: "Enables tracking latency for HCE"
- bug: "379849603"
-}
+# Unless you are adding a flag for a file under nfc-non-updatable, you should
+# not add a flag here for Android 16+ targeting features. Use the flags
+# in com.android.nfc.module.flags (packages/modules/Nfc/flags) instead.
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
index 93d6eb7..e83b9f1 100644
--- a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -572,8 +572,10 @@
if (mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false)) {
return true;
}
- List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream()
- .filter(p -> p.matcher(plf).matches()).toList();
+ boolean isPattern = plf.contains("?") || plf.contains("*");
+ List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream().filter(
+ p -> isPattern ? p.toString().equals(plf) : p.matcher(plf).matches()).toList();
+
if (patternMatches == null || patternMatches.size() == 0) {
return false;
}
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 3cb6c5a..7af03ed 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -44,6 +44,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.icu.util.ULocale;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -81,6 +82,7 @@
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
@@ -228,6 +230,9 @@
RuntimeInit.redirectLogStreams();
dumpCommandLineArgs();
+ dumpEnvironment();
+ dumpJavaProperties();
+ dumpOtherInfo();
// We haven't initialized liblog yet, so directly write to System.out here.
RavenwoodCommonUtils.log(TAG, "globalInitInner()");
@@ -564,4 +569,34 @@
Log.v(TAG, " " + arg);
}
}
+
+ private static void dumpJavaProperties() {
+ Log.v(TAG, "JVM properties:");
+ dumpMap(System.getProperties());
+ }
+
+ private static void dumpEnvironment() {
+ Log.v(TAG, "Environment:");
+ dumpMap(System.getenv());
+ }
+
+ private static void dumpMap(Map<?, ?> map) {
+ for (var key : map.keySet().stream().sorted().toList()) {
+ Log.v(TAG, " " + key + "=" + map.get(key));
+ }
+ }
+
+ private static void dumpOtherInfo() {
+ Log.v(TAG, "Other key information:");
+ var jloc = Locale.getDefault();
+ Log.v(TAG, " java.util.Locale=" + jloc + " / " + jloc.toLanguageTag());
+ var uloc = ULocale.getDefault();
+ Log.v(TAG, " android.icu.util.ULocale=" + uloc + " / " + uloc.toLanguageTag());
+
+ var jtz = java.util.TimeZone.getDefault();
+ Log.v(TAG, " java.util.TimeZone=" + jtz.getDisplayName() + " / " + jtz);
+
+ var itz = android.icu.util.TimeZone.getDefault();
+ Log.v(TAG, " android.icu.util.TimeZone=" + itz.getDisplayName() + " / " + itz);
+ }
}
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 23cee9d..16209b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -50,6 +50,7 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -147,6 +148,10 @@
@Override
public void onReceive(final Context context, Intent intent) {
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ return;
+ }
+
// Log boot events in the background to avoid blocking the main thread with I/O
new Thread() {
@Override
@@ -212,6 +217,8 @@
} catch (Exception e) {
Slog.wtf(TAG, "Error watching for trace events", e);
return 0; // Unregister the handler.
+ } finally {
+ IoUtils.closeQuietly(fd);
}
return OnFileDescriptorEventListener.EVENT_INPUT;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19676eb..6045f63 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -946,7 +946,6 @@
refreshZramSettings();
if (mmdEnabled()) {
- // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
ZramMaintenance.startZramMaintenance(mContext);
} else {
// Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
index cdb4812..099e5b3 100644
--- a/services/core/java/com/android/server/ZramMaintenance.java
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -24,11 +24,15 @@
import android.content.Context;
import android.os.IBinder;
import android.os.IMmd;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.time.Duration;
/**
@@ -46,43 +50,45 @@
private static final String TAG = ZramMaintenance.class.getName();
// Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
// as the job id.
- private static final int JOB_ID = 375432472;
+ @VisibleForTesting
+ public static final int JOB_ID = 375432472;
private static final ComponentName sZramMaintenance =
new ComponentName("android", ZramMaintenance.class.getName());
+ @VisibleForTesting
+ public static final String KEY_CHECK_STATUS = "check_status";
+ private static final String SYSTEM_PROPERTY_PREFIX = "mm.";
private static final String FIRST_DELAY_SECONDS_PROP =
- "mm.zram.maintenance.first_delay_seconds";
+ "zram.maintenance.first_delay_seconds";
// The default is 1 hour.
private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
private static final String PERIODIC_DELAY_SECONDS_PROP =
- "mm.zram.maintenance.periodic_delay_seconds";
+ "zram.maintenance.periodic_delay_seconds";
// The default is 1 hour.
private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
private static final String REQUIRE_DEVICE_IDLE_PROP =
- "mm.zram.maintenance.require_device_idle";
+ "zram.maintenance.require_device_idle";
private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
true;
private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
- "mm.zram.maintenance.require_battry_not_low";
+ "zram.maintenance.require_battry_not_low";
private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
true;
@Override
public boolean onStartJob(JobParameters params) {
- IBinder binder = ServiceManager.getService("mmd");
- if (binder != null) {
- IMmd mmd = IMmd.Stub.asInterface(binder);
- try {
- mmd.doZramMaintenance();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to doZramMaintenance", e);
+ new Thread("ZramMaintenance") {
+ @Override
+ public void run() {
+ try {
+ IBinder binder = ServiceManager.getService("mmd");
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ startJob(ZramMaintenance.this, params, mmd);
+ } finally {
+ jobFinished(params, false);
+ }
}
- } else {
- Slog.w(TAG, "binder not found");
- }
- Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
- DEFAULT_PERIODIC_DELAY_SECONDS));
- scheduleZramMaintenance(this, delay);
+ }.start();
return true;
}
@@ -92,27 +98,75 @@
}
/**
+ * This is public to test ZramMaintenance logic.
+ *
+ * <p>
+ * We need to pass mmd as parameter because we can't mock "IMmd.Stub.asInterface".
+ *
+ * <p>
+ * Since IMmd.isZramMaintenanceSupported() is blocking call, this method should be executed on
+ * a worker thread.
+ */
+ @VisibleForTesting
+ public static void startJob(Context context, JobParameters params, IMmd mmd) {
+ boolean checkStatus = params.getExtras().getBoolean(KEY_CHECK_STATUS);
+ if (mmd != null) {
+ try {
+ if (checkStatus && !mmd.isZramMaintenanceSupported()) {
+ Slog.i(TAG, "zram maintenance is not supported");
+ return;
+ }
+ // Status check is required before the first doZramMaintenanceAsync() call once.
+ checkStatus = false;
+
+ mmd.doZramMaintenanceAsync();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to binder call to mmd", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(getLongProperty(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, checkStatus);
+ }
+
+ /**
* Starts periodical zram maintenance.
*/
public static void startZramMaintenance(Context context) {
Duration delay = Duration.ofSeconds(
- SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
- scheduleZramMaintenance(context, delay);
+ getLongProperty(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, true);
}
- private static void scheduleZramMaintenance(Context context, Duration delay) {
+ private static void scheduleZramMaintenance(Context context, Duration delay,
+ boolean checkStatus) {
JobScheduler js = context.getSystemService(JobScheduler.class);
if (js != null) {
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CHECK_STATUS, checkStatus);
js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
.setMinimumLatency(delay.toMillis())
.setRequiresDeviceIdle(
- SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ getBooleanProperty(REQUIRE_DEVICE_IDLE_PROP,
DEFAULT_REQUIRE_DEVICE_IDLE))
.setRequiresBatteryNotLow(
- SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ getBooleanProperty(REQUIRE_BATTERY_NOT_LOW_PROP,
DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .setExtras(bundle)
.build());
}
}
+
+ private static long getLongProperty(String name, long defaultValue) {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getLong(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
+
+ private static boolean getBooleanProperty(String name, boolean defaultValue) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getBoolean(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 8121701..16658e3 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -536,8 +536,12 @@
user.getIdentifier());
String inputMethodPackageName = null;
if (inputMethodComponent != null) {
- inputMethodPackageName = ComponentName.unflattenFromString(
- inputMethodComponent).getPackageName();
+ ComponentName component = ComponentName.unflattenFromString(inputMethodComponent);
+ if (component != null) {
+ inputMethodPackageName = component.getPackageName();
+ } else {
+ Log.w(TAG, "Failed to parse inputMethodComponent: " + inputMethodComponent);
+ }
}
int capability;
diff --git a/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
new file mode 100644
index 0000000..5448a05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.zram
+
+import android.app.job.JobInfo
+import android.app.job.JobParameters
+import android.app.job.JobScheduler
+import android.os.IMmd
+import android.os.PersistableBundle
+import android.os.RemoteException
+import android.testing.TestableContext
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.server.ZramMaintenance
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+private fun generateJobParameters(jobId: Int, extras: PersistableBundle): JobParameters {
+ return JobParameters(
+ null, "", jobId, extras, null, null, 0, false, false, false, null, null, null
+ )
+}
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ZramMaintenanceTest {
+ private val context = TestableContext(InstrumentationRegistry.getInstrumentation().context)
+
+ @Captor
+ private lateinit var jobInfoCaptor: ArgumentCaptor<JobInfo>
+
+ @Mock
+ private lateinit var mockJobScheduler: JobScheduler
+
+ @Mock
+ private lateinit var mockMmd: IMmd
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.addMockSystemService(JobScheduler::class.java, mockJobScheduler)
+ }
+
+ @Test
+ fun startZramMaintenance() {
+ ZramMaintenance.startZramMaintenance(context)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val job = jobInfoCaptor.value
+ assertThat(job.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(job.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+
+ @Test
+ fun startJobForFirstTime() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(true)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobWithoutCheckStatus() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, false)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, never()).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobZramIsDisabled() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(false)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, never()).doZramMaintenanceAsync()
+ verify(mockJobScheduler, never()).schedule(any())
+ }
+
+ @Test
+ fun startJobMmdIsNotReadyYet() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, null)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index ec53127..899cd7f 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -180,7 +180,14 @@
def dump_representative_locales(representative_locales):
"""Dump the set of representative locales."""
- print()
+ print('''
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */''')
print('bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {')
print(' const uint64_t packed_locale =')
print(' ((static_cast<uint64_t>(language_and_region)) << 32u) |')