TileAdapter.java revision c767a1f988d6847bc4873d57ec4485c54120fe2c
1/*
2 * Copyright (C) 2015 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.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
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.util.Log;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.view.ViewGroup;
36import android.widget.BaseAdapter;
37import android.widget.GridLayout;
38import android.widget.ImageView;
39import android.widget.TextView;
40
41import com.android.systemui.R;
42import com.android.systemui.qs.QSTile;
43import com.android.systemui.qs.QSTile.Icon;
44import com.android.systemui.qs.external.CustomTile;
45import com.android.systemui.statusbar.phone.QSTileHost;
46
47import java.util.ArrayList;
48import java.util.Collection;
49import java.util.HashMap;
50import java.util.List;
51
52public class TileAdapter extends BaseAdapter {
53
54    private static final String TAG = "TileAdapter";
55
56    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
57    private final Context mContext;
58
59    private TileSelectedListener mListener;
60    private ArrayList<String> mCurrentTiles;
61
62    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
63        mContext = context;
64        addSystemTiles(currentTiles, host);
65        // TODO: Live?
66    }
67
68    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
69        try {
70            ArrayList<String> tileSpecs = new ArrayList<>();
71            for (QSTile<?> tile : currentTiles) {
72                tileSpecs.add(tile.getTileSpec());
73            }
74            mCurrentTiles = tileSpecs;
75            final TileGroup group = new TileGroup("com.android.settings", mContext);
76            boolean hasColorMod = host.getDisplayController().isEnabled();
77            String possible = mContext.getString(R.string.quick_settings_tiles_default)
78                    + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
79            String[] possibleTiles = possible.split(",");
80            for (int i = 0; i < possibleTiles.length; i++) {
81                final String spec = possibleTiles[i];
82                if (spec.startsWith("q")) {
83                    // Quick tiles can't be customized.
84                    continue;
85                }
86                if (tileSpecs.contains(spec)) {
87                    Log.d(TAG, "Skipping " + spec);
88                    continue;
89                }
90                Log.d(TAG, "Trying " + spec);
91                final QSTile<?> tile = host.createTile(spec);
92                if (tile == null) {
93                    continue;
94                }
95                // Bad, bad, very bad.
96                tile.setListening(true);
97                tile.clearState();
98                tile.refreshState();
99                tile.setListening(false);
100                new Handler(host.getLooper()).post(new Runnable() {
101                    @Override
102                    public void run() {
103                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
104                    }
105                });
106            }
107            // Error: Badness (10000).
108            // Serialize this work after the host's looper's queue is empty.
109            new Handler(host.getLooper()).post(new Runnable() {
110                @Override
111                public void run() {
112                    new Handler(Looper.getMainLooper()).post(new Runnable() {
113                        @Override
114                        public void run() {
115                            if (group.mTiles.size() > 0) {
116                                mGroups.add(group);
117                                notifyDataSetChanged();
118                            }
119                            new QueryTilesTask().execute();
120                        }
121                    });
122                }
123            });
124        } catch (NameNotFoundException e) {
125            Log.e(TAG, "Couldn't load system tiles", e);
126        }
127    }
128
129    public void setListener(TileSelectedListener listener) {
130        mListener = listener;
131    }
132
133    @Override
134    public int getCount() {
135        return mGroups.size();
136    }
137
138    @Override
139    public Object getItem(int position) {
140        return mGroups.get(position);
141    }
142
143    @Override
144    public long getItemId(int position) {
145        return position;
146    }
147
148    @Override
149    public View getView(int position, View convertView, ViewGroup parent) {
150        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
151    }
152
153    private static class TileGroup {
154        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
155        private CharSequence mLabel;
156        private Drawable mIcon;
157
158        public TileGroup(String pkg, Context context) throws NameNotFoundException {
159            PackageManager pm = context.getPackageManager();
160            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
161            mLabel = info.loadLabel(pm);
162            mIcon = info.loadIcon(pm);
163            Log.d(TAG, "Added " + mLabel);
164        }
165
166        private void addTile(String spec, Drawable icon, CharSequence label) {
167            TileInfo info = new TileInfo();
168            info.label = label;
169            info.drawable = icon;
170            info.spec = spec;
171            mTiles.add(info);
172        }
173
174        private void addTile(String spec, Icon icon, CharSequence label, Context context) {
175            addTile(spec, icon != null ? icon.getDrawable(context) : null, label);
176        }
177
178        private View getView(Context context, View convertView, ViewGroup parent,
179                final TileSelectedListener listener) {
180            if (convertView == null) {
181                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
182                        false);
183            }
184            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
185            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
186            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
187            final int N = mTiles.size();
188            if (grid.getChildCount() != N) {
189                grid.removeAllViews();
190            }
191            for (int i = 0; i < N; i++) {
192                if (grid.getChildCount() <= i) {
193                    grid.addView(createTile(context));
194                }
195                View view = grid.getChildAt(i);
196                final TileInfo tileInfo = mTiles.get(i);
197                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
198                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
199                view.setClickable(true);
200                view.setOnClickListener(new OnClickListener() {
201                    @Override
202                    public void onClick(View v) {
203                        listener.onTileSelected(tileInfo.spec);
204                    }
205                });
206            }
207            return convertView;
208        }
209
210        private View createTile(Context context) {
211            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
212        }
213    }
214
215    private static class TileInfo {
216        private String spec;
217        private Drawable drawable;
218        private CharSequence label;
219    }
220
221    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
222        @Override
223        protected Collection<TileGroup> doInBackground(Void... params) {
224            HashMap<String, TileGroup> pkgMap = new HashMap<>();
225            PackageManager pm = mContext.getPackageManager();
226            // TODO: Handle userness.
227            List<ResolveInfo> services = pm.queryIntentServices(
228                    new Intent(TileService.ACTION_QS_TILE), 0);
229            for (ResolveInfo info : services) {
230                String packageName = info.serviceInfo.packageName;
231                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
232                String spec = CustomTile.toSpec(componentName);
233                if (mCurrentTiles.contains(spec)) {
234                    continue;
235                }
236                try {
237                    TileGroup group = pkgMap.get(packageName);
238                    if (group == null) {
239                        group = new TileGroup(packageName, mContext);
240                        pkgMap.put(packageName, group);
241                    }
242                    Drawable icon = info.serviceInfo.loadIcon(pm);
243                    CharSequence label = info.serviceInfo.loadLabel(pm);
244                    group.addTile(spec, icon, label != null ? label.toString() : "null");
245                } catch (NameNotFoundException e) {
246                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
247                }
248            }
249            return pkgMap.values();
250        }
251
252        @Override
253        protected void onPostExecute(Collection<TileGroup> result) {
254            mGroups.addAll(result);
255            notifyDataSetChanged();
256        }
257    }
258
259    public interface TileSelectedListener {
260        void onTileSelected(String spec);
261    }
262}
263