Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d3736ae8 authored by Shintaro Kawamura's avatar Shintaro Kawamura
Browse files

Check isZramMaintenanceSupported on ZramMaintenance

Sending doZramMaintenanceAsync() request to mmd on the devices which
does not support zram writeback and zram recompression is useless.

Check the zram status before the first doZramMaintenanceAsync() and stop
the job scheduler loop of ZramMaintenance if the device does not support
zram maintenance.

Bug: 375432472
Test: atest FrameworksServicesTests:ZramMaintenanceTest

Change-Id: Idc76bae87f6f974a6c276cd030135b050d444dc1
parent 4c27cb4b
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -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
+56 −14
Original line number Diff line number Diff line
@@ -24,11 +24,14 @@ 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.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.time.Duration;

/**
@@ -46,9 +49,12 @@ 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 FIRST_DELAY_SECONDS_PROP =
            "mm.zram.maintenance.first_delay_seconds";
@@ -69,26 +75,58 @@ public class ZramMaintenance extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        new Thread("ZramMaintenance") {
            @Override
            public void run() {
                try {
                    IBinder binder = ServiceManager.getService("mmd");
        if (binder != null) {
                    IMmd mmd = IMmd.Stub.asInterface(binder);
                    startJob(ZramMaintenance.this, params, mmd);
                } finally {
                    jobFinished(params, false);
                }
            }
        }.start();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    /**
     * 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 doZramMaintenance", e);
                Slog.e(TAG, "Failed to binder call to mmd", e);
            }
        } else {
            Slog.w(TAG, "binder not found");
        }
        Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
                DEFAULT_PERIODIC_DELAY_SECONDS));
        scheduleZramMaintenance(this, delay);
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
        scheduleZramMaintenance(context, delay, checkStatus);
    }

    /**
@@ -97,13 +135,16 @@ public class ZramMaintenance extends JobService {
    public static void startZramMaintenance(Context context) {
        Duration delay = Duration.ofSeconds(
                SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
        scheduleZramMaintenance(context, delay);
        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(
@@ -112,6 +153,7 @@ public class ZramMaintenance extends JobService {
                    .setRequiresBatteryNotLow(
                            SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
                                    DEFAULT_REQUIRE_BATTERY_NOT_LOW))
                    .setExtras(bundle)
                    .build());
        }
    }
+155 −0
Original line number Diff line number Diff line
/*
 * 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