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