1 /* 2 * Copyright (C) 2019 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.networkstack.ipmemorystore; 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.net.ipmemorystore.IOnStatusListener; 26 import android.net.ipmemorystore.Status; 27 import android.net.ipmemorystore.StatusParcelable; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.VisibleForTesting; 34 35 import java.util.concurrent.CopyOnWriteArrayList; 36 import java.util.concurrent.TimeUnit; 37 38 /** 39 * Regular maintenance job service. 40 * @hide 41 */ 42 public class RegularMaintenanceJobService extends JobService { 43 // Must be unique within the system server uid. 44 public static final int REGULAR_MAINTENANCE_ID = 3345678; 45 46 /** 47 * Class for interrupt check of maintenance job. 48 */ 49 public static final class InterruptMaintenance { 50 private volatile boolean mIsInterrupted; 51 private final int mJobId; 52 InterruptMaintenance(int jobId)53 public InterruptMaintenance(int jobId) { 54 mJobId = jobId; 55 mIsInterrupted = false; 56 } 57 getJobId()58 public int getJobId() { 59 return mJobId; 60 } 61 setInterrupted(boolean interrupt)62 public void setInterrupted(boolean interrupt) { 63 mIsInterrupted = interrupt; 64 } 65 isInterrupted()66 public boolean isInterrupted() { 67 return mIsInterrupted; 68 } 69 } 70 71 private static final CopyOnWriteArrayList<InterruptMaintenance> sInterruptList = 72 new CopyOnWriteArrayList<>(); 73 private static IpMemoryStoreService sIpMemoryStoreService; 74 75 @Override onStartJob(JobParameters params)76 public boolean onStartJob(JobParameters params) { 77 if (sIpMemoryStoreService == null) { 78 Log.wtf("RegularMaintenanceJobService", 79 "Can not start job because sIpMemoryStoreService is null."); 80 return false; 81 } 82 final InterruptMaintenance im = new InterruptMaintenance(params.getJobId()); 83 sInterruptList.add(im); 84 85 sIpMemoryStoreService.fullMaintenance(new IOnStatusListener() { 86 @Override 87 public void onComplete(final StatusParcelable statusParcelable) throws RemoteException { 88 final Status result = new Status(statusParcelable); 89 if (!result.isSuccess()) { 90 Log.e("RegularMaintenanceJobService", "Regular maintenance failed." 91 + " Error is " + result.resultCode); 92 } 93 sInterruptList.remove(im); 94 callJobFinished(params, !result.isSuccess()); 95 } 96 97 @Override 98 public int getInterfaceVersion() { 99 return this.VERSION; 100 } 101 102 @Override 103 public String getInterfaceHash() { 104 return this.HASH; 105 } 106 107 @Override 108 public IBinder asBinder() { 109 return null; 110 } 111 }, im); 112 return true; 113 } 114 115 // Unfortunately jobFinished is final in JobService and sends a message through a maze 116 // of messengers and callbacks, all of which are private and/or final. Although this method 117 // represents a small change to the production code, it looks like a reasonably safe way 118 // to write some of the tests for this service. 119 @VisibleForTesting callJobFinished(@onNull final JobParameters params, final boolean reschedule)120 protected void callJobFinished(@NonNull final JobParameters params, final boolean reschedule) { 121 jobFinished(params, reschedule); 122 } 123 124 @Override onStopJob(JobParameters params)125 public boolean onStopJob(JobParameters params) { 126 final int jobId = params.getJobId(); 127 for (InterruptMaintenance im : sInterruptList) { 128 if (im.getJobId() == jobId) { 129 im.setInterrupted(true); 130 } 131 } 132 return true; 133 } 134 135 /** Schedule regular maintenance job */ schedule(Context context, IpMemoryStoreService ipMemoryStoreService)136 static void schedule(Context context, IpMemoryStoreService ipMemoryStoreService) { 137 final JobScheduler jobScheduler = 138 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 139 140 final ComponentName maintenanceJobName = 141 new ComponentName(context, RegularMaintenanceJobService.class); 142 143 // Regular maintenance is scheduled for when the device is idle with access power and a 144 // minimum interval of one day. 145 final JobInfo regularMaintenanceJob = 146 new JobInfo.Builder(REGULAR_MAINTENANCE_ID, maintenanceJobName) 147 .setRequiresDeviceIdle(true) 148 .setRequiresCharging(true) 149 .setRequiresBatteryNotLow(true) 150 .setPeriodic(TimeUnit.HOURS.toMillis(24)).build(); 151 152 jobScheduler.schedule(regularMaintenanceJob); 153 sIpMemoryStoreService = ipMemoryStoreService; 154 } 155 156 /** Unschedule regular maintenance job */ unschedule(Context context)157 static void unschedule(Context context) { 158 final JobScheduler jobScheduler = 159 (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 160 jobScheduler.cancel(REGULAR_MAINTENANCE_ID); 161 sIpMemoryStoreService = null; 162 } 163 } 164