• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.data
18 
19 import android.app.AppOpsManager
20 import android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED
21 import android.app.Application
22 import android.os.Parcel
23 import android.os.Parcelable
24 import android.os.UserHandle
25 import android.util.Log
26 import com.android.permissioncontroller.PermissionControllerApplication
27 import kotlinx.coroutines.GlobalScope
28 import kotlinx.coroutines.Job
29 import kotlinx.coroutines.delay
30 import kotlinx.coroutines.launch
31 
32 /**
33  * LiveData that loads the last usage of each of a list of app ops for every package.
34  *
35  * <p>For app-ops with duration the end of the access is considered.
36  *
37  * <p>Returns map op-name -> {@link OpAccess}
38  *
39  * @param app The current application
40  * @param opNames The names of the app ops we wish to search for
41  * @param usageDurationMs how much ago can an access have happened to be considered
42  */
43 class OpUsageLiveData(
44     private val app: Application,
45     private val opNames: List<String>,
46     private val usageDurationMs: Long
47 ) : SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map<String, List<OpAccess>>>(),
48         AppOpsManager.OnOpActiveChangedListener {
49     private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
50 
loadDataAndPostValuenull51     override suspend fun loadDataAndPostValue(job: Job) {
52         val now = System.currentTimeMillis()
53         val opMap = mutableMapOf<String, MutableList<OpAccess>>()
54 
55         val packageOps = try {
56             appOpsManager.getPackagesForOps(opNames.toTypedArray())
57         } catch (e: NullPointerException) {
58             // older builds might not support all the app-ops requested
59             emptyList<AppOpsManager.PackageOps>()
60         }
61         for (packageOp in packageOps) {
62             for (opEntry in packageOp.ops) {
63                 for ((attributionTag, attributedOpEntry) in opEntry.attributedOpEntries) {
64                     val user = UserHandle.getUserHandleForUid(packageOp.uid)
65                     val lastAccessTime: Long = attributedOpEntry.getLastAccessTime(
66                             OP_FLAGS_ALL_TRUSTED)
67 
68                     if (lastAccessTime == -1L) {
69                         // There was no access, so skip
70                         continue
71                     }
72 
73                     var lastAccessDuration = attributedOpEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED)
74 
75                     // Some accesses have no duration
76                     if (lastAccessDuration == -1L) {
77                         lastAccessDuration = 0
78                     }
79 
80                     if (attributedOpEntry.isRunning ||
81                             lastAccessTime + lastAccessDuration > (now - usageDurationMs)) {
82                         val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() }
83                         val accessTime = if (attributedOpEntry.isRunning) {
84                             OpAccess.IS_RUNNING
85                         } else {
86                             lastAccessTime
87                         }
88                         val proxy = attributedOpEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED)
89                         var proxyAccess: OpAccess? = null
90                         if (proxy != null && proxy.packageName != null) {
91                             proxyAccess = OpAccess(proxy.packageName!!, proxy.attributionTag,
92                                 UserHandle.getUserHandleForUid(proxy.uid), accessTime)
93                         }
94                         accessList.add(OpAccess(packageOp.packageName, attributionTag,
95                             user, accessTime, proxyAccess))
96 
97                         // TODO ntmyren: remove logs once b/160724034 is fixed
98                         Log.i("OpUsageLiveData", "adding ${opEntry.opStr} for " +
99                                 "${packageOp.packageName}/$attributionTag, access time of " +
100                                 "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
101                                 "current time $now, duration $lastAccessDuration, proxy: " +
102                                 "${proxy?.packageName}")
103                     } else {
104                         Log.i("OpUsageLiveData", "NOT adding ${opEntry.opStr} for " +
105                                 "${packageOp.packageName}/$attributionTag, access time of " +
106                                 "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
107                                 "current time $now, duration $lastAccessDuration")
108                     }
109                 }
110             }
111         }
112 
113         postValue(opMap)
114     }
115 
onActivenull116     override fun onActive() {
117         super.onActive()
118 
119         // appOpsManager.startWatchingNoted() is not exposed, hence force update regularly :-(
120         GlobalScope.launch {
121             while (hasActiveObservers()) {
122                 delay(1000)
123                 update()
124             }
125         }
126 
127         try {
128             appOpsManager.startWatchingActive(opNames.toTypedArray(), { it.run() }, this)
129         } catch (ignored: IllegalArgumentException) {
130             // older builds might not support all the app-ops requested
131         }
132     }
133 
onInactivenull134     override fun onInactive() {
135         super.onInactive()
136 
137         appOpsManager.stopWatchingActive(this)
138     }
139 
onOpActiveChangednull140     override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
141         update()
142     }
143 
144     companion object : DataRepository<Pair<List<String>, Long>, OpUsageLiveData>() {
newValuenull145         override fun newValue(key: Pair<List<String>, Long>): OpUsageLiveData {
146             return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second)
147         }
148 
getnull149         operator fun get(ops: List<String>, usageDurationMs: Long): OpUsageLiveData {
150             return get(ops to usageDurationMs)
151         }
152     }
153 }
154 
155 data class OpAccess(
156     val packageName: String,
157     val attributionTag: String?,
158     val user: UserHandle,
159     val lastAccessTime: Long,
160     val proxyAccess: OpAccess? = null
161 ) : Parcelable {
162     val isRunning = lastAccessTime == IS_RUNNING
163 
writeToParcelnull164     override fun writeToParcel(parcel: Parcel, flags: Int) {
165         parcel.writeString(packageName)
166         parcel.writeString(attributionTag)
167         parcel.writeParcelable(user, flags)
168         parcel.writeLong(lastAccessTime)
169         parcel.writeString(proxyAccess?.packageName)
170         parcel.writeString(proxyAccess?.attributionTag)
171         parcel.writeParcelable(proxyAccess?.user, flags)
172     }
173 
describeContentsnull174     override fun describeContents(): Int {
175         return 0
176     }
177 
178     companion object {
179         const val IS_RUNNING = -1L
180 
181         @JvmField
182         val CREATOR = object : Parcelable.Creator<OpAccess> {
createFromParcelnull183             override fun createFromParcel(parcel: Parcel): OpAccess {
184                 val packageName = parcel.readString()!!
185                 val attributionTag = parcel.readString()
186                 val user: UserHandle = parcel.readParcelable(UserHandle::class.java.classLoader)!!
187                 val lastAccessTime = parcel.readLong()
188                 var proxyAccess: OpAccess? = null
189                 val proxyPackageName = parcel.readString()
190                 if (proxyPackageName != null) {
191                     proxyAccess = OpAccess(proxyPackageName,
192                         parcel.readString(),
193                         parcel.readParcelable(UserHandle::class.java.classLoader)!!,
194                         lastAccessTime)
195                 }
196                 return OpAccess(packageName, attributionTag, user, lastAccessTime, proxyAccess)
197             }
198 
newArraynull199             override fun newArray(size: Int): Array<OpAccess?> {
200                 return arrayOfNulls(size)
201             }
202         }
203     }
204 }
205