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