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