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