1 /* 2 * Copyright (C) 2010 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 17 package com.android.contacts.socialwidget; 18 19 import android.app.PendingIntent; 20 import android.appwidget.AppWidgetManager; 21 import android.appwidget.AppWidgetProvider; 22 import android.content.ContentUris; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.Loader; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.Typeface; 29 import android.net.Uri; 30 import android.provider.ContactsContract.StreamItems; 31 import android.text.SpannableStringBuilder; 32 import android.text.TextUtils; 33 import android.text.style.AbsoluteSizeSpan; 34 import android.text.style.StyleSpan; 35 import android.util.Log; 36 import android.util.SparseArray; 37 import android.view.View; 38 import android.widget.RemoteViews; 39 40 import com.android.contacts.R; 41 import com.android.contacts.common.model.AccountTypeManager; 42 import com.android.contacts.model.Contact; 43 import com.android.contacts.model.ContactLoader; 44 import com.android.contacts.common.model.account.AccountType; 45 import com.android.contacts.quickcontact.QuickContactBroadcastReceiver; 46 import com.android.contacts.util.ContactBadgeUtil; 47 import com.android.contacts.util.HtmlUtils; 48 import com.android.contacts.util.StreamItemEntry; 49 50 import java.util.List; 51 52 public class SocialWidgetProvider extends AppWidgetProvider { 53 private static final String TAG = "SocialWidgetProvider"; 54 55 /** 56 * Max length of a snippet that is considered "short" and displayed in 57 * a separate line. 58 */ 59 private static final int SHORT_SNIPPET_LENGTH = 48; 60 61 private static SparseArray<ContactLoader> sLoaders = new SparseArray<ContactLoader>(); 62 63 @Override onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)64 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 65 for (int appWidgetId : appWidgetIds) { 66 Log.d(TAG, "onUpdate called for " + appWidgetId); 67 } 68 69 for (int appWidgetId : appWidgetIds) { 70 loadWidgetData(context, appWidgetManager, appWidgetId, false); 71 } 72 } 73 74 @Override onDeleted(Context context, int[] appWidgetIds)75 public void onDeleted(Context context, int[] appWidgetIds) { 76 for (int appWidgetId : appWidgetIds) { 77 ContactLoader loader = sLoaders.get(appWidgetId); 78 if (loader != null) { 79 Log.d(TAG, "Stopping loader for widget with id=" + appWidgetId); 80 loader.reset(); 81 sLoaders.delete(appWidgetId); 82 } 83 } 84 SocialWidgetSettings.getInstance().remove(context, appWidgetIds); 85 } 86 loadWidgetData(final Context context, final AppWidgetManager appWidgetManager, final int widgetId, boolean forceLoad)87 public static void loadWidgetData(final Context context, 88 final AppWidgetManager appWidgetManager, final int widgetId, boolean forceLoad) { 89 ContactLoader previousLoader = sLoaders.get(widgetId); 90 91 if (previousLoader != null && !forceLoad) { 92 previousLoader.startLoading(); 93 return; 94 } 95 96 if (previousLoader != null) { 97 previousLoader.reset(); 98 } 99 100 // Show that we are loading 101 final RemoteViews loadingViews = 102 new RemoteViews(context.getPackageName(), R.layout.social_widget); 103 loadingViews.setTextViewText(R.id.name, 104 context.getString(R.string.social_widget_loading)); 105 loadingViews.setViewVisibility(R.id.name, View.VISIBLE); 106 loadingViews.setViewVisibility(R.id.name_and_snippet, View.GONE); 107 appWidgetManager.updateAppWidget(widgetId, loadingViews); 108 109 // Load 110 final Uri contactUri = 111 SocialWidgetSettings.getInstance().getContactUri(context, widgetId); 112 if (contactUri == null) { 113 // Not yet set-up (this can happen while the Configuration activity is visible) 114 return; 115 } 116 final ContactLoader contactLoader = new ContactLoader(context, contactUri, false, true, 117 false, true, false); 118 contactLoader.registerListener(0, 119 new ContactLoader.OnLoadCompleteListener<Contact>() { 120 @Override 121 public void onLoadComplete(Loader<Contact> loader, 122 Contact contactData) { 123 bindRemoteViews(context, widgetId, appWidgetManager, contactData); 124 } 125 }); 126 contactLoader.startLoading(); 127 sLoaders.append(widgetId, contactLoader); 128 } 129 bindRemoteViews(final Context context, final int widgetId, final AppWidgetManager widgetManager, Contact contactData)130 private static void bindRemoteViews(final Context context, final int widgetId, 131 final AppWidgetManager widgetManager, Contact contactData) { 132 Log.d(TAG, "Loaded " + contactData.getLookupKey() 133 + " for widget with id=" + widgetId); 134 final RemoteViews views = new RemoteViews(context.getPackageName(), 135 R.layout.social_widget); 136 137 if (!contactData.isLoaded()) { 138 setDisplayNameAndSnippet(context, views, 139 context.getString(R.string.invalidContactMessage), null, null, null); 140 setPhoto(views, ContactBadgeUtil.loadDefaultAvatarPhoto(context, false, false)); 141 } else { 142 byte[] photo = contactData.getPhotoBinaryData(); 143 setPhoto(views, photo != null 144 ? BitmapFactory.decodeByteArray(photo, 0, photo.length) 145 : ContactBadgeUtil.loadDefaultAvatarPhoto(context, false, false)); 146 147 // TODO: Rotate between all the stream items? 148 149 final Intent intent = new Intent(context, QuickContactBroadcastReceiver.class); 150 intent.setData(contactData.getLookupUri()); 151 final PendingIntent pendingIntent = PendingIntent.getBroadcast( 152 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 153 views.setOnClickPendingIntent(R.id.border, pendingIntent); 154 155 setDisplayNameAndSnippet(context, views, contactData.getDisplayName(), 156 contactData.getPhoneticName(), contactData.getStreamItems(), pendingIntent); 157 } 158 159 // Configure UI 160 widgetManager.updateAppWidget(widgetId, views); 161 } 162 163 setPhoto(RemoteViews views, Bitmap photo)164 private static void setPhoto(RemoteViews views, Bitmap photo) { 165 views.setImageViewBitmap(R.id.image, photo); 166 } 167 168 /** 169 * Set the display name, phonetic name and the social snippet. 170 */ setDisplayNameAndSnippet(Context context, RemoteViews views, CharSequence displayName, CharSequence phoneticName, List<StreamItemEntry> streamItems, PendingIntent defaultIntent)171 private static void setDisplayNameAndSnippet(Context context, RemoteViews views, 172 CharSequence displayName, CharSequence phoneticName, 173 List<StreamItemEntry> streamItems, PendingIntent defaultIntent) { 174 SpannableStringBuilder sb = new SpannableStringBuilder(); 175 176 CharSequence name = displayName; 177 // If there is no display name, use the default missing name string 178 if (TextUtils.isEmpty(name)) { 179 name = context.getString(R.string.missing_name); 180 } 181 if (!TextUtils.isEmpty(phoneticName)) { 182 name = context.getString(R.string.widget_name_and_phonetic, 183 name, phoneticName); 184 } 185 sb.append(name); 186 187 AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan( 188 context.getResources().getDimensionPixelSize(R.dimen.widget_text_size_name)); 189 StyleSpan styleSpan = new StyleSpan(Typeface.BOLD); 190 sb.setSpan(sizeSpan, 0, name.length(), 0); 191 sb.setSpan(styleSpan, 0, name.length(), 0); 192 193 if (streamItems == null || streamItems.isEmpty()) { 194 views.setTextViewText(R.id.name, sb); 195 views.setViewVisibility(R.id.name, View.VISIBLE); 196 views.setViewVisibility(R.id.name_and_snippet, View.GONE); 197 // Don't set a pending intent if the intent is null, otherwise the system will try 198 // to write the null intent to a Parcel. 199 if (defaultIntent != null) { 200 views.setOnClickPendingIntent(R.id.widget_container, defaultIntent); 201 } 202 } else { 203 // TODO: Rotate between all the stream items? 204 StreamItemEntry streamItem = streamItems.get(0); 205 CharSequence status = HtmlUtils.fromHtml(context, streamItem.getText()); 206 if (status == null) { 207 status = ""; 208 } 209 if (status.length() <= SHORT_SNIPPET_LENGTH) { 210 sb.append("\n"); 211 } else { 212 sb.append(" "); 213 } 214 sb.append(status); 215 views.setTextViewText(R.id.name_and_snippet, sb); 216 views.setViewVisibility(R.id.name, View.GONE); 217 views.setViewVisibility(R.id.name_and_snippet, View.VISIBLE); 218 final AccountTypeManager manager = AccountTypeManager.getInstance(context); 219 final AccountType accountType = 220 manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet()); 221 if (accountType.getViewStreamItemActivity() != null) { 222 final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI, 223 streamItem.getId()); 224 final Intent intent = new Intent(Intent.ACTION_VIEW, uri); 225 intent.setClassName(accountType.syncAdapterPackageName, 226 accountType.getViewStreamItemActivity()); 227 views.setOnClickPendingIntent(R.id.name_and_snippet_container, 228 PendingIntent.getActivity(context, 0, intent, 0)); 229 } 230 } 231 } 232 } 233