Home | History | Annotate | Download | only in dashboard
      1 /*
      2  * Copyright (C) 2015 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 package com.android.settings.dashboard;
     17 
     18 import android.app.Activity;
     19 import android.content.BroadcastReceiver;
     20 import android.content.ComponentName;
     21 import android.content.IntentFilter;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.HandlerThread;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.Process;
     28 import android.util.ArrayMap;
     29 import android.util.ArraySet;
     30 import android.util.Log;
     31 
     32 import com.android.settings.SettingsActivity;
     33 import com.android.settingslib.drawer.DashboardCategory;
     34 import com.android.settingslib.drawer.SettingsDrawerActivity;
     35 import com.android.settingslib.drawer.Tile;
     36 
     37 import java.lang.reflect.Field;
     38 import java.util.ArrayList;
     39 import java.util.List;
     40 
     41 public class SummaryLoader {
     42     private static final boolean DEBUG = DashboardSummary.DEBUG;
     43     private static final String TAG = "SummaryLoader";
     44 
     45     public static final String SUMMARY_PROVIDER_FACTORY = "SUMMARY_PROVIDER_FACTORY";
     46 
     47     private final Activity mActivity;
     48     private final ArrayMap<SummaryProvider, ComponentName> mSummaryMap = new ArrayMap<>();
     49     private final List<Tile> mTiles = new ArrayList<>();
     50 
     51     private final Worker mWorker;
     52     private final Handler mHandler;
     53     private final HandlerThread mWorkerThread;
     54 
     55     private DashboardAdapter mAdapter;
     56     private boolean mListening;
     57     private boolean mWorkerListening;
     58     private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>();
     59 
     60     public SummaryLoader(Activity activity, List<DashboardCategory> categories) {
     61         mHandler = new Handler();
     62         mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
     63         mWorkerThread.start();
     64         mWorker = new Worker(mWorkerThread.getLooper());
     65         mActivity = activity;
     66         for (int i = 0; i < categories.size(); i++) {
     67             List<Tile> tiles = categories.get(i).tiles;
     68             for (int j = 0; j < tiles.size(); j++) {
     69                 Tile tile = tiles.get(j);
     70                 mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
     71             }
     72         }
     73     }
     74 
     75     public void release() {
     76         mWorkerThread.quitSafely();
     77         // Make sure we aren't listening.
     78         setListeningW(false);
     79     }
     80 
     81     public void setAdapter(DashboardAdapter adapter) {
     82         mAdapter = adapter;
     83     }
     84 
     85     public void setSummary(SummaryProvider provider, final CharSequence summary) {
     86         final ComponentName component= mSummaryMap.get(provider);
     87         mHandler.post(new Runnable() {
     88             @Override
     89             public void run() {
     90                 // Since tiles are not always cached (like on locale change for instance),
     91                 // we need to always get the latest one.
     92                 if (!(mActivity instanceof SettingsDrawerActivity)) {
     93                     if (DEBUG) {
     94                         Log.d(TAG, "Can't get category list.");
     95                     }
     96                     return;
     97                 }
     98                 final List<DashboardCategory> categories =
     99                         ((SettingsDrawerActivity) mActivity).getDashboardCategories();
    100                 final Tile tile = getTileFromCategory(categories, component);
    101                 if (tile == null) {
    102                     if (DEBUG) {
    103                         Log.d(TAG, "Can't find tile for " + component);
    104                     }
    105                     return;
    106                 }
    107                 if (DEBUG) {
    108                     Log.d(TAG, "setSummary " + tile.title + " - " + summary);
    109                 }
    110                 tile.summary = summary;
    111                 mAdapter.notifyChanged(tile);
    112             }
    113         });
    114     }
    115 
    116     /**
    117      * Only call from the main thread.
    118      */
    119     public void setListening(boolean listening) {
    120         if (mListening == listening) return;
    121         mListening = listening;
    122         // Unregister listeners immediately.
    123         for (int i = 0; i < mReceivers.size(); i++) {
    124             mActivity.unregisterReceiver(mReceivers.valueAt(i));
    125         }
    126         mReceivers.clear();
    127         mWorker.removeMessages(Worker.MSG_SET_LISTENING);
    128         mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget();
    129     }
    130 
    131     private SummaryProvider getSummaryProvider(Tile tile) {
    132         if (!mActivity.getPackageName().equals(tile.intent.getComponent().getPackageName())) {
    133             // Not within Settings, can't load Summary directly.
    134             // TODO: Load summary indirectly.
    135             return null;
    136         }
    137         Bundle metaData = getMetaData(tile);
    138         if (metaData == null) {
    139             if (DEBUG) Log.d(TAG, "No metadata specified for " + tile.intent.getComponent());
    140             return null;
    141         }
    142         String clsName = metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
    143         if (clsName == null) {
    144             if (DEBUG) Log.d(TAG, "No fragment specified for " + tile.intent.getComponent());
    145             return null;
    146         }
    147         try {
    148             Class<?> cls = Class.forName(clsName);
    149             Field field = cls.getField(SUMMARY_PROVIDER_FACTORY);
    150             SummaryProviderFactory factory = (SummaryProviderFactory) field.get(null);
    151             return factory.createSummaryProvider(mActivity, this);
    152         } catch (ClassNotFoundException e) {
    153             if (DEBUG) Log.d(TAG, "Couldn't find " + clsName, e);
    154         } catch (NoSuchFieldException e) {
    155             if (DEBUG) Log.d(TAG, "Couldn't find " + SUMMARY_PROVIDER_FACTORY, e);
    156         } catch (ClassCastException e) {
    157             if (DEBUG) Log.d(TAG, "Couldn't cast " + SUMMARY_PROVIDER_FACTORY, e);
    158         } catch (IllegalAccessException e) {
    159             if (DEBUG) Log.d(TAG, "Couldn't get " + SUMMARY_PROVIDER_FACTORY, e);
    160         }
    161         return null;
    162     }
    163 
    164     private Bundle getMetaData(Tile tile) {
    165         return tile.metaData;
    166     }
    167 
    168     /**
    169      * Registers a receiver and automatically unregisters it when the activity is stopping.
    170      * This ensures that the receivers are unregistered immediately, since most summary loader
    171      * operations are asynchronous.
    172      */
    173     public void registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) {
    174         mActivity.runOnUiThread(new Runnable() {
    175             @Override
    176             public void run() {
    177                 if (!mListening) {
    178                     return;
    179                 }
    180                 mReceivers.add(receiver);
    181                 mActivity.registerReceiver(receiver, filter);
    182             }
    183         });
    184     }
    185 
    186     private synchronized void setListeningW(boolean listening) {
    187         if (mWorkerListening == listening) return;
    188         mWorkerListening = listening;
    189         if (DEBUG) Log.d(TAG, "Listening " + listening);
    190         for (SummaryProvider p : mSummaryMap.keySet()) {
    191             try {
    192                 p.setListening(listening);
    193             } catch (Exception e) {
    194                 Log.d(TAG, "Problem in setListening", e);
    195             }
    196         }
    197     }
    198 
    199     private synchronized void makeProviderW(Tile tile) {
    200         SummaryProvider provider = getSummaryProvider(tile);
    201         if (provider != null) {
    202             if (DEBUG) Log.d(TAG, "Creating " + tile);
    203             mSummaryMap.put(provider, tile.intent.getComponent());
    204         }
    205     }
    206 
    207     private Tile getTileFromCategory(List<DashboardCategory> categories, ComponentName component) {
    208         if (categories == null) {
    209             if (DEBUG) {
    210                 Log.d(TAG, "Category is null, can't find tile");
    211             }
    212             return null;
    213         }
    214         final int categorySize = categories.size();
    215         for (int i = 0; i < categorySize; i++) {
    216             final DashboardCategory category = categories.get(i);
    217             final int tileCount = category.tiles.size();
    218             for (int j = 0; j < tileCount; j++) {
    219                 final Tile tile = category.tiles.get(j);
    220                 if (component.equals(tile.intent.getComponent())) {
    221                     return tile;
    222                 }
    223             }
    224         }
    225         return null;
    226     }
    227 
    228     public interface SummaryProvider {
    229         void setListening(boolean listening);
    230     }
    231 
    232     public interface SummaryProviderFactory {
    233         SummaryProvider createSummaryProvider(Activity activity, SummaryLoader summaryLoader);
    234     }
    235 
    236     private class Worker extends Handler {
    237         private static final int MSG_GET_PROVIDER = 1;
    238         private static final int MSG_SET_LISTENING = 2;
    239 
    240         public Worker(Looper looper) {
    241             super(looper);
    242         }
    243 
    244         @Override
    245         public void handleMessage(Message msg) {
    246             switch (msg.what) {
    247                 case MSG_GET_PROVIDER:
    248                     Tile tile = (Tile) msg.obj;
    249                     makeProviderW(tile);
    250                     break;
    251                 case MSG_SET_LISTENING:
    252                     boolean listening = msg.arg1 != 0;
    253                     setListeningW(listening);
    254                     break;
    255             }
    256         }
    257     }
    258 }
    259