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 ) :
49     SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map<String, List<OpAccess>>>(),
50     AppOpsManager.OnOpActiveChangedListener {
51     private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
52 
loadDataAndPostValuenull53     override suspend fun loadDataAndPostValue(job: Job) {
54         val now = System.currentTimeMillis()
55         val opMap = mutableMapOf<String, MutableList<OpAccess>>()
56 
57         val packageOps =
58             try {
59                 appOpsManager.getPackagesForOps(opNames.toTypedArray())
60             } catch (e: NullPointerException) {
61                 // older builds might not support all the app-ops requested
62                 emptyList<AppOpsManager.PackageOps>()
63             }
64         for (packageOp in packageOps) {
65             for (opEntry in packageOp.ops) {
66                 for ((attributionTag, attributedOpEntry) in opEntry.attributedOpEntries) {
67                     val user = UserHandle.getUserHandleForUid(packageOp.uid)
68                     val lastAccessTime: Long =
69                         attributedOpEntry.getLastAccessTime(OP_FLAGS_ALL_TRUSTED)
70 
71                     if (lastAccessTime == -1L) {
72                         // There was no access, so skip
73                         continue
74                     }
75 
76                     var lastAccessDuration = attributedOpEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED)
77 
78                     // Some accesses have no duration
79                     if (lastAccessDuration == -1L) {
80                         lastAccessDuration = 0
81                     }
82 
83                     if (
84                         attributedOpEntry.isRunning ||
85                             lastAccessTime + lastAccessDuration > (now - usageDurationMs)
86                     ) {
87                         val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() }
88                         val accessTime =
89                             if (attributedOpEntry.isRunning) {
90                                 OpAccess.IS_RUNNING
91                             } else {
92                                 lastAccessTime
93                             }
94                         val proxy = attributedOpEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED)
95                         var proxyAccess: OpAccess? = null
96                         if (proxy != null && proxy.packageName != null) {
97                             proxyAccess =
98                                 OpAccess(
99                                     proxy.packageName!!,
100                                     proxy.attributionTag,
101                                     UserHandle.getUserHandleForUid(proxy.uid),
102                                     accessTime
103                                 )
104                         }
105                         accessList.add(
106                             OpAccess(
107                                 packageOp.packageName,
108                                 attributionTag,
109                                 user,
110                                 accessTime,
111                                 proxyAccess
112                             )
113                         )
114 
115                         // TODO ntmyren: remove logs once b/160724034 is fixed
116                         Log.i(
117                             "OpUsageLiveData",
118                             "adding ${opEntry.opStr} for " +
119                                 "${packageOp.packageName}/$attributionTag, access time of " +
120                                 "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
121                                 "current time $now, duration $lastAccessDuration, proxy: " +
122                                 "${proxy?.packageName}"
123                         )
124                     } else {
125                         Log.i(
126                             "OpUsageLiveData",
127                             "NOT adding ${opEntry.opStr} for " +
128                                 "${packageOp.packageName}/$attributionTag, access time of " +
129                                 "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " +
130                                 "current time $now, duration $lastAccessDuration"
131                         )
132                     }
133                 }
134             }
135         }
136 
137         postValue(opMap)
138     }
139 
onActivenull140     override fun onActive() {
141         super.onActive()
142 
143         // appOpsManager.startWatchingNoted() is not exposed, hence force update regularly :-(
144         GlobalScope.launch {
145             while (hasActiveObservers()) {
146                 delay(1000)
147                 update()
148             }
149         }
150 
151         try {
152             appOpsManager.startWatchingActive(opNames.toTypedArray(), { it.run() }, this)
153         } catch (ignored: IllegalArgumentException) {
154             // older builds might not support all the app-ops requested
155         }
156     }
157 
onInactivenull158     override fun onInactive() {
159         super.onInactive()
160 
161         appOpsManager.stopWatchingActive(this)
162     }
163 
onOpActiveChangednull164     override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
165         update()
166     }
167 
168     companion object : DataRepository<Pair<List<String>, Long>, OpUsageLiveData>() {
newValuenull169         override fun newValue(key: Pair<List<String>, Long>): OpUsageLiveData {
170             return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second)
171         }
172 
getnull173         operator fun get(ops: List<String>, usageDurationMs: Long): OpUsageLiveData {
174             return get(ops to usageDurationMs)
175         }
176     }
177 }
178 
179 data class OpAccess(
180     val packageName: String,
181     val attributionTag: String?,
182     val user: UserHandle,
183     val lastAccessTime: Long,
184     val proxyAccess: OpAccess? = null
185 ) : Parcelable {
186     val isRunning = lastAccessTime == IS_RUNNING
187 
writeToParcelnull188     override fun writeToParcel(parcel: Parcel, flags: Int) {
189         parcel.writeString(packageName)
190         parcel.writeString(attributionTag)
191         parcel.writeParcelable(user, flags)
192         parcel.writeLong(lastAccessTime)
193         parcel.writeString(proxyAccess?.packageName)
194         parcel.writeString(proxyAccess?.attributionTag)
195         parcel.writeParcelable(proxyAccess?.user, flags)
196     }
197 
describeContentsnull198     override fun describeContents(): Int {
199         return 0
200     }
201 
202     companion object {
203         const val IS_RUNNING = -1L
204 
205         @JvmField
206         val CREATOR =
207             object : Parcelable.Creator<OpAccess> {
createFromParcelnull208                 override fun createFromParcel(parcel: Parcel): OpAccess {
209                     val packageName = parcel.readString()!!
210                     val attributionTag = parcel.readString()
211                     val user: UserHandle =
212                         parcel.readParcelable(UserHandle::class.java.classLoader)!!
213                     val lastAccessTime = parcel.readLong()
214                     var proxyAccess: OpAccess? = null
215                     val proxyPackageName = parcel.readString()
216                     if (proxyPackageName != null) {
217                         proxyAccess =
218                             OpAccess(
219                                 proxyPackageName,
220                                 parcel.readString(),
221                                 parcel.readParcelable(UserHandle::class.java.classLoader)!!,
222                                 lastAccessTime
223                             )
224                     }
225                     return OpAccess(packageName, attributionTag, user, lastAccessTime, proxyAccess)
226                 }
227 
newArraynull228                 override fun newArray(size: Int): Array<OpAccess?> {
229                     return arrayOfNulls(size)
230                 }
231             }
232     }
233 }
234