Merge "Fix qs tiles disappearing when leaving edit"
This commit is contained in:
committed by
Android (Google) Code Review
commit
bb80cfa7a7
@@ -58,7 +58,7 @@ public abstract class QSTile<TState extends State> {
|
||||
|
||||
protected final Host mHost;
|
||||
protected final Context mContext;
|
||||
protected final H mHandler;
|
||||
protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
|
||||
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
|
||||
private final ArraySet<Object> mListeners = new ArraySet<>();
|
||||
|
||||
@@ -86,7 +86,6 @@ public abstract class QSTile<TState extends State> {
|
||||
protected QSTile(Host host) {
|
||||
mHost = host;
|
||||
mContext = host.getContext();
|
||||
mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +169,7 @@ public abstract class QSTile<TState extends State> {
|
||||
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
|
||||
}
|
||||
|
||||
public final void refreshState() {
|
||||
public void refreshState() {
|
||||
refreshState(null);
|
||||
}
|
||||
|
||||
@@ -178,7 +177,7 @@ public abstract class QSTile<TState extends State> {
|
||||
mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
|
||||
}
|
||||
|
||||
public final void clearState() {
|
||||
public void clearState() {
|
||||
mHandler.sendEmptyMessage(H.CLEAR_STATE);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.animation.Animator.AnimatorListener;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
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;
|
||||
@@ -69,6 +71,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
|
||||
private boolean mCustomizing;
|
||||
private NotificationsQuickSettingsContainer mNotifQsContainer;
|
||||
private QS mQs;
|
||||
private boolean mFinishedFetchingTiles = false;
|
||||
|
||||
public QSCustomizer(Context context, AttributeSet attrs) {
|
||||
super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
|
||||
@@ -136,7 +139,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
|
||||
setTileSpecs();
|
||||
setVisibility(View.VISIBLE);
|
||||
mClipper.animateCircularClip(x, y, true, mExpandAnimationListener);
|
||||
new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
|
||||
queryTiles();
|
||||
mNotifQsContainer.setCustomizerAnimating(true);
|
||||
mNotifQsContainer.setCustomizerShowing(true);
|
||||
announceForAccessibility(mContext.getString(
|
||||
@@ -145,6 +148,15 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
|
||||
}
|
||||
}
|
||||
|
||||
private void queryTiles() {
|
||||
mFinishedFetchingTiles = false;
|
||||
Runnable tileQueryFetchCompletion = () -> {
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(() -> mFinishedFetchingTiles = true);
|
||||
};
|
||||
new TileQueryHelper(mContext, mHost, mTileAdapter, tileQueryFetchCompletion);
|
||||
}
|
||||
|
||||
public void hide(int x, int y) {
|
||||
if (isShown) {
|
||||
MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
|
||||
@@ -204,7 +216,9 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
|
||||
}
|
||||
|
||||
private void save() {
|
||||
mTileAdapter.saveSpecs(mHost);
|
||||
if (mFinishedFetchingTiles) {
|
||||
mTileAdapter.saveSpecs(mHost);
|
||||
}
|
||||
}
|
||||
|
||||
private final Callback mKeyguardCallback = () -> {
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.service.quicksettings.TileService;
|
||||
@@ -49,22 +48,36 @@ public class TileQueryHelper {
|
||||
private final ArrayList<TileInfo> mTiles = new ArrayList<>();
|
||||
private final ArrayList<String> mSpecs = new ArrayList<>();
|
||||
private final Context mContext;
|
||||
private TileStateListener mListener;
|
||||
private final TileStateListener mListener;
|
||||
private final QSTileHost mHost;
|
||||
private final Runnable mCompletion;
|
||||
|
||||
public TileQueryHelper(Context context, QSTileHost host) {
|
||||
public TileQueryHelper(Context context, QSTileHost host,
|
||||
TileStateListener listener, Runnable completion) {
|
||||
mContext = context;
|
||||
addSystemTiles(host);
|
||||
mListener = listener;
|
||||
mHost = host;
|
||||
mCompletion = completion;
|
||||
addSystemTiles();
|
||||
// TODO: Live?
|
||||
}
|
||||
|
||||
private void addSystemTiles(final QSTileHost host) {
|
||||
String possible = mContext.getString(R.string.quick_settings_tiles_stock);
|
||||
String[] possibleTiles = possible.split(",");
|
||||
private void addSystemTiles() {
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void addStockTiles(Handler mainHandler, Handler bgHandler) {
|
||||
String possible = mContext.getString(R.string.quick_settings_tiles_stock);
|
||||
String[] possibleTiles = possible.split(",");
|
||||
for (int i = 0; i < possibleTiles.length; i++) {
|
||||
final String spec = possibleTiles[i];
|
||||
final QSTile<?> tile = host.createTile(spec);
|
||||
final QSTile<?> tile = mHost.createTile(spec);
|
||||
if (tile == null) {
|
||||
continue;
|
||||
} else if (!tile.isAvailable()) {
|
||||
@@ -75,7 +88,7 @@ public class TileQueryHelper {
|
||||
tile.clearState();
|
||||
tile.refreshState();
|
||||
tile.setListening(this, false);
|
||||
qsHandler.post(new Runnable() {
|
||||
bgHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final QSTile.State state = tile.newTileState();
|
||||
@@ -93,21 +106,60 @@ public class TileQueryHelper {
|
||||
}
|
||||
});
|
||||
}
|
||||
qsHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new QueryTilesTask().execute(host.getTiles());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addPackageTiles(Handler mainHandler, Handler bgHandler) {
|
||||
bgHandler.post(() -> {
|
||||
Collection<QSTile<?>> params = mHost.getTiles();
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
|
||||
new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
|
||||
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
|
||||
|
||||
for (ResolveInfo info : services) {
|
||||
String packageName = info.serviceInfo.packageName;
|
||||
ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
|
||||
|
||||
// Don't include apps that are a part of the default tile set.
|
||||
if (stockTiles.contains(componentName.flattenToString())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
|
||||
String spec = CustomTile.toSpec(componentName);
|
||||
State state = getState(params, spec);
|
||||
if (state != null) {
|
||||
addTile(spec, appLabel, state, false);
|
||||
continue;
|
||||
}
|
||||
if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
|
||||
continue;
|
||||
}
|
||||
Drawable icon = info.serviceInfo.loadIcon(pm);
|
||||
if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
|
||||
continue;
|
||||
}
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
mainHandler.post(() -> mListener.onTilesChanged(mTiles));
|
||||
});
|
||||
}
|
||||
|
||||
public void setListener(TileStateListener listener) {
|
||||
mListener = listener;
|
||||
private State getState(Collection<QSTile<?>> tiles, String spec) {
|
||||
for (QSTile<?> tile : tiles) {
|
||||
if (spec.equals(tile.getTileSpec())) {
|
||||
final QSTile.State state = tile.newTileState();
|
||||
tile.getState().copyTo(state);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
|
||||
@@ -142,67 +194,6 @@ public class TileQueryHelper {
|
||||
public boolean isSystem;
|
||||
}
|
||||
|
||||
private class QueryTilesTask extends
|
||||
AsyncTask<Collection<QSTile<?>>, Void, Collection<TileInfo>> {
|
||||
@Override
|
||||
protected Collection<TileInfo> doInBackground(Collection<QSTile<?>>... params) {
|
||||
List<TileInfo> tiles = new ArrayList<>();
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
|
||||
new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
|
||||
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
|
||||
for (ResolveInfo info : services) {
|
||||
String packageName = info.serviceInfo.packageName;
|
||||
ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
|
||||
|
||||
// Don't include apps that are a part of the default tile set.
|
||||
if (stockTiles.contains(componentName.flattenToString())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
|
||||
String spec = CustomTile.toSpec(componentName);
|
||||
State state = getState(params[0], spec);
|
||||
if (state != null) {
|
||||
addTile(spec, appLabel, state, false);
|
||||
continue;
|
||||
}
|
||||
if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
|
||||
continue;
|
||||
}
|
||||
Drawable icon = info.serviceInfo.loadIcon(pm);
|
||||
if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
|
||||
continue;
|
||||
}
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
private State getState(Collection<QSTile<?>> tiles, String spec) {
|
||||
for (QSTile<?> tile : tiles) {
|
||||
if (spec.equals(tile.getTileSpec())) {
|
||||
final QSTile.State state = tile.newTileState();
|
||||
tile.getState().copyTo(state);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Collection<TileInfo> result) {
|
||||
mTiles.addAll(result);
|
||||
mListener.onTilesChanged(mTiles);
|
||||
}
|
||||
}
|
||||
|
||||
public interface TileStateListener {
|
||||
void onTilesChanged(List<TileInfo> tiles);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.qs.customize;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.os.Message;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.SysUIRunner;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.qs.QSTile;
|
||||
import com.android.systemui.qs.QSTile.State;
|
||||
import com.android.systemui.statusbar.phone.QSTileHost;
|
||||
|
||||
import com.android.systemui.utils.TestableLooper;
|
||||
import com.android.systemui.utils.TestableLooper.MessageHandler;
|
||||
import com.android.systemui.utils.TestableLooper.RunWithLooper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(SysUIRunner.class)
|
||||
@RunWithLooper
|
||||
public class TileQueryHelperTest extends SysuiTestCase {
|
||||
private TestableLooper mBGLooper;
|
||||
private Runnable mLastCallback;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mBGLooper = TestableLooper.get(this);
|
||||
injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper());
|
||||
}
|
||||
|
||||
@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);
|
||||
mBGLooper.processAllMessages();
|
||||
verify(mockCompletion).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompletionCalledAfterTilesFetched() {
|
||||
QSTile mockTile = mock(QSTile.class);
|
||||
State mockState = mock(State.class);
|
||||
when(mockTile.newTileState()).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
|
||||
mBGLooper.processAllMessages();
|
||||
assertEquals(mockCompletion, mLastCallback);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user