1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.fakeadbserver.shellcommandhandlers
import com.android.fakeadbserver.DeviceState
import com.android.fakeadbserver.FakeAdbServer
import com.android.fakeadbserver.ShellProtocolType
import com.android.fakeadbserver.devicecommandhandlers.DeviceCommandHandler
import com.android.fakeadbserver.services.ShellCommandOutput
import com.google.common.base.Charsets
import java.net.Socket
/**
* ShellHandler is a pre-supplied convenience construct to plug in and handle general shell
* commands. This reflects the "shell,v2:command" local service as stated in the ADB protocol.
*
* TODO: Rename to reflect this can handle both "shell" and "shell,v2" protocol.
*/
abstract class ShellHandler protected constructor(
protected val shellProtocolType: ShellProtocolType
) : DeviceCommandHandler(shellProtocolType.command) {
override fun accept(
server: FakeAdbServer,
socket: Socket,
device: DeviceState,
command: String,
args: String
): Boolean {
if (this.command != command) {
return false
}
val split = args.trim().split(" ", limit = 2)
val shellCommand = split[0]
val shellCommandArgs = if (split.size > 1) split[1] else null
if (shouldExecute(shellCommand, shellCommandArgs)) {
val statusWriter = StatusWriter(socket)
execute(
server,
statusWriter,
shellProtocolType.createServiceOutput(socket, device),
device,
shellCommand,
shellCommandArgs,
)
statusWriter.verifyStatusWritten()
return true
}
return false
}
/**
* Return true if the derived class will be able to act on this shell command
* @param shellCommand Shell command, e.g. for "adb shell ls -l" [shellCommand] would be "ls"
* @param shellCommandArgs Arguments for the command, e.g. for "adb shell ls -l" [shellCommandArgs] would be "-l"
*/
abstract fun shouldExecute(
shellCommand: String,
shellCommandArgs: String?
): Boolean
/**
* This is the main execution method of the command.
*
* @param fakeAdbServer Fake ADB Server itself.
* @param shellCommandOutput Shell protocol for standard in/out
* @param device Target device for the command, if any.
* @param shellCommand Shell command, e.g. for "adb shell ls -l" [shellCommand] would be "ls"
* @param shellCommandArgs Arguments for the command, e.g. for "adb shell ls -l" [shellCommandArgs] would be "-l"
*/
abstract fun execute(
fakeAdbServer: FakeAdbServer,
statusWriter: StatusWriter,
shellCommandOutput: ShellCommandOutput,
device: DeviceState,
shellCommand: String,
shellCommandArgs: String?
)
}
class StatusWriter(val socket: Socket) {
private var writeOkCalled = false
private var writeFailCalled = false
fun writeOk() {
assert(!writeOkCalled)
writeOkCalled = true
socket.getOutputStream().write("OKAY".toByteArray(Charsets.UTF_8))
}
fun writeFail() {
assert(!writeFailCalled)
writeFailCalled = true
socket.getOutputStream().write("FAIL".toByteArray(Charsets.UTF_8))
}
fun verifyStatusWritten() {
assert(writeOkCalled != writeFailCalled) {
"OKAY or FAIL message, but not both should be written to output stream"
}
}
}
|