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