1 /* <lambda>null2 * Copyright (C) 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 * 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 package com.google.jetpackcamera 17 18 import android.app.Instrumentation 19 import android.content.ComponentName 20 import android.content.Context 21 import android.content.Intent 22 import android.net.Uri 23 import android.os.Environment 24 import androidx.activity.result.ActivityResultRegistry 25 import androidx.activity.result.contract.ActivityResultContract 26 import androidx.activity.result.contract.ActivityResultContracts 27 import androidx.core.app.ActivityOptionsCompat 28 import androidx.test.core.app.ActivityScenario 29 import androidx.test.core.app.ApplicationProvider 30 import androidx.test.ext.junit.runners.AndroidJUnit4 31 import androidx.test.platform.app.InstrumentationRegistry 32 import androidx.test.rule.GrantPermissionRule 33 import androidx.test.uiautomator.By 34 import androidx.test.uiautomator.UiDevice 35 import androidx.test.uiautomator.Until 36 import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON 37 import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_FAILURE_TAG 38 import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_SUCCESS_TAG 39 import kotlinx.coroutines.test.runTest 40 import java.io.File 41 import java.net.URLConnection 42 import org.junit.Rule 43 import org.junit.Test 44 import org.junit.runner.RunWith 45 46 @RunWith(AndroidJUnit4::class) 47 internal class ImageCaptureDeviceTest { 48 // TODO(b/319733374): Return bitmap for external mediastore capture without URI 49 50 @get:Rule 51 val permissionsRule: GrantPermissionRule = 52 GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray()) 53 54 private val instrumentation = InstrumentationRegistry.getInstrumentation() 55 private var activityScenario: ActivityScenario<MainActivity>? = null 56 private val uiDevice = UiDevice.getInstance(instrumentation) 57 private val context = InstrumentationRegistry.getInstrumentation().targetContext 58 59 @Test 60 fun image_capture() = runTest { 61 val timeStamp = System.currentTimeMillis() 62 activityScenario = ActivityScenario.launch(MainActivity::class.java) 63 uiDevice.wait( 64 Until.findObject(By.res(CAPTURE_BUTTON)), 65 5000 66 ) 67 uiDevice.findObject(By.res(CAPTURE_BUTTON)).click() 68 uiDevice.wait( 69 Until.findObject(By.res(IMAGE_CAPTURE_SUCCESS_TAG)), 70 5000 71 ) 72 assert(deleteFilesInDirAfterTimestamp(timeStamp)) 73 } 74 75 @Test 76 fun image_capture_external() = runTest { 77 val timeStamp = System.currentTimeMillis() 78 val uri = getTestUri(timeStamp) 79 getTestRegistry { 80 activityScenario = ActivityScenario.launchActivityForResult(it) 81 uiDevice.wait( 82 Until.findObject(By.res(CAPTURE_BUTTON)), 83 5000 84 ) 85 uiDevice.findObject(By.res(CAPTURE_BUTTON)).click() 86 uiDevice.wait( 87 Until.findObject(By.res(IMAGE_CAPTURE_SUCCESS_TAG)), 88 5000 89 ) 90 activityScenario!!.result 91 }.register("key", TEST_CONTRACT) { result -> 92 assert(result) 93 assert(doesImageFileExist(uri)) 94 }.launch(uri) 95 deleteFilesInDirAfterTimestamp(timeStamp) 96 } 97 98 @Test 99 fun image_capture_external_illegal_uri() = run { 100 val timeStamp = System.currentTimeMillis() 101 val inputUri = Uri.parse("asdfasdf") 102 getTestRegistry { 103 activityScenario = ActivityScenario.launchActivityForResult(it) 104 uiDevice.wait( 105 Until.findObject(By.res(CAPTURE_BUTTON)), 106 5000 107 ) 108 uiDevice.findObject(By.res(CAPTURE_BUTTON)).click() 109 uiDevice.wait( 110 Until.findObject(By.res(IMAGE_CAPTURE_FAILURE_TAG)), 111 5000 112 ) 113 uiDevice.pressBack() 114 activityScenario!!.result 115 }.register("key_illegal_uri", TEST_CONTRACT) { result -> 116 assert(!result) 117 }.launch(inputUri) 118 } 119 120 private fun doesImageFileExist(uri: Uri): Boolean { 121 val file = File(uri.path) 122 if (file.exists()) { 123 val mimeType = URLConnection.guessContentTypeFromName(uri.path) 124 return mimeType != null && mimeType.startsWith("image") 125 } 126 return false 127 } 128 129 private fun deleteFilesInDirAfterTimestamp(timeStamp: Long): Boolean { 130 var hasDeletedFile = false 131 for (file in File(DIR_PATH).listFiles()) { 132 if (file.lastModified() >= timeStamp) { 133 file.delete() 134 if (file.exists()) { 135 file.getCanonicalFile().delete() 136 if (file.exists()) { 137 instrumentation.targetContext.applicationContext.deleteFile(file.getName()) 138 } 139 } 140 hasDeletedFile = true 141 } 142 } 143 return hasDeletedFile 144 } 145 146 private fun getTestRegistry( 147 launch: (Intent) -> Instrumentation.ActivityResult 148 ): ActivityResultRegistry { 149 val testRegistry = object : ActivityResultRegistry() { 150 override fun <I, O> onLaunch( 151 requestCode: Int, 152 contract: ActivityResultContract<I, O>, 153 input: I, 154 options: ActivityOptionsCompat? 155 ) { 156 // contract.create 157 val launchIntent = contract.createIntent( 158 ApplicationProvider.getApplicationContext(), 159 input 160 ) 161 val result: Instrumentation.ActivityResult = launch(launchIntent) 162 dispatchResult(requestCode, result.resultCode, result.resultData) 163 } 164 } 165 return testRegistry 166 } 167 168 private fun getTestUri(timeStamp: Long): Uri { 169 return Uri.fromFile( 170 File( 171 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), 172 "$timeStamp.jpg" 173 ) 174 ) 175 } 176 177 companion object { 178 val DIR_PATH: String = 179 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).path 180 181 val TEST_CONTRACT = object : ActivityResultContracts.TakePicture() { 182 override fun createIntent(context: Context, uri: Uri): Intent { 183 return super.createIntent(context, uri).apply { 184 component = ComponentName( 185 ApplicationProvider.getApplicationContext(), 186 MainActivity::class.java 187 ) 188 } 189 } 190 } 191 } 192 } 193