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