• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 kotlinx.coroutines.Dispatchers
30 import kotlinx.coroutines.GlobalScope
31 import kotlinx.coroutines.Job
32 import kotlinx.coroutines.launch
33 import java.util.concurrent.TimeUnit
34 
35 /**
36  * A job to clean up old permission events.
37  */
38 class PermissionEventCleanupJobService : JobService() {
39 
40     companion object {
41         const val LOG_TAG = "PermissionEventCleanupJobService"
42         val DEFAULT_CLEAR_OLD_EVENTS_CHECK_FREQUENCY = TimeUnit.DAYS.toMillis(1)
43 
scheduleOldDataCleanupIfNecessarynull44         fun scheduleOldDataCleanupIfNecessary(context: Context, jobScheduler: JobScheduler) {
45             if (isNewJobScheduleRequired(jobScheduler)) {
46                 val jobInfo = JobInfo.Builder(
47                     Constants.OLD_PERMISSION_EVENT_CLEANUP_JOB_ID,
48                     ComponentName(context, PermissionEventCleanupJobService::class.java))
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? = jobScheduler
68                 .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(DeviceConfig.NAMESPACE_PERMISSIONS,
87                 Utils.PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS,
88                 DEFAULT_CLEAR_OLD_EVENTS_CHECK_FREQUENCY)
89     }
90 
91     var job: Job? = null
92     var jobStartTime: Long = -1L
93 
94     override fun onStartJob(params: JobParameters?): Boolean {
95         DumpableLog.i(LOG_TAG, "onStartJob")
96         val storages = PermissionEventStorageImpls.getInstance()
97         if (storages.isEmpty()) {
98             return false
99         }
100         jobStartTime = System.currentTimeMillis()
101         job = GlobalScope.launch(Dispatchers.IO) {
102             for (storage in storages) {
103                 val success = storage.removeOldData()
104                 if (!success) {
105                     DumpableLog.e(LOG_TAG, "Failed to remove old data for $storage")
106                 }
107             }
108             jobFinished(params, false)
109         }
110         return true
111     }
112 
onStopJobnull113     override fun onStopJob(params: JobParameters?): Boolean {
114         DumpableLog.w(LOG_TAG, "onStopJob after ${System.currentTimeMillis() - jobStartTime}ms")
115         job?.cancel()
116         return true
117     }
118 }
119