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