/*
* Copyright (C) 2010-2011 yvolk (Yuri Volkov), http://yurivolkov.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xorcode.andtweet.appwidget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.widget.RemoteViews;
import com.xorcode.andtweet.MessageListActivity;
import com.xorcode.andtweet.R;
import com.xorcode.andtweet.AndTweetService;
import com.xorcode.andtweet.TweetListActivity;
import com.xorcode.andtweet.data.AndTweetDatabase;
import com.xorcode.andtweet.util.I18n;
import com.xorcode.andtweet.util.RelativeTime;
/**
* A widget provider. If uses AndTweetAppWidgetData to store preferences and to
* accumulate data (notifications...) received.
*
* <p>
* See also the following files:
* <ul>
* <li>AndTweetAppWidgetData.java</li>
* <li>AndTweetAppWidgetConfigure.java</li>
* <li>res/layout/appwidget_configure.xml</li>
* <li>res/layout/appwidget.xml</li>
* <li>res/xml/appwidget_info.xml</li>
* </ul>
*
* @author yvolk (Yuri Volkov), http://yurivolkov.com
*/
public class AndTweetAppWidgetProvider extends AppWidgetProvider {
// log tag
private static final String TAG = AndTweetAppWidgetProvider.class
.getSimpleName();
private AndTweetService.CommandEnum msgType = AndTweetService.CommandEnum.UNKNOWN;
private int numSomethingReceived = 0;
private static Object xlock = new Object();
private long instanceId = Math.abs(new java.util.Random().nextInt());
@Override
public void onReceive(Context context, Intent intent) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onReceive; intent=" + intent);
}
boolean done = false;
String action = intent.getAction();
if (AndTweetService.ACTION_APPWIDGET_UPDATE.equals(action)) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "inst=" + instanceId + "; Intent from AndTweetService received!");
}
Bundle extras = intent.getExtras();
if (extras != null) {
msgType = AndTweetService.CommandEnum.load(extras.getString(AndTweetService.EXTRA_MSGTYPE));
numSomethingReceived = extras
.getInt(AndTweetService.EXTRA_NUMTWEETS);
int[] appWidgetIds = extras
.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds == null || appWidgetIds.length == 0) {
/**
* Update All AndTweet AppWidgets
* */
appWidgetIds = AppWidgetManager
.getInstance(context)
.getAppWidgetIds(
new ComponentName(context, this.getClass()));
}
if (appWidgetIds != null && appWidgetIds.length > 0) {
onUpdate(context, AppWidgetManager.getInstance(context),
appWidgetIds);
done = true;
}
}
if (!done) {
// This will effectively reset the Widget view
updateAppWidget(context, AppWidgetManager.getInstance(context),
AppWidgetManager.INVALID_APPWIDGET_ID);
done = true;
}
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "inst=" + instanceId + "; Intent from AndTweetService processed");
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "Action APPWIDGET_DELETED was received");
}
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras
.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
onDeleted(context, appWidgetIds);
done = true;
} else {
// For some reason this is required for Android v.1.5
int appWidgetId = extras
.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
if (appWidgetId != 0) {
int[] appWidgetIds2 = { appWidgetId };
onDeleted(context, appWidgetIds2);
done = true;
}
}
}
if (!done) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) {
Log.d(TAG, "Deletion was not done, extras='"
+ extras.toString() + "'");
}
}
}
if (!done) {
super.onReceive(context, intent);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onUpdate");
}
// For each widget that needs an update, get the text that we should
// display:
// - Create a RemoteViews object for it
// - Set the text in the RemoteViews object
// - Tell the AppWidgetManager to show that views object for the widget.
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onDeleted");
}
// When the user deletes the widget, delete all preferences associated
// with it.
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
new AndTweetAppWidgetData(context, appWidgetIds[i]).delete();
}
}
@Override
public void onEnabled(Context context) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onEnabled");
}
}
@Override
public void onDisabled(Context context) {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "onDisabled");
}
}
/**
* Update the AppWidget view (i.e. on the Home screen)
*
* @param context
* @param appWidgetManager
* @param appWidgetId
* Id of the The AppWidget instance which view should be updated
*/
void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
boolean Ok = false;
try {
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "inst=" + instanceId + "; updateAppWidget appWidgetId=" + appWidgetId
+ "; msgType=" + msgType);
}
// TODO: Do we need AlarmManager here?
// see /ApiDemos/src/com/example/android/apis/app/AlarmController.java
// on how to implement AlarmManager...
AndTweetAppWidgetData data;
synchronized(xlock) {
data = new AndTweetAppWidgetData(context,
appWidgetId);
data.load();
if (AndTweetService.updateWidgetsOnEveryUpdate || (numSomethingReceived != 0)) {
data.changed = true;
}
// Calculate new values
switch (msgType) {
case NOTIFY_REPLIES:
data.numMentions += numSomethingReceived;
data.checked();
break;
case NOTIFY_DIRECT_MESSAGE:
data.numMessages += numSomethingReceived;
data.checked();
break;
case NOTIFY_TIMELINE:
data.numTweets += numSomethingReceived;
data.checked();
break;
case NOTIFY_CLEAR:
data.clearCounters();
break;
default:
// change nothing
}
if (data.changed) {
data.save();
}
}
// TODO: Widget design...
// "Text" is what is shown in bold
String widgetText = "";
// And the "Comment" is less visible, below the "Text"
String widgetComment = "";
// Construct period of counting...
String widgetTime = "";
if (data.dateChecked == 0) {
Log.e(TAG, "data.dateChecked==0");
widgetComment = context.getString(R.string.appwidget_nodata);
} else {
widgetTime = formatWidgetTime(context, data.dateCleared, data.dateChecked);
boolean isFound = false;
if (data.numMentions > 0) {
isFound = true;
widgetText += (widgetText.length() > 0 ? "\n" : "")
+ I18n.formatQuantityMessage(context,
R.string.appwidget_new_mention_format,
data.numMentions,
R.array.appwidget_mention_patterns,
R.array.appwidget_mention_formats);
}
if (data.numMessages > 0) {
isFound = true;
widgetText += (widgetText.length() > 0 ? "\n" : "")
+ I18n.formatQuantityMessage(context,
R.string.appwidget_new_message_format,
data.numMessages,
R.array.appwidget_message_patterns,
R.array.appwidget_message_formats);
}
if (data.numTweets > 0) {
isFound = true;
widgetText += (widgetText.length() > 0 ? "\n" : "")
+ I18n.formatQuantityMessage(context,
R.string.appwidget_new_tweet_format,
data.numTweets, R.array.appwidget_tweet_patterns,
R.array.appwidget_tweet_formats);
}
if (!isFound) {
widgetComment = data.nothingPref;
}
}
if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.v(TAG, "updateAppWidget text=\"" + widgetText.replaceAll("\n", "; ") + "\"; comment=\""
+ widgetComment + "\"");
}
// Construct the RemoteViews object. It takes the package name (in our
// case, it's our
// package, but it needs this because on the other side it's the widget
// host inflating
// the layout from our package).
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget);
if (widgetText.length() == 0) {
views
.setViewVisibility(R.id.appwidget_text,
android.view.View.GONE);
}
if (widgetComment.length() == 0) {
views.setViewVisibility(R.id.appwidget_comment,
android.view.View.GONE);
}
if (widgetText.length() > 0) {
views.setViewVisibility(R.id.appwidget_text,
android.view.View.VISIBLE);
views.setTextViewText(R.id.appwidget_text, widgetText);
}
if (widgetComment.length() > 0) {
views.setViewVisibility(R.id.appwidget_comment,
android.view.View.VISIBLE);
views.setTextViewText(R.id.appwidget_comment, widgetComment);
}
views.setTextViewText(R.id.appwidget_time, widgetTime);
// When user clicks on widget, launch main AndTweet activity,
// Open timeline, where there are new tweets, or "Friends" timeline
Intent intent;
int timeLineType = AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS;
if (data.numMessages > 0) {
intent = new Intent(context, MessageListActivity.class);
timeLineType = AndTweetDatabase.Tweets.TIMELINE_TYPE_MESSAGES;
} else {
intent = new Intent(context, TweetListActivity.class);
if (data.numMentions > 0) {
timeLineType = AndTweetDatabase.Tweets.TIMELINE_TYPE_MENTIONS;
}
}
intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE,
timeLineType);
// This line is necessary to actually bring Extra to the target intent
// see http://stackoverflow.com/questions/1198558/how-to-send-parameters-from-a-notification-click-to-an-activity
intent.setData((android.net.Uri.parse(AndTweetDatabase.Tweets.CONTENT_URI.toString() + "#" + android.os.SystemClock.elapsedRealtime())));
PendingIntent pendingIntent = PendingIntent.getActivity(context,
0 /* no requestCode */, intent, 0 /* no flags */);
views.setOnClickPendingIntent(R.id.widget, pendingIntent);
// Tell the widget manager
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
// TODO: Is this right?
// All instances will be updated
appWidgetManager.updateAppWidget(new ComponentName(context, this
.getClass()), views);
} else {
appWidgetManager.updateAppWidget(appWidgetId, views);
}
Ok = true;
} catch (Exception e) {
Log.e(TAG, "inst=" + instanceId + "; updateAppWidget exception: " + e.toString() );
} finally {
if ( !Ok || Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) {
Log.d(TAG, "inst=" + instanceId + "; updateAppWidget " + (Ok ? "succeded" : "failed") );
}
}
}
public String formatWidgetTime(Context context, long startMillis,
long endMillis) {
String widgetTime = "";
String strStart = "";
String strEnd = "";
if (endMillis == 0) {
widgetTime = "=0 ???";
Log.e(TAG, "data.dateUpdated==0");
} else {
Time timeStart = new Time();
timeStart.set(startMillis);
Time timeEnd = new Time();
timeEnd.set(endMillis);
int flags = 0;
if (timeStart.yearDay < timeEnd.yearDay) {
strStart = RelativeTime.getDifference(context, startMillis);
if (DateUtils.isToday(endMillis)) {
// End - today
flags = DateUtils.FORMAT_SHOW_TIME;
if (DateFormat.is24HourFormat(context)) {
flags |= DateUtils.FORMAT_24HOUR;
}
strEnd = DateUtils.formatDateTime(context, endMillis, flags);
} else {
strEnd = RelativeTime.getDifference(context, endMillis);
}
} else {
// Same day
if (DateUtils.isToday(endMillis)) {
// Start and end - today
flags = DateUtils.FORMAT_SHOW_TIME;
if (DateFormat.is24HourFormat(context)) {
flags |= DateUtils.FORMAT_24HOUR;
}
strStart = DateUtils.formatDateTime(context, startMillis, flags);
strEnd = DateUtils.formatDateTime(context, endMillis, flags);
} else {
strStart = RelativeTime.getDifference(context, endMillis);
}
}
widgetTime = strStart;
if (strEnd.length()>0) {
if (strEnd.compareTo(strStart) != 0) {
if (widgetTime.length()>0) {
widgetTime += " - ";
}
widgetTime += strEnd;
}
}
}
return widgetTime;
}
}