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