1 /*
<lambda>null2  * 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.content.Intent
20 import android.testing.AndroidTestingRunner
21 import android.view.Choreographer
22 import android.view.View
23 import android.view.WindowManagerGlobal
24 import androidx.test.ext.junit.rules.ActivityScenarioRule
25 import androidx.test.filters.SmallTest
26 import androidx.test.platform.app.InstrumentationRegistry
27 import com.android.app.motiontool.DdmHandleMotionTool.Companion.CHUNK_MOTO
28 import com.android.app.motiontool.util.TestActivity
29 import junit.framework.Assert
30 import junit.framework.Assert.assertEquals
31 import org.apache.harmony.dalvik.ddmc.Chunk
32 import org.apache.harmony.dalvik.ddmc.ChunkHandler.wrapChunk
33 import org.junit.After
34 import org.junit.Before
35 import org.junit.Rule
36 import org.junit.Test
37 import org.junit.runner.RunWith
38 
39 @SmallTest
40 @RunWith(AndroidTestingRunner::class)
41 class DdmHandleMotionToolTest {
42 
43     private val windowManagerGlobal = WindowManagerGlobal.getInstance()
44     private val motionToolManager = MotionToolManager.getInstance(windowManagerGlobal)
45     private val ddmHandleMotionTool = DdmHandleMotionTool.getInstance(motionToolManager)
46     private val CLIENT_VERSION = 1
47 
48     private val activityIntent =
49         Intent(InstrumentationRegistry.getInstrumentation().context, TestActivity::class.java)
50 
51     @get:Rule
52     val activityScenarioRule = ActivityScenarioRule<TestActivity>(activityIntent)
53 
54     @Before
55     fun setup() {
56         ddmHandleMotionTool.register()
57     }
58 
59     @After
60     fun cleanup() {
61         ddmHandleMotionTool.unregister()
62     }
63 
64     @Test
65     fun testHandshakeErrorWithInvalidWindowId() {
66         val handshakeResponse = performHandshakeRequest("InvalidWindowId")
67         assertEquals(HandshakeResponse.Status.WINDOW_NOT_FOUND, handshakeResponse.handshake.status)
68     }
69 
70     @Test
71     fun testHandshakeOkWithValidWindowId() {
72         val handshakeResponse = performHandshakeRequest(getActivityViewRootId())
73         assertEquals(HandshakeResponse.Status.OK, handshakeResponse.handshake.status)
74     }
75 
76     @Test
77     fun testBeginFailsWithInvalidWindowId() {
78         val errorResponse = performBeginTraceRequest("InvalidWindowId")
79         assertEquals(ErrorResponse.Code.WINDOW_NOT_FOUND, errorResponse.error.code)
80     }
81 
82     @Test
83     fun testEndTraceFailsWithoutPrecedingBeginTrace() {
84         val errorResponse = performEndTraceRequest(0)
85         assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, errorResponse.error.code)
86     }
87 
88     @Test
89     fun testPollTraceFailsWithoutPrecedingBeginTrace() {
90         val errorResponse = performPollTraceRequest(0)
91         assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, errorResponse.error.code)
92     }
93 
94     @Test
95     fun testEndTraceFailsWithInvalidTraceId() {
96         val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId())
97         val endTraceResponse = performEndTraceRequest(beginTraceResponse.beginTrace.traceId + 1)
98         assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, endTraceResponse.error.code)
99     }
100 
101     @Test
102     fun testPollTraceFailsWithInvalidTraceId() {
103         val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId())
104         val endTraceResponse = performPollTraceRequest(beginTraceResponse.beginTrace.traceId + 1)
105         assertEquals(ErrorResponse.Code.UNKNOWN_TRACE_ID, endTraceResponse.error.code)
106     }
107 
108     @Test
109     fun testMalformedRequestFails() {
110         val requestBytes = ByteArray(9)
111         val requestChunk = Chunk(CHUNK_MOTO, requestBytes, 0, requestBytes.size)
112         val responseChunk = ddmHandleMotionTool.handleChunk(requestChunk)
113         val response = MotionToolsResponse.parseFrom(wrapChunk(responseChunk).array()).error
114         assertEquals(ErrorResponse.Code.INVALID_REQUEST, response.code)
115     }
116 
117     @Test
118     fun testNoOnDrawCallReturnsEmptyTrace() {
119         activityScenarioRule.scenario.onActivity {
120             val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId())
121             val endTraceResponse = performEndTraceRequest(beginTraceResponse.beginTrace.traceId)
122             Assert.assertTrue(endTraceResponse.endTrace.data.frameDataList.isEmpty())
123         }
124     }
125 
126     @Test
127     fun testOneOnDrawCallReturnsOneFrameResponse() {
128         activityScenarioRule.scenario.onActivity { activity ->
129             val beginTraceResponse = performBeginTraceRequest(getActivityViewRootId())
130             val traceId = beginTraceResponse.beginTrace.traceId
131 
132             Choreographer.getInstance().postFrameCallback {
133                 activity
134                     .requireViewById<View>(android.R.id.content)
135                     .viewTreeObserver
136                     .dispatchOnDraw()
137 
138                 val pollTraceResponse = performPollTraceRequest(traceId)
139                 assertEquals(1, pollTraceResponse.pollTrace.data.frameDataList.size)
140 
141                 // Verify that frameData is only included once and is not returned again
142                 val endTraceResponse = performEndTraceRequest(traceId)
143                 assertEquals(0, endTraceResponse.endTrace.data.frameDataList.size)
144             }
145         }
146     }
147 
148     private fun performPollTraceRequest(requestTraceId: Int): MotionToolsResponse {
149         val pollTraceRequest = MotionToolsRequest.newBuilder()
150                 .setPollTrace(PollTraceRequest.newBuilder()
151                         .setTraceId(requestTraceId))
152                 .build()
153         return performRequest(pollTraceRequest)
154     }
155 
156     private fun performEndTraceRequest(requestTraceId: Int): MotionToolsResponse {
157         val endTraceRequest = MotionToolsRequest.newBuilder()
158                 .setEndTrace(EndTraceRequest.newBuilder()
159                         .setTraceId(requestTraceId))
160                 .build()
161         return performRequest(endTraceRequest)
162     }
163 
164     private fun performBeginTraceRequest(windowId: String): MotionToolsResponse {
165         val beginTraceRequest = MotionToolsRequest.newBuilder()
166                 .setBeginTrace(BeginTraceRequest.newBuilder()
167                         .setWindow(WindowIdentifier.newBuilder()
168                                 .setRootWindow(windowId)))
169                 .build()
170         return performRequest(beginTraceRequest)
171     }
172 
173     private fun performHandshakeRequest(windowId: String): MotionToolsResponse {
174         val handshakeRequest = MotionToolsRequest.newBuilder()
175                 .setHandshake(HandshakeRequest.newBuilder()
176                         .setWindow(WindowIdentifier.newBuilder()
177                                 .setRootWindow(windowId))
178                         .setClientVersion(CLIENT_VERSION))
179                 .build()
180         return performRequest(handshakeRequest)
181     }
182 
183     private fun performRequest(motionToolsRequest: MotionToolsRequest): MotionToolsResponse {
184         val requestBytes = motionToolsRequest.toByteArray()
185         val requestChunk = Chunk(CHUNK_MOTO, requestBytes, 0, requestBytes.size)
186         val responseChunk = ddmHandleMotionTool.handleChunk(requestChunk)
187         return MotionToolsResponse.parseFrom(wrapChunk(responseChunk).array())
188     }
189 
190     private fun getActivityViewRootId(): String {
191         var activityViewRootId = ""
192         activityScenarioRule.scenario.onActivity {
193             activityViewRootId = WindowManagerGlobal.getInstance().viewRootNames.first()
194         }
195         return activityViewRootId
196     }
197 
198 }
199