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.Build; 28 import android.os.Handler; 29 import android.provider.Settings; 30 import android.service.quicksettings.Tile; 31 import android.service.quicksettings.TileService; 32 import android.text.TextUtils; 33 import android.util.ArraySet; 34 import android.widget.Button; 35 36 import com.android.systemui.Dependency; 37 import com.android.systemui.R; 38 import com.android.systemui.plugins.qs.QSTile; 39 import com.android.systemui.plugins.qs.QSTile.State; 40 import com.android.systemui.qs.QSTileHost; 41 import com.android.systemui.qs.external.CustomTile; 42 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; 43 import com.android.systemui.util.leak.GarbageMonitor; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.List; 49 50 public class TileQueryHelper { 51 private static final String TAG = "TileQueryHelper"; 52 53 private final ArrayList<TileInfo> mTiles = new ArrayList<>(); 54 private final ArraySet<String> mSpecs = new ArraySet<>(); 55 private final Handler mBgHandler; 56 private final Handler mMainHandler; 57 private final Context mContext; 58 private final TileStateListener mListener; 59 60 private boolean mFinished; 61 TileQueryHelper(Context context, TileStateListener listener)62 public TileQueryHelper(Context context, TileStateListener listener) { 63 mContext = context; 64 mListener = listener; 65 mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); 66 mMainHandler = Dependency.get(Dependency.MAIN_HANDLER); 67 } 68 queryTiles(QSTileHost host)69 public void queryTiles(QSTileHost host) { 70 mTiles.clear(); 71 mSpecs.clear(); 72 mFinished = false; 73 // Enqueue jobs to fetch every system tile and then ever package tile. 74 addCurrentAndStockTiles(host); 75 76 addPackageTiles(host); 77 } 78 isFinished()79 public boolean isFinished() { 80 return mFinished; 81 } 82 addCurrentAndStockTiles(QSTileHost host)83 private void addCurrentAndStockTiles(QSTileHost host) { 84 String stock = mContext.getString(R.string.quick_settings_tiles_stock); 85 String current = Settings.Secure.getString(mContext.getContentResolver(), 86 Settings.Secure.QS_TILES); 87 final ArrayList<String> possibleTiles = new ArrayList<>(); 88 if (current != null) { 89 // The setting QS_TILES is not populated immediately upon Factory Reset 90 possibleTiles.addAll(Arrays.asList(current.split(","))); 91 } else { 92 current = ""; 93 } 94 String[] stockSplit = stock.split(","); 95 for (String spec : stockSplit) { 96 if (!current.contains(spec)) { 97 possibleTiles.add(spec); 98 } 99 } 100 if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) { 101 possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); 102 } 103 104 final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); 105 for (String spec : possibleTiles) { 106 // Only add current and stock tiles that can be created from QSFactoryImpl. 107 // Do not include CustomTile. Those will be created by `addPackageTiles`. 108 if (spec.startsWith(CustomTile.PREFIX)) continue; 109 final QSTile tile = host.createTile(spec); 110 if (tile == null) { 111 continue; 112 } else if (!tile.isAvailable()) { 113 tile.destroy(); 114 continue; 115 } 116 tile.setListening(this, true); 117 tile.refreshState(); 118 tile.setListening(this, false); 119 tile.setTileSpec(spec); 120 tilesToAdd.add(tile); 121 } 122 123 mBgHandler.post(() -> { 124 for (QSTile tile : tilesToAdd) { 125 final QSTile.State state = tile.getState().copy(); 126 // Ignore the current state and get the generic label instead. 127 state.label = tile.getTileLabel(); 128 tile.destroy(); 129 addTile(tile.getTileSpec(), null, state, true); 130 } 131 notifyTilesChanged(false); 132 }); 133 } 134 addPackageTiles(final QSTileHost host)135 private void addPackageTiles(final QSTileHost host) { 136 mBgHandler.post(() -> { 137 Collection<QSTile> params = host.getTiles(); 138 PackageManager pm = mContext.getPackageManager(); 139 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 140 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); 141 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); 142 143 for (ResolveInfo info : services) { 144 String packageName = info.serviceInfo.packageName; 145 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); 146 147 // Don't include apps that are a part of the default tile set. 148 if (stockTiles.contains(componentName.flattenToString())) { 149 continue; 150 } 151 152 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); 153 String spec = CustomTile.toSpec(componentName); 154 State state = getState(params, spec); 155 if (state != null) { 156 addTile(spec, appLabel, state, false); 157 continue; 158 } 159 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) { 160 continue; 161 } 162 Drawable icon = info.serviceInfo.loadIcon(pm); 163 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) { 164 continue; 165 } 166 if (icon == null) { 167 continue; 168 } 169 icon.mutate(); 170 icon.setTint(mContext.getColor(android.R.color.white)); 171 CharSequence label = info.serviceInfo.loadLabel(pm); 172 createStateAndAddTile(spec, icon, label != null ? label.toString() : "null", 173 appLabel); 174 } 175 176 notifyTilesChanged(true); 177 }); 178 } 179 notifyTilesChanged(final boolean finished)180 private void notifyTilesChanged(final boolean finished) { 181 final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles); 182 mMainHandler.post(() -> { 183 mListener.onTilesChanged(tilesToReturn); 184 mFinished = finished; 185 }); 186 } 187 getState(Collection<QSTile> tiles, String spec)188 private State getState(Collection<QSTile> tiles, String spec) { 189 for (QSTile tile : tiles) { 190 if (spec.equals(tile.getTileSpec())) { 191 return tile.getState().copy(); 192 } 193 } 194 return null; 195 } 196 addTile(String spec, CharSequence appLabel, State state, boolean isSystem)197 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) { 198 if (mSpecs.contains(spec)) { 199 return; 200 } 201 TileInfo info = new TileInfo(); 202 info.state = state; 203 info.state.dualTarget = false; // No dual targets in edit. 204 info.state.expandedAccessibilityClassName = 205 Button.class.getName(); 206 info.spec = spec; 207 info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel)) 208 ? null : appLabel; 209 info.isSystem = isSystem; 210 mTiles.add(info); 211 mSpecs.add(spec); 212 } 213 createStateAndAddTile( String spec, Drawable drawable, CharSequence label, CharSequence appLabel)214 private void createStateAndAddTile( 215 String spec, Drawable drawable, CharSequence label, CharSequence appLabel) { 216 QSTile.State state = new QSTile.State(); 217 state.state = Tile.STATE_INACTIVE; 218 state.label = label; 219 state.contentDescription = label; 220 state.icon = new DrawableIcon(drawable); 221 addTile(spec, appLabel, state, false); 222 } 223 224 public static class TileInfo { 225 public String spec; 226 public QSTile.State state; 227 public boolean isSystem; 228 } 229 230 public interface TileStateListener { onTilesChanged(List<TileInfo> tiles)231 void onTilesChanged(List<TileInfo> tiles); 232 } 233 } 234