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