Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tests which don't specify explicit URI #265

Merged
merged 6 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_
import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_1_1_BUTTON
import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_RATIO_BUTTON
import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.runScenarioTest
import org.junit.Before
import org.junit.Rule
Expand All @@ -46,7 +46,7 @@ import org.junit.runner.RunWith
class BackgroundDeviceTest {
@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ import com.google.jetpackcamera.feature.preview.ui.FLIP_CAMERA_BUTTON
import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_SUCCESS_TAG
import com.google.jetpackcamera.feature.preview.ui.SCREEN_FLASH_OVERLAY
import com.google.jetpackcamera.settings.model.LensFacing
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.assume
import com.google.jetpackcamera.utils.getCurrentLensFacing
import com.google.jetpackcamera.utils.onNodeWithContentDescription
Expand All @@ -52,7 +52,7 @@ internal class FlashDeviceTest {

@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,16 @@ import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON
import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_FAILURE_TAG
import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_SUCCESS_TAG
import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_EXTERNAL_UNSUPPORTED_TAG
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.VIDEO_CAPTURE_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.deleteFilesInDirAfterTimestamp
import com.google.jetpackcamera.utils.doesImageFileExist
import com.google.jetpackcamera.utils.getIntent
import com.google.jetpackcamera.utils.getTestUri
import com.google.jetpackcamera.utils.runScenarioTest
import com.google.jetpackcamera.utils.runMediaStoreAutoDeleteScenarioTest
import com.google.jetpackcamera.utils.runScenarioTestForResult
import java.io.File
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -55,7 +54,7 @@ internal class ImageCaptureDeviceTest {

@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand All @@ -64,8 +63,10 @@ internal class ImageCaptureDeviceTest {
private val uiDevice = UiDevice.getInstance(instrumentation)

@Test
fun image_capture() = runScenarioTest<MainActivity> {
val timeStamp = System.currentTimeMillis()
fun image_capture() = runMediaStoreAutoDeleteScenarioTest<MainActivity>(
mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
filePrefix = "JCA"
) {
// Wait for the capture button to be displayed
composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) {
composeTestRule.onNodeWithTag(CAPTURE_BUTTON).isDisplayed()
Expand All @@ -77,8 +78,6 @@ internal class ImageCaptureDeviceTest {
composeTestRule.waitUntil(timeoutMillis = IMAGE_CAPTURE_TIMEOUT_MILLIS) {
composeTestRule.onNodeWithTag(IMAGE_CAPTURE_SUCCESS_TAG).isDisplayed()
}
Truth.assertThat(File(DIR_PATH).lastModified() > timeStamp).isTrue()
deleteFilesInDirAfterTimestamp(DIR_PATH, instrumentation, timeStamp)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import com.google.jetpackcamera.feature.preview.ui.FLIP_CAMERA_BUTTON
import com.google.jetpackcamera.feature.preview.ui.SETTINGS_BUTTON
import com.google.jetpackcamera.settings.R
import com.google.jetpackcamera.settings.ui.BACK_BUTTON
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.assume
import com.google.jetpackcamera.utils.onNodeWithText
import com.google.jetpackcamera.utils.runScenarioTest
Expand All @@ -45,7 +45,7 @@ import org.junit.runner.RunWith
class NavigationTest {
@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import com.google.jetpackcamera.feature.preview.quicksettings.ui.QUICK_SETTINGS_
import com.google.jetpackcamera.feature.preview.ui.FLIP_CAMERA_BUTTON
import com.google.jetpackcamera.feature.preview.ui.PREVIEW_DISPLAY
import com.google.jetpackcamera.settings.model.LensFacing
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.assume
import com.google.jetpackcamera.utils.getCurrentLensFacing
import com.google.jetpackcamera.utils.runScenarioTest
Expand All @@ -44,7 +44,7 @@ import org.junit.runner.RunWith
class SwitchCameraTest {
@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
import com.google.jetpackcamera.feature.preview.ui.AMPLITUDE_HOT_TAG
import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.runScenarioTest
import org.junit.Before
import org.junit.Rule
Expand All @@ -43,7 +43,7 @@ import org.junit.runner.RunWith
class VideoAudioTest {
@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,17 @@ import com.google.jetpackcamera.feature.preview.ui.CAPTURE_BUTTON
import com.google.jetpackcamera.feature.preview.ui.IMAGE_CAPTURE_EXTERNAL_UNSUPPORTED_TAG
import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_FAILURE_TAG
import com.google.jetpackcamera.feature.preview.ui.VIDEO_CAPTURE_SUCCESS_TAG
import com.google.jetpackcamera.utils.APP_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.APP_START_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.IMAGE_CAPTURE_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.TEST_REQUIRED_PERMISSIONS
import com.google.jetpackcamera.utils.VIDEO_CAPTURE_TIMEOUT_MILLIS
import com.google.jetpackcamera.utils.VIDEO_DURATION_MILLIS
import com.google.jetpackcamera.utils.deleteFilesInDirAfterTimestamp
import com.google.jetpackcamera.utils.doesImageFileExist
import com.google.jetpackcamera.utils.getIntent
import com.google.jetpackcamera.utils.getTestUri
import com.google.jetpackcamera.utils.runScenarioTest
import com.google.jetpackcamera.utils.runMediaStoreAutoDeleteScenarioTest
import com.google.jetpackcamera.utils.runScenarioTestForResult
import java.io.File
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -54,7 +53,7 @@ import org.junit.runner.RunWith
internal class VideoRecordingDeviceTest {
@get:Rule
val permissionsRule: GrantPermissionRule =
GrantPermissionRule.grant(*(APP_REQUIRED_PERMISSIONS).toTypedArray())
GrantPermissionRule.grant(*(TEST_REQUIRED_PERMISSIONS).toTypedArray())

@get:Rule
val composeTestRule = createEmptyComposeRule()
Expand All @@ -63,7 +62,9 @@ internal class VideoRecordingDeviceTest {
private val uiDevice = UiDevice.getInstance(instrumentation)

@Test
fun video_capture() = runScenarioTest<MainActivity> {
fun video_capture(): Unit = runMediaStoreAutoDeleteScenarioTest<MainActivity>(
mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
) {
val timeStamp = System.currentTimeMillis()
// Wait for the capture button to be displayed
composeTestRule.waitUntil(timeoutMillis = APP_START_TIMEOUT_MILLIS) {
Expand All @@ -73,8 +74,6 @@ internal class VideoRecordingDeviceTest {
composeTestRule.waitUntil(timeoutMillis = VIDEO_CAPTURE_TIMEOUT_MILLIS) {
composeTestRule.onNodeWithTag(VIDEO_CAPTURE_SUCCESS_TAG).isDisplayed()
}
Truth.assertThat(File(DIR_PATH).lastModified() > timeStamp).isTrue()
deleteFilesInDirAfterTimestamp(DIR_PATH, instrumentation, timeStamp)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,124 @@
*/
package com.google.jetpackcamera.utils

import android.app.Instrumentation
import android.database.ContentObserver
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.BaseColumns
import android.provider.MediaStore
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.transform

val APP_REQUIRED_PERMISSIONS: List<String> = buildList {
private val APP_REQUIRED_PERMISSIONS: List<String> = buildList {
add(android.Manifest.permission.CAMERA)
add(android.Manifest.permission.RECORD_AUDIO)
if (Build.VERSION.SDK_INT <= 28) {
add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}

val TEST_REQUIRED_PERMISSIONS: List<String> = buildList {
addAll(APP_REQUIRED_PERMISSIONS)
if (Build.VERSION.SDK_INT >= 33) {
add(android.Manifest.permission.READ_MEDIA_IMAGES)
add(android.Manifest.permission.READ_MEDIA_VIDEO)
}
}

fun mediaStoreInsertedFlow(
mediaUri: Uri,
instrumentation: Instrumentation,
filePrefix: String = ""
): Flow<Pair<String, Uri>> = with(instrumentation.targetContext.contentResolver) {
// Creates a map of the display names and corresponding URIs for all files contained within
// the URI argument. If the URI is a single file, the map will contain a single file.
// On API 29+, this will also only return files that are not "pending". Pending files
// have not yet been fully written.
fun queryWrittenFiles(uri: Uri): Map<String, Uri> {
return buildMap {
query(
uri,
buildList {
add(BaseColumns._ID)
add(MediaStore.MediaColumns.DISPLAY_NAME)
if (Build.VERSION.SDK_INT >= 29) {
add(MediaStore.MediaColumns.IS_PENDING)
}
}.toTypedArray(),
null,
null,
null
)?.use { cursor: Cursor ->
cursor.moveToFirst()
val idCol = cursor.getColumnIndex(BaseColumns._ID)
val displayNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)

while (!cursor.isAfterLast) {
val id = cursor.getLong(idCol)
val displayName = cursor.getString(displayNameCol)
val isPending = if (Build.VERSION.SDK_INT >= 29) {
cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.IS_PENDING))
} else {
// On devices pre-API 29, we don't have an is_pending column, so never
// say that the file is pending
0
}
if (isPending == 0 &&
(filePrefix.isEmpty() || displayName.startsWith(filePrefix))
) {
// Construct URI for a single file
val outputUri = if (uri.lastPathSegment?.equals("$id") == false) {
uri.buildUpon().appendPath("$id").build()
} else {
uri
}
put(displayName, outputUri)
}
cursor.moveToNext()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just making sure, this cursor doesn't need to be closed right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cursor is automatically closed by virtue of accessing it via a use {} block (on line 69).

}
}
}
}

// Get the full list of initially written files. We'll append files to this as we
// publish them.
val existingFiles = queryWrittenFiles(mediaUri).toMutableMap()
return callbackFlow {
val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
onChange(selfChange, null)
}

override fun onChange(selfChange: Boolean, uri: Uri?) {
onChange(selfChange, uri, 0)
}

override fun onChange(selfChange: Boolean, uri: Uri?, flags: Int) {
onChange(selfChange, uri?.let { setOf(it) } ?: emptySet(), flags)
}

override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int) {
uris.forEach { uri ->
queryWrittenFiles(uri).forEach {
trySend(it)
}
}
}
}

registerContentObserver(mediaUri, true, observer)

awaitClose {
unregisterContentObserver(observer)
}
}.transform {
if (!existingFiles.containsKey(it.key)) {
existingFiles[it.key] = it.value
emit(it.toPair())
}
}
}
Loading
Loading