Merge "Wait for state refreshed in TileQueryHelper"

This commit is contained in:
Fabian Kozynski
2020-09-18 15:48:12 +00:00
committed by Android (Google) Code Review
4 changed files with 236 additions and 19 deletions

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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;
}
}
}