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