1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.memory; 18 19 import android.app.job.JobInfo; 20 import android.app.job.JobParameters; 21 import android.app.job.JobScheduler; 22 import android.app.job.JobService; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.os.IBinder; 26 import android.os.IMmd; 27 import android.os.PersistableBundle; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.SystemProperties; 31 import android.provider.DeviceConfig; 32 import android.util.Slog; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.time.Duration; 37 38 /** 39 * Schedules zram maintenance (e.g. zram writeback, zram recompression). 40 * 41 * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on: 42 * 43 * <ul> 44 * <li>Enough interval has passed. 45 * <li>The system is idle. 46 * <li>The battery is not low. 47 * </ul> 48 */ 49 public class ZramMaintenance extends JobService { 50 private static final String TAG = ZramMaintenance.class.getName(); 51 // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number 52 // as the job id. 53 @VisibleForTesting 54 public static final int JOB_ID = 375432472; 55 private static final ComponentName sZramMaintenance = 56 new ComponentName("android", ZramMaintenance.class.getName()); 57 @VisibleForTesting 58 public static final String KEY_CHECK_STATUS = "check_status"; 59 60 private static final String SYSTEM_PROPERTY_PREFIX = "mm."; 61 private static final String FIRST_DELAY_SECONDS_PROP = 62 "zram.maintenance.first_delay_seconds"; 63 // The default is 1 hour. 64 private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600; 65 private static final String PERIODIC_DELAY_SECONDS_PROP = 66 "zram.maintenance.periodic_delay_seconds"; 67 // The default is 1 hour. 68 private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600; 69 private static final String REQUIRE_DEVICE_IDLE_PROP = 70 "zram.maintenance.require_device_idle"; 71 private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE = 72 true; 73 private static final String REQUIRE_BATTERY_NOT_LOW_PROP = 74 "zram.maintenance.require_battery_not_low"; 75 private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW = 76 true; 77 78 @Override onStartJob(JobParameters params)79 public boolean onStartJob(JobParameters params) { 80 new Thread("ZramMaintenance") { 81 @Override 82 public void run() { 83 try { 84 IBinder binder = ServiceManager.getService("mmd"); 85 IMmd mmd = IMmd.Stub.asInterface(binder); 86 startJob(ZramMaintenance.this, params, mmd); 87 } finally { 88 jobFinished(params, false); 89 } 90 } 91 }.start(); 92 return true; 93 } 94 95 @Override onStopJob(JobParameters params)96 public boolean onStopJob(JobParameters params) { 97 return false; 98 } 99 100 /** 101 * This is public to test ZramMaintenance logic. 102 * 103 * <p> 104 * We need to pass mmd as parameter because we can't mock "IMmd.Stub.asInterface". 105 * 106 * <p> 107 * Since IMmd.isZramMaintenanceSupported() is blocking call, this method should be executed on 108 * a worker thread. 109 */ 110 @VisibleForTesting startJob(Context context, JobParameters params, IMmd mmd)111 public static void startJob(Context context, JobParameters params, IMmd mmd) { 112 boolean checkStatus = params.getExtras().getBoolean(KEY_CHECK_STATUS); 113 if (mmd != null) { 114 try { 115 if (checkStatus && !mmd.isZramMaintenanceSupported()) { 116 Slog.i(TAG, "zram maintenance is not supported"); 117 return; 118 } 119 // Status check is required before the first doZramMaintenanceAsync() call once. 120 checkStatus = false; 121 122 mmd.doZramMaintenanceAsync(); 123 } catch (RemoteException e) { 124 Slog.e(TAG, "Failed to binder call to mmd", e); 125 } 126 } else { 127 Slog.w(TAG, "binder not found"); 128 } 129 Duration delay = Duration.ofSeconds(getLongProperty(PERIODIC_DELAY_SECONDS_PROP, 130 DEFAULT_PERIODIC_DELAY_SECONDS)); 131 scheduleZramMaintenance(context, delay, checkStatus); 132 } 133 134 /** 135 * Starts periodical zram maintenance. 136 */ startZramMaintenance(Context context)137 public static void startZramMaintenance(Context context) { 138 Duration delay = Duration.ofSeconds( 139 getLongProperty(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS)); 140 scheduleZramMaintenance(context, delay, true); 141 } 142 scheduleZramMaintenance(Context context, Duration delay, boolean checkStatus)143 private static void scheduleZramMaintenance(Context context, Duration delay, 144 boolean checkStatus) { 145 JobScheduler js = context.getSystemService(JobScheduler.class); 146 147 if (js != null) { 148 final PersistableBundle bundle = new PersistableBundle(); 149 bundle.putBoolean(KEY_CHECK_STATUS, checkStatus); 150 js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance) 151 .setMinimumLatency(delay.toMillis()) 152 .setRequiresDeviceIdle( 153 getBooleanProperty(REQUIRE_DEVICE_IDLE_PROP, 154 DEFAULT_REQUIRE_DEVICE_IDLE)) 155 .setRequiresBatteryNotLow( 156 getBooleanProperty(REQUIRE_BATTERY_NOT_LOW_PROP, 157 DEFAULT_REQUIRE_BATTERY_NOT_LOW)) 158 .setExtras(bundle) 159 .build()); 160 } 161 } 162 getLongProperty(String name, long defaultValue)163 private static long getLongProperty(String name, long defaultValue) { 164 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_MM, name, 165 SystemProperties.getLong(SYSTEM_PROPERTY_PREFIX + name, defaultValue)); 166 } 167 getBooleanProperty(String name, boolean defaultValue)168 private static boolean getBooleanProperty(String name, boolean defaultValue) { 169 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_MM, name, 170 SystemProperties.getBoolean(SYSTEM_PROPERTY_PREFIX + name, defaultValue)); 171 } 172 } 173