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
17package com.android.contacts.socialwidget;
18
19import android.app.PendingIntent;
20import android.appwidget.AppWidgetManager;
21import android.appwidget.AppWidgetProvider;
22import android.content.ContentUris;
23import android.content.Context;
24import android.content.Intent;
25import android.content.Loader;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Typeface;
29import android.net.Uri;
30import android.provider.ContactsContract.StreamItems;
31import android.text.SpannableStringBuilder;
32import android.text.TextUtils;
33import android.text.style.AbsoluteSizeSpan;
34import android.text.style.StyleSpan;
35import android.util.Log;
36import android.util.SparseArray;
37import android.view.View;
38import android.widget.RemoteViews;
39
40import com.android.contacts.R;
41import com.android.contacts.model.AccountTypeManager;
42import com.android.contacts.model.Contact;
43import com.android.contacts.model.ContactLoader;
44import com.android.contacts.model.account.AccountType;
45import com.android.contacts.quickcontact.QuickContactBroadcastReceiver;
46import com.android.contacts.util.ContactBadgeUtil;
47import com.android.contacts.util.HtmlUtils;
48import com.android.contacts.util.StreamItemEntry;
49
50import java.util.List;
51
52public 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
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
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
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
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
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     */
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