• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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