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