Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb80cfa7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix qs tiles disappearing when leaving edit"

parents 06b11b70 6b284736
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -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);
    }

+16 −2
Original line number Diff line number Diff line
@@ -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,8 +216,10 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
    }

    private void save() {
        if (mFinishedFetchingTiles) {
            mTileAdapter.saveSpecs(mHost);
        }
    }

    private final Callback mKeyguardCallback = () -> {
        if (Dependency.get(KeyguardMonitor.class).isShowing()) {
+67 −76
Original line number Diff line number Diff line
@@ -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,64 +106,16 @@ public class TileQueryHelper {
                }
            });
        }
        qsHandler.post(new Runnable() {
            @Override
            public void run() {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        new QueryTilesTask().execute(host.getTiles());
                    }
                });
            }
        });
    }

    public void setListener(TileStateListener listener) {
        mListener = listener;
    }

    private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
        if (mSpecs.contains(spec)) {
            return;
        }
        TileInfo info = new TileInfo();
        info.state = state;
        info.state.minimalAccessibilityClassName = info.state.expandedAccessibilityClassName =
                Button.class.getName();
        info.spec = spec;
        info.appLabel = appLabel;
        info.isSystem = isSystem;
        mTiles.add(info);
        mSpecs.add(spec);
    }

    private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel,
            Context context) {
        QSTile.State state = new QSTile.State();
        state.label = label;
        state.contentDescription = label;
        state.icon = new DrawableIcon(drawable);
        state.autoMirrorDrawable = false;
        addTile(spec, appLabel, state, false);
    }

    public static class TileInfo {
        public String spec;
        public CharSequence appLabel;
        public QSTile.State state;
        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<>();
    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);
@@ -162,7 +127,7 @@ public class TileQueryHelper {

                final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
                String spec = CustomTile.toSpec(componentName);
                State state = getState(params[0], spec);
                State state = getState(params, spec);
                if (state != null) {
                    addTile(spec, appLabel, state, false);
                    continue;
@@ -182,7 +147,8 @@ public class TileQueryHelper {
                CharSequence label = info.serviceInfo.loadLabel(pm);
                addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
            }
            return tiles;
            mainHandler.post(() -> mListener.onTilesChanged(mTiles));
        });
    }

    private State getState(Collection<QSTile<?>> tiles, String spec) {
@@ -196,11 +162,36 @@ public class TileQueryHelper {
        return null;
    }

        @Override
        protected void onPostExecute(Collection<TileInfo> result) {
            mTiles.addAll(result);
            mListener.onTilesChanged(mTiles);
    private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
        if (mSpecs.contains(spec)) {
            return;
        }
        TileInfo info = new TileInfo();
        info.state = state;
        info.state.minimalAccessibilityClassName = info.state.expandedAccessibilityClassName =
                Button.class.getName();
        info.spec = spec;
        info.appLabel = appLabel;
        info.isSystem = isSystem;
        mTiles.add(info);
        mSpecs.add(spec);
    }

    private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel,
            Context context) {
        QSTile.State state = new QSTile.State();
        state.label = label;
        state.contentDescription = label;
        state.icon = new DrawableIcon(drawable);
        state.autoMirrorDrawable = false;
        addTile(spec, appLabel, state, false);
    }

    public static class TileInfo {
        public String spec;
        public CharSequence appLabel;
        public QSTile.State state;
        public boolean isSystem;
    }

    public interface TileStateListener {
+87 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}