1 /*
<lambda>null2  * Copyright 2024 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  *      https://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.devicediagnostics
18 
19 import android.app.Activity
20 import android.view.View
21 import androidx.test.core.app.ActivityScenario.launch
22 import androidx.test.espresso.Espresso.onView
23 import androidx.test.espresso.UiController
24 import androidx.test.espresso.ViewAction
25 import androidx.test.espresso.action.MotionEvents
26 import androidx.test.espresso.action.ViewActions.click
27 import androidx.test.espresso.assertion.ViewAssertions.matches
28 import androidx.test.espresso.intent.Intents.intended
29 import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
30 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
31 import androidx.test.espresso.matcher.ViewMatchers.withId
32 import androidx.test.espresso.matcher.ViewMatchers.withText
33 import androidx.test.ext.junit.runners.AndroidJUnit4
34 import com.android.devicediagnostics.Protos.BluetoothPacket
35 import com.android.devicediagnostics.Protos.DeviceReport
36 import com.android.devicediagnostics.Protos.TestResults
37 import com.android.devicediagnostics.Protos.TrustedDeviceInfo
38 import com.android.devicediagnostics.bluetooth.BluetoothClient
39 import com.android.devicediagnostics.bluetooth.BluetoothConnectionData
40 import com.android.devicediagnostics.bluetooth.BluetoothServer
41 import com.android.devicediagnostics.evaluated.ScreenTestFinalizeActivity
42 import com.android.devicediagnostics.evaluated.TouchTestFinalizeActivity
43 import com.android.devicediagnostics.trusted.DisplayResultActivity
44 import com.android.devicediagnostics.trusted.WaitForResultActivity
45 import com.android.settingslib.qrcode.QrCamera
46 import com.google.android.attestation.AuthorizationList
47 import com.google.android.attestation.ParsedAttestationRecord
48 import com.google.protobuf.ByteString
49 import java.nio.charset.Charset
50 import org.hamcrest.Matcher
51 import org.junit.Assert.assertEquals
52 import org.junit.Test
53 import org.junit.runner.RunWith
54 import org.mockito.Mockito
55 import org.mockito.kotlin.any
56 import org.mockito.kotlin.argumentCaptor
57 import org.mockito.kotlin.times
58 import org.mockito.kotlin.verify
59 import org.mockito.kotlin.whenever
60 
61 private fun touchDownAndUp(): ViewAction? {
62     return object : ViewAction {
63         override fun getConstraints(): Matcher<View> {
64             return isDisplayed()
65         }
66 
67         override fun getDescription(): String {
68             return "Send touch events."
69         }
70 
71         override fun perform(uiController: UiController, view: View) {
72             // Get view absolute position
73             val location = IntArray(2)
74             view.getLocationOnScreen(location)
75 
76             // Offset coordinates by view position
77             val coordinates =
78                 floatArrayOf(
79                     view.width.toFloat() / 2 + location[0],
80                     view.height.toFloat() / 2 + location[1]
81                 )
82             val precision = floatArrayOf(1f, 1f)
83 
84             // Send down event, pause, and send up
85             val down = MotionEvents.sendDown(uiController, coordinates, precision).down
86             uiController.loopMainThreadForAtLeast(50)
87             MotionEvents.sendUp(uiController, down, coordinates)
88         }
89     }
90 }
91 
92 @RunWith(AndroidJUnit4::class)
93 class MainActivityTest : ActivityTest() {
94     @Test
testEvaluationFlownull95     fun testEvaluationFlow() {
96         val btServer = app.getBluetoothServer()
97         whenever(btServer.bluetoothEnabled).thenReturn(true)
98 
99         val activityScenario = launch(MainActivity::class.java)
100         activityScenario.onActivity { activity: Activity ->
101             assertEquals(activity.title, "Device diagnostics")
102         }
103         onView(withText("Evaluation mode")).perform(click())
104         onView(withText("Evaluated device")).check(matches(isDisplayed()))
105 
106         // Set up the mock QR code scanner.
107         val qrCaptor = argumentCaptor<QrCamera.ScannerCallback>()
108         val qrCamera = Mockito.mock(QrCamera::class.java)
109         whenever(app.getQrCamera(any(), qrCaptor.capture())).thenAnswer { qrCamera }
110 
111         val btClient = app.getBluetoothClient()
112 
113         // Set up the mock bluetooth client.
114         val challenge = ByteString.copyFrom("test challenge", Charset.defaultCharset())
115         val deviceInfo = TrustedDeviceInfo.newBuilder().setChallenge(challenge).build()
116 
117         // Move to the QrCodeScanner activity.
118         onView(withText("Evaluated device")).perform(click())
119 
120         // Check that the camera was started.
121         verify(qrCamera, times(1)).start(any())
122 
123         // Simulate scanning a QR code.
124         val qrScanner = qrCaptor.firstValue
125         qrScanner.handleSuccessfulResult(generateQrPayload(challenge.toByteArray()))
126 
127         // Check that bluetooth was started.
128         val btCaptor = argumentCaptor<BluetoothClient.ScanListener>()
129         verify(btClient).start(btCaptor.capture())
130         btCaptor.firstValue.onBluetoothConnected(deviceInfo)
131 
132         setupAttestation()
133 
134         onView(withText("Continue")).check(matches(isDisplayed()))
135         onView(withText("Continue")).perform(click())
136         onView(withId(R.id.mainLayout)).perform(touchDownAndUp())
137         onView(withId(R.id.mainLayout)).perform(touchDownAndUp())
138         onView(withId(R.id.mainLayout)).perform(touchDownAndUp())
139         intended(hasComponent(ScreenTestFinalizeActivity::class.java.name))
140         onView(withText("No obvious defects")).check(matches(isDisplayed()))
141         onView(withText("Screen has defects")).check(matches(isDisplayed()))
142         onView(withText("No obvious defects")).perform(click())
143         onView(withText("Continue")).check(matches(isDisplayed()))
144         onView(withText("Continue")).perform(click())
145         onView(withId(R.id.touchTest)).check(matches(isDisplayed()))
146         onView(withId(R.id.touchTest)).perform(touchDownAndUp())
147         intended(hasComponent(TouchTestFinalizeActivity::class.java.name))
148         onView(withText("The touch sensor test failed")).check(matches(isDisplayed()))
149         onView(withText("Continue")).perform(click())
150         onView(withText("Send report")).check(matches(isDisplayed()))
151         onView(withText("Send report")).perform(click())
152 
153         // Verify the report was sent.
154         val reportCaptor = argumentCaptor<DeviceReport>()
155         verify(btClient, times(1)).sendReport(reportCaptor.capture())
156         val report = reportCaptor.firstValue
157 
158         assert(report.tests.screenTest)
159         assert(!report.tests.touchTest)
160     }
161 
162     @Test
testTrustedFlownull163     fun testTrustedFlow() {
164         val btServer = app.getBluetoothServer()
165         whenever(btServer.bluetoothEnabled).thenReturn(true)
166 
167         val activityScenario = launch(MainActivity::class.java)
168         activityScenario.onActivity { activity: Activity ->
169             assertEquals(activity.title, "Device diagnostics")
170         }
171 
172         val challenge = ByteArray(32)
173         whenever(app.getRandomBytes(any())).thenReturn(challenge)
174 
175         onView(withText("Evaluation mode")).perform(click())
176         onView(withText("Trusted device")).check(matches(isDisplayed()))
177         onView(withText("Trusted device")).perform(click())
178         onView(withText("Scan now")).check(matches(isDisplayed()))
179 
180         // Verify that we started waiting for the bluetooth server.
181         val callbackCaptor = argumentCaptor<BluetoothServer.StartListener>()
182         verify(btServer, times(1)).start(any(), callbackCaptor.capture())
183 
184         // Set up the mocked test results.
185         val deviceReportBuilder = DeviceReport.newBuilder()
186         val testResultsBuilder = TestResults.newBuilder()
187         testResultsBuilder.setScreenTest(true)
188         testResultsBuilder.setTouchTest(false)
189         deviceReportBuilder.setTests(testResultsBuilder)
190         val deviceReport = deviceReportBuilder.build()
191 
192         // Invoke the server completion callbacks.
193         handler.post() {
194             callbackCaptor.firstValue.onServerAvailable(generateConnectionData(challenge))
195             callbackCaptor.firstValue.onServerStarted()
196         }
197 
198         intended(hasComponent(WaitForResultActivity::class.java.name))
199         waitForActivity<WaitForResultActivity>()
200 
201         onView(withText("Continue on the device being evaluated")).check(matches(isDisplayed()))
202 
203         setupAttestation()
204 
205         val readCaptor = argumentCaptor<BluetoothServer.ReadListener>()
206         verify(btServer, times(1)).startRead(readCaptor.capture())
207 
208         handler.post() {
209             val packet = BluetoothPacket.newBuilder().setDeviceReport(deviceReport).build()
210             readCaptor.firstValue.onBluetoothReadPacket(packet)
211         }
212 
213         intended(hasComponent(DisplayResultActivity::class.java.name))
214         waitForActivity<DisplayResultActivity>()
215 
216         onView(withText("Pass")).check(matches(isDisplayed()))
217         onView(withText("Fail")).check(matches(isDisplayed()))
218     }
219 
setupAttestationnull220     private fun setupAttestation() {
221         // Set up the verifyAttestation call.
222         val authorizationList = AuthorizationList.builder().build()
223         val attestationRecord =
224             ParsedAttestationRecord.create(
225                 1,
226                 ParsedAttestationRecord.SecurityLevel.SOFTWARE,
227                 1,
228                 ParsedAttestationRecord.SecurityLevel.SOFTWARE,
229                 ByteArray(0),
230                 ByteArray(0),
231                 authorizationList,
232                 authorizationList
233             )
234 
235         val attestationResult = Pair(attestationRecord, AttestationResult.UNVERIFIED)
236         whenever(app.verifyAttestation(any(), any())).thenReturn(attestationResult)
237     }
238 
generateConnectionDatanull239     private fun generateConnectionData(challenge: ByteArray): BluetoothConnectionData {
240         return BluetoothConnectionData(100, challenge)
241     }
generateQrPayloadnull242     private fun generateQrPayload(challenge: ByteArray): String {
243         return generateConnectionData(challenge).toString()
244     }
245 }
246