• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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