1 /* 2 * Copyright (C) 2014 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; 18 19 import java.util.Calendar; 20 21 import android.app.ActivityManager; 22 import android.app.job.JobInfo; 23 import android.app.job.JobParameters; 24 import android.app.job.JobScheduler; 25 import android.app.job.JobService; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.os.RemoteException; 29 import android.util.Slog; 30 import java.util.concurrent.TimeUnit; 31 32 public class MountServiceIdler extends JobService { 33 private static final String TAG = "MountServiceIdler"; 34 35 private static ComponentName sIdleService = 36 new ComponentName("android", MountServiceIdler.class.getName()); 37 38 private static int MOUNT_JOB_ID = 808; 39 40 private boolean mStarted; 41 private JobParameters mJobParams; 42 private Runnable mFinishCallback = new Runnable() { 43 @Override 44 public void run() { 45 Slog.i(TAG, "Got mount service completion callback"); 46 synchronized (mFinishCallback) { 47 if (mStarted) { 48 jobFinished(mJobParams, false); 49 mStarted = false; 50 } 51 } 52 // ... and try again right away or tomorrow 53 scheduleIdlePass(MountServiceIdler.this); 54 } 55 }; 56 57 @Override onStartJob(JobParameters params)58 public boolean onStartJob(JobParameters params) { 59 // First have the activity manager do its idle maintenance. (Yes this job 60 // is really more than just mount, some day it should be renamed to be system 61 // idleer). 62 try { 63 ActivityManager.getService().performIdleMaintenance(); 64 } catch (RemoteException e) { 65 } 66 // The mount service will run an fstrim operation asynchronously 67 // on a designated separate thread, so we provide it with a callback 68 // that lets us cleanly end our idle timeslice. It's safe to call 69 // finishIdle() from any thread. 70 mJobParams = params; 71 StorageManagerService ms = StorageManagerService.sSelf; 72 if (ms != null) { 73 synchronized (mFinishCallback) { 74 mStarted = true; 75 } 76 ms.runIdleMaint(mFinishCallback); 77 } 78 return ms != null; 79 } 80 81 @Override onStopJob(JobParameters params)82 public boolean onStopJob(JobParameters params) { 83 // Once we kick off the fstrim we aren't actually interruptible; just note 84 // that we don't need to call jobFinished(), and let everything happen in 85 // the callback from the mount service. 86 StorageManagerService ms = StorageManagerService.sSelf; 87 if (ms != null) { 88 ms.abortIdleMaint(mFinishCallback); 89 synchronized (mFinishCallback) { 90 mStarted = false; 91 } 92 } 93 return false; 94 } 95 96 /** 97 * Schedule the idle job that will ping the mount service 98 */ scheduleIdlePass(Context context)99 public static void scheduleIdlePass(Context context) { 100 JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 101 102 final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis(); 103 final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis(); 104 final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis(); 105 106 long nextScheduleTime; 107 if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) { 108 nextScheduleTime = TimeUnit.SECONDS.toMillis(10); 109 } else { 110 nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow 111 } 112 113 JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService); 114 builder.setRequiresDeviceIdle(true); 115 builder.setRequiresBatteryNotLow(true); 116 builder.setMinimumLatency(nextScheduleTime); 117 tm.schedule(builder.build()); 118 } 119 offsetFromTodayMidnight(int nDays, int nHours)120 private static Calendar offsetFromTodayMidnight(int nDays, int nHours) { 121 Calendar calendar = Calendar.getInstance(); 122 calendar.setTimeInMillis(System.currentTimeMillis()); 123 calendar.set(Calendar.HOUR_OF_DAY, nHours); 124 calendar.set(Calendar.MINUTE, 0); 125 calendar.set(Calendar.SECOND, 0); 126 calendar.set(Calendar.MILLISECOND, 0); 127 calendar.add(Calendar.DAY_OF_MONTH, nDays); 128 return calendar; 129 } 130 } 131