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