10b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka/*
20b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * Copyright (C) 2012 The Android Open Source Project
30b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka *
40b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
50b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * you may not use this file except in compliance with the License.
60b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * You may obtain a copy of the License at
70b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka *
80b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
90b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka *
100b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * Unless required by applicable law or agreed to in writing, software
110b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
120b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * See the License for the specific language governing permissions and
140b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka * limitations under the License.
150b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka */
160b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
170b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkapackage com.android.settings;
180b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
190b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.appwidget.AppWidgetManager;
200b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.appwidget.AppWidgetProviderInfo;
210b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.content.Context;
220b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.content.Intent;
230b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.os.Bundle;
240b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.os.Parcelable;
250b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport android.util.Log;
260b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
270b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport java.text.Collator;
280b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport java.util.ArrayList;
290b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport java.util.Collections;
300b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport java.util.Comparator;
310b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkaimport java.util.List;
320b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
330b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurkapublic class AppWidgetLoader<Item extends AppWidgetLoader.LabelledItem> {
340b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    private static final String TAG = "AppWidgetAdapter";
350b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    private static final boolean LOGD = AppWidgetPickActivity.LOGD;
360b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
370b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    private Context mContext;
380b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    private AppWidgetManager mAppWidgetManager;
390b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    ItemConstructor<Item> mItemConstructor;
400b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
410b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    interface LabelledItem {
420b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        CharSequence getLabel();
430b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
440b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
450b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    public AppWidgetLoader(Context context, AppWidgetManager appWidgetManager,
460b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            ItemConstructor<Item> itemConstructor) {
470b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        mContext = context;
480b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        mAppWidgetManager = appWidgetManager;
490b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        mItemConstructor = itemConstructor;
500b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
510b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
520b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    /**
530b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * Create list entries for any custom widgets requested through
540b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * {@link AppWidgetManager#EXTRA_CUSTOM_INFO}.
550b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     */
560b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    void putCustomAppWidgets(List<Item> items, Intent intent) {
570b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        // get and validate the extras they gave us
580b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        ArrayList<AppWidgetProviderInfo> customInfo = null;
590b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        ArrayList<Bundle> customExtras = null;
600b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        try_custom_items: {
610b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            customInfo = intent.getParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO);
620b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            if (customInfo == null || customInfo.size() == 0) {
630b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                Log.i(TAG, "EXTRA_CUSTOM_INFO not present.");
640b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                break try_custom_items;
650b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
660b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
670b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            int customInfoSize = customInfo.size();
680b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            for (int i=0; i<customInfoSize; i++) {
690b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                Parcelable p = customInfo.get(i);
700b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                if (p == null || !(p instanceof AppWidgetProviderInfo)) {
710b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    customInfo = null;
720b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    Log.e(TAG, "error using EXTRA_CUSTOM_INFO index=" + i);
730b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    break try_custom_items;
740b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                }
750b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
760b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
770b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            customExtras = intent.getParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS);
780b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            if (customExtras == null) {
790b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                customInfo = null;
800b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                Log.e(TAG, "EXTRA_CUSTOM_INFO without EXTRA_CUSTOM_EXTRAS");
810b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                break try_custom_items;
820b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
830b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
840b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            int customExtrasSize = customExtras.size();
850b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            if (customInfoSize != customExtrasSize) {
860b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                customInfo = null;
870b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                customExtras = null;
880b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                Log.e(TAG, "list size mismatch: EXTRA_CUSTOM_INFO: " + customInfoSize
890b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                        + " EXTRA_CUSTOM_EXTRAS: " + customExtrasSize);
900b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                break try_custom_items;
910b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
920b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
930b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
940b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            for (int i=0; i<customExtrasSize; i++) {
950b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                Parcelable p = customExtras.get(i);
960b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                if (p == null || !(p instanceof Bundle)) {
970b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    customInfo = null;
980b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    customExtras = null;
990b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    Log.e(TAG, "error using EXTRA_CUSTOM_EXTRAS index=" + i);
1000b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    break try_custom_items;
1010b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                }
1020b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
1030b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        }
1040b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1050b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        if (LOGD) Log.d(TAG, "Using " + customInfo.size() + " custom items");
1060b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        putAppWidgetItems(customInfo, customExtras, items, 0, true);
1070b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
1080b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1090b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1100b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    /**
1110b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * Create list entries for the given {@link AppWidgetProviderInfo} widgets,
1120b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * inserting extras if provided.
1130b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     */
1140b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    void putAppWidgetItems(List<AppWidgetProviderInfo> appWidgets,
1150b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            List<Bundle> customExtras, List<Item> items, int categoryFilter,
1160b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            boolean ignoreFilter) {
1170b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        if (appWidgets == null) return;
1180b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        final int size = appWidgets.size();
1190b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        for (int i = 0; i < size; i++) {
1200b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            AppWidgetProviderInfo info = appWidgets.get(i);
1210b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1220b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            // We remove any widgets whose category isn't included in the filter
1230b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            if (!ignoreFilter && (info.widgetCategory & categoryFilter) == 0) {
1240b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                continue;
1250b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
1260b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1270b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            Item item = mItemConstructor.createItem(mContext, info,
1280b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                    customExtras != null ? customExtras.get(i) : null);
1290b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1300b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            items.add(item);
1310b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        }
1320b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
1330b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1340b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    public interface ItemConstructor<Item> {
1350b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras);
1360b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
1370b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1380b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1390b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    /**
1400b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * Build and return list of items to be shown in dialog. This will mix both
1410b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * installed {@link AppWidgetProviderInfo} and those provided through
1420b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * {@link AppWidgetManager#EXTRA_CUSTOM_INFO}, sorting them alphabetically.
1430b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     */
1440b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    protected List<Item> getItems(Intent intent) {
1450b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        boolean sortCustomAppWidgets =
1460b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                intent.getBooleanExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, true);
1470b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1480b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        List<Item> items = new ArrayList<Item>();
1490b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1500b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        // Default category is home screen
1510b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        int categoryFilter = intent.getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
1520b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
1530b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1540b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        putInstalledAppWidgets(items, categoryFilter);
1550b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1560b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        // Sort all items together by label
1570b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        if (sortCustomAppWidgets) {
1580b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            putCustomAppWidgets(items, intent);
1590b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        }
1600b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        Collections.sort(items, new Comparator<Item>() {
1610b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            Collator mCollator = Collator.getInstance();
1620b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1630b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            public int compare(Item lhs, Item rhs) {
1640b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka                return mCollator.compare(lhs.getLabel(), rhs.getLabel());
1650b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            }
1660b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        });
1670b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        if (!sortCustomAppWidgets) {
1680b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            List<Item> customItems = new ArrayList<Item>();
1690b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            putCustomAppWidgets(customItems, intent);
1700b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka            items.addAll(customItems);
1710b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        }
1720b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        return items;
1730b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
1740b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka
1750b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    /**
1760b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     * Create list entries for installed {@link AppWidgetProviderInfo} widgets.
1770b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka     */
1780b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    void putInstalledAppWidgets(List<Item> items, int categoryFilter) {
179441588d2213e1088f3af737a59e589ddcf0cf867Adam Cohen        List<AppWidgetProviderInfo> installed =
180441588d2213e1088f3af737a59e589ddcf0cf867Adam Cohen                mAppWidgetManager.getInstalledProviders(categoryFilter);
1810b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka        putAppWidgetItems(installed, null, items, categoryFilter, false);
1820b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka    }
1830b2bd8d11d6947a6ce71db0691ba341aec7a2964Michael Jurka}
184