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