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.support.annotation.VisibleForTesting;
     29 import android.text.TextUtils;
     30 import android.util.ArrayMap;
     31 import android.util.ArraySet;
     32 import android.util.Log;
     33 
     34 import com.android.settings.SettingsActivity;
     35 import com.android.settings.overlay.FeatureFactory;
     36 import com.android.settingslib.drawer.DashboardCategory;
     37 import com.android.settingslib.drawer.Tile;
     38 
     39 import java.lang.reflect.Field;
     40 import java.util.List;
     41 
     42 public class SummaryLoader {
     43     private static final boolean DEBUG = DashboardSummary.DEBUG;
     44     private static final String TAG = "SummaryLoader";
     45 
     46     public static final String SUMMARY_PROVIDER_FACTORY = "SUMMARY_PROVIDER_FACTORY";
     47 
     48     private final Activity mActivity;
     49     private final ArrayMap<SummaryProvider, ComponentName> mSummaryProviderMap = new ArrayMap<>();
     50     private final ArrayMap<String, CharSequence> mSummaryTextMap = new ArrayMap<>();
     51     private final DashboardFeatureProvider mDashboardFeatureProvider;
     52     private final String mCategoryKey;
     53 
     54     private final Worker mWorker;
     55     private final Handler mHandler;
     56     private final HandlerThread mWorkerThread;
     57 
     58     private SummaryConsumer mSummaryConsumer;
     59     private boolean mListening;
     60     private boolean mWorkerListening;
     61     private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>();
     62 
     63     public SummaryLoader(Activity activity, List<DashboardCategory> categories) {
     64         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
     65                 .getDashboardFeatureProvider(activity);
     66         mCategoryKey = null;
     67         mHandler = new Handler();
     68         mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
     69         mWorkerThread.start();
     70         mWorker = new Worker(mWorkerThread.getLooper());
     71         mActivity = activity;
     72         for (int i = 0; i < categories.size(); i++) {
     73             List<Tile> tiles = categories.get(i).tiles;
     74             for (int j = 0; j < tiles.size(); j++) {
     75                 Tile tile = tiles.get(j);
     76                 mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
     77             }
     78         }
     79     }
     80 
     81     public SummaryLoader(Activity activity, String categoryKey) {
     82         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
     83                 .getDashboardFeatureProvider(activity);
     84         mCategoryKey = categoryKey;
     85         mHandler = new Handler();
     86         mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
     87         mWorkerThread.start();
     88         mWorker = new Worker(mWorkerThread.getLooper());
     89         mActivity = activity;
     90 
     91         final DashboardCategory category =
     92                 mDashboardFeatureProvider.getTilesForCategory(categoryKey);
     93         if (category == null || category.tiles == null) {
     94             return;
     95         }
     96 
     97         List<Tile> tiles = category.tiles;
     98         for (Tile tile : tiles) {
     99             mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
    100         }
    101     }
    102 
    103     public void release() {
    104         mWorkerThread.quitSafely();
    105         // Make sure we aren't listening.
    106         setListeningW(false);
    107     }
    108 
    109     public void setSummaryConsumer(SummaryConsumer summaryConsumer) {
    110         mSummaryConsumer = summaryConsumer;
    111     }
    112 
    113     public void setSummary(SummaryProvider provider, final CharSequence summary) {
    114         final ComponentName component = mSummaryProviderMap.get(provider);
    115         mHandler.post(new Runnable() {
    116             @Override
    117             public void run() {
    118 
    119                 final Tile tile = getTileFromCategory(
    120                         mDashboardFeatureProvider.getTilesForCategory(mCategoryKey), component);
    121 
    122                 if (tile == null) {
    123                     if (DEBUG) {
    124                         Log.d(TAG, "Can't find tile for " + component);
    125                     }
    126                     return;
    127                 }
    128                 if (DEBUG) {
    129                     Log.d(TAG, "setSummary " + tile.title + " - " + summary);
    130                 }
    131 
    132                 updateSummaryIfNeeded(tile, summary);
    133             }
    134         });
    135     }
    136 
    137     @VisibleForTesting
    138     void updateSummaryIfNeeded(Tile tile, CharSequence summary) {
    139         if (TextUtils.equals(tile.summary, summary)) {
    140             if (DEBUG) {
    141                 Log.d(TAG, "Summary doesn't change, skipping summary update for " + tile.title);
    142             }
    143             return;
    144         }
    145         mSummaryTextMap.put(mDashboardFeatureProvider.getDashboardKeyForTile(tile), summary);
    146         tile.summary = summary;
    147         if (mSummaryConsumer != null) {
    148             mSummaryConsumer.notifySummaryChanged(tile);
    149         } else {
    150             if (DEBUG) {
    151                 Log.d(TAG, "SummaryConsumer is null, skipping summary update for "
    152                         + tile.title);
    153             }
    154         }
    155     }
    156 
    157     /**
    158      * Only call from the main thread.
    159      */
    160     public void setListening(boolean listening) {
    161         if (mListening == listening) return;
    162         mListening = listening;
    163         // Unregister listeners immediately.
    164         for (int i = 0; i < mReceivers.size(); i++) {
    165             mActivity.unregisterReceiver(mReceivers.valueAt(i));
    166         }
    167         mReceivers.clear();
    168         mWorker.removeMessages(Worker.MSG_SET_LISTENING);
    169         mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget();
    170     }
    171 
    172     private SummaryProvider getSummaryProvider(Tile tile) {
    173         if (!mActivity.getPackageName().equals(tile.intent.getComponent().getPackageName())) {
    174             // Not within Settings, can't load Summary directly.
    175             // TODO: Load summary indirectly.
    176             return null;
    177         }
    178         Bundle metaData = getMetaData(tile);
    179         if (metaData == null) {
    180             if (DEBUG) Log.d(TAG, "No metadata specified for " + tile.intent.getComponent());
    181             return null;
    182         }
    183         String clsName = metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
    184         if (clsName == null) {
    185             if (DEBUG) Log.d(TAG, "No fragment specified for " + tile.intent.getComponent());
    186             return null;
    187         }
    188         try {
    189             Class<?> cls = Class.forName(clsName);
    190             Field field = cls.getField(SUMMARY_PROVIDER_FACTORY);
    191             SummaryProviderFactory factory = (SummaryProviderFactory) field.get(null);
    192             return factory.createSummaryProvider(mActivity, this);
    193         } catch (ClassNotFoundException e) {
    194             if (DEBUG) Log.d(TAG, "Couldn't find " + clsName, e);
    195         } catch (NoSuchFieldException e) {
    196             if (DEBUG) Log.d(TAG, "Couldn't find " + SUMMARY_PROVIDER_FACTORY, e);
    197         } catch (ClassCastException e) {
    198             if (DEBUG) Log.d(TAG, "Couldn't cast " + SUMMARY_PROVIDER_FACTORY, e);
    199         } catch (IllegalAccessException e) {
    200             if (DEBUG) Log.d(TAG, "Couldn't get " + SUMMARY_PROVIDER_FACTORY, e);
    201         }
    202         return null;
    203     }
    204 
    205     private Bundle getMetaData(Tile tile) {
    206         return tile.metaData;
    207     }
    208 
    209     /**
    210      * Registers a receiver and automatically unregisters it when the activity is stopping.
    211      * This ensures that the receivers are unregistered immediately, since most summary loader
    212      * operations are asynchronous.
    213      */
    214     public void registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) {
    215         mActivity.runOnUiThread(new Runnable() {
    216             @Override
    217             public void run() {
    218                 if (!mListening) {
    219                     return;
    220                 }
    221                 mReceivers.add(receiver);
    222                 mActivity.registerReceiver(receiver, filter);
    223             }
    224         });
    225     }
    226 
    227     /**
    228      * Updates all tile's summary to latest cached version. This is necessary to handle the case
    229      * where category is updated after summary change.
    230      */
    231     public void updateSummaryToCache(DashboardCategory category) {
    232         if (category == null) {
    233             return;
    234         }
    235         for (Tile tile : category.tiles) {
    236             final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
    237             if (mSummaryTextMap.containsKey(key)) {
    238                 tile.summary = mSummaryTextMap.get(key);
    239             }
    240         }
    241     }
    242 
    243     private synchronized void setListeningW(boolean listening) {
    244         if (mWorkerListening == listening) return;
    245         mWorkerListening = listening;
    246         if (DEBUG) Log.d(TAG, "Listening " + listening);
    247         for (SummaryProvider p : mSummaryProviderMap.keySet()) {
    248             try {
    249                 p.setListening(listening);
    250             } catch (Exception e) {
    251                 Log.d(TAG, "Problem in setListening", e);
    252             }
    253         }
    254     }
    255 
    256     private synchronized void makeProviderW(Tile tile) {
    257         SummaryProvider provider = getSummaryProvider(tile);
    258         if (provider != null) {
    259             if (DEBUG) Log.d(TAG, "Creating " + tile);
    260             mSummaryProviderMap.put(provider, tile.intent.getComponent());
    261         }
    262     }
    263 
    264     private Tile getTileFromCategory(DashboardCategory category, ComponentName component) {
    265         if (category == null || category.tiles == null) {
    266             return null;
    267         }
    268         final int tileCount = category.tiles.size();
    269         for (int j = 0; j < tileCount; j++) {
    270             final Tile tile = category.tiles.get(j);
    271             if (component.equals(tile.intent.getComponent())) {
    272                 return tile;
    273             }
    274         }
    275         return null;
    276     }
    277 
    278 
    279 
    280     public interface SummaryProvider {
    281         void setListening(boolean listening);
    282     }
    283 
    284     public interface SummaryConsumer {
    285         void notifySummaryChanged(Tile tile);
    286     }
    287 
    288     public interface SummaryProviderFactory {
    289         SummaryProvider createSummaryProvider(Activity activity, SummaryLoader summaryLoader);
    290     }
    291 
    292     private class Worker extends Handler {
    293         private static final int MSG_GET_PROVIDER = 1;
    294         private static final int MSG_SET_LISTENING = 2;
    295 
    296         public Worker(Looper looper) {
    297             super(looper);
    298         }
    299 
    300         @Override
    301         public void handleMessage(Message msg) {
    302             switch (msg.what) {
    303                 case MSG_GET_PROVIDER:
    304                     Tile tile = (Tile) msg.obj;
    305                     makeProviderW(tile);
    306                     break;
    307                 case MSG_SET_LISTENING:
    308                     boolean listening = msg.arg1 != 0;
    309                     setListeningW(listening);
    310                     break;
    311             }
    312         }
    313     }
    314 }
    315