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