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) |')