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

Commit a0a129fc authored by Shin Kawamura's avatar Shin Kawamura Committed by Gerrit Code Review
Browse files

Merge changes from topic "zram_maintenance_support_check" into main

* changes:
  Add DeviceConfig overlay for mm. properties in ZramMaintenance
  Check isZramMaintenanceSupported on ZramMaintenance
  Rename IMmd.doZramMaintenanceAsync()
parents 8e731f05 03748c6f
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
+77 −23
Original line number Diff line number Diff line
@@ -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) {
        new Thread("ZramMaintenance") {
            @Override
            public void run() {
                try {
                    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);
                    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;
    }

@@ -91,28 +97,76 @@ public class ZramMaintenance extends JobService {
        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 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));
    }
}
+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