1 /* 2 * Copyright (C) 2024 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 android.app.appops.appthatusesappops 18 19 import android.app.AppOpsManager 20 import android.app.AppOpsManager.OPSTR_COARSE_LOCATION 21 import android.app.AsyncNotedAppOp 22 import android.app.Service 23 import android.app.SyncNotedAppOp 24 import android.content.Intent 25 import android.os.Handler 26 import android.os.IBinder 27 import android.os.Looper 28 import android.util.Log 29 import com.android.frameworks.coretests.aidl.IAppOpsUserClient 30 import com.android.frameworks.coretests.aidl.IAppOpsUserService 31 import com.google.common.truth.Truth.assertThat 32 import java.io.PrintWriter 33 import java.io.StringWriter 34 35 private const val LOG_TAG = "AppOpsUserService" 36 private const val TIMEOUT_MILLIS = 10000L 37 38 class AppOpsUserService : Service() { <lambda>null39 private val testUid by lazy { 40 packageManager.getPackageUid("com.android.frameworks.coretests", 0) 41 } 42 43 /** 44 * Make sure that a lambda eventually finishes without throwing an exception. 45 * 46 * @param r The lambda to run. 47 * @param timeout the maximum time to wait 48 * 49 * @return the return value from the lambda 50 * 51 * @throws NullPointerException If the return value never becomes non-null 52 */ eventuallynull53 fun <T> eventually(timeout: Long = TIMEOUT_MILLIS, r: () -> T): T { 54 val start = System.currentTimeMillis() 55 56 while (true) { 57 try { 58 return r() 59 } catch (e: Throwable) { 60 val elapsed = System.currentTimeMillis() - start 61 62 if (elapsed < timeout) { 63 Log.d(LOG_TAG, "Ignoring exception", e) 64 65 Thread.sleep(minOf(100, timeout - elapsed)) 66 } else { 67 throw e 68 } 69 } 70 } 71 } 72 onBindnull73 override fun onBind(intent: Intent?): IBinder { 74 return object : IAppOpsUserService.Stub() { 75 private val appOpsManager = getSystemService(AppOpsManager::class.java)!! 76 private val handler = Handler(Looper.getMainLooper()) 77 78 // Collected note-op calls inside of this process 79 private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>() 80 private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>() 81 private val asyncNoted = mutableListOf<AsyncNotedAppOp>() 82 83 private fun setNotedAppOpsCollector() { 84 appOpsManager.setOnOpNotedCallback(mainExecutor, 85 object : AppOpsManager.OnOpNotedCallback() { 86 override fun onNoted(op: SyncNotedAppOp) { 87 noted.add(op to Throwable().stackTrace) 88 } 89 90 override fun onSelfNoted(op: SyncNotedAppOp) { 91 selfNoted.add(op to Throwable().stackTrace) 92 } 93 94 override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) { 95 asyncNoted.add(asyncOp) 96 } 97 }) 98 } 99 100 init { 101 try { 102 appOpsManager.setOnOpNotedCallback(null, null) 103 } catch (ignored: IllegalStateException) { 104 } 105 setNotedAppOpsCollector() 106 } 107 108 /** 109 * Cheapo variant of {@link ParcelableException} 110 */ 111 inline fun forwardThrowableFrom(r: () -> Unit) { 112 try { 113 r() 114 } catch (t: Throwable) { 115 val sw = StringWriter() 116 t.printStackTrace(PrintWriter(sw)) 117 118 throw IllegalArgumentException("\n" + sw.toString() + "called by") 119 } 120 } 121 122 override fun callApiThatNotesSyncOpNativelyAndCheckLog(client: IAppOpsUserClient) { 123 forwardThrowableFrom { 124 client.noteSyncOpNative() 125 126 // All native notes will be reported as async notes 127 eventually { 128 assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION) 129 } 130 assertThat(noted).isEmpty() 131 assertThat(selfNoted).isEmpty() 132 } 133 } 134 135 override fun callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog( 136 client: IAppOpsUserClient 137 ) { 138 forwardThrowableFrom { 139 client.noteNonPermissionSyncOpNative() 140 141 // All native notes will be reported as async notes 142 assertThat(noted).isEmpty() 143 assertThat(selfNoted).isEmpty() 144 assertThat(asyncNoted).isEmpty() 145 } 146 } 147 148 override fun callOnewayApiThatNotesSyncOpNativelyAndCheckLog( 149 client: IAppOpsUserClient 150 ) { 151 forwardThrowableFrom { 152 client.noteSyncOpOnewayNative() 153 154 // There is no return value from a one-way call, hence async note is the only 155 // option 156 eventually { 157 assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION) 158 } 159 assertThat(noted).isEmpty() 160 assertThat(selfNoted).isEmpty() 161 } 162 } 163 164 override fun callApiThatNotesSyncOpOtherUidNativelyAndCheckLog( 165 client: IAppOpsUserClient 166 ) { 167 forwardThrowableFrom { 168 client.noteSyncOpOtherUidNative() 169 170 assertThat(noted).isEmpty() 171 assertThat(selfNoted).isEmpty() 172 assertThat(asyncNoted).isEmpty() 173 } 174 } 175 176 override fun callApiThatNotesAsyncOpNativelyAndCheckLog(client: IAppOpsUserClient) { 177 forwardThrowableFrom { 178 client.noteAsyncOpNative() 179 180 eventually { 181 assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION) 182 } 183 assertThat(noted).isEmpty() 184 assertThat(selfNoted).isEmpty() 185 } 186 } 187 188 override fun callFreezeAndNoteSyncOp(client: IAppOpsUserClient) { 189 handler.post { 190 client.freezeAndNoteSyncOp() 191 } 192 } 193 194 override fun assertEmptyAsyncNoted() { 195 forwardThrowableFrom { 196 assertThat(asyncNoted).isEmpty() 197 } 198 } 199 200 override fun callApiThatNotesAsyncOpNativelyAndCheckCustomMessage( 201 client: IAppOpsUserClient 202 ) { 203 forwardThrowableFrom { 204 client.noteAsyncOpNativeWithCustomMessage() 205 206 eventually { 207 assertThat(asyncNoted[0].notingUid).isEqualTo(testUid) 208 assertThat(asyncNoted[0].message).isEqualTo("native custom msg") 209 } 210 } 211 } 212 } 213 } 214 } 215