Home | History | Annotate | Download | only in customize
      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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 {
    195         void onTilesChanged(List<TileInfo> tiles);
    196     }
    197 }
    198