1 /* 2 * Copyright (C) 2016 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 17 package com.android.systemui.qs.customize; 18 19 import android.Manifest.permission; 20 import android.app.ActivityManager; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.graphics.drawable.Drawable; 27 import android.os.AsyncTask; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.service.quicksettings.TileService; 31 import android.widget.Button; 32 33 import com.android.systemui.R; 34 import com.android.systemui.qs.QSTile; 35 import com.android.systemui.qs.QSTile.DrawableIcon; 36 import com.android.systemui.qs.QSTile.State; 37 import com.android.systemui.qs.external.CustomTile; 38 import com.android.systemui.statusbar.phone.QSTileHost; 39 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.List; 43 44 public class TileQueryHelper { 45 46 private static final String TAG = "TileQueryHelper"; 47 48 private final ArrayList<TileInfo> mTiles = new ArrayList<>(); 49 private final ArrayList<String> mSpecs = new ArrayList<>(); 50 private final Context mContext; 51 private TileStateListener mListener; 52 TileQueryHelper(Context context, QSTileHost host)53 public TileQueryHelper(Context context, QSTileHost host) { 54 mContext = context; 55 addSystemTiles(host); 56 // TODO: Live? 57 } 58 addSystemTiles(final QSTileHost host)59 private void addSystemTiles(final QSTileHost host) { 60 String possible = mContext.getString(R.string.quick_settings_tiles_stock); 61 String[] possibleTiles = possible.split(","); 62 final Handler qsHandler = new Handler(host.getLooper()); 63 final Handler mainHandler = new Handler(Looper.getMainLooper()); 64 for (int i = 0; i < possibleTiles.length; i++) { 65 final String spec = possibleTiles[i]; 66 final QSTile<?> tile = host.createTile(spec); 67 if (tile == null || !tile.isAvailable()) { 68 continue; 69 } 70 tile.setListening(this, true); 71 tile.clearState(); 72 tile.refreshState(); 73 tile.setListening(this, false); 74 qsHandler.post(new Runnable() { 75 @Override 76 public void run() { 77 final QSTile.State state = tile.newTileState(); 78 tile.getState().copyTo(state); 79 // Ignore the current state and get the generic label instead. 80 state.label = tile.getTileLabel(); 81 mainHandler.post(new Runnable() { 82 @Override 83 public void run() { 84 addTile(spec, null, state, true); 85 mListener.onTilesChanged(mTiles); 86 } 87 }); 88 } 89 }); 90 } 91 qsHandler.post(new Runnable() { 92 @Override 93 public void run() { 94 mainHandler.post(new Runnable() { 95 @Override 96 public void run() { 97 new QueryTilesTask().execute(host.getTiles()); 98 } 99 }); 100 } 101 }); 102 } 103 setListener(TileStateListener listener)104 public void setListener(TileStateListener listener) { 105 mListener = listener; 106 } 107 addTile(String spec, CharSequence appLabel, State state, boolean isSystem)108 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) { 109 if (mSpecs.contains(spec)) { 110 return; 111 } 112 TileInfo info = new TileInfo(); 113 info.state = state; 114 info.state.minimalAccessibilityClassName = info.state.expandedAccessibilityClassName = 115 Button.class.getName(); 116 info.spec = spec; 117 info.appLabel = appLabel; 118 info.isSystem = isSystem; 119 mTiles.add(info); 120 mSpecs.add(spec); 121 } 122 addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel, Context context)123 private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel, 124 Context context) { 125 QSTile.State state = new QSTile.State(); 126 state.label = label; 127 state.contentDescription = label; 128 state.icon = new DrawableIcon(drawable); 129 addTile(spec, appLabel, state, false); 130 } 131 132 public static class TileInfo { 133 public String spec; 134 public CharSequence appLabel; 135 public QSTile.State state; 136 public boolean isSystem; 137 } 138 139 private class QueryTilesTask extends 140 AsyncTask<Collection<QSTile<?>>, Void, Collection<TileInfo>> { 141 @Override doInBackground(Collection<QSTile<?>>.... params)142 protected Collection<TileInfo> doInBackground(Collection<QSTile<?>>... params) { 143 List<TileInfo> tiles = new ArrayList<>(); 144 PackageManager pm = mContext.getPackageManager(); 145 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 146 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); 147 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); 148 for (ResolveInfo info : services) { 149 String packageName = info.serviceInfo.packageName; 150 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); 151 152 // Don't include apps that are a part of the default tile set. 153 if (stockTiles.contains(componentName.flattenToString())) { 154 continue; 155 } 156 157 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); 158 String spec = CustomTile.toSpec(componentName); 159 State state = getState(params[0], spec); 160 if (state != null) { 161 addTile(spec, appLabel, state, false); 162 continue; 163 } 164 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) { 165 continue; 166 } 167 Drawable icon = info.serviceInfo.loadIcon(pm); 168 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) { 169 continue; 170 } 171 if (icon == null) { 172 continue; 173 } 174 icon.mutate(); 175 icon.setTint(mContext.getColor(android.R.color.white)); 176 CharSequence label = info.serviceInfo.loadLabel(pm); 177 addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext); 178 } 179 return tiles; 180 } 181 getState(Collection<QSTile<?>> tiles, String spec)182 private State getState(Collection<QSTile<?>> tiles, String spec) { 183 for (QSTile<?> tile : tiles) { 184 if (spec.equals(tile.getTileSpec())) { 185 final QSTile.State state = tile.newTileState(); 186 tile.getState().copyTo(state); 187 return state; 188 } 189 } 190 return null; 191 } 192 193 @Override onPostExecute(Collection<TileInfo> result)194 protected void onPostExecute(Collection<TileInfo> result) { 195 mTiles.addAll(result); 196 mListener.onTilesChanged(mTiles); 197 } 198 } 199 200 public interface TileStateListener { onTilesChanged(List<TileInfo> tiles)201 void onTilesChanged(List<TileInfo> tiles); 202 } 203 } 204