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.Handler; 28 import android.os.Looper; 29 import android.service.quicksettings.TileService; 30 import android.widget.Button; 31 32 import com.android.systemui.Dependency; 33 import com.android.systemui.R; 34 import com.android.systemui.plugins.qs.QSTile; 35 import com.android.systemui.plugins.qs.QSTile.State; 36 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; 37 import com.android.systemui.qs.external.CustomTile; 38 import com.android.systemui.qs.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 final TileStateListener mListener; 52 private final QSTileHost mHost; 53 private final Runnable mCompletion; 54 TileQueryHelper(Context context, QSTileHost host, TileStateListener listener, Runnable completion)55 public TileQueryHelper(Context context, QSTileHost host, 56 TileStateListener listener, Runnable completion) { 57 mContext = context; 58 mListener = listener; 59 mHost = host; 60 mCompletion = completion; 61 addSystemTiles(); 62 // TODO: Live? 63 } 64 addSystemTiles()65 private void addSystemTiles() { 66 // Enqueue jobs to fetch every system tile and then ever package tile. 67 final Handler qsHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER)); 68 final Handler mainHandler = new Handler(Looper.getMainLooper()); 69 addStockTiles(mainHandler, qsHandler); 70 addPackageTiles(mainHandler, qsHandler); 71 // Then enqueue the completion. It should always be last 72 qsHandler.post(mCompletion); 73 } 74 addStockTiles(Handler mainHandler, Handler bgHandler)75 private void addStockTiles(Handler mainHandler, Handler bgHandler) { 76 String possible = mContext.getString(R.string.quick_settings_tiles_stock); 77 String[] possibleTiles = possible.split(","); 78 for (int i = 0; i < possibleTiles.length; i++) { 79 final String spec = possibleTiles[i]; 80 final QSTile tile = mHost.createTile(spec); 81 if (tile == null) { 82 continue; 83 } else if (!tile.isAvailable()) { 84 tile.destroy(); 85 continue; 86 } 87 tile.setListening(this, true); 88 tile.clearState(); 89 tile.refreshState(); 90 tile.setListening(this, false); 91 bgHandler.post(new Runnable() { 92 @Override 93 public void run() { 94 final QSTile.State state = tile.getState().copy(); 95 // Ignore the current state and get the generic label instead. 96 state.label = tile.getTileLabel(); 97 tile.destroy(); 98 mainHandler.post(new Runnable() { 99 @Override 100 public void run() { 101 addTile(spec, null, state, true); 102 mListener.onTilesChanged(mTiles); 103 } 104 }); 105 } 106 }); 107 } 108 } 109 addPackageTiles(Handler mainHandler, Handler bgHandler)110 private void addPackageTiles(Handler mainHandler, Handler bgHandler) { 111 bgHandler.post(() -> { 112 Collection<QSTile> params = mHost.getTiles(); 113 PackageManager pm = mContext.getPackageManager(); 114 List<ResolveInfo> services = pm.queryIntentServicesAsUser( 115 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); 116 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); 117 118 for (ResolveInfo info : services) { 119 String packageName = info.serviceInfo.packageName; 120 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); 121 122 // Don't include apps that are a part of the default tile set. 123 if (stockTiles.contains(componentName.flattenToString())) { 124 continue; 125 } 126 127 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); 128 String spec = CustomTile.toSpec(componentName); 129 State state = getState(params, spec); 130 if (state != null) { 131 addTile(spec, appLabel, state, false); 132 continue; 133 } 134 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) { 135 continue; 136 } 137 Drawable icon = info.serviceInfo.loadIcon(pm); 138 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) { 139 continue; 140 } 141 if (icon == null) { 142 continue; 143 } 144 icon.mutate(); 145 icon.setTint(mContext.getColor(android.R.color.white)); 146 CharSequence label = info.serviceInfo.loadLabel(pm); 147 addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext); 148 } 149 mainHandler.post(() -> mListener.onTilesChanged(mTiles)); 150 }); 151 } 152 getState(Collection<QSTile> tiles, String spec)153 private State getState(Collection<QSTile> tiles, String spec) { 154 for (QSTile tile : tiles) { 155 if (spec.equals(tile.getTileSpec())) { 156 return tile.getState().copy(); 157 } 158 } 159 return null; 160 } 161 addTile(String spec, CharSequence appLabel, State state, boolean isSystem)162 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) { 163 if (mSpecs.contains(spec)) { 164 return; 165 } 166 TileInfo info = new TileInfo(); 167 info.state = state; 168 info.state.dualTarget = false; // No dual targets in edit. 169 info.state.expandedAccessibilityClassName = 170 Button.class.getName(); 171 info.spec = spec; 172 info.appLabel = appLabel; 173 info.isSystem = isSystem; 174 mTiles.add(info); 175 mSpecs.add(spec); 176 } 177 addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel, Context context)178 private void addTile(String spec, Drawable drawable, CharSequence label, CharSequence appLabel, 179 Context context) { 180 QSTile.State state = new QSTile.State(); 181 state.label = label; 182 state.contentDescription = label; 183 state.icon = new DrawableIcon(drawable); 184 addTile(spec, appLabel, state, false); 185 } 186 187 public static class TileInfo { 188 public String spec; 189 public CharSequence appLabel; 190 public QSTile.State state; 191 public boolean isSystem; 192 } 193 194 public interface TileStateListener { onTilesChanged(List<TileInfo> tiles)195 void onTilesChanged(List<TileInfo> tiles); 196 } 197 } 198