| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.settings.dashboard; |
| |
| import android.app.Activity; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.IntentFilter; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Process; |
| import android.support.annotation.VisibleForTesting; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import com.android.settings.SettingsActivity; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settingslib.drawer.DashboardCategory; |
| import com.android.settingslib.drawer.SettingsDrawerActivity; |
| import com.android.settingslib.drawer.Tile; |
| |
| import java.lang.reflect.Field; |
| import java.util.List; |
| |
| public class SummaryLoader { |
| private static final boolean DEBUG = DashboardSummary.DEBUG; |
| private static final String TAG = "SummaryLoader"; |
| |
| public static final String SUMMARY_PROVIDER_FACTORY = "SUMMARY_PROVIDER_FACTORY"; |
| |
| private final Activity mActivity; |
| private final ArrayMap<SummaryProvider, ComponentName> mSummaryMap = new ArrayMap<>(); |
| private final DashboardFeatureProvider mDashboardFeatureProvider; |
| private final String mCategoryKey; |
| |
| private final Worker mWorker; |
| private final Handler mHandler; |
| private final HandlerThread mWorkerThread; |
| |
| private SummaryConsumer mSummaryConsumer; |
| private boolean mListening; |
| private boolean mWorkerListening; |
| private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>(); |
| |
| public SummaryLoader(Activity activity, List<DashboardCategory> categories) { |
| mDashboardFeatureProvider = FeatureFactory.getFactory(activity) |
| .getDashboardFeatureProvider(activity); |
| mCategoryKey = null; |
| mHandler = new Handler(); |
| mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND); |
| mWorkerThread.start(); |
| mWorker = new Worker(mWorkerThread.getLooper()); |
| mActivity = activity; |
| for (int i = 0; i < categories.size(); i++) { |
| List<Tile> tiles = categories.get(i).tiles; |
| for (int j = 0; j < tiles.size(); j++) { |
| Tile tile = tiles.get(j); |
| mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget(); |
| } |
| } |
| } |
| |
| public SummaryLoader(Activity activity, String categoryKey) { |
| mDashboardFeatureProvider = FeatureFactory.getFactory(activity) |
| .getDashboardFeatureProvider(activity); |
| mCategoryKey = categoryKey; |
| mHandler = new Handler(); |
| mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND); |
| mWorkerThread.start(); |
| mWorker = new Worker(mWorkerThread.getLooper()); |
| mActivity = activity; |
| |
| final DashboardCategory category = |
| mDashboardFeatureProvider.getTilesForCategory(categoryKey); |
| if (category == null || category.tiles == null) { |
| return; |
| } |
| |
| List<Tile> tiles = category.tiles; |
| for (Tile tile : tiles) { |
| mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget(); |
| } |
| } |
| |
| public void release() { |
| mWorkerThread.quitSafely(); |
| // Make sure we aren't listening. |
| setListeningW(false); |
| } |
| |
| public void setSummaryConsumer(SummaryConsumer summaryConsumer) { |
| mSummaryConsumer = summaryConsumer; |
| } |
| |
| public void setSummary(SummaryProvider provider, final CharSequence summary) { |
| final ComponentName component = mSummaryMap.get(provider); |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| |
| final Tile tile = getTileFromCategory( |
| mDashboardFeatureProvider.getTilesForCategory(mCategoryKey), component); |
| |
| if (tile == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Can't find tile for " + component); |
| } |
| return; |
| } |
| if (DEBUG) { |
| Log.d(TAG, "setSummary " + tile.title + " - " + summary); |
| } |
| |
| updateSummaryIfNeeded(tile, summary); |
| } |
| }); |
| } |
| |
| @VisibleForTesting |
| void updateSummaryIfNeeded(Tile tile, CharSequence summary) { |
| if (TextUtils.equals(tile.summary, summary)) { |
| if (DEBUG) { |
| Log.d(TAG, "Summary doesn't change, skipping summary update for " + tile.title); |
| } |
| return; |
| } |
| tile.summary = summary; |
| if (mSummaryConsumer != null) { |
| mSummaryConsumer.notifySummaryChanged(tile); |
| } else { |
| if (DEBUG) { |
| Log.d(TAG, "SummaryConsumer is null, skipping summary update for " |
| + tile.title); |
| } |
| } |
| } |
| |
| /** |
| * Only call from the main thread. |
| */ |
| public void setListening(boolean listening) { |
| if (mListening == listening) return; |
| mListening = listening; |
| // Unregister listeners immediately. |
| for (int i = 0; i < mReceivers.size(); i++) { |
| mActivity.unregisterReceiver(mReceivers.valueAt(i)); |
| } |
| mReceivers.clear(); |
| mWorker.removeMessages(Worker.MSG_SET_LISTENING); |
| mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget(); |
| } |
| |
| private SummaryProvider getSummaryProvider(Tile tile) { |
| if (!mActivity.getPackageName().equals(tile.intent.getComponent().getPackageName())) { |
| // Not within Settings, can't load Summary directly. |
| // TODO: Load summary indirectly. |
| return null; |
| } |
| Bundle metaData = getMetaData(tile); |
| if (metaData == null) { |
| if (DEBUG) Log.d(TAG, "No metadata specified for " + tile.intent.getComponent()); |
| return null; |
| } |
| String clsName = metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS); |
| if (clsName == null) { |
| if (DEBUG) Log.d(TAG, "No fragment specified for " + tile.intent.getComponent()); |
| return null; |
| } |
| try { |
| Class<?> cls = Class.forName(clsName); |
| Field field = cls.getField(SUMMARY_PROVIDER_FACTORY); |
| SummaryProviderFactory factory = (SummaryProviderFactory) field.get(null); |
| return factory.createSummaryProvider(mActivity, this); |
| } catch (ClassNotFoundException e) { |
| if (DEBUG) Log.d(TAG, "Couldn't find " + clsName, e); |
| } catch (NoSuchFieldException e) { |
| if (DEBUG) Log.d(TAG, "Couldn't find " + SUMMARY_PROVIDER_FACTORY, e); |
| } catch (ClassCastException e) { |
| if (DEBUG) Log.d(TAG, "Couldn't cast " + SUMMARY_PROVIDER_FACTORY, e); |
| } catch (IllegalAccessException e) { |
| if (DEBUG) Log.d(TAG, "Couldn't get " + SUMMARY_PROVIDER_FACTORY, e); |
| } |
| return null; |
| } |
| |
| private Bundle getMetaData(Tile tile) { |
| return tile.metaData; |
| } |
| |
| /** |
| * Registers a receiver and automatically unregisters it when the activity is stopping. |
| * This ensures that the receivers are unregistered immediately, since most summary loader |
| * operations are asynchronous. |
| */ |
| public void registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) { |
| mActivity.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| if (!mListening) { |
| return; |
| } |
| mReceivers.add(receiver); |
| mActivity.registerReceiver(receiver, filter); |
| } |
| }); |
| } |
| |
| private synchronized void setListeningW(boolean listening) { |
| if (mWorkerListening == listening) return; |
| mWorkerListening = listening; |
| if (DEBUG) Log.d(TAG, "Listening " + listening); |
| for (SummaryProvider p : mSummaryMap.keySet()) { |
| try { |
| p.setListening(listening); |
| } catch (Exception e) { |
| Log.d(TAG, "Problem in setListening", e); |
| } |
| } |
| } |
| |
| private synchronized void makeProviderW(Tile tile) { |
| SummaryProvider provider = getSummaryProvider(tile); |
| if (provider != null) { |
| if (DEBUG) Log.d(TAG, "Creating " + tile); |
| mSummaryMap.put(provider, tile.intent.getComponent()); |
| } |
| } |
| |
| private Tile getTileFromCategory(List<DashboardCategory> categories, ComponentName component) { |
| if (categories == null) { |
| if (DEBUG) { |
| Log.d(TAG, "Category is null, can't find tile"); |
| } |
| return null; |
| } |
| final int categorySize = categories.size(); |
| for (int i = 0; i < categorySize; i++) { |
| final DashboardCategory category = categories.get(i); |
| final Tile tile = getTileFromCategory(category, component); |
| if (tile != null) { |
| return tile; |
| } |
| } |
| return null; |
| } |
| |
| private Tile getTileFromCategory(DashboardCategory category, ComponentName component) { |
| if (category == null || category.tiles == null) { |
| return null; |
| } |
| final int tileCount = category.tiles.size(); |
| for (int j = 0; j < tileCount; j++) { |
| final Tile tile = category.tiles.get(j); |
| if (component.equals(tile.intent.getComponent())) { |
| return tile; |
| } |
| } |
| return null; |
| } |
| |
| public interface SummaryProvider { |
| void setListening(boolean listening); |
| } |
| |
| public interface SummaryConsumer { |
| void notifySummaryChanged(Tile tile); |
| } |
| |
| public interface SummaryProviderFactory { |
| SummaryProvider createSummaryProvider(Activity activity, SummaryLoader summaryLoader); |
| } |
| |
| private class Worker extends Handler { |
| private static final int MSG_GET_PROVIDER = 1; |
| private static final int MSG_SET_LISTENING = 2; |
| |
| public Worker(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_GET_PROVIDER: |
| Tile tile = (Tile) msg.obj; |
| makeProviderW(tile); |
| break; |
| case MSG_SET_LISTENING: |
| boolean listening = msg.arg1 != 0; |
| setListeningW(listening); |
| break; |
| } |
| } |
| } |
| } |