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