Home | History | Annotate | Download | only in socialwidget
      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