diff options
4 files changed, 236 insertions, 19 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index e246917842b0..26cead206310 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -86,6 +86,10 @@ public interface QSTile { */ InstanceId getInstanceId(); + default boolean isTileReady() { + return false; + } + @ProvidesInterface(version = Callback.VERSION) public interface Callback { public static final int VERSION = 1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index db77e08c204b..73c6504b9983 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -80,8 +80,6 @@ public class TileQueryHelper { mFinished = false; // Enqueue jobs to fetch every system tile and then ever package tile. addCurrentAndStockTiles(host); - - addPackageTiles(host); } public boolean isFinished() { @@ -122,23 +120,86 @@ public class TileQueryHelper { tile.destroy(); continue; } - tile.setListening(this, true); - tile.refreshState(); - tile.setListening(this, false); tile.setTileSpec(spec); tilesToAdd.add(tile); } - mBgExecutor.execute(() -> { - for (QSTile tile : tilesToAdd) { - final QSTile.State state = tile.getState().copy(); - // Ignore the current state and get the generic label instead. - state.label = tile.getTileLabel(); - tile.destroy(); - addTile(tile.getTileSpec(), null, state, true); + new TileCollector(tilesToAdd, host).startListening(); + } + + private static class TilePair { + QSTile mTile; + boolean mReady = false; + } + + private class TileCollector implements QSTile.Callback { + + private final List<TilePair> mQSTileList = new ArrayList<>(); + private final QSTileHost mQSTileHost; + + TileCollector(List<QSTile> tilesToAdd, QSTileHost host) { + for (QSTile tile: tilesToAdd) { + TilePair pair = new TilePair(); + pair.mTile = tile; + mQSTileList.add(pair); } + mQSTileHost = host; + if (tilesToAdd.isEmpty()) { + mBgExecutor.execute(this::finished); + } + } + + private void finished() { notifyTilesChanged(false); - }); + addPackageTiles(mQSTileHost); + } + + private void startListening() { + for (TilePair pair: mQSTileList) { + pair.mTile.addCallback(this); + pair.mTile.setListening(this, true); + // Make sure that at least one refresh state happens + pair.mTile.refreshState(); + } + } + + // This is called in the Bg thread + @Override + public void onStateChanged(State s) { + boolean allReady = true; + for (TilePair pair: mQSTileList) { + if (!pair.mReady && pair.mTile.isTileReady()) { + pair.mTile.removeCallback(this); + pair.mTile.setListening(this, false); + pair.mReady = true; + } else if (!pair.mReady) { + allReady = false; + } + } + if (allReady) { + for (TilePair pair : mQSTileList) { + QSTile tile = pair.mTile; + final QSTile.State state = tile.getState().copy(); + // Ignore the current state and get the generic label instead. + state.label = tile.getTileLabel(); + tile.destroy(); + addTile(tile.getTileSpec(), null, state, true); + } + finished(); + } + } + + @Override + public void onShowDetail(boolean show) {} + + @Override + public void onToggleStateChanged(boolean state) {} + + @Override + public void onScanStateChanged(boolean state) {} + + @Override + public void onAnnouncementRequested(CharSequence announcement) {} } private void addPackageTiles(final QSTileHost host) { 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 255513a31c75..dfd7e2c8fdb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -90,6 +90,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS; protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object(); + private static final int READY_STATE_NOT_READY = 0; + private static final int READY_STATE_READYING = 1; + private static final int READY_STATE_READY = 2; + protected final QSHost mHost; protected final Context mContext; // @NonFinalForTesting @@ -101,6 +105,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy protected final ActivityStarter mActivityStarter; private final UiEventLogger mUiEventLogger; private final QSLogger mQSLogger; + private volatile int mReadyState; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private final Object mStaleListener = new Object(); @@ -386,7 +391,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy protected void handleRefreshState(Object arg) { handleUpdateState(mTmpState, arg); - final boolean changed = mTmpState.copyTo(mState); + boolean changed = mTmpState.copyTo(mState); + if (mReadyState == READY_STATE_READYING) { + mReadyState = READY_STATE_READY; + changed = true; + } if (changed) { mQSLogger.logTileUpdated(mTileSpec, mState); handleStateChanged(); @@ -459,6 +468,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy // should not refresh it anymore. if (mLifecycle.getCurrentState().equals(DESTROYED)) return; mLifecycle.setCurrentState(RESUMED); + if (mReadyState == READY_STATE_NOT_READY) { + mReadyState = READY_STATE_READYING; + } refreshState(); // Ensure we get at least one refresh after listening. }); } @@ -531,6 +543,15 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy */ public abstract CharSequence getTileLabel(); + /** + * @return {@code true} if the tile has refreshed state at least once after having set its + * lifecycle to {@link Lifecycle.State#RESUMED}. + */ + @Override + public boolean isTileReady() { + return mReadyState == READY_STATE_READY; + } + public static int getColorForState(Context context, int state) { switch (state) { case Tile.STATE_UNAVAILABLE: diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 53ef86660867..2ef7c65acb0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -47,8 +48,11 @@ import android.util.ArraySet; import androidx.test.filters.SmallTest; +import com.android.internal.logging.InstanceId; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.qs.DetailAdapter; +import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; import com.android.systemui.util.concurrency.FakeExecutor; @@ -68,6 +72,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -116,11 +121,9 @@ public class TileQueryHelperTest extends SysuiTestCase { doAnswer(invocation -> { String spec = (String) invocation.getArguments()[0]; if (FACTORY_TILES.contains(spec)) { - QSTile m = mock(QSTile.class); - when(m.isAvailable()).thenReturn(true); - when(m.getTileSpec()).thenReturn(spec); - when(m.getState()).thenReturn(mState); - return m; + FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor); + tile.setState(mState); + return tile; } else { return null; } @@ -292,4 +295,132 @@ public class TileQueryHelperTest extends SysuiTestCase { verifier.verify(t).setTileSpec("hotspot"); verifier.verify(t).destroy(); } + + private static class FakeQSTile implements QSTile { + + private String mSpec = ""; + private List<Callback> mCallbacks = new ArrayList<>(); + private boolean mRefreshed; + private boolean mListening; + private State mState = new State(); + private final Executor mBgExecutor; + private final Executor mMainExecutor; + + FakeQSTile(Executor bgExecutor, Executor mainExecutor) { + mBgExecutor = bgExecutor; + mMainExecutor = mainExecutor; + } + + @Override + public String getTileSpec() { + return mSpec; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void setTileSpec(String tileSpec) { + mSpec = tileSpec; + } + + public void setState(State state) { + mState = state; + notifyChangedState(mState); + } + + @Override + public void refreshState() { + mBgExecutor.execute(() -> { + mRefreshed = true; + notifyChangedState(mState); + }); + } + + private void notifyChangedState(State state) { + List<Callback> callbacks = new ArrayList<>(mCallbacks); + callbacks.forEach(callback -> callback.onStateChanged(state)); + } + + @Override + public void addCallback(Callback callback) { + mCallbacks.add(callback); + } + + @Override + public void removeCallback(Callback callback) { + mCallbacks.remove(callback); + } + + @Override + public void removeCallbacks() { + mCallbacks.clear(); + } + + @Override + public void setListening(Object client, boolean listening) { + if (listening) { + mMainExecutor.execute(() -> { + mListening = true; + refreshState(); + }); + } + } + + @Override + public CharSequence getTileLabel() { + return mSpec; + } + + @Override + public State getState() { + return mState; + } + + @Override + public boolean isTileReady() { + return mListening && mRefreshed; + } + + @Override + public QSIconView createTileView(Context context) { + return null; + } + + @Override + public void click() {} + + @Override + public void secondaryClick() {} + + @Override + public void longClick() {} + + @Override + public void userSwitch(int currentUser) {} + + @Override + public int getMetricsCategory() { + return 0; + } + + @Override + public InstanceId getInstanceId() { + return null; + } + + @Override + public void setDetailListening(boolean show) {} + + @Override + public void destroy() {} + + + @Override + public DetailAdapter getDetailAdapter() { + return null; + } + } } |