Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +25 −10 Original line number Diff line number Diff line Loading @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase import com.android.systemui.TestUiOffloadThread import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test Loading Loading @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() // Use main thread instead of UI offload thread to fix flakes. mDependency.injectTestDependency( UiOffloadThread::class.java, TestUiOffloadThread(looper.looper) ) helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around Loading Loading @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() Loading @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. Loading @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action2) waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() Loading @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() looper.processAllMessages() ViewUtils.attachView(root) waitForUiOffloadThread() looper.processAllMessages() waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } Loading @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) Loading @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener Loading @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. Loading Loading @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } private fun waitForActionDisabled(action: View) { waitForCondition { looper.processAllMessages() !action.isEnabled } } } packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * 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.systemui; import android.os.Handler; import android.os.Looper; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; /** * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. */ public class TestUiOffloadThread extends UiOffloadThread { private final Handler mTestHandler; public TestUiOffloadThread(Looper looper) { mTestHandler = new Handler(looper); } @Override public Future<?> execute(Runnable runnable) { Looper myLooper = Looper.myLooper(); if (myLooper != null && myLooper.isCurrentThread()) { try { runnable.run(); return CompletableFuture.completedFuture(null); } catch (Exception e) { return CompletableFuture.failedFuture(e); } } final CompletableFuture<?> future = new CompletableFuture<>(); mTestHandler.post(() -> { try { runnable.run(); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); } }); return future; } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +25 −10 Original line number Diff line number Diff line Loading @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase import com.android.systemui.TestUiOffloadThread import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test Loading Loading @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() // Use main thread instead of UI offload thread to fix flakes. mDependency.injectTestDependency( UiOffloadThread::class.java, TestUiOffloadThread(looper.looper) ) helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around Loading Loading @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() Loading @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. Loading @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) waitForUiOffloadThread() // Wait for cancellation registration to execute. looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() looper.processAllMessages() // Wait for listener callbacks to execute waitForActionDisabled(action2) waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() Loading @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() looper.processAllMessages() ViewUtils.attachView(root) waitForUiOffloadThread() looper.processAllMessages() waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } Loading @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) Loading @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener Loading @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. Loading Loading @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } private fun waitForActionDisabled(action: View) { waitForCondition { looper.processAllMessages() !action.isEnabled } } }
packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * 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.systemui; import android.os.Handler; import android.os.Looper; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; /** * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. */ public class TestUiOffloadThread extends UiOffloadThread { private final Handler mTestHandler; public TestUiOffloadThread(Looper looper) { mTestHandler = new Handler(looper); } @Override public Future<?> execute(Runnable runnable) { Looper myLooper = Looper.myLooper(); if (myLooper != null && myLooper.isCurrentThread()) { try { runnable.run(); return CompletableFuture.completedFuture(null); } catch (Exception e) { return CompletableFuture.failedFuture(e); } } final CompletableFuture<?> future = new CompletableFuture<>(); mTestHandler.post(() -> { try { runnable.run(); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); } }); return future; } }