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.permission.service 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.provider.DeviceConfig 26 import com.android.permissioncontroller.Constants 27 import com.android.permissioncontroller.DumpableLog 28 import com.android.permissioncontroller.permission.utils.Utils 29 import java.util.concurrent.TimeUnit 30 import kotlinx.coroutines.Dispatchers 31 import kotlinx.coroutines.GlobalScope 32 import kotlinx.coroutines.Job 33 import kotlinx.coroutines.launch 34 35 /** A job to clean up old permission events. */ 36 class PermissionEventCleanupJobService : JobService() { 37 38 companion object { 39 const val LOG_TAG = "PermissionEventCleanupJobService" 40 val DEFAULT_CLEAR_OLD_EVENTS_CHECK_FREQUENCY = TimeUnit.DAYS.toMillis(1) 41 scheduleOldDataCleanupIfNecessarynull42 fun scheduleOldDataCleanupIfNecessary(context: Context, jobScheduler: JobScheduler) { 43 if (isNewJobScheduleRequired(jobScheduler)) { 44 val jobInfo = 45 JobInfo.Builder( 46 Constants.OLD_PERMISSION_EVENT_CLEANUP_JOB_ID, 47 ComponentName(context, PermissionEventCleanupJobService::class.java) 48 ) 49 .setPeriodic(getClearOldEventsCheckFrequencyMs()) 50 // persist this job across boots 51 .setPersisted(true) 52 .build() 53 val status = jobScheduler.schedule(jobInfo) 54 if (status != JobScheduler.RESULT_SUCCESS) { 55 DumpableLog.e(LOG_TAG, "Could not schedule job: $status") 56 } 57 } 58 } 59 60 /** 61 * Returns whether a new job needs to be scheduled. A persisted job is used to keep the 62 * schedule across boots, but that job needs to be scheduled a first time and whenever the 63 * check frequency changes. 64 */ isNewJobScheduleRequirednull65 private fun isNewJobScheduleRequired(jobScheduler: JobScheduler): Boolean { 66 var scheduleNewJob = false 67 val existingJob: JobInfo? = 68 jobScheduler.getPendingJob(Constants.OLD_PERMISSION_EVENT_CLEANUP_JOB_ID) 69 when { 70 existingJob == null -> { 71 DumpableLog.i(LOG_TAG, "No existing job, scheduling a new one") 72 scheduleNewJob = true 73 } 74 existingJob.intervalMillis != getClearOldEventsCheckFrequencyMs() -> { 75 DumpableLog.i(LOG_TAG, "Interval frequency has changed, updating job") 76 scheduleNewJob = true 77 } 78 else -> { 79 DumpableLog.i(LOG_TAG, "Job already scheduled.") 80 } 81 } 82 return scheduleNewJob 83 } 84 getClearOldEventsCheckFrequencyMsnull85 private fun getClearOldEventsCheckFrequencyMs() = 86 DeviceConfig.getLong( 87 DeviceConfig.NAMESPACE_PERMISSIONS, 88 Utils.PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS, 89 DEFAULT_CLEAR_OLD_EVENTS_CHECK_FREQUENCY 90 ) 91 } 92 93 var job: Job? = null 94 var jobStartTime: Long = -1L 95 96 override fun onStartJob(params: JobParameters?): Boolean { 97 DumpableLog.i(LOG_TAG, "onStartJob") 98 val storages = PermissionEventStorageImpls.getInstance() 99 if (storages.isEmpty()) { 100 return false 101 } 102 jobStartTime = System.currentTimeMillis() 103 job = 104 GlobalScope.launch(Dispatchers.IO) { 105 for (storage in storages) { 106 val success = storage.removeOldData() 107 if (!success) { 108 DumpableLog.e(LOG_TAG, "Failed to remove old data for $storage") 109 } 110 } 111 jobFinished(params, false) 112 } 113 return true 114 } 115 onStopJobnull116 override fun onStopJob(params: JobParameters?): Boolean { 117 DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms") 118 job?.cancel() 119 return true 120 } 121 } 122