/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("DEPRECATION") package com.android.permissioncontroller.permission.data import android.app.AppOpsManager import android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED import android.app.Application import android.os.Parcel import android.os.Parcelable import android.os.UserHandle import android.util.Log import com.android.permissioncontroller.PermissionControllerApplication import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch /** * LiveData that loads the last usage of each of a list of app ops for every package. * *

For app-ops with duration the end of the access is considered. * *

Returns map op-name -> {@link OpAccess} * * @param app The current application * @param opNames The names of the app ops we wish to search for * @param usageDurationMs how much ago can an access have happened to be considered */ class OpUsageLiveData( private val app: Application, private val opNames: List, private val usageDurationMs: Long ) : SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map>>(), AppOpsManager.OnOpActiveChangedListener { private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!! override suspend fun loadDataAndPostValue(job: Job) { val now = System.currentTimeMillis() val opMap = mutableMapOf>() val packageOps = try { appOpsManager.getPackagesForOps(opNames.toTypedArray()) } catch (e: NullPointerException) { // older builds might not support all the app-ops requested emptyList() } for (packageOp in packageOps) { for (opEntry in packageOp.ops) { for ((attributionTag, attributedOpEntry) in opEntry.attributedOpEntries) { val user = UserHandle.getUserHandleForUid(packageOp.uid) val lastAccessTime: Long = attributedOpEntry.getLastAccessTime(OP_FLAGS_ALL_TRUSTED) if (lastAccessTime == -1L) { // There was no access, so skip continue } var lastAccessDuration = attributedOpEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED) // Some accesses have no duration if (lastAccessDuration == -1L) { lastAccessDuration = 0 } if ( attributedOpEntry.isRunning || lastAccessTime + lastAccessDuration > (now - usageDurationMs) ) { val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() } val accessTime = if (attributedOpEntry.isRunning) { OpAccess.IS_RUNNING } else { lastAccessTime } val proxy = attributedOpEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED) var proxyAccess: OpAccess? = null if (proxy != null && proxy.packageName != null) { proxyAccess = OpAccess( proxy.packageName!!, proxy.attributionTag, UserHandle.getUserHandleForUid(proxy.uid), accessTime ) } accessList.add( OpAccess( packageOp.packageName, attributionTag, user, accessTime, proxyAccess ) ) // TODO ntmyren: remove logs once b/160724034 is fixed Log.i( "OpUsageLiveData", "adding ${opEntry.opStr} for " + "${packageOp.packageName}/$attributionTag, access time of " + "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " + "current time $now, duration $lastAccessDuration, proxy: " + "${proxy?.packageName}" ) } else { Log.i( "OpUsageLiveData", "NOT adding ${opEntry.opStr} for " + "${packageOp.packageName}/$attributionTag, access time of " + "$lastAccessTime, isRunning: ${attributedOpEntry.isRunning} " + "current time $now, duration $lastAccessDuration" ) } } } } postValue(opMap) } override fun onActive() { super.onActive() // appOpsManager.startWatchingNoted() is not exposed, hence force update regularly :-( GlobalScope.launch { while (hasActiveObservers()) { delay(1000) update() } } try { appOpsManager.startWatchingActive(opNames.toTypedArray(), { it.run() }, this) } catch (ignored: IllegalArgumentException) { // older builds might not support all the app-ops requested } } override fun onInactive() { super.onInactive() appOpsManager.stopWatchingActive(this) } override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) { update() } companion object : DataRepository, Long>, OpUsageLiveData>() { override fun newValue(key: Pair, Long>): OpUsageLiveData { return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second) } operator fun get(ops: List, usageDurationMs: Long): OpUsageLiveData { return get(ops to usageDurationMs) } } } data class OpAccess( val packageName: String, val attributionTag: String?, val user: UserHandle, val lastAccessTime: Long, val proxyAccess: OpAccess? = null ) : Parcelable { val isRunning = lastAccessTime == IS_RUNNING override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(packageName) parcel.writeString(attributionTag) parcel.writeParcelable(user, flags) parcel.writeLong(lastAccessTime) parcel.writeString(proxyAccess?.packageName) parcel.writeString(proxyAccess?.attributionTag) parcel.writeParcelable(proxyAccess?.user, flags) } override fun describeContents(): Int { return 0 } companion object { const val IS_RUNNING = -1L @JvmField val CREATOR = object : Parcelable.Creator { override fun createFromParcel(parcel: Parcel): OpAccess { val packageName = parcel.readString()!! val attributionTag = parcel.readString() val user: UserHandle = parcel.readParcelable(UserHandle::class.java.classLoader)!! val lastAccessTime = parcel.readLong() var proxyAccess: OpAccess? = null val proxyPackageName = parcel.readString() if (proxyPackageName != null) { proxyAccess = OpAccess( proxyPackageName, parcel.readString(), parcel.readParcelable(UserHandle::class.java.classLoader)!!, lastAccessTime ) } return OpAccess(packageName, attributionTag, user, lastAccessTime, proxyAccess) } override fun newArray(size: Int): Array { return arrayOfNulls(size) } } } }