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.content.IContentProvider
23 import android.database.Cursor
24 import android.net.Uri
25 import android.os.Binder
26 import android.os.Bundle
27 import android.os.Process
28 import android.os.ResultReceiver
29 import android.os.SystemProperties
30 import android.os.UserHandle
31 import java.io.FileDescriptor
32 import kotlin.system.exitProcess
33
34 class Tokenizer(private val args: Array<String>) {
35 private var cursor = 0
36
nextnull37 fun next(): String? {
38 if (cursor >= args.size) return null
39 return args[cursor++]
40 }
41
morenull42 fun more(): Boolean {
43 return cursor < args.size
44 }
45 }
46
isDebuggablenull47 fun isDebuggable(): Boolean {
48 return SystemProperties.getInt("ro.debuggable", 0) == 1
49 }
50
isTradeInModeEnablednull51 fun isTradeInModeEnabled(): Boolean {
52 return SystemProperties.getInt("persist.adb.tradeinmode", 0) > 0
53 }
54
ensureTradeInModeAllowednull55 fun ensureTradeInModeAllowed() {
56 if (!isDebuggable() && !isTradeInModeEnabled()) {
57 throw Exception("Command not available.")
58 }
59 }
60
61 class Commands {
62 companion object {
63 @JvmStatic
mainnull64 fun main(args: Array<String>) {
65 if (SystemProperties.getInt("sys.boot_completed", 0) != 1) {
66 System.err.println("Device not fully booted")
67 exitProcess(1)
68 }
69 try {
70 main_wrapper(Tokenizer(args))
71 } catch (e: Exception) {
72 System.err.println("Error: $e")
73 exitProcess(1)
74 }
75 }
76
main_wrappernull77 fun main_wrapper(args: Tokenizer) {
78 val cmd = args.next()
79 if (cmd == null) {
80 throw IllegalArgumentException("Expected command.")
81 }
82
83 ensureTradeInModeAllowed()
84
85 if (cmd == "getstatus") {
86 doGetStatus(args)
87 } else if (cmd == "evaluate") {
88 doEvaluate(args)
89 } else {
90 throw IllegalArgumentException("Unknown command.")
91 }
92 }
93 }
94 }
95
callingPackagenull96 fun callingPackage(): String? {
97 return when (Process.myUid()) {
98 Process.ROOT_UID -> "root"
99 Process.SHELL_UID -> "com.android.shell"
100 else -> null
101 }
102 }
103
doEvaluatenull104 fun doEvaluate(args: Tokenizer) {
105 if (args.more()) {
106 throw IllegalArgumentException("Unexpected argument.")
107 }
108
109 val am = ActivityManager.getService()
110 if (am == null) {
111 throw Exception("ActivityManager is not available.")
112 }
113
114 val path = "com.android.devicediagnostics/.EnterEvaluationMode"
115 val shellArgs = arrayOf("start", "-n", path)
116 am.asBinder()
117 .shellCommand(
118 FileDescriptor.`in`,
119 FileDescriptor.out,
120 FileDescriptor.err,
121 shellArgs,
122 null,
123 ResultReceiver(null),
124 )
125 }
126
queryGetStatusProvidernull127 fun queryGetStatusProvider(provider: IContentProvider, uri: Uri, extras: Bundle): Int {
128 val cursor =
129 provider.query(
130 AttributionSource(Binder.getCallingUid(), callingPackage(), null),
131 uri,
132 arrayOf<String>(),
133 extras,
134 null,
135 )
136 if (cursor == null) {
137 System.err.println("No result found.")
138 return 1
139 }
140 try {
141 if (!cursor.moveToFirst()) {
142 System.err.println("No result found.")
143 return 1
144 }
145 if (cursor.getColumnCount() < 1) {
146 System.err.println("No result found.")
147 return 1
148 }
149 if (cursor.getType(0) != Cursor.FIELD_TYPE_STRING) {
150 System.err.println("No result found.")
151 return 1
152 }
153 println(cursor.getString(0))
154 } finally {
155 cursor.close()
156 }
157 return 0
158 }
159
doGetStatusnull160 fun doGetStatus(args: Tokenizer) {
161 val extras = Bundle()
162
163 val arg = args.next()
164 if (arg == "--challenge") {
165 val challenge = args.next()
166 if (challenge == null) {
167 throw IllegalArgumentException("Expected challenge.")
168 }
169 extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, challenge)
170 } else if (arg != null) {
171 throw IllegalArgumentException("Unexpected argument.")
172 }
173
174 if (args.more()) {
175 throw IllegalArgumentException("Unexpected argument.")
176 }
177
178 val am = ActivityManager.getService()
179 if (am == null) {
180 throw Exception("ActivityManager is not available.")
181 }
182
183 val token = Binder()
184 val uriString = "content://com.android.devicediagnostics/.GetStatusContentProvider"
185 val uri = Uri.parse(uriString)
186 var holder: ContentProviderHolder? = null
187 try {
188 holder =
189 am.getContentProviderExternal(uri.authority, UserHandle.USER_SYSTEM, token, "*cmd*")
190 if (holder == null) {
191 throw Exception("Could not find provider: " + uri.authority)
192 }
193 queryGetStatusProvider(holder.provider, uri, extras)
194 } finally {
195 if (holder != null && holder.provider != null) {
196 am.removeContentProviderExternalAsUser(uri.authority, token, UserHandle.USER_SYSTEM)
197 }
198 }
199 }
200