1 /*
2  * Copyright (C) 2022 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.permissioncontroller.safetycenter.service;
18 
19 import static android.app.job.JobScheduler.RESULT_SUCCESS;
20 import static android.content.Intent.ACTION_BOOT_COMPLETED;
21 import static android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED;
22 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
23 import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC;
24 
25 import static com.android.permissioncontroller.Constants.SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID;
26 
27 import android.app.job.JobInfo;
28 import android.app.job.JobParameters;
29 import android.app.job.JobScheduler;
30 import android.app.job.JobService;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.safetycenter.SafetyCenterManager;
36 import android.util.Log;
37 
38 import androidx.annotation.Nullable;
39 
40 import com.android.modules.utils.build.SdkLevel;
41 
42 import java.time.Duration;
43 
44 /**
45  * Uses {@link android.app.job.JobScheduler} to schedule periodic calls to {@link
46  * SafetyCenterManager#refreshSafetySources} after boot completed if safety center is already
47  * enabled, or after safety center is enabled otherwise.
48  *
49  * <p>The job waits until the device is in idle mode to minimize impact on system health.
50  */
51 public final class SafetyCenterBackgroundRefreshJobService extends JobService {
52     private static final String TAG = "SafetyCenterBackgroundR";
53 
54     /** Schedules a periodic background refresh. */
55     public static final class SetupSafetyCenterBackgroundRefreshReceiver extends BroadcastReceiver {
56         @Override
onReceive(Context context, Intent intent)57         public void onReceive(Context context, Intent intent) {
58             schedulePeriodicBackgroundRefresh(context, intent.getAction());
59         }
60     }
61 
62     /**
63      * Schedules a periodic call to {@link SafetyCenterManager#refreshSafetySources} to be run when
64      * the device is idle, after either {@link android.content.Intent#ACTION_BOOT_COMPLETED} or
65      * {@link android.safetycenter.SafetyCenterManager#ACTION_SAFETY_CENTER_ENABLED_CHANGED}.
66      *
67      * <p>The {@link SafetyCenterManager#isSafetyCenterEnabled} check ensures that jobs are never
68      * scheduled if SafetyCenter is disabled, we check again in {@link
69      * SafetyCenterBackgroundRefreshJobService#onStartJob} in case SafetyCenter becomes disabled
70      * later.
71      *
72      * <p>{@link SafetyCenterJobServiceFlags#areBackgroundRefreshesEnabled} is only checked in
73      * {@link SafetyCenterBackgroundRefreshJobService#onStartJob} as we do not receive a new
74      * broadcast if this flag gets enabled.
75      */
schedulePeriodicBackgroundRefresh( Context context, @Nullable String actionString)76     private static void schedulePeriodicBackgroundRefresh(
77             Context context, @Nullable String actionString) {
78 
79         if (!isActionStringValid(actionString)) {
80             Log.i(TAG, "Ignoring a " + actionString + " broadcast.");
81             return;
82         }
83 
84         SafetyCenterManager safetyCenterManager =
85                 context.getSystemService(SafetyCenterManager.class);
86         if (safetyCenterManager == null) {
87             Log.w(TAG, "SafetyCenterManager is null, cannot schedule background refresh.");
88             return;
89         }
90 
91         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
92         if (jobScheduler == null) {
93             Log.w(TAG, "JobScheduler is null, cannot schedule background refresh.");
94             return;
95         }
96 
97         if (!safetyCenterManager.isSafetyCenterEnabled()) {
98             Log.i(
99                     TAG,
100                     "Received a "
101                             + actionString
102                             + " broadcast, but safety center is currently disabled. Cancelling any"
103                             + " existing job.");
104             jobScheduler.cancel(SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID);
105             return;
106         }
107 
108         Duration periodicBackgroundRefreshInterval =
109                 SafetyCenterJobServiceFlags.getPeriodicBackgroundRefreshInterval();
110         JobInfo jobInfo =
111                 new JobInfo.Builder(
112                                 SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID,
113                                 new ComponentName(
114                                         context, SafetyCenterBackgroundRefreshJobService.class))
115                         .setRequiresDeviceIdle(true)
116                         .setRequiresCharging(true)
117                         .setPeriodic(periodicBackgroundRefreshInterval.toMillis())
118                         .build();
119 
120         Log.v(
121                 TAG,
122                 "Scheduling a periodic background refresh with interval="
123                         + periodicBackgroundRefreshInterval);
124 
125         int scheduleResult = jobScheduler.schedule(jobInfo);
126         if (scheduleResult != RESULT_SUCCESS) {
127             Log.w(
128                     TAG,
129                     "Could not schedule the background refresh job, scheduleResult="
130                             + scheduleResult);
131         }
132     }
133 
134     @Override
onStartJob(JobParameters params)135     public boolean onStartJob(JobParameters params) {
136         // background thread not required, PC APK makes all API calls in main thread
137         if (!SafetyCenterJobServiceFlags.areBackgroundRefreshesEnabled()) {
138             Log.i(TAG, "Background refreshes are not enabled, skipping job.");
139             return false; // job is no longer running
140         }
141         SafetyCenterManager safetyCenterManager = this.getSystemService(SafetyCenterManager.class);
142         if (safetyCenterManager == null) {
143             Log.w(TAG, "Safety center manager is null, skipping job.");
144             return false; // job is no longer running
145         }
146         if (!safetyCenterManager.isSafetyCenterEnabled()) {
147             Log.i(TAG, "Safety center is not enabled, skipping job.");
148             return false; // job is no longer running
149         }
150 
151         Log.v(TAG, "Background refresh job has started.");
152         safetyCenterManager.refreshSafetySources(getRefreshReason());
153         return false; // job is no longer running
154     }
155 
156     @Override
onStopJob(JobParameters params)157     public boolean onStopJob(JobParameters params) {
158         return false; // never want job to be rescheduled
159     }
160 
isActionStringValid(@ullable String actionString)161     private static boolean isActionStringValid(@Nullable String actionString) {
162         return ACTION_BOOT_COMPLETED.equals(actionString)
163                 || ACTION_SAFETY_CENTER_ENABLED_CHANGED.equals(actionString);
164     }
165 
getRefreshReason()166     private static int getRefreshReason() {
167         if (SdkLevel.isAtLeastU()) {
168             return REFRESH_REASON_PERIODIC;
169         }
170         return REFRESH_REASON_OTHER;
171     }
172 }
173