1 /*
2  * Copyright (C) 2022 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 
17 package com.android.app.motiontool
18 
19 import android.ddm.DdmHandle
20 import com.google.protobuf.InvalidProtocolBufferException
21 import org.apache.harmony.dalvik.ddmc.Chunk
22 import org.apache.harmony.dalvik.ddmc.ChunkHandler
23 import org.apache.harmony.dalvik.ddmc.DdmServer
24 
25 /**
26  * This class handles the 'MOTO' type DDM requests (defined in [motion_tool.proto]).
27  *
28  * It executes some validity checks and forwards valid requests to the [MotionToolManager]. It
29  * requires a [MotionToolsRequest] as parameter and returns a [MotionToolsResponse]. Failures will
30  * return a [MotionToolsResponse] with the [error][MotionToolsResponse.error] field set instead of
31  * the respective return value.
32  *
33  * To activate this server, call [register]. This will register the DdmHandleMotionTool with the
34  * [DdmServer]. The DdmHandleMotionTool can be registered once per process. To unregister from the
35  * DdmServer, call [unregister].
36  */
37 class DdmHandleMotionTool private constructor(
38     private val motionToolManager: MotionToolManager
39 ) : DdmHandle() {
40 
41     companion object {
42         val CHUNK_MOTO = ChunkHandler.type("MOTO")
43         private const val SERVER_VERSION = 1
44 
45         private var INSTANCE: DdmHandleMotionTool? = null
46 
47         @Synchronized
getInstancenull48         fun getInstance(motionToolManager: MotionToolManager): DdmHandleMotionTool {
49             return INSTANCE ?: DdmHandleMotionTool(motionToolManager).also {
50                 INSTANCE = it
51             }
52         }
53     }
54 
registernull55     fun register() {
56         DdmServer.registerHandler(CHUNK_MOTO, this)
57     }
58 
unregisternull59     fun unregister() {
60         DdmServer.unregisterHandler(CHUNK_MOTO)
61     }
62 
handleChunknull63     override fun handleChunk(request: Chunk): Chunk {
64         val requestDataBuffer = wrapChunk(request)
65         val protoRequest =
66             try {
67                 MotionToolsRequest.parseFrom(requestDataBuffer.array())
68             } catch (e: InvalidProtocolBufferException) {
69                 val responseData: ByteArray = MotionToolsResponse.newBuilder()
70                         .setError(ErrorResponse.newBuilder()
71                                 .setCode(ErrorResponse.Code.INVALID_REQUEST)
72                                 .setMessage("Invalid request format (Protobuf parse exception)"))
73                         .build()
74                         .toByteArray()
75                 return Chunk(CHUNK_MOTO, responseData, 0, responseData.size)
76             }
77 
78         val response =
79             when (protoRequest.typeCase.number) {
80                 MotionToolsRequest.HANDSHAKE_FIELD_NUMBER ->
81                     handleHandshakeRequest(protoRequest.handshake)
82                 MotionToolsRequest.BEGIN_TRACE_FIELD_NUMBER ->
83                     handleBeginTraceRequest(protoRequest.beginTrace)
84                 MotionToolsRequest.POLL_TRACE_FIELD_NUMBER ->
85                     handlePollTraceRequest(protoRequest.pollTrace)
86                 MotionToolsRequest.END_TRACE_FIELD_NUMBER ->
87                     handleEndTraceRequest(protoRequest.endTrace)
88                 else ->
89                     MotionToolsResponse.newBuilder().setError(ErrorResponse.newBuilder()
90                             .setCode(ErrorResponse.Code.INVALID_REQUEST)
91                             .setMessage("Unknown request type")).build()
92             }
93 
94         val responseData = response.toByteArray()
95         return Chunk(CHUNK_MOTO, responseData, 0, responseData.size)
96     }
97 
handleBeginTraceRequestnull98     private fun handleBeginTraceRequest(beginTraceRequest: BeginTraceRequest): MotionToolsResponse =
99         MotionToolsResponse.newBuilder().apply {
100             tryCatchingMotionToolManagerExceptions {
101                 setBeginTrace(BeginTraceResponse.newBuilder().setTraceId(
102                         motionToolManager.beginTrace(beginTraceRequest.window.rootWindow)))
103             }
104         }.build()
105 
handlePollTraceRequestnull106     private fun handlePollTraceRequest(pollTraceRequest: PollTraceRequest): MotionToolsResponse =
107         MotionToolsResponse.newBuilder().apply {
108             tryCatchingMotionToolManagerExceptions {
109                 setPollTrace(PollTraceResponse.newBuilder()
110                         .setData(motionToolManager.pollTrace(pollTraceRequest.traceId)))
111             }
112         }.build()
113 
handleEndTraceRequestnull114     private fun handleEndTraceRequest(endTraceRequest: EndTraceRequest): MotionToolsResponse =
115         MotionToolsResponse.newBuilder().apply {
116             tryCatchingMotionToolManagerExceptions {
117                 setEndTrace(EndTraceResponse.newBuilder()
118                         .setData(motionToolManager.endTrace(endTraceRequest.traceId)))
119             }
120         }.build()
121 
handleHandshakeRequestnull122     private fun handleHandshakeRequest(handshakeRequest: HandshakeRequest): MotionToolsResponse {
123         val status = if (motionToolManager.hasWindow(handshakeRequest.window))
124             HandshakeResponse.Status.OK
125         else
126             HandshakeResponse.Status.WINDOW_NOT_FOUND
127 
128         return MotionToolsResponse.newBuilder()
129                 .setHandshake(HandshakeResponse.newBuilder()
130                         .setServerVersion(SERVER_VERSION)
131                         .setStatus(status))
132                 .build()
133     }
134 
135     /**
136      * Executes the [block] and catches all Exceptions thrown by [MotionToolManager]. In case of an
137      * exception being caught, the error response field of the [MotionToolsResponse] is being set
138      * with the according [ErrorResponse].
139      */
MotionToolsResponsenull140     private fun MotionToolsResponse.Builder.tryCatchingMotionToolManagerExceptions(block: () -> Unit) {
141         try {
142             block()
143         } catch (e: UnknownTraceIdException) {
144             setError(createUnknownTraceIdResponse(e.traceId))
145         } catch (e: WindowNotFoundException) {
146             setError(createWindowNotFoundResponse(e.windowId))
147         }
148     }
149 
createUnknownTraceIdResponsenull150     private fun createUnknownTraceIdResponse(traceId: Int) =
151         ErrorResponse.newBuilder().apply {
152             this.code = ErrorResponse.Code.UNKNOWN_TRACE_ID
153             this.message = "No running Trace found with traceId $traceId"
154         }
155 
createWindowNotFoundResponsenull156     private fun createWindowNotFoundResponse(windowId: String) =
157         ErrorResponse.newBuilder().apply {
158             this.code = ErrorResponse.Code.WINDOW_NOT_FOUND
159             this.message = "No window found with windowId $windowId"
160         }
161 
onConnectednull162     override fun onConnected() {}
163 
onDisconnectednull164     override fun onDisconnected() {}
165 }
166