1 /* <lambda>null2 * 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 18 19 import android.app.AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY 20 import android.app.AppOpsManager.OPSTR_COARSE_LOCATION 21 import android.app.AppOpsManager.OnOpNotedCallback 22 import android.app.AppOpsManager.strOpToOp 23 import android.content.BroadcastReceiver 24 import android.content.ComponentName 25 import android.content.Context 26 import android.content.Context.BIND_AUTO_CREATE 27 import android.content.Intent 28 import android.content.ServiceConnection 29 import android.location.LocationManager 30 import android.os.Binder 31 import android.os.Handler 32 import android.os.IBinder 33 import android.os.Looper 34 import android.os.Process 35 import android.platform.test.annotations.AppModeFull 36 import android.platform.test.annotations.RequiresFlagsEnabled 37 import android.platform.test.flag.junit.CheckFlagsRule 38 import android.platform.test.flag.junit.DeviceFlagsValueProvider 39 import android.util.Log 40 import androidx.test.platform.app.InstrumentationRegistry 41 import com.android.frameworks.coretests.aidl.IAppOpsUserClient 42 import com.android.frameworks.coretests.aidl.IAppOpsUserService 43 import com.google.common.truth.Truth.assertThat 44 import org.junit.After 45 import org.junit.Assert.fail 46 import org.junit.Before 47 import org.junit.Rule 48 import org.junit.Test 49 import java.util.concurrent.CompletableFuture 50 import java.util.concurrent.LinkedBlockingQueue 51 import java.util.concurrent.TimeUnit.MILLISECONDS 52 import java.util.concurrent.TimeUnit 53 import kotlin.time.Duration 54 import kotlin.time.Duration.Companion.milliseconds 55 import kotlin.time.TimeSource 56 57 private const val LOG_TAG = "AppOpsLoggingTest" 58 59 private const val TEST_SERVICE_PKG = "android.app.appops.appthatusesappops" 60 private const val TIMEOUT_MILLIS = 10000L 61 private const val TEST_ATTRIBUTION_TAG = "testAttribution" 62 63 private external fun nativeNoteOp( 64 op: Int, 65 uid: Int, 66 packageName: String, 67 attributionTag: String? = null, 68 message: String? = null 69 ) 70 71 @AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps") 72 class AppOpsLoggingTest { 73 74 private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context 75 private val appOpsManager = context.getSystemService(AppOpsManager::class.java)!! 76 77 private val myUid = Process.myUid() 78 private val myUserHandle = Process.myUserHandle() 79 private val myPackage = context.packageName 80 81 private var wasLocationEnabled = false 82 83 @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 84 85 private lateinit var testService: IAppOpsUserService 86 private lateinit var serviceConnection: ServiceConnection 87 private lateinit var freezingTestCompletion: CompletableFuture<Unit> 88 89 // Collected note-op calls inside of this process 90 private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>() 91 private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>() 92 private val asyncNoted = mutableListOf<AsyncNotedAppOp>() 93 94 @Before 95 fun setLocationEnabled() { 96 val locationManager = context.getSystemService(LocationManager::class.java)!! 97 wasLocationEnabled = locationManager.isLocationEnabled 98 locationManager.setLocationEnabledForUser(true, myUserHandle) 99 } 100 101 @After 102 fun restoreLocationEnabled() { 103 val locationManager = context.getSystemService(LocationManager::class.java)!! 104 locationManager.setLocationEnabledForUser(wasLocationEnabled, myUserHandle) 105 } 106 107 @Before 108 fun loadNativeCode() { 109 System.loadLibrary("AppOpsTest_jni") 110 } 111 112 @Before 113 fun setNotedAppOpsCollectorAndClearCollectedNoteOps() { 114 setNotedAppOpsCollector() 115 clearCollectedNotedOps() 116 } 117 118 @Before 119 fun connectToService() { 120 val serviceIntent = Intent() 121 serviceIntent.component = ComponentName(TEST_SERVICE_PKG, 122 "$TEST_SERVICE_PKG.AppOpsUserService" 123 ) 124 125 val newService = CompletableFuture<IAppOpsUserService>() 126 serviceConnection = object : ServiceConnection { 127 override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 128 newService.complete(IAppOpsUserService.Stub.asInterface(service)) 129 } 130 131 override fun onServiceDisconnected(name: ComponentName?) { 132 fail("test service disconnected") 133 } 134 } 135 136 context.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE) 137 testService = newService.get(TIMEOUT_MILLIS, MILLISECONDS) 138 freezingTestCompletion = CompletableFuture<Unit>() 139 } 140 141 private fun clearCollectedNotedOps() { 142 noted.clear() 143 selfNoted.clear() 144 asyncNoted.clear() 145 } 146 147 private fun setNotedAppOpsCollector() { 148 appOpsManager.setOnOpNotedCallback( 149 { it.run() }, 150 object : OnOpNotedCallback() { 151 override fun onNoted(op: SyncNotedAppOp) { 152 Log.i("OPALA", "sync op: $, stack: $".format(op, Throwable().stackTrace)) 153 noted.add(op to Throwable().stackTrace) 154 } 155 156 override fun onSelfNoted(op: SyncNotedAppOp) { 157 Log.i("OPALA", "self op: $, stack: $".format(op, Throwable().stackTrace)) 158 selfNoted.add(op to Throwable().stackTrace) 159 } 160 161 override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) { 162 Log.i("OPALA", "async op: $".format(asyncOp)) 163 asyncNoted.add(asyncOp) 164 } 165 }) 166 } 167 168 private inline fun rethrowThrowableFrom(r: () -> Unit) { 169 try { 170 r() 171 } catch (e: Throwable) { 172 throw e.cause ?: e 173 } 174 } 175 176 private fun <T> eventually(timeout: Long = TIMEOUT_MILLIS, r: () -> T): T { 177 val start = System.currentTimeMillis() 178 179 while (true) { 180 try { 181 return r() 182 } catch (e: Throwable) { 183 val elapsed = System.currentTimeMillis() - start 184 185 if (elapsed < timeout) { 186 Log.d(LOG_TAG, "Ignoring exception", e) 187 188 Thread.sleep(minOf(100, timeout - elapsed)) 189 } else { 190 throw e 191 } 192 } 193 } 194 } 195 196 @Test 197 fun noteSyncOpOnewayNative() { 198 rethrowThrowableFrom { 199 testService.callOnewayApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context)) 200 } 201 } 202 203 @Test 204 fun noteSyncOpOtherUidNativeAndCheckLog() { 205 rethrowThrowableFrom { 206 testService.callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(AppOpsUserClient(context)) 207 } 208 } 209 210 @Test 211 fun nativeSelfNoteAndCheckLog() { 212 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage) 213 214 assertThat(noted).isEmpty() 215 assertThat(selfNoted).isEmpty() 216 217 // All native notes will be reported as async notes 218 eventually { 219 assertThat(asyncNoted[0].attributionTag).isEqualTo(null) 220 // There is always a message. 221 assertThat(asyncNoted[0].message).isNotEqualTo(null) 222 assertThat(asyncNoted[0].op).isEqualTo(OPSTR_COARSE_LOCATION) 223 assertThat(asyncNoted[0].notingUid).isEqualTo(myUid) 224 } 225 } 226 227 @Test 228 fun noteSyncOpNativeAndCheckLog() { 229 rethrowThrowableFrom { 230 testService.callApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context)) 231 } 232 } 233 234 @Test 235 fun noteNonPermissionSyncOpNativeAndCheckLog() { 236 rethrowThrowableFrom { 237 testService.callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog( 238 AppOpsUserClient(context)) 239 } 240 } 241 242 @Test 243 fun noteAsyncOpNativelyAndCheckCustomMessage() { 244 rethrowThrowableFrom { 245 testService.callApiThatNotesAsyncOpNativelyAndCheckCustomMessage( 246 AppOpsUserClient(context)) 247 } 248 } 249 250 @Test 251 fun noteAsyncOpNativeAndCheckLog() { 252 rethrowThrowableFrom { 253 testService.callApiThatNotesAsyncOpNativelyAndCheckLog(AppOpsUserClient(context)) 254 } 255 } 256 257 @Test 258 fun nativeSelfNoteWithAttributionAndMsgAndCheckLog() { 259 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage, 260 attributionTag = TEST_ATTRIBUTION_TAG, message = "testMsg") 261 262 // All native notes will be reported as async notes 263 eventually { 264 assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG) 265 assertThat(asyncNoted[0].message).isEqualTo("testMsg") 266 } 267 } 268 269 @Test 270 @RequiresFlagsEnabled(android.os.Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK, 271 android.permission.flags.Flags.FLAG_USE_FROZEN_AWARE_REMOTE_CALLBACK_LIST) 272 fun dropAsyncOpNotedWhenFrozen() { 273 // Here's what the test does: 274 // 1. AppOpsLoggingTest calls AppOpsUserService 275 // 2. AppOpsUserService calls freezeAndNoteSyncOp in AppOpsLoggingTest 276 // 3. freezeAndNoteSyncOp freezes AppOpsUserService 277 // 4. freezeAndNoteSyncOp calls nativeNoteOp which leads to an async op noted callback 278 // 5. AppOpsService is expected to drop the callback (via RemoteCallbackList) since 279 // AppOpsUserService is frozen 280 // 6. freezeAndNoteSyncOp unfreezes AppOpsUserService 281 // 7. AppOpsLoggingTest calls AppOpsUserService.assertEmptyAsyncNoted 282 rethrowThrowableFrom { 283 testService.callFreezeAndNoteSyncOp(AppOpsUserClient(context)) 284 freezingTestCompletion.get() 285 testService.assertEmptyAsyncNoted() 286 } 287 } 288 289 @After 290 fun removeNotedAppOpsCollector() { 291 appOpsManager.setOnOpNotedCallback(null, null) 292 } 293 294 @After 295 fun disconnectFromService() { 296 context.unbindService(serviceConnection) 297 } 298 299 fun <T> waitForState(queue: LinkedBlockingQueue<T>, state: T, duration: Duration): T? { 300 val timeSource = TimeSource.Monotonic 301 val start = timeSource.markNow() 302 var remaining = duration 303 while (remaining.inWholeMilliseconds > 0) { 304 val v = queue.poll(remaining.inWholeMilliseconds, TimeUnit.MILLISECONDS) 305 if (v == state) { 306 return v 307 } 308 remaining -= timeSource.markNow() - start 309 } 310 return null 311 } 312 313 private inner class AppOpsUserClient( 314 context: Context 315 ) : IAppOpsUserClient.Stub() { 316 private val handler = Handler(Looper.getMainLooper()) 317 318 private val myUid = Process.myUid() 319 private val myPackage = context.packageName 320 321 override fun noteSyncOpNative() { 322 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), Binder.getCallingUid(), TEST_SERVICE_PKG) 323 } 324 325 override fun noteNonPermissionSyncOpNative() { 326 nativeNoteOp( 327 strOpToOp(OPSTR_ACCESS_ACCESSIBILITY), Binder.getCallingUid(), TEST_SERVICE_PKG 328 ) 329 } 330 331 override fun noteSyncOpOnewayNative() { 332 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), Binder.getCallingUid(), TEST_SERVICE_PKG) 333 } 334 335 override fun freezeAndNoteSyncOp() { 336 handler.post { 337 var stateChanges = LinkedBlockingQueue<Int>() 338 // Leave some time for any pending binder transactions to complete. 339 // 340 // TODO(327047060) Remove this sleep and instead make am freeze wait for binder 341 // transactions to complete 342 Thread.sleep(1000) 343 testService.asBinder().addFrozenStateChangeCallback { 344 _, state -> stateChanges.put(state) 345 } 346 InstrumentationRegistry.getInstrumentation().uiAutomation 347 .executeShellCommand("am freeze $TEST_SERVICE_PKG") 348 waitForState(stateChanges, IBinder.FrozenStateChangeCallback.STATE_FROZEN, 349 1000.milliseconds) 350 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), Binder.getCallingUid(), 351 TEST_SERVICE_PKG) 352 InstrumentationRegistry.getInstrumentation().uiAutomation 353 .executeShellCommand("am unfreeze $TEST_SERVICE_PKG") 354 waitForState(stateChanges, IBinder.FrozenStateChangeCallback.STATE_UNFROZEN, 355 1000.milliseconds) 356 freezingTestCompletion.complete(Unit) 357 } 358 } 359 360 override fun noteSyncOpOtherUidNative() { 361 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage) 362 } 363 364 override fun noteAsyncOpNative() { 365 val callingUid = Binder.getCallingUid() 366 367 handler.post { 368 nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), callingUid, TEST_SERVICE_PKG) 369 } 370 } 371 372 override fun noteAsyncOpNativeWithCustomMessage() { 373 val callingUid = Binder.getCallingUid() 374 375 handler.post { 376 nativeNoteOp( 377 strOpToOp(OPSTR_COARSE_LOCATION), 378 callingUid, 379 TEST_SERVICE_PKG, 380 message = "native custom msg" 381 ) 382 } 383 } 384 } 385 } 386 387 class PublicActionReceiver : BroadcastReceiver() { onReceivenull388 override fun onReceive(context: Context, intent: Intent?) { 389 } 390 } 391 392 class ProtectedActionReceiver : BroadcastReceiver() { onReceivenull393 override fun onReceive(context: Context, intent: Intent?) { 394 } 395 } 396