• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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