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