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