From a9a6a1c4494529fe2a8e1672d7f6c56aca978743 Mon Sep 17 00:00:00 2001 From: Andrey Epin Date: Thu, 29 Dec 2022 20:47:57 -0800 Subject: EnterTransitionAnimationDelegate to track animation hold timeout Make EnterTransitionAnimationDelegate to track transition animation hold timeout instead of relying on image lading timeout in ChooserContentPreviewCoordinator. As after removing timeout tracking from the coordinator, not much functionality left in it, the following collateral changes were made: - ChooserContentPreviewUi$ContentPreviewCoordinator interface is replaced with a new ImageLoader interface that defines both suspend and callback methods for image loading by an URI; - ImagePreviewImageLoader implements the new ImageLoader interface; all image loading logic is moved from ChooserActivity and ChooserContentPreviewCoordinator is moved into it, ChooserContentPreviewCoordinator is removed. Flag: IntentResolver package entirely behind the CHOOSER_UNBUNDLED which is in teamfood Bug: 262280076 Test: manual basic functionality test for all preview types with and without work profile Test: atest IntentResolverUnitTests Change-Id: I310927e664e8de83d63472cc826665d36d994ed9 --- .../intentresolver/ChooserWrapperActivity.java | 11 +- .../EnterTransitionAnimationDelegateTest.kt | 111 +++++++++++++++++++++ .../android/intentresolver/TestLifecycleOwner.kt | 33 ++++++ .../intentresolver/TestPreviewImageLoader.kt | 37 +++++++ 4 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt create mode 100644 java/tests/src/com/android/intentresolver/TestLifecycleOwner.kt create mode 100644 java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt (limited to 'java/tests/src') diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java index 97de97f5..6bc5e12a 100644 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -30,10 +30,8 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.Bitmap; import android.net.Uri; import android.os.UserHandle; -import android.util.Size; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider; @@ -208,11 +206,10 @@ public class ChooserWrapperActivity } @Override - protected Bitmap loadThumbnail(Uri uri, Size size) { - if (sOverrides.previewThumbnail != null) { - return sOverrides.previewThumbnail; - } - return super.loadThumbnail(uri, size); + protected ImageLoader createPreviewImageLoader() { + return new TestPreviewImageLoader( + super.createPreviewImageLoader(), + () -> sOverrides.previewThumbnail); } @Override diff --git a/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt b/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt new file mode 100644 index 00000000..ffe89400 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver + +import android.content.res.Resources +import android.view.View +import android.view.Window +import androidx.activity.ComponentActivity +import androidx.lifecycle.Lifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +private const val TIMEOUT_MS = 200 + +@OptIn(ExperimentalCoroutinesApi::class) +class EnterTransitionAnimationDelegateTest { + private val scheduler = TestCoroutineScheduler() + private val dispatcher = StandardTestDispatcher(scheduler) + private val lifecycleOwner = TestLifecycleOwner() + + private val transitionTargetView = mock { + // avoid the request-layout path in the delegate + whenever(isInLayout).thenReturn(true) + } + + private val windowMock = mock() + private val resourcesMock = mock { + whenever(getInteger(anyInt())).thenReturn(TIMEOUT_MS) + } + private val activity = mock { + whenever(lifecycle).thenReturn(lifecycleOwner.lifecycle) + whenever(resources).thenReturn(resourcesMock) + whenever(isActivityTransitionRunning).thenReturn(true) + whenever(window).thenReturn(windowMock) + } + + private val testSubject = EnterTransitionAnimationDelegate(activity) { + transitionTargetView + } + + @Before + fun setup() { + Dispatchers.setMain(dispatcher) + lifecycleOwner.state = Lifecycle.State.CREATED + } + + @After + fun cleanup() { + lifecycleOwner.state = Lifecycle.State.DESTROYED + Dispatchers.resetMain() + } + + @Test + fun test_postponeTransition_timeout() { + testSubject.postponeTransition() + testSubject.markOffsetCalculated() + + scheduler.advanceTimeBy(TIMEOUT_MS + 1L) + verify(activity, times(1)).startPostponedEnterTransition() + verify(windowMock, never()).setWindowAnimations(anyInt()) + } + + @Test + fun test_postponeTransition_animation_resumes_only_once() { + testSubject.postponeTransition() + testSubject.markOffsetCalculated() + testSubject.markImagePreviewReady(true) + testSubject.markOffsetCalculated() + testSubject.markImagePreviewReady(true) + + scheduler.advanceTimeBy(TIMEOUT_MS + 1L) + verify(activity, times(1)).startPostponedEnterTransition() + } + + @Test + fun test_postponeTransition_resume_animation_conditions() { + testSubject.postponeTransition() + verify(activity, never()).startPostponedEnterTransition() + + testSubject.markOffsetCalculated() + verify(activity, never()).startPostponedEnterTransition() + + testSubject.markImagePreviewReady(true) + verify(activity, times(1)).startPostponedEnterTransition() + } +} diff --git a/java/tests/src/com/android/intentresolver/TestLifecycleOwner.kt b/java/tests/src/com/android/intentresolver/TestLifecycleOwner.kt new file mode 100644 index 00000000..f47e343f --- /dev/null +++ b/java/tests/src/com/android/intentresolver/TestLifecycleOwner.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry + +internal class TestLifecycleOwner : LifecycleOwner { + private val lifecycleRegistry = LifecycleRegistry.createUnsafe(this) + + override fun getLifecycle(): Lifecycle = lifecycleRegistry + + var state: Lifecycle.State + get() = lifecycle.currentState + set(value) { + lifecycleRegistry.currentState = value + } +} \ No newline at end of file diff --git a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt new file mode 100644 index 00000000..fd617fdd --- /dev/null +++ b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver + +import android.graphics.Bitmap +import android.net.Uri +import java.util.function.Consumer + +internal class TestPreviewImageLoader( + private val imageLoader: ImageLoader, + private val imageOverride: () -> Bitmap? +) : ImageLoader { + override fun loadImage(uri: Uri, callback: Consumer) { + val override = imageOverride() + if (override != null) { + callback.accept(override) + } else { + imageLoader.loadImage(uri, callback) + } + } + + override suspend fun invoke(uri: Uri): Bitmap? = imageOverride() ?: imageLoader(uri) +} -- cgit v1.2.3-59-g8ed1b