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 package com.android.devicediagnostics.commands
17
18 import android.app.ActivityManager
19 import android.app.ContentProviderHolder
20 import android.content.AttributionSource
21 import android.content.ContentResolver
22 import android.database.Cursor
23 import android.net.Uri
24 import android.os.Binder
25 import android.os.Bundle
26 import android.os.Process
27 import android.os.ResultReceiver
28 import android.os.SystemProperties
29 import android.os.UserHandle
30 import java.io.FileDescriptor
31 import kotlin.system.exitProcess
32
33 const val STATE_PROP = "persist.adb.tradeinmode"
34
35 class Tokenizer(private val args: Array<String>) {
36 private var cursor = 0
37
nextnull38 fun next(): String? {
39 if (cursor >= args.size) return null
40 return args[cursor++]
41 }
42
morenull43 fun more(): Boolean {
44 return cursor < args.size
45 }
46 }
47
isDebuggablenull48 fun isDebuggable(): Boolean {
49 return SystemProperties.getInt("ro.debuggable", 0) == 1
50 }
51
isTradeInModeEnablednull52 fun isTradeInModeEnabled(): Boolean {
53 return SystemProperties.getInt(STATE_PROP, 0) > 0
54 }
55
isBootCompletednull56 fun isBootCompleted(): Boolean {
57 return SystemProperties.getInt("sys.boot_completed", 0) != 0
58 }
59
ensureTradeInModeAllowednull60 fun ensureTradeInModeAllowed() {
61 if (!isDebuggable() && !isTradeInModeEnabled()) {
62 throw Exception("Command not available.")
63 }
64 }
65
66 class Commands {
67 companion object {
68 @JvmStatic
mainnull69 fun main(args: Array<String>) {
70 try {
71 doMain(Tokenizer(args))
72 } catch (e: Exception) {
73 System.err.println("Error: $e")
74 exitProcess(1)
75 }
76 }
77 }
78 }
79
doMainnull80 fun doMain(args: Tokenizer) {
81 var cmd = args.next()
82 if (cmd == null) {
83 throw IllegalArgumentException("Expected command.")
84 }
85
86 // Optional wait-until-ready prefix to make testing easier.
87 if (cmd == "wait-until-ready") {
88 doWaitUntilReady()
89
90 cmd = args.next()
91 if (cmd == null) {
92 return
93 }
94 }
95
96 if (cmd == "testing" && isDebuggable()) {
97 doTesting(args)
98 return
99 }
100
101 if (!isBootCompleted()) {
102 throw Exception("Device not fully booted")
103 }
104
105 ensureTradeInModeAllowed()
106
107 if (cmd == "getstatus") {
108 doGetStatus(args)
109 } else if (cmd == "evaluate") {
110 doEvaluate(args)
111 } else if (cmd == "poweroff") {
112 SystemProperties.set("sys.powerctl", "shutdown")
113 } else if (cmd == "reboot") {
114 SystemProperties.set("sys.powerctl", "reboot")
115 } else {
116 throw IllegalArgumentException("Unknown command.")
117 }
118 }
119
callingPackagenull120 fun callingPackage(): String? {
121 return when (Process.myUid()) {
122 Process.ROOT_UID -> "root"
123 Process.SHELL_UID -> "com.android.shell"
124 else -> null
125 }
126 }
127
startActivitynull128 fun startActivity(activity: String) {
129 val am = ActivityManager.getService()
130 if (am == null) {
131 throw Exception("ActivityManager is not available.")
132 }
133
134 val shellArgs = arrayOf("start", "-n", activity)
135 am.asBinder()
136 .shellCommand(
137 FileDescriptor.`in`,
138 FileDescriptor.out,
139 FileDescriptor.err,
140 shellArgs,
141 null,
142 ResultReceiver(null),
143 )
144 }
145
doTestingnull146 fun doTesting(args: Tokenizer) {
147 if (!args.more()) {
148 throw IllegalArgumentException("Expected argument.")
149 }
150
151 val uriString = "content://com.android.devicediagnostics.TradeInModeTestingContentProvider"
152
153 val cmd = args.next()
154
155 if (cmd == "status") {
156 val result = queryStringContentProvider(uriString, cmd)
157 println(result)
158 return
159 }
160
161 var reboot: Boolean = false
162 if (cmd == "start") {
163 println("Device will reboot in trade-in mode.")
164 reboot = true
165 } else if (cmd == "wipe") {
166 println("Device will reboot to wipe userdata.")
167 reboot = true
168 } else if (cmd == "stop") {
169 println("Device will restart adb.")
170 } else {
171 throw IllegalArgumentException("Unknown argument.")
172 }
173
174 val result = queryStringContentProvider(uriString, cmd)
175 if (result != "ok") {
176 throw Exception("Failed to query testing content provider")
177 }
178
179 if (reboot) {
180 SystemProperties.set("sys.powerctl", "reboot")
181 }
182 }
183
doEvaluatenull184 fun doEvaluate(args: Tokenizer) {
185 if (args.more()) {
186 throw IllegalArgumentException("Unexpected argument.")
187 }
188
189 val uriString = "content://com.android.devicediagnostics.EvaluateContentProvider"
190 queryStringContentProvider(uriString, null)
191 println("Entering evaluation mode.")
192 }
193
doGetStatusnull194 fun doGetStatus(args: Tokenizer) {
195 val arg = args.next()
196 var challenge: String? = null
197 if (arg == "--challenge") {
198 challenge = args.next()
199 if (challenge == null) {
200 throw IllegalArgumentException("Expected challenge.")
201 }
202 } else if (arg != null) {
203 throw IllegalArgumentException("Unexpected argument.")
204 }
205
206 if (args.more()) {
207 throw IllegalArgumentException("Unexpected argument.")
208 }
209
210 val uriString = "content://com.android.devicediagnostics.GetStatusContentProvider"
211 val result = queryStringContentProvider(uriString, challenge)
212 if (result == null) {
213 System.err.println("No result found.")
214 return
215 }
216 println(result)
217 }
218
queryStringContentProvidernull219 fun queryStringContentProvider(uriString: String, selection: String?): String? {
220 val am = ActivityManager.getService()
221 if (am == null) {
222 throw Exception("ActivityManager is not available.")
223 }
224
225 val extras = Bundle()
226 if (selection != null) {
227 extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
228 }
229
230 val token = Binder()
231 val uri = Uri.parse(uriString)
232 var holder: ContentProviderHolder? = null
233 try {
234 holder =
235 am.getContentProviderExternal(uri.authority, UserHandle.USER_SYSTEM, token, "*cmd*")
236 if (holder == null) {
237 throw Exception("Could not find provider: " + uri.authority)
238 }
239 val cursor =
240 holder.provider.query(
241 AttributionSource(Binder.getCallingUid(), callingPackage(), null),
242 uri,
243 arrayOf<String>(),
244 extras,
245 null,
246 )
247 if (cursor == null) {
248 return null
249 }
250 try {
251 if (!cursor.moveToFirst()) {
252 return null
253 }
254 if (cursor.getColumnCount() < 1) {
255 return null
256 }
257 if (cursor.getType(0) != Cursor.FIELD_TYPE_STRING) {
258 return null
259 }
260 return cursor.getString(0)
261 } finally {
262 cursor.close()
263 }
264 } finally {
265 if (holder != null && holder.provider != null) {
266 am.removeContentProviderExternalAsUser(uri.authority, token, UserHandle.USER_SYSTEM)
267 }
268 }
269 }
270
isTradeInModeReadynull271 fun isTradeInModeReady(): Boolean {
272 if (!isBootCompleted()) {
273 return false
274 }
275
276 val am = ActivityManager.getService()
277 if (am == null) {
278 return false
279 }
280
281 val token = Binder()
282 val uriString = "content://com.android.devicediagnostics.GetStatusContentProvider"
283 val uri = Uri.parse(uriString)
284 var holder: ContentProviderHolder? = null
285 try {
286 holder =
287 am.getContentProviderExternal(uri.authority, UserHandle.USER_SYSTEM, token, "*cmd*")
288 return holder != null && holder.provider != null
289 } catch (e: Exception) {
290 return false
291 } finally {
292 if (holder != null && holder.provider != null) {
293 am.removeContentProviderExternalAsUser(uri.authority, token, UserHandle.USER_SYSTEM)
294 }
295 }
296 }
297
doWaitUntilReadynull298 fun doWaitUntilReady() {
299 while (!isTradeInModeReady()) {
300 Thread.sleep(500)
301 }
302 }
303