• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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