Fix widget loading and Loader leaks
Change-Id: I1b5a80383b3c56574598298683b4f88afe8121c0
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7701349..58851e1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1446,4 +1446,7 @@
<!-- Label for the widget that shows picture and social status of a contact [CHAR LIMIT=20] -->
<string name="social_widget_label">Contact</string>
+
+ <!-- Message of widget while it is loading data [CHAR LIMIT=20] -->
+ <string name="social_widget_loading">Loading \u2026</string>
</resources>
diff --git a/res/xml/social_widget_info.xml b/res/xml/social_widget_info.xml
index 43b1922..4fb849c 100644
--- a/res/xml/social_widget_info.xml
+++ b/res/xml/social_widget_info.xml
@@ -14,10 +14,12 @@
limitations under the License.
-->
+ <!-- It is enough to update once per day, as the widget watches the database for changes -->
+
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="220dip"
android:minHeight="72dip"
- android:updatePeriodMillis="3600000"
+ android:updatePeriodMillis="86400000"
android:initialLayout="@layout/social_widget"
android:configure="com.android.contacts.socialwidget.SocialWidgetConfigureActivity" >
</appwidget-provider>
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java b/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
index f5033fe..0d63036 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
@@ -46,10 +46,10 @@
// Save the setting
final SocialWidgetConfigureActivity context = SocialWidgetConfigureActivity.this;
- SocialWidgetSettings.setContactUri(context, widgetId, data.getData());
+ SocialWidgetSettings.getInstance().setContactUri(context, widgetId, data.getData());
// Update the widget
- SocialWidgetProvider.startLoading(context, widgetId);
+ SocialWidgetProvider.loadWidgetData(context, widgetId);
// Return OK so that the system won't remove the widget
final Intent resultValue = new Intent();
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index f2641fa..591ca87 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -31,117 +31,146 @@
import android.provider.ContactsContract.QuickContact;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import android.widget.RemoteViews;
public class SocialWidgetProvider extends AppWidgetProvider {
private static final String TAG = "SocialWidgetProvider";
+ private static SparseArray<ContactLoader> sLoaders = new SparseArray<ContactLoader>();
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
- startLoading(context, appWidgetId);
+ Log.d(TAG, "onUpdate called for " + appWidgetId);
+ }
+
+ for (int appWidgetId : appWidgetIds) {
+ loadWidgetData(context, appWidgetId);
}
}
- public static void startLoading(final Context context, final int widgetId) {
- // Show that we are loading
- final AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
- final RemoteViews loadingViews =
- new RemoteViews(context.getPackageName(), R.layout.social_widget);
- loadingViews.setTextViewText(R.id.name, "Loading...");
- widgetManager.updateAppWidget(widgetId, loadingViews);
-
- // Load
- final Uri contactUri = SocialWidgetSettings.getContactUri(context, widgetId);
- if (contactUri == null) {
- // Not yet set-up (this can happen while the Configuration activity is visible)
- return;
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ for (int appWidgetId : appWidgetIds) {
+ ContactLoader loader = sLoaders.get(appWidgetId);
+ if (loader != null) {
+ Log.d(TAG, "Stopping loader for widget with id=" + appWidgetId);
+ loader.stopLoading();
+ sLoaders.delete(appWidgetId);
+ }
}
- final ContactLoader contactLoader = new ContactLoader(context, contactUri);
- contactLoader.registerListener(0,
- new ContactLoader.OnLoadCompleteListener<ContactLoader.Result>() {
- @Override
- public void onLoadComplete(Loader<ContactLoader.Result> loader,
- ContactLoader.Result contactData) {
- if (contactData == ContactLoader.Result.ERROR ||
- contactData == ContactLoader.Result.NOT_FOUND) {
- return;
+ SocialWidgetSettings.getInstance().remove(context, appWidgetIds);
+ }
+
+ public static void loadWidgetData(final Context context, final int widgetId) {
+ final ContactLoader previousLoader = sLoaders.get(widgetId);
+
+ if (previousLoader != null) {
+ previousLoader.startLoading();
+ } else {
+ // Show that we are loading
+ final AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
+ final RemoteViews loadingViews =
+ new RemoteViews(context.getPackageName(), R.layout.social_widget);
+ loadingViews.setTextViewText(R.id.name,
+ context.getString(R.string.social_widget_loading));
+ widgetManager.updateAppWidget(widgetId, loadingViews);
+
+ // Load
+ final Uri contactUri =
+ SocialWidgetSettings.getInstance().getContactUri(context, widgetId);
+ if (contactUri == null) {
+ // Not yet set-up (this can happen while the Configuration activity is visible)
+ return;
+ }
+ final ContactLoader contactLoader = new ContactLoader(context, contactUri);
+ contactLoader.registerListener(0,
+ new ContactLoader.OnLoadCompleteListener<ContactLoader.Result>() {
+ @Override
+ public void onLoadComplete(Loader<ContactLoader.Result> loader,
+ ContactLoader.Result contactData) {
+ if (contactData == ContactLoader.Result.ERROR ||
+ contactData == ContactLoader.Result.NOT_FOUND) {
+ return;
+ }
+ Log.d(TAG, "Loaded " + contactData.getLookupKey()
+ + " for widget with id=" + widgetId);
+ final RemoteViews views = new RemoteViews(context.getPackageName(),
+ R.layout.social_widget);
+
+ setDisplayName(views, contactData.getDisplayName(),
+ contactData.getPhoneticName());
+ final Bitmap bitmap = ContactBadgeUtil.getPhoto(contactData);
+ setPhoto(views, bitmap == null
+ ? ContactBadgeUtil.loadPlaceholderPhoto(context) : bitmap);
+ setSocialSnippet(views, contactData.getSocialSnippet());
+ setStatusAttribution(views, ContactBadgeUtil.getSocialDate(
+ contactData, context));
+
+ // OnClick launch QuickContact
+ final Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+ intent.setData(contactData.getLookupUri());
+ intent.putExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_SMALL);
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context,
+ 0, intent, 0);
+ views.setOnClickPendingIntent(R.id.image, pendingIntent);
+
+ // Configure Ui
+ widgetManager.updateAppWidget(widgetId, views);
}
- Log.d(TAG, "Loaded " + contactData.getLookupKey());
- final RemoteViews views = new RemoteViews(context.getPackageName(),
- R.layout.social_widget);
- setDisplayName(views, contactData.getDisplayName(),
- contactData.getPhoneticName());
- final Bitmap bitmap = ContactBadgeUtil.getPhoto(contactData);
- setPhoto(views, bitmap == null
- ? ContactBadgeUtil.loadPlaceholderPhoto(context) : bitmap);
- setSocialSnippet(views, contactData.getSocialSnippet());
- setStatusAttribution(views, ContactBadgeUtil.getSocialDate(
- contactData, context));
-
- // OnClick launch QuickContact
- final Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
- intent.setData(contactData.getLookupUri());
- intent.putExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_SMALL);
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
- intent, 0);
- views.setOnClickPendingIntent(R.id.image, pendingIntent);
-
- // Configure Ui
- widgetManager.updateAppWidget(widgetId, views);
- }
-
- private void setPhoto(RemoteViews views, Bitmap photo) {
- views.setImageViewBitmap(R.id.image, photo);
- }
-
- /**
- * Set the display name and phonetic name to show in the header.
- */
- private void setDisplayName(RemoteViews views, CharSequence displayName,
- CharSequence phoneticName) {
- if (TextUtils.isEmpty(phoneticName)) {
- views.setTextViewText(R.id.name, displayName);
- } else {
- final String combinedName =
- context.getString(R.string.widget_name_and_phonetic,
- displayName, phoneticName);
- views.setTextViewText(R.id.name, combinedName);
+ private void setPhoto(RemoteViews views, Bitmap photo) {
+ views.setImageViewBitmap(R.id.image, photo);
}
- }
- /**
- * Set the social snippet text to display in the header.
- */
- private void setSocialSnippet(RemoteViews views, CharSequence snippet) {
- if (TextUtils.isEmpty(snippet)) {
- views.setViewVisibility(R.id.status, View.GONE);
- } else {
- views.setTextViewText(R.id.status, snippet);
- views.setViewVisibility(R.id.status, View.VISIBLE);
+ /**
+ * Set the display name and phonetic name to show in the header.
+ */
+ private void setDisplayName(RemoteViews views, CharSequence displayName,
+ CharSequence phoneticName) {
+ if (TextUtils.isEmpty(phoneticName)) {
+ views.setTextViewText(R.id.name, displayName);
+ } else {
+ final String combinedName =
+ context.getString(R.string.widget_name_and_phonetic,
+ displayName, phoneticName);
+ views.setTextViewText(R.id.name, combinedName);
+ }
}
- }
- /**
- * Set the status attribution text to display in the header.
- */
- private void setStatusAttribution(RemoteViews views,
- CharSequence attribution) {
- if (attribution == null) {
- views.setViewVisibility(R.id.status_date, View.GONE);
- } else {
- views.setTextViewText(R.id.status_date, attribution);
- views.setViewVisibility(R.id.status_date, View.VISIBLE);
+ /**
+ * Set the social snippet text to display in the header.
+ */
+ private void setSocialSnippet(RemoteViews views, CharSequence snippet) {
+ if (TextUtils.isEmpty(snippet)) {
+ views.setViewVisibility(R.id.status, View.GONE);
+ } else {
+ views.setTextViewText(R.id.status, snippet);
+ views.setViewVisibility(R.id.status, View.VISIBLE);
+ }
}
- }
- });
- contactLoader.startLoading();
+
+ /**
+ * Set the status attribution text to display in the header.
+ */
+ private void setStatusAttribution(RemoteViews views,
+ CharSequence attribution) {
+ if (attribution == null) {
+ views.setViewVisibility(R.id.status_date, View.GONE);
+ } else {
+ views.setTextViewText(R.id.status_date, attribution);
+ views.setViewVisibility(R.id.status_date, View.VISIBLE);
+ }
+ }
+ });
+ contactLoader.startLoading();
+ sLoaders.append(widgetId, contactLoader);
+ }
}
}
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetSettings.java b/src/com/android/contacts/socialwidget/SocialWidgetSettings.java
index 8469d61..18b5041 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetSettings.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetSettings.java
@@ -26,16 +26,30 @@
private static final String TAG = "SocialWidgetSettings";
private static final String PREFS_NAME = "WidgetSettings";
- private static final String CONTACT_URI = "CONTACT_URI_%";
+ private static final String CONTACT_URI_PREFIX = "CONTACT_URI_";
- private static String getSettingsString(int widgetId) {
- return CONTACT_URI.replace("%", Integer.toString(widgetId));
+ private static final SocialWidgetSettings sInstance = new SocialWidgetSettings();
+
+ public static SocialWidgetSettings getInstance() {
+ return sInstance;
}
- // TODO: Think about how to remove not-used Ids...we need a way to detect removed
- // widgets
+ private final String getSettingsString(int widgetId) {
+ return CONTACT_URI_PREFIX + Integer.toString(widgetId);
+ }
- public static Uri getContactUri(Context context, int widgetId) {
+ public void remove(Context context, int[] widgetIds) {
+ final SharedPreferences settings =
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ final Editor editor = settings.edit();
+ for (int widgetId : widgetIds) {
+ Log.d(TAG, "remove(" + widgetId + ")");
+ editor.remove(getSettingsString(widgetId));
+ }
+ editor.apply();
+ }
+
+ public Uri getContactUri(Context context, int widgetId) {
final SharedPreferences settings =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
final String resultString = settings.getString(getSettingsString(widgetId), null);
@@ -44,7 +58,7 @@
return result;
}
- public static void setContactUri(Context context, int widgetId, Uri contactLookupUri) {
+ public void setContactUri(Context context, int widgetId, Uri contactLookupUri) {
Log.d(TAG, "setContactUri(" + widgetId + ", " + contactLookupUri + ")");
final SharedPreferences settings =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);