diff options
3 files changed, 232 insertions, 24 deletions
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 |