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 final QSTile tile = host.createTile(spec); 108 if (tile == null) { 109 continue; 110 } else if (!tile.isAvailable()) { 111 tile.destroy(); 112 continue; 113 } 114 tile.setListening(this, true); 115 tile.refreshState(); 116 tile.setListening(this, false); 117 tile.setTileSpec(spec); 118 tilesToAdd.add(tile); 119 } 120 121 mBgHandler.post(() -> { 122 for (QSTile tile : tilesToAdd) { 123 final QSTile.State state = tile.getState().copy(); 124 // Ignore the current state and get the generic label instead. 125 state.label = tile.getTileLabel(); 126 tile.destroy(); 127 addTile(tile.getTileSpec(), null, state, true); 128 } 129 notifyTilesChanged(false); 130 }); 131 } 132 addPackageTiles(final QSTileHost host)133 private void addPackageTiles(final QSTileHost host) { 134 mBgHandler.post(() -> { 135 Collection<QSTile> params = host.getTiles(); 136 PackageManager pm = mContext.getPackageManager(); 137 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 138 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); 139 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); 140 141 for (ResolveInfo info : services) { 142 String packageName = info.serviceInfo.packageName; 143 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); 144 145 // Don't include apps that are a part of the default tile set. 146 if (stockTiles.contains(componentName.flattenToString())) { 147 continue; 148 } 149 150 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); 151 String spec = CustomTile.toSpec(componentName); 152 State state = getState(params, spec); 153 if (state != null) { 154 addTile(spec, appLabel, state, false); 155 continue; 156 } 157 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) { 158 continue; 159 } 160 Drawable icon = info.serviceInfo.loadIcon(pm); 161 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) { 162 continue; 163 } 164 if (icon == null) { 165 continue; 166 } 167 icon.mutate(); 168 icon.setTint(mContext.getColor(android.R.color.white)); 169 CharSequence label = info.serviceInfo.loadLabel(pm); 170 createStateAndAddTile(spec, icon, label != null ? label.toString() : "null", 171 appLabel); 172 } 173 174 notifyTilesChanged(true); 175 }); 176 } 177 notifyTilesChanged(final boolean finished)178 private void notifyTilesChanged(final boolean finished) { 179 final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles); 180 mMainHandler.post(() -> { 181 mListener.onTilesChanged(tilesToReturn); 182 mFinished = finished; 183 }); 184 } 185 getState(Collection<QSTile> tiles, String spec)186 private State getState(Collection<QSTile> tiles, String spec) { 187 for (QSTile tile : tiles) { 188 if (spec.equals(tile.getTileSpec())) { 189 return tile.getState().copy(); 190 } 191 } 192 return null; 193 } 194 addTile(String spec, CharSequence appLabel, State state, boolean isSystem)195 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) { 196 if (mSpecs.contains(spec)) { 197 return; 198 } 199 TileInfo info = new TileInfo(); 200 info.state = state; 201 info.state.dualTarget = false; // No dual targets in edit. 202 info.state.expandedAccessibilityClassName = 203 Button.class.getName(); 204 info.spec = spec; 205 info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel)) 206 ? null : appLabel; 207 info.isSystem = isSystem; 208 mTiles.add(info); 209 mSpecs.add(spec); 210 } 211 createStateAndAddTile( String spec, Drawable drawable, CharSequence label, CharSequence appLabel)212 private void createStateAndAddTile( 213 String spec, Drawable drawable, CharSequence label, CharSequence appLabel) { 214 QSTile.State state = new QSTile.State(); 215 state.state = Tile.STATE_INACTIVE; 216 state.label = label; 217 state.contentDescription = label; 218 state.icon = new DrawableIcon(drawable); 219 addTile(spec, appLabel, state, false); 220 } 221 222 public static class TileInfo { 223 public String spec; 224 public QSTile.State state; 225 public boolean isSystem; 226 } 227 228 public interface TileStateListener { onTilesChanged(List<TileInfo> tiles)229 void onTilesChanged(List<TileInfo> tiles); 230 } 231 } 232