summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt
blob: 805d497c47fa699ca1887e95d69bdc133693e3bc (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
 * Copyright (C) 2020 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.
 */
@file:Suppress("DEPRECATION")

package com.android.permissioncontroller.permission.data

import android.app.AppOpsManager
import android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED
import android.app.Application
import android.os.Parcel
import android.os.Parcelable
import android.os.UserHandle
import android.util.Log
import com.android.permissioncontroller.PermissionControllerApplication
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * LiveData that loads the last usage of each of a list of app ops for every package.
 *
 * <p>For app-ops with duration the end of the access is considered.
 *
 * <p>Returns map op-name -> {@link OpAccess}
 *
 * @param app The current application
 * @param opNames The names of the app ops we wish to search for
 * @param usageDurationMs how much ago can an access have happened to be considered
 */
class OpUsageLiveData(
    private val app: Application,
    private val opNames: List<String>,
    private val usageDurationMs: Long
) : SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map<String, List<OpAccess>>>(),
        AppOpsManager.OnOpActiveChangedListener {
    private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!

    override suspend fun loadDataAndPostValue(job: Job) {
        val now = System.currentTimeMillis()
        val opMap = mutableMapOf<String, MutableList<OpAccess>>()

        val packageOps = try {
            appOpsManager.getPackagesForOps(opNames.toTypedArray())
        } catch (e: NullPointerException) {
            // older builds might not support all the app-ops requested
            emptyList<AppOpsManager.PackageOps>()
        }
        for (packageOp in packageOps) {
            for (opEntry in packageOp.ops) {
                for ((attributionTag, attributedOpEntry) in opEntry.attributedOpEntries) {
                    val user = UserHandle.getUserHandleForUid(packageOp.uid)
                    val lastAccessTime: Long = attributedOpEntry.getLastAccessTime(
                            OP_FLAGS_ALL_TRUSTED)

                    if (lastAccessTime == -1L) {
                        // There was no access, so skip
                        continue
                    }

                    var lastAccessDuration = attributedOpEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED)

                    // Some accesses have no duration
                    if (lastAccessDuration == -1L) {
                        lastAccessDuration = 0
                    }

                    if (attributedOpEntry.isRunning ||
                            lastAccessTime + lastAccessDuration > (now - usageDurationMs)) {
                        val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() }
                        val accessTime = if (attributedOpEntry.isRunning) {
                            OpAccess.IS_RUNNING
                        } else {
                            lastAccessTime
                        }
                        val proxy = attributedOpEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED)
                        var proxyAccess: OpAccess? = null
                        if (proxy != null && proxy.packageName != null) {
                            proxyAccess = OpAccess(proxy.packageName!!, proxy.attributionTag,
                                UserHandle.getUserHandleForUid(proxy.uid), accessTime)
                        }
                        accessList.add(OpAccess(packageOp.packageName, attributionTag,
                            user, accessTime, proxyAccess))

                        // TODO ntmyren: remove logs once b/160724034 is fixed
                        Log.i("OpUsageLiveData", "adding ${opEntry.opStr} for " +
                                "${packageOp.packageName}/$attributionTag, access time of " +
                                "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
                                "current time $now, duration $lastAccessDuration, proxy: " +
                                "${proxy?.packageName}")
                    } else {
                        Log.i("OpUsageLiveData", "NOT adding ${opEntry.opStr} for " +
                                "${packageOp.packageName}/$attributionTag, access time of " +
                                "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
                                "current time $now, duration $lastAccessDuration")
                    }
                }
            }
        }

        postValue(opMap)
    }

    override fun onActive() {
        super.onActive()

        // appOpsManager.startWatchingNoted() is not exposed, hence force update regularly :-(
        GlobalScope.launch {
            while (hasActiveObservers()) {
                delay(1000)
                update()
            }
        }

        try {
            appOpsManager.startWatchingActive(opNames.toTypedArray(), { it.run() }, this)
        } catch (ignored: IllegalArgumentException) {
            // older builds might not support all the app-ops requested
        }
    }

    override fun onInactive() {
        super.onInactive()

        appOpsManager.stopWatchingActive(this)
    }

    override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
        update()
    }

    companion object : DataRepository<Pair<List<String>, Long>, OpUsageLiveData>() {
        override fun newValue(key: Pair<List<String>, Long>): OpUsageLiveData {
            return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second)
        }

        operator fun get(ops: List<String>, usageDurationMs: Long): OpUsageLiveData {
            return get(ops to usageDurationMs)
        }
    }
}

data class OpAccess(
    val packageName: String,
    val attributionTag: String?,
    val user: UserHandle,
    val lastAccessTime: Long,
    val proxyAccess: OpAccess? = null
) : Parcelable {
    val isRunning = lastAccessTime == IS_RUNNING

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(packageName)
        parcel.writeString(attributionTag)
        parcel.writeParcelable(user, flags)
        parcel.writeLong(lastAccessTime)
        parcel.writeString(proxyAccess?.packageName)
        parcel.writeString(proxyAccess?.attributionTag)
        parcel.writeParcelable(proxyAccess?.user, flags)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object {
        const val IS_RUNNING = -1L

        @JvmField
        val CREATOR = object : Parcelable.Creator<OpAccess> {
            override fun createFromParcel(parcel: Parcel): OpAccess {
                val packageName = parcel.readString()!!
                val attributionTag = parcel.readString()
                val user: UserHandle = parcel.readParcelable(UserHandle::class.java.classLoader)!!
                val lastAccessTime = parcel.readLong()
                var proxyAccess: OpAccess? = null
                val proxyPackageName = parcel.readString()
                if (proxyPackageName != null) {
                    proxyAccess = OpAccess(proxyPackageName,
                        parcel.readString(),
                        parcel.readParcelable(UserHandle::class.java.classLoader)!!,
                        lastAccessTime)
                }
                return OpAccess(packageName, attributionTag, user, lastAccessTime, proxyAccess)
            }

            override fun newArray(size: Int): Array<OpAccess?> {
                return arrayOfNulls(size)
            }
        }
    }
}