package com.nolanlawson.apptracker; import java.util.ArrayList; import java.util.List; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.view.View; import android.widget.RemoteViews; import com.nolanlawson.apptracker.db.AppHistoryDbHelper; import com.nolanlawson.apptracker.db.AppHistoryEntry; import com.nolanlawson.apptracker.db.SortType; import com.nolanlawson.apptracker.helper.ActivityInfoHelper; import com.nolanlawson.apptracker.helper.PreferenceHelper; import com.nolanlawson.apptracker.helper.ResourceIdHelper; import com.nolanlawson.apptracker.helper.SubtextHelper; import com.nolanlawson.apptracker.util.Pair; import com.nolanlawson.apptracker.util.StopWatch; import com.nolanlawson.apptracker.util.UtilLogger; public class WidgetUpdater { public static final int APPS_PER_PAGE = 4; public static final String NEW_PAGE_NUMBER = "newPageNumber"; private static final String URI_SCHEME = "app_tracker_widget"; private static UtilLogger log = new UtilLogger(WidgetUpdater.class); /** * update only the app widgets associated with the given id * @param context * @param dbHelper * @param appWidgetId */ public static void updateWidget(Context context, AppHistoryDbHelper dbHelper, int appWidgetId) { log.d("updating widget for appWidgetId: " + appWidgetId); AppWidgetManager manager = AppWidgetManager.getInstance(context); RemoteViews updateViews = buildUpdate(context, dbHelper, appWidgetId); if (updateViews != null) { manager.updateAppWidget(appWidgetId, updateViews); } } /** * Update all app tracker app widgets * @param context * @param dbHelper */ public static void updateWidget(Context context, AppHistoryDbHelper dbHelper) { ComponentName widget = new ComponentName(context, AppTrackerWidgetProvider.class); AppWidgetManager manager = AppWidgetManager.getInstance(context); int[] appWidgetIds = manager.getAppWidgetIds(widget); log.d("updating widget for all app widget ids: %s", appWidgetIds); for (int appWidgetId : appWidgetIds) { // perform this check because there is a bug in Android where, if you // exit from the configuration screen, Android will never forget that // appWidgetId and it will remain as a "stale" appWidgetId boolean exists = PreferenceHelper.checkIfAppExists(context, appWidgetId); if (!exists) { log.d("skipping stale appWidgetId %d", appWidgetId); continue; } RemoteViews updateViews = buildUpdate(context, dbHelper, appWidgetId); if (updateViews == null) { // nothing to see yet continue; } manager.updateAppWidget(appWidgetId, updateViews); } } private static RemoteViews buildUpdate(Context context, AppHistoryDbHelper dbHelper, int appWidgetId) { StopWatch stopWatch1 = new StopWatch("buildUpdate()"); RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.tracker_widget); int pageNumber = PreferenceHelper.getCurrentPageNumber(context, appWidgetId); String sortTypeAsString = PreferenceHelper.getSortTypePreference(context, appWidgetId); SortType sortType = SortType.findByName(context, sortTypeAsString); PackageManager packageManager = context.getPackageManager(); StopWatch stopWatch2 = new StopWatch("getActivityInfos()"); List<Pair<AppHistoryEntry,ActivityInfo>> activityInfos = ActivityInfoHelper.getActivityInfos( context, dbHelper, packageManager, pageNumber, APPS_PER_PAGE, sortType, false); stopWatch2.log(log); if (activityInfos.isEmpty()) { // nothing to show for now; just return stopWatch1.log(log); return null; } StopWatch stopWatch4 = new StopWatch("the forloop"); List<CharSequence> labels = new ArrayList<CharSequence>(); for (int i = 0; i < APPS_PER_PAGE; i++) { if (i < activityInfos.size()) { Pair<AppHistoryEntry,ActivityInfo> pair = activityInfos.get(i); AppHistoryEntry appHistoryEntry = pair.getFirst(); ActivityInfo activityInfo = pair.getSecond(); String label = ActivityInfoHelper.loadLabelFromAppHistoryEntry(context, appHistoryEntry, activityInfo, packageManager); Bitmap iconBitmap = ActivityInfoHelper.loadIconFromAppHistoryEntry(context, appHistoryEntry, activityInfo, packageManager); String subtextText = SubtextHelper.createSubtext(context, sortType, appHistoryEntry, false); updateViews.setTextViewText(ResourceIdHelper.getAppTitleId(i), label); updateViews.setTextViewText(ResourceIdHelper.getAppDescriptionId(i), subtextText); updateViews.setImageViewBitmap(ResourceIdHelper.getAppIconId(i), iconBitmap); updateViews.setViewVisibility(ResourceIdHelper.getRelativeLayoutId(i), View.VISIBLE); Intent intent = appHistoryEntry.toIntent(); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* no requestCode */, intent, 0 /* no flags */); updateViews.setOnClickPendingIntent(ResourceIdHelper.getRelativeLayoutId(i), pendingIntent); labels.add(label); } else { // no entry; just hide the icon and text updateViews.setViewVisibility(ResourceIdHelper.getRelativeLayoutId(i), View.INVISIBLE); } } stopWatch4.log(log); log.d("Labels are: %s", labels); StopWatch stopWatch3 = new StopWatch("a bunch of set() functions"); setAppTitleVisibility(context, appWidgetId, updateViews); setSubtextVisibility(context, appWidgetId, updateViews, sortType); setBackgroundVisibility(context, appWidgetId, updateViews); setBackAndForwardButtons(context, appWidgetId, updateViews, pageNumber, dbHelper, sortType); stopWatch3.log(log); stopWatch1.log(log); return updateViews; } private static void setBackgroundVisibility(Context context, int appWidgetId, RemoteViews updateViews) { boolean showBackground = PreferenceHelper.getShowBackgroundPreference(context, appWidgetId); updateViews.setViewVisibility(R.id.widget_background, showBackground ? View.VISIBLE : View.INVISIBLE); } private static void setBackAndForwardButtons(Context context, int appWidgetId, RemoteViews updateViews, int pageNumber, AppHistoryDbHelper dbHelper, SortType sortType) { boolean lockPage = PreferenceHelper.getLockPagePreference(context, appWidgetId); if (lockPage) { int goneOrInvisible = chooseGoneOrInvisible(context, appWidgetId); updateViews.setViewVisibility(R.id.back_button, goneOrInvisible); updateViews.setViewVisibility(R.id.forward_button, goneOrInvisible); } else { // if no more app results, disable forward button boolean noMoreAppResults; synchronized (AppHistoryDbHelper.class) { noMoreAppResults = dbHelper.findCountOfInstalledAppHistoryEntries(sortType, APPS_PER_PAGE, (pageNumber + 1) * APPS_PER_PAGE, false) == 0; } updateViews.setViewVisibility(R.id.forward_button, noMoreAppResults ? View.INVISIBLE : View.VISIBLE); log.d("page number is: %d", pageNumber); updateViews.setOnClickPendingIntent(R.id.forward_button, getPendingIntentForForwardOrBackButton(context, true, pageNumber + 1, appWidgetId)); // if no previous app results, disable back button updateViews.setViewVisibility(R.id.back_button, pageNumber == 0 ? View.INVISIBLE : View.VISIBLE); updateViews.setOnClickPendingIntent(R.id.back_button, getPendingIntentForForwardOrBackButton(context, false, pageNumber - 1, appWidgetId)); } } private static void setSubtextVisibility(Context context, int appWidgetId, RemoteViews updateViews, SortType sortType) { boolean hideSubtext = PreferenceHelper.getHideSubtextPreference(context, appWidgetId, sortType); int subTextVisibility = hideSubtext ? chooseGoneOrInvisible(context, appWidgetId) : View.VISIBLE; updateViews.setViewVisibility(R.id.app_description_1, subTextVisibility); updateViews.setViewVisibility(R.id.app_description_2, subTextVisibility); updateViews.setViewVisibility(R.id.app_description_3, subTextVisibility); updateViews.setViewVisibility(R.id.app_description_4, subTextVisibility); } private static void setAppTitleVisibility(Context context, int appWidgetId, RemoteViews updateViews) { boolean hideAppTitle = PreferenceHelper.getHideAppTitlePreference(context, appWidgetId); int subTextVisibility = hideAppTitle ? chooseGoneOrInvisible(context, appWidgetId) : View.VISIBLE; updateViews.setViewVisibility(R.id.app_title_1, subTextVisibility); updateViews.setViewVisibility(R.id.app_title_2, subTextVisibility); updateViews.setViewVisibility(R.id.app_title_3, subTextVisibility); updateViews.setViewVisibility(R.id.app_title_4, subTextVisibility); } private static PendingIntent getPendingIntentForForwardOrBackButton( Context context, boolean forward, int newPageNumber, int appWidgetId) { Intent intent = new Intent(); intent.setAction(forward ? AppTrackerWidgetProvider.ACTION_UPDATE_PAGE_FORWARD : AppTrackerWidgetProvider.ACTION_UPDATE_PAGE_BACK); intent.putExtra(NEW_PAGE_NUMBER, newPageNumber); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); // gotta make this unique for this appwidgetid - otherwise, the PendingIntents conflict // it seems to be a quasi-bug in Android Uri data = Uri.withAppendedPath(Uri.parse(URI_SCHEME + "://widget/id/#"+forward + newPageNumber), String.valueOf(appWidgetId)); intent.setData(data); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 /* no requestCode */, intent, PendingIntent.FLAG_ONE_SHOT); return pendingIntent; } private static int chooseGoneOrInvisible(Context context, int appWidgetId) { return PreferenceHelper.getStretchToFillPreference(context, appWidgetId) ? View.GONE : View.INVISIBLE; } }