summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionStorageTimeChangeReceiver.kt
blob: ccb3acbad1fa0d1bfe3a5733705e0a512ab82f7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.permissioncontroller.permission.service

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import androidx.preference.PreferenceManager
import com.android.permissioncontroller.DumpableLog
import com.android.permissioncontroller.permission.data.PermissionEvent
import com.android.permissioncontroller.permission.utils.SystemTimeSource
import com.android.permissioncontroller.permission.utils.TimeSource
import kotlin.math.abs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
 * [BroadcastReceiver] to update the persisted timestamps when the date changes. Receives broadcasts
 * for [Intent.ACTION_TIME_CHANGED] to know when the system time has changed. However, this action
 * does not include metadata that allows for us to know how much the time has changed. To keep track
 * of the delta we take a snapshot of the current time and time since boot after the
 * [Intent.ACTION_BOOT_COMPLETED] action.
 */
class PermissionStorageTimeChangeReceiver(
    private val storages: List<PermissionEventStorage<out PermissionEvent>> =
        PermissionEventStorageImpls.getInstance(),
    private val timeSource: TimeSource = SystemTimeSource()
) : BroadcastReceiver() {

    companion object {
        private const val LOG_TAG = "PermissionStorageTimeChangeReceiver"

        /**
         * Key for the last known system time from the system. First initialized after boot
         * complete.
         */
        @VisibleForTesting const val PREF_KEY_SYSTEM_TIME_SNAPSHOT = "system_time_snapshot"

        /**
         * Key for the last known elapsed time since boot. First initialized after boot complete.
         */
        @VisibleForTesting
        const val PREF_KEY_ELAPSED_REALTIME_SNAPSHOT = "elapsed_realtime_snapshot"

        @VisibleForTesting const val SNAPSHOT_UNINITIALIZED = -1L

        /** The millisecond threshold for a time delta to be considered a time change. */
        private const val TIME_CHANGE_THRESHOLD_MILLIS = 60 * 1000L
    }

    override fun onReceive(context: Context, intent: Intent) {
        if (storages.isEmpty()) {
            return
        }
        when (val action = intent.action) {
            Intent.ACTION_BOOT_COMPLETED -> {
                persistTimeSnapshots(
                    context,
                    timeSource.currentTimeMillis(),
                    timeSource.elapsedRealtime()
                )
            }
            Intent.ACTION_TIME_CHANGED -> {
                checkForTimeChanged(context)
            }
            else -> {
                DumpableLog.e(LOG_TAG, "Unexpected action $action")
            }
        }
    }

    private fun checkForTimeChanged(context: Context) {
        val systemTimeSnapshot = getSystemTimeSnapshot(context)
        val realtimeSnapshot = getElapsedRealtimeSnapshot(context)
        if (
            realtimeSnapshot == SNAPSHOT_UNINITIALIZED ||
                systemTimeSnapshot == SNAPSHOT_UNINITIALIZED
        ) {
            DumpableLog.e(LOG_TAG, "Snapshots not initialized")
            return
        }
        val actualSystemTime = timeSource.currentTimeMillis()
        val actualRealtime = timeSource.elapsedRealtime()
        val expectedSystemTime = (actualRealtime - realtimeSnapshot) + systemTimeSnapshot
        val diffSystemTime = actualSystemTime - expectedSystemTime
        if (abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS) {
            DumpableLog.d(LOG_TAG, "Time changed by ${diffSystemTime / 1000} seconds")
            onTimeChanged(diffSystemTime)
            persistTimeSnapshots(context, actualSystemTime, actualRealtime)
        }
    }

    @VisibleForTesting
    fun onTimeChanged(diffSystemTime: Long) {
        GlobalScope.launch(Dispatchers.IO) {
            for (storage in storages) {
                storage.updateEventsBySystemTimeDelta(diffSystemTime)
            }
        }
    }

    private fun persistTimeSnapshots(
        context: Context,
        systemTimeSnapshot: Long,
        realtimeSnapshot: Long
    ) {
        DumpableLog.d(LOG_TAG, "systemTimeSnapshot set to $systemTimeSnapshot")
        DumpableLog.d(LOG_TAG, "realtimeSnapshot set to $realtimeSnapshot")
        context.sharedPreferences.edit().apply {
            putLong(PREF_KEY_SYSTEM_TIME_SNAPSHOT, systemTimeSnapshot)
            putLong(PREF_KEY_ELAPSED_REALTIME_SNAPSHOT, realtimeSnapshot)
            apply()
        }
    }

    private fun getSystemTimeSnapshot(context: Context): Long {
        return context.sharedPreferences.getLong(
            PREF_KEY_SYSTEM_TIME_SNAPSHOT,
            SNAPSHOT_UNINITIALIZED
        )
    }

    private fun getElapsedRealtimeSnapshot(context: Context): Long {
        return context.sharedPreferences.getLong(
            PREF_KEY_ELAPSED_REALTIME_SNAPSHOT,
            SNAPSHOT_UNINITIALIZED
        )
    }

    val Context.sharedPreferences: SharedPreferences
        get() {
            return PreferenceManager.getDefaultSharedPreferences(this)
        }
}