1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.qs.customize;
18
19import android.Manifest.permission;
20import android.app.ActivityManager;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.graphics.drawable.Drawable;
27import android.os.AsyncTask;
28import android.os.Handler;
29import android.os.Looper;
30import android.service.quicksettings.TileService;
31import android.widget.Button;
32
33import com.android.systemui.R;
34import com.android.systemui.qs.QSTile;
35import com.android.systemui.qs.QSTile.DrawableIcon;
36import com.android.systemui.qs.QSTile.State;
37import com.android.systemui.qs.external.CustomTile;
38import com.android.systemui.statusbar.phone.QSTileHost;
39
40import java.util.ArrayList;
41import java.util.Collection;
42import java.util.List;
43
44public class TileQueryHelper {
45
46    private static final String TAG = "TileQueryHelper";
47
48    private final ArrayList<TileInfo> mTiles = new ArrayList<>();
49    private final ArrayList<String> mSpecs = new ArrayList<>();
50    private final Context mContext;
51    private TileStateListener mListener;
52
53    public TileQueryHelper(Context context, QSTileHost host) {
54        mContext = context;
55        addSystemTiles(host);
56        // TODO: Live?
57    }
58
59    private void addSystemTiles(final QSTileHost host) {
60        String possible = mContext.getString(R.string.quick_settings_tiles_default)
61                + ",hotspot,inversion,saver,work,cast,night";
62        String[] possibleTiles = possible.split(",");
63        final Handler qsHandler = new Handler(host.getLooper());
64        final Handler mainHandler = new Handler(Looper.getMainLooper());
65        for (int i = 0; i < possibleTiles.length; i++) {
66            final String spec = possibleTiles[i];
67            final QSTile<?> tile = host.createTile(spec);
68            if (tile == null || !tile.isAvailable()) {
69                continue;
70            }
71            tile.setListening(this, true);
72            tile.clearState();
73            tile.refreshState();
74            tile.setListening(this, false);
75            qsHandler.post(new Runnable() {
76                @Override
77                public void run() {
78                    final QSTile.State state = tile.newTileState();
79                    tile.getState().copyTo(state);
80                    // Ignore the current state and get the generic label instead.
81                    state.label = tile.getTileLabel();
82                    mainHandler.post(new Runnable() {
83                        @Override
84                        public void run() {
85                            addTile(spec, null, state, true);
86                            mListener.onTilesChanged(mTiles);
87                        }
88                    });
89                }
90            });
91        }
92        qsHandler.post(new Runnable() {
93            @Override
94            public void run() {
95                mainHandler.post(new Runnable() {
96                    @Override
97                    public void run() {
98                        new QueryTilesTask().execute(host.getTiles());
99                    }
100                });
101            }
102        });
103    }
104
105    public void setListener(TileStateListener listener) {
106        mListener = listener;
107    }
108
109    private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
110        if (mSpecs.contains(spec)) {
111            return;
112        }
113        TileInfo info = new TileInfo();
114        info.state = state;
115        info.state.minimalAccessibilityClassName = info.state.expandedAccessibilityClassName =
116                Button.class.getName();
117        info.spec = spec;
118        info.appLabel = appLabel;
119        info.isSystem = isSystem;
120        mTiles.add(info);
121        mSpecs.add(spec);
122    }
123
124    private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel,
125            Context context) {
126        QSTile.State state = new QSTile.State();
127        state.label = label;
128        state.contentDescription = label;
129        state.icon = new DrawableIcon(drawable);
130        addTile(spec, appLabel, state, false);
131    }
132
133    public static class TileInfo {
134        public String spec;
135        public CharSequence appLabel;
136        public QSTile.State state;
137        public boolean isSystem;
138    }
139
140    private class QueryTilesTask extends
141            AsyncTask<Collection<QSTile<?>>, Void, Collection<TileInfo>> {
142        @Override
143        protected Collection<TileInfo> doInBackground(Collection<QSTile<?>>... params) {
144            List<TileInfo> tiles = new ArrayList<>();
145            PackageManager pm = mContext.getPackageManager();
146            List<ResolveInfo> services = pm.queryIntentServicesAsUser(
147                    new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
148            for (ResolveInfo info : services) {
149                String packageName = info.serviceInfo.packageName;
150                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
151                final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
152                String spec = CustomTile.toSpec(componentName);
153                State state = getState(params[0], spec);
154                if (state != null) {
155                    addTile(spec, appLabel, state, false);
156                    continue;
157                }
158                if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
159                    continue;
160                }
161                Drawable icon = info.serviceInfo.loadIcon(pm);
162                if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
163                    continue;
164                }
165                if (icon == null) {
166                    continue;
167                }
168                icon.mutate();
169                icon.setTint(mContext.getColor(android.R.color.white));
170                CharSequence label = info.serviceInfo.loadLabel(pm);
171                addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
172            }
173            return tiles;
174        }
175
176        private State getState(Collection<QSTile<?>> tiles, String spec) {
177            for (QSTile<?> tile : tiles) {
178                if (spec.equals(tile.getTileSpec())) {
179                    final QSTile.State state = tile.newTileState();
180                    tile.getState().copyTo(state);
181                    return state;
182                }
183            }
184            return null;
185        }
186
187        @Override
188        protected void onPostExecute(Collection<TileInfo> result) {
189            mTiles.addAll(result);
190            mListener.onTilesChanged(mTiles);
191        }
192    }
193
194    public interface TileStateListener {
195        void onTilesChanged(List<TileInfo> tiles);
196    }
197}
198