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