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