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