• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SummaryLoader(Activity activity, List<DashboardCategory> categories)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 
SummaryLoader(Activity activity, String categoryKey)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 
release()103     public void release() {
104         mWorkerThread.quitSafely();
105         // Make sure we aren't listening.
106         setListeningW(false);
107     }
108 
setSummaryConsumer(SummaryConsumer summaryConsumer)109     public void setSummaryConsumer(SummaryConsumer summaryConsumer) {
110         mSummaryConsumer = summaryConsumer;
111     }
112 
setSummary(SummaryProvider provider, final CharSequence summary)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
updateSummaryIfNeeded(Tile tile, CharSequence summary)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      */
setListening(boolean listening)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 
getSummaryProvider(Tile tile)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 
getMetaData(Tile tile)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      */
registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter)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      */
updateSummaryToCache(DashboardCategory category)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 
setListeningW(boolean listening)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 
makeProviderW(Tile tile)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 
getTileFromCategory(DashboardCategory category, ComponentName component)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 {
setListening(boolean listening)281         void setListening(boolean listening);
282     }
283 
284     public interface SummaryConsumer {
notifySummaryChanged(Tile tile)285         void notifySummaryChanged(Tile tile);
286     }
287 
288     public interface SummaryProviderFactory {
createSummaryProvider(Activity activity, SummaryLoader summaryLoader)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 
Worker(Looper looper)296         public Worker(Looper looper) {
297             super(looper);
298         }
299 
300         @Override
handleMessage(Message msg)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