diff options
10 files changed, 351 insertions, 27 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index a13dd78a23a1..104ba018fb62 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -287,6 +287,7 @@ cc_benchmark { "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 6e751a77f355..ea9e9a2d4280 100644 --- a/libs/androidfw/LocaleDataLookup.cpp +++ b/libs/androidfw/LocaleDataLookup.cpp @@ -7518,6 +7518,13 @@ const char* lookupLikelyScript(uint32_t packed_lang_region) { } } +/* + * 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 000000000000..60ce3b944551 --- /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/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java index 93d6eb73dcae..e83b9f1afddb 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 @@ public final class ApduServiceInfo implements Parcelable { 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 3cb6c5a6bd16..7af03ed2e6c8 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.app.UiAutomation; 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.IOException; 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 @@ public class RavenwoodRuntimeEnvironmentController { 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 @@ public class RavenwoodRuntimeEnvironmentController { 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 23cee9db2138..16209b1dc3d3 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.TypedXmlPullParser; 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 @@ public class BootReceiver extends BroadcastReceiver { @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 @@ public class BootReceiver extends BroadcastReceiver { } 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 19676ebdbfcd..6045f63439f3 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -946,7 +946,6 @@ class StorageManagerService extends IStorageManager.Stub 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 cdb48122e321..099e5b3fe440 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.ComponentName; 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 @@ public class ZramMaintenance extends JobService { 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 @@ public class ZramMaintenance extends JobService { } /** + * 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/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 000000000000..5448a05aafc3 --- /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 ec531275af1c..899cd7f9ce5e 100755 --- a/tools/localedata/extract_icu_data.py +++ b/tools/localedata/extract_icu_data.py @@ -180,7 +180,14 @@ def pack_script_to_uint32(script): 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) |') |