diff --git a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING new file mode 100644 index 0000000000000..55da2038754d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + { + "name": "CtsAppTestCases", + "options": [ + { + "include-filter": "android.app.cts.TileServiceTest" + }, + { + "include-filter": "android.app.cts.BooleanTileServiceTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 396eca5c1bee9..c5fa76e794af6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -63,6 +63,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -98,6 +99,8 @@ public class CustomTile extends QSTileImpl implements TileChangeListener private final TileServiceKey mKey; + private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false); + private CustomTile( QSHost host, Looper backgroundLooper, @@ -128,6 +131,12 @@ public class CustomTile extends QSTileImpl implements TileChangeListener @Override protected void handleInitialize() { updateDefaultTileAndIcon(); + if (mInitialDefaultIconFetched.compareAndSet(false, true)) { + if (mDefaultIcon == null) { + mQSLogger.logTileDestroyed(getTileSpec(), + "Custom tile default icon not available"); + } + } if (mServiceManager.isToggleableTile()) { // Replace states with BooleanState resetStates(); @@ -213,9 +222,18 @@ public class CustomTile extends QSTileImpl implements TileChangeListener mHandler.post(this::updateDefaultTileAndIcon); } + /** + * Custom tile is considered available if there is a default icon (obtained from PM). + * + * It will return {@code true} before initialization, so tiles are not destroyed prematurely. + */ @Override public boolean isAvailable() { - return mDefaultIcon != null; + if (mInitialDefaultIconFetched.get()) { + return mDefaultIcon != null; + } else { + return true; + } } public int getUser() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 4616be8f79376..70e3a2b2bf268 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -105,7 +105,7 @@ public abstract class QSTileImpl implements QSTile, Lifecy protected final ActivityStarter mActivityStarter; private final UiEventLogger mUiEventLogger; private final FalsingManager mFalsingManager; - private final QSLogger mQSLogger; + protected final QSLogger mQSLogger; private volatile int mReadyState; private final ArrayList mCallbacks = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index fbb0a9555df5e..c746bcae6e2e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -20,8 +20,8 @@ package com.android.systemui.qs; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; @@ -424,7 +424,7 @@ public class QSTileHostTest extends SysuiTestCase { mock(MetricsLogger.class), mock(StatusBarStateController.class), mock(ActivityStarter.class), - mQSLogger + QSTileHostTest.this.mQSLogger ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 9b5c1619ef316..9755d91d72d8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -270,4 +270,20 @@ class CustomTileTest : SysuiTestCase() { verify(customTileStatePersister) .persistState(TileServiceKey(componentName, customTile.user), t) } + + @Test + fun testAvailableBeforeInitialization() { + `when`(packageManager.getApplicationInfo(anyString(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException()) + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + assertTrue(tile.isAvailable) + } + + @Test + fun testNotAvailableAfterInitializationWithoutIcon() { + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + testableLooper.processAllMessages() + assertFalse(tile.isAvailable) + } } \ No newline at end of file