/* * Copyright (C) 2022 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.app.motiontool import android.ddm.DdmHandle import com.google.protobuf.InvalidProtocolBufferException import org.apache.harmony.dalvik.ddmc.Chunk import org.apache.harmony.dalvik.ddmc.ChunkHandler import org.apache.harmony.dalvik.ddmc.DdmServer /** * This class handles the 'MOTO' type DDM requests (defined in [motion_tool.proto]). * * It executes some validity checks and forwards valid requests to the [MotionToolManager]. It * requires a [MotionToolsRequest] as parameter and returns a [MotionToolsResponse]. Failures will * return a [MotionToolsResponse] with the [error][MotionToolsResponse.error] field set instead of * the respective return value. * * To activate this server, call [register]. This will register the DdmHandleMotionTool with the * [DdmServer]. The DdmHandleMotionTool can be registered once per process. To unregister from the * DdmServer, call [unregister]. */ class DdmHandleMotionTool private constructor( private val motionToolManager: MotionToolManager ) : DdmHandle() { companion object { val CHUNK_MOTO = ChunkHandler.type("MOTO") private const val SERVER_VERSION = 1 private var INSTANCE: DdmHandleMotionTool? = null @Synchronized fun getInstance(motionToolManager: MotionToolManager): DdmHandleMotionTool { return INSTANCE ?: DdmHandleMotionTool(motionToolManager).also { INSTANCE = it } } } fun register() { DdmServer.registerHandler(CHUNK_MOTO, this) } fun unregister() { DdmServer.unregisterHandler(CHUNK_MOTO) } override fun handleChunk(request: Chunk): Chunk { val requestDataBuffer = wrapChunk(request) val protoRequest = try { MotionToolsRequest.parseFrom(requestDataBuffer.array()) } catch (e: InvalidProtocolBufferException) { val responseData: ByteArray = MotionToolsResponse.newBuilder() .setError(ErrorResponse.newBuilder() .setCode(ErrorResponse.Code.INVALID_REQUEST) .setMessage("Invalid request format (Protobuf parse exception)")) .build() .toByteArray() return Chunk(CHUNK_MOTO, responseData, 0, responseData.size) } val response = when (protoRequest.typeCase.number) { MotionToolsRequest.HANDSHAKE_FIELD_NUMBER -> handleHandshakeRequest(protoRequest.handshake) MotionToolsRequest.BEGIN_TRACE_FIELD_NUMBER -> handleBeginTraceRequest(protoRequest.beginTrace) MotionToolsRequest.POLL_TRACE_FIELD_NUMBER -> handlePollTraceRequest(protoRequest.pollTrace) MotionToolsRequest.END_TRACE_FIELD_NUMBER -> handleEndTraceRequest(protoRequest.endTrace) else -> MotionToolsResponse.newBuilder().setError(ErrorResponse.newBuilder() .setCode(ErrorResponse.Code.INVALID_REQUEST) .setMessage("Unknown request type")).build() } val responseData = response.toByteArray() return Chunk(CHUNK_MOTO, responseData, 0, responseData.size) } private fun handleBeginTraceRequest(beginTraceRequest: BeginTraceRequest): MotionToolsResponse = MotionToolsResponse.newBuilder().apply { tryCatchingMotionToolManagerExceptions { setBeginTrace(BeginTraceResponse.newBuilder().setTraceId( motionToolManager.beginTrace(beginTraceRequest.window.rootWindow))) } }.build() private fun handlePollTraceRequest(pollTraceRequest: PollTraceRequest): MotionToolsResponse = MotionToolsResponse.newBuilder().apply { tryCatchingMotionToolManagerExceptions { setPollTrace(PollTraceResponse.newBuilder() .setExportedData(motionToolManager.pollTrace(pollTraceRequest.traceId))) } }.build() private fun handleEndTraceRequest(endTraceRequest: EndTraceRequest): MotionToolsResponse = MotionToolsResponse.newBuilder().apply { tryCatchingMotionToolManagerExceptions { setEndTrace(EndTraceResponse.newBuilder() .setExportedData(motionToolManager.endTrace(endTraceRequest.traceId))) } }.build() private fun handleHandshakeRequest(handshakeRequest: HandshakeRequest): MotionToolsResponse { val status = if (motionToolManager.hasWindow(handshakeRequest.window)) HandshakeResponse.Status.OK else HandshakeResponse.Status.WINDOW_NOT_FOUND return MotionToolsResponse.newBuilder() .setHandshake(HandshakeResponse.newBuilder() .setServerVersion(SERVER_VERSION) .setStatus(status)) .build() } /** * Executes the [block] and catches all Exceptions thrown by [MotionToolManager]. In case of an * exception being caught, the error response field of the [MotionToolsResponse] is being set * with the according [ErrorResponse]. */ private fun MotionToolsResponse.Builder.tryCatchingMotionToolManagerExceptions(block: () -> Unit) { try { block() } catch (e: UnknownTraceIdException) { setError(createUnknownTraceIdResponse(e.traceId)) } catch (e: WindowNotFoundException) { setError(createWindowNotFoundResponse(e.windowId)) } } private fun createUnknownTraceIdResponse(traceId: Int) = ErrorResponse.newBuilder().apply { this.code = ErrorResponse.Code.UNKNOWN_TRACE_ID this.message = "No running Trace found with traceId $traceId" } private fun createWindowNotFoundResponse(windowId: String) = ErrorResponse.newBuilder().apply { this.code = ErrorResponse.Code.WINDOW_NOT_FOUND this.message = "No window found with windowId $windowId" } override fun onConnected() {} override fun onDisconnected() {} }