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 
SummaryLoader(Activity activity, List<DashboardCategory> categories)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 
release()73     public void release() {
74         mWorkerThread.quitSafely();
75         // Make sure we aren't listening.
76         setListeningW(false);
77     }
78 
setAdapter(DashboardAdapter adapter)79     public void setAdapter(DashboardAdapter adapter) {
80         mAdapter = adapter;
81     }
82 
setSummary(SummaryProvider provider, final CharSequence summary)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      */
setListening(boolean listening)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 
getSummaryProvider(Tile tile)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 
getMetaData(Tile tile)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      */
registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter)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 
setListeningW(boolean listening)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 
makeProviderW(Tile tile)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 {
setListening(boolean listening)191         void setListening(boolean listening);
192     }
193 
194     public interface SummaryProviderFactory {
createSummaryProvider(Activity activity, SummaryLoader summaryLoader)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 
Worker(Looper looper)202         public Worker(Looper looper) {
203             super(looper);
204         }
205 
206         @Override
handleMessage(Message msg)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