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