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