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