import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
import android.widget.LinearLayout;
import android.widget.Toolbar;
import android.widget.Toolbar.OnMenuItemClickListener;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
private final QSDetailClipper mClipper;
private final LightBarController mLightBarController;
+ private final TileQueryHelper mTileQueryHelper;
private boolean isShown;
private QSTileHost mHost;
private boolean mCustomizing;
private NotificationsQuickSettingsContainer mNotifQsContainer;
private QS mQs;
- private boolean mFinishedFetchingTiles = false;
private int mX;
private int mY;
private boolean mOpening;
mRecyclerView = findViewById(android.R.id.list);
mTileAdapter = new TileAdapter(getContext());
+ mTileQueryHelper = new TileQueryHelper(context, mTileAdapter);
mRecyclerView.setAdapter(mTileAdapter);
mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView);
GridLayoutManager layout = new GridLayoutManager(getContext(), 3);
}
private void queryTiles() {
- mFinishedFetchingTiles = false;
- Runnable tileQueryFetchCompletion = () -> {
- Handler mainHandler = new Handler(Looper.getMainLooper());
- mainHandler.post(() -> mFinishedFetchingTiles = true);
- };
- new TileQueryHelper(mContext, mHost, mTileAdapter, tileQueryFetchCompletion);
+ mTileQueryHelper.queryTiles(mHost);
}
public void hide(int x, int y) {
}
private void save() {
- if (mFinishedFetchingTiles) {
+ if (mTileQueryHelper.isFinished()) {
mTileAdapter.saveSpecs(mHost);
}
}
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.Looper;
import android.service.quicksettings.TileService;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.widget.Button;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
-import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class TileQueryHelper {
-
private static final String TAG = "TileQueryHelper";
private final ArrayList<TileInfo> mTiles = new ArrayList<>();
- private final ArrayList<String> mSpecs = new ArrayList<>();
+ private final ArraySet<String> mSpecs = new ArraySet<>();
+ private final Handler mBgHandler;
+ private final Handler mMainHandler;
private final Context mContext;
private final TileStateListener mListener;
- private final QSTileHost mHost;
- private final Runnable mCompletion;
- public TileQueryHelper(Context context, QSTileHost host,
- TileStateListener listener, Runnable completion) {
+ private boolean mFinished;
+
+ public TileQueryHelper(Context context, TileStateListener listener) {
mContext = context;
mListener = listener;
- mHost = host;
- mCompletion = completion;
- addSystemTiles();
- // TODO: Live?
+ mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
}
- private void addSystemTiles() {
+ public void queryTiles(QSTileHost host) {
+ mTiles.clear();
+ mSpecs.clear();
+ mFinished = false;
// Enqueue jobs to fetch every system tile and then ever package tile.
- final Handler qsHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
- final Handler mainHandler = new Handler(Looper.getMainLooper());
- addStockTiles(mainHandler, qsHandler);
- addPackageTiles(mainHandler, qsHandler);
- // Then enqueue the completion. It should always be last
- qsHandler.post(mCompletion);
+ addStockTiles(host);
+ addPackageTiles(host);
+ // TODO: Live?
+ }
+
+ public boolean isFinished() {
+ return mFinished;
}
- private void addStockTiles(Handler mainHandler, Handler bgHandler) {
+ private void addStockTiles(QSTileHost host) {
String possible = mContext.getString(R.string.quick_settings_tiles_stock);
String[] possibleTiles = possible.split(",");
+ final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
- final QSTile tile = mHost.createTile(spec);
+ final QSTile tile = host.createTile(spec);
if (tile == null) {
continue;
} else if (!tile.isAvailable()) {
tile.clearState();
tile.refreshState();
tile.setListening(this, false);
- bgHandler.post(new Runnable() {
- @Override
- public void run() {
- final QSTile.State state = tile.getState().copy();
- // Ignore the current state and get the generic label instead.
- state.label = tile.getTileLabel();
- tile.destroy();
- mainHandler.post(new Runnable() {
- @Override
- public void run() {
- addTile(spec, null, state, true);
- mListener.onTilesChanged(mTiles);
- }
- });
- }
- });
+ tile.setTileSpec(spec);
+ tilesToAdd.add(tile);
}
+
+ mBgHandler.post(() -> {
+ 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);
+ }
+ notifyTilesChanged(false);
+ });
}
- private void addPackageTiles(Handler mainHandler, Handler bgHandler) {
- bgHandler.post(() -> {
- Collection<QSTile> params = mHost.getTiles();
+ private void addPackageTiles(final QSTileHost host) {
+ mBgHandler.post(() -> {
+ Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
icon.mutate();
icon.setTint(mContext.getColor(android.R.color.white));
CharSequence label = info.serviceInfo.loadLabel(pm);
- addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
+ addTile(spec, icon, label != null ? label.toString() : "null", appLabel);
}
- mainHandler.post(() -> mListener.onTilesChanged(mTiles));
+
+ notifyTilesChanged(true);
+ });
+ }
+
+ private void notifyTilesChanged(final boolean finished) {
+ final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
+ mMainHandler.post(() -> {
+ mListener.onTilesChanged(tilesToReturn);
+ mFinished = finished;
});
}
mSpecs.add(spec);
}
- private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel,
- Context context) {
+ private void addTile(
+ String spec, Drawable drawable, CharSequence label, CharSequence appLabel) {
QSTile.State state = new QSTile.State();
state.label = label;
state.contentDescription = label;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import java.util.List;
package com.android.systemui.qs.customize;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import com.android.systemui.Dependency;
+import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.QSTileHost;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
-import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
+import java.util.ArrayList;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TileQueryHelperTest extends SysuiTestCase {
+ @Mock private TileQueryHelper.TileStateListener mListener;
+ @Mock private QSTileHost mQSTileHost;
+
private TestableLooper mBGLooper;
- private Runnable mLastCallback;
+
+ private TileQueryHelper mTileQueryHelper;
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
mBGLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper());
+ mTileQueryHelper = new TileQueryHelper(mContext, mListener);
+ }
+
+ @Test
+ public void testIsFinished_falseBeforeQuerying() {
+ assertFalse(mTileQueryHelper.isFinished());
+ }
+
+ @Test
+ public void testIsFinished_trueAfterQuerying() {
+ mTileQueryHelper.queryTiles(mQSTileHost);
+
+ mBGLooper.processAllMessages();
+ waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER));
+
+ assertTrue(mTileQueryHelper.isFinished());
}
@Test
- public void testCompletionCalled() {
- QSTileHost mockHost = mock(QSTileHost.class);
- TileAdapter mockAdapter = mock(TileAdapter.class);
- Runnable mockCompletion = mock(Runnable.class);
- new TileQueryHelper(mContext, mockHost, mockAdapter, mockCompletion);
+ public void testQueryTiles_callsListenerTwice() {
+ mTileQueryHelper.queryTiles(mQSTileHost);
+
mBGLooper.processAllMessages();
- verify(mockCompletion).run();
+ waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER));
+
+ verify(mListener, times(2)).onTilesChanged(any());
}
@Test
- public void testCompletionCalledAfterTilesFetched() {
- QSTile mockTile = mock(QSTile.class);
- State mockState = mock(State.class);
- when(mockState.copy()).thenReturn(mockState);
- when(mockTile.getState()).thenReturn(mockState);
- when(mockTile.isAvailable()).thenReturn(true);
-
- QSTileHost mockHost = mock(QSTileHost.class);
- when(mockHost.createTile(any())).thenReturn(mockTile);
-
- mBGLooper.setMessageHandler((Message m) -> {
- mLastCallback = m.getCallback();
- return true;
- });
- TileAdapter mockAdapter = mock(TileAdapter.class);
- Runnable mockCompletion = mock(Runnable.class);
- new TileQueryHelper(mContext, mockHost, mockAdapter, mockCompletion);
-
- // Verify that the last thing in the queue was our callback
+ public void testQueryTiles_isFinishedFalseOnListenerCalls_thenTrueAfterCompletion() {
+ doAnswer(invocation -> {
+ assertFalse(mTileQueryHelper.isFinished());
+ return null;
+ }).when(mListener).onTilesChanged(any());
+
+ mTileQueryHelper.queryTiles(mQSTileHost);
+
mBGLooper.processAllMessages();
- assertEquals(mockCompletion, mLastCallback);
+ waitForIdleSync(Dependency.get(Dependency.MAIN_HANDLER));
+
+ assertTrue(mTileQueryHelper.isFinished());
}
}