/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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.google.samples.apps.iosched.appwidget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.text.format.DateUtils;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.model.ScheduleHelper;
import com.google.samples.apps.iosched.model.ScheduleItem;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.samples.apps.iosched.ui.MyScheduleActivity;
import com.google.samples.apps.iosched.ui.SimpleSectionedListAdapter;
import com.google.samples.apps.iosched.ui.TaskStackBuilderProxyActivity;
import com.google.samples.apps.iosched.util.AccountUtils;
import com.google.samples.apps.iosched.util.PrefUtils;
import com.google.samples.apps.iosched.util.TimeUtils;
import com.google.samples.apps.iosched.util.UIUtils;
import java.util.*;
import static com.google.samples.apps.iosched.util.LogUtils.*;
/**
* This is the service that provides the factory to be bound to the collection service.
*/
public class ScheduleWidgetRemoteViewsService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new WidgetRemoteViewsFactory(this.getApplicationContext());
}
/**
* This is the factory that will provide data to the collection widget.
*/
private static class WidgetRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private static final String TAG = makeLogTag(WidgetRemoteViewsFactory.class);
private Context mContext;
private SparseIntArray mPMap;
private List<SimpleSectionedListAdapter.Section> mSections;
private SparseBooleanArray mHeaderPositionMap;
StringBuilder mBuffer = new StringBuilder();
Formatter mFormatter = new Formatter(mBuffer, Locale.getDefault());
private ArrayList<ScheduleItem> mScheduleItems;
private int mDefaultSessionColor;
private int mDefaultStartTimeColor;
private int mDefaultEndTimeColor;
public WidgetRemoteViewsFactory(Context context) {
mContext = context;
}
public void onCreate() {
// Since we reload the cursor in onDataSetChanged() which gets called immediately after
// onCreate(), we do nothing here.
}
public void onDestroy() {
}
public int getCount() {
if (mScheduleItems == null || !AccountUtils.hasActiveAccount(mContext)) {
return 0;
}
if (mScheduleItems.size() < 10) {
init();
}
return mScheduleItems.size();
}
public int getItemViewType(int position) {
if (position < 0 || position >= mScheduleItems.size()) {
LOGE(TAG, "Invalid view position passed to MyScheduleAdapter: " + position);
return VIEW_TYPE_NORMAL;
}
ScheduleItem item = mScheduleItems.get(position);
long now = UIUtils.getCurrentTime(mContext);
if (item.startTime <= now && now <= item.endTime && item.type == ScheduleItem.SESSION) {
return VIEW_TYPE_NOW;
} else {
return VIEW_TYPE_NORMAL;
}
}
private static final int VIEW_TYPE_NORMAL = 0;
private static final int VIEW_TYPE_NOW = 1;
public RemoteViews getViewAt(int position) {
RemoteViews rv;
boolean isSectionHeader = mHeaderPositionMap.get(position);
int offset = mPMap.get(position);
if (isSectionHeader) {
rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_schedule_header);
SimpleSectionedListAdapter.Section section = mSections.get(offset - 1);
rv.setTextViewText(R.id.widget_schedule_day, section.getTitle());
} else {
int itemPosition = position - offset;
Intent homeIntent = new Intent(mContext, MyScheduleActivity.class);
final ScheduleItem item = mScheduleItems.get(itemPosition);
ScheduleItem nextItem = (itemPosition < mScheduleItems.size() - 1) ? mScheduleItems.get(itemPosition + 1) : null;
if (mDefaultSessionColor < 0) {
mDefaultSessionColor = mContext.getResources().getColor(R.color.default_session_color);
}
int itemViewType = getItemViewType(itemPosition);
boolean isNowPlaying = false;
boolean isPastDuringConference = false;
int layoutResId = R.layout.widget_schedule_item;
mDefaultStartTimeColor = R.color.body_text_2;
mDefaultEndTimeColor = R.color.body_text_3;
if (itemViewType == VIEW_TYPE_NOW) {
isNowPlaying = true;
layoutResId = R.layout.widget_schedule_item_now;
mDefaultStartTimeColor = mDefaultEndTimeColor = R.color.body_text_1_inverse;
} else {
if (item.type == ScheduleItem.BREAK) {
layoutResId = R.layout.widget_schedule_item_break;
}
}
rv = new RemoteViews(mContext.getPackageName(), layoutResId);
if (itemPosition < 0 || itemPosition >= mScheduleItems.size()) {
LOGE(TAG, "Invalid view position passed to MyScheduleAdapter: " + position);
return rv;
}
long now = UIUtils.getCurrentTime(mContext);
boolean showEndTime;
if (item.startTime <= now) {
// session is happening now!
rv.setTextViewText(R.id.start_time, mContext.getString(R.string.session_now));
showEndTime = nextItem == null || nextItem.startTime != item.endTime;
} else {
// session in the future
rv.setTextViewText(R.id.start_time, TimeUtils.formatShortTime(mContext, new Date(item.startTime)));
// do we need and end time view?
showEndTime = nextItem == null || nextItem.startTime != item.endTime;
}
if (showEndTime) {
rv.setViewVisibility(R.id.end_time, View.VISIBLE);
rv.setTextViewText(R.id.end_time, mContext.getString(R.string.schedule_end_time,
TimeUtils.formatShortTime(mContext, new Date(item.endTime))));
} else {
// no need to show end time
rv.setViewVisibility(R.id.end_time, View.GONE);
}
rv.setViewVisibility(R.id.live_now_badge, View.GONE);
rv.setViewVisibility(R.id.conflict_warning, View.GONE);
// Set default colors to time indicators, in case they were overridden by conflict warning:
if (!isNowPlaying) {
rv.setTextColor(R.id.start_time, mContext.getResources().getColor(mDefaultStartTimeColor));
rv.setTextColor(R.id.end_time, mContext.getResources().getColor(mDefaultEndTimeColor));
}
if (item.type == ScheduleItem.FREE) {
rv.setImageViewResource(R.id.background_image, R.drawable.schedule_item_free);
rv.setTextViewText(R.id.slot_title, mContext.getText(R.string.browse_sessions));
rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.theme_primary));
rv.setTextViewText(R.id.slot_subtitle, item.subtitle);
rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2));
Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent(
homeIntent,
new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions.buildUnscheduledSessionsInInterval(
item.startTime, item.endTime))
);
rv.setOnClickFillInIntent(R.id.box, fillIntent);
} else if (item.type == ScheduleItem.BREAK) {
rv.setImageViewResource(R.id.background_image, R.drawable.schedule_item_break);
rv.setTextViewText(R.id.slot_title, item.title);
rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.body_text_1));
rv.setTextViewText(R.id.slot_subtitle, item.subtitle);
rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2));
} else if (item.type == ScheduleItem.SESSION) {
final int color = UIUtils.scaleSessionColorToDefaultBG(
item.backgroundColor == 0 ? mDefaultSessionColor : item.backgroundColor);
Bitmap image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
image.eraseColor(color);
rv.setImageViewBitmap(R.id.background_image, image);
rv.setTextViewText(R.id.slot_title, item.title);
rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.body_text_1_inverse));
rv.setTextViewText(R.id.slot_subtitle, item.subtitle);
rv.setTextColor(R.id.slot_subtitle, mContext.getResources().getColor(R.color.body_text_2_inverse));
// show or hide the "LIVE NOW" badge
final boolean showLiveBadge = 0 != (item.flags & ScheduleItem.FLAG_HAS_LIVESTREAM)
&& now >= item.startTime && now <= item.endTime;
rv.setViewVisibility(R.id.live_now_badge, (showLiveBadge ? View.VISIBLE : View.GONE));
// show or hide the "conflict" warning
if (!isPastDuringConference) {
final boolean showConflict = 0 != (item.flags & ScheduleItem.FLAG_CONFLICTS_WITH_PREVIOUS);
rv.setViewVisibility(R.id.conflict_warning, (showConflict ? View.VISIBLE : View.GONE));
if (showConflict && !isNowPlaying) {
int conflictColor = mContext.getResources().getColor(R.color.my_schedule_conflict);
rv.setTextColor(R.id.start_time, conflictColor);
rv.setTextColor(R.id.end_time, conflictColor);
}
}
Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent(
homeIntent,
new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions.buildSessionUri(item.sessionId)));
rv.setOnClickFillInIntent(R.id.box, fillIntent);
} else {
LOGE(TAG, "Invalid item type in MyScheduleAdapter: " + item.type);
}
}
return rv;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 4;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public void onDataSetChanged() {
init();
}
private void init() {
ScheduleHelper scheduleHelper = new ScheduleHelper(mContext);
//Fetch all sessions and blocks
List<ScheduleItem> allScheduleItems = scheduleHelper.getScheduleData(Long.MIN_VALUE, Long.MAX_VALUE);
String displayTimeZone = PrefUtils.getDisplayTimeZone(mContext).getID();
mSections = new ArrayList<SimpleSectionedListAdapter.Section>();
long previousTime = -1;
long time;
mPMap = new SparseIntArray();
mHeaderPositionMap = new SparseBooleanArray();
int offset = 0;
int globalPosition = 0;
int position = 0;
mScheduleItems = new ArrayList<ScheduleItem>();
for (ScheduleItem item : allScheduleItems) {
if (item.endTime <= UIUtils.getCurrentTime(mContext)) {
continue;
}
mScheduleItems.add(item);
time = item.startTime;
if (!UIUtils.isSameDayDisplay(previousTime, time, mContext)) {
mBuffer.setLength(0);
mSections.add(new SimpleSectionedListAdapter.Section(position,
DateUtils.formatDateRange(
mContext, mFormatter,
time, time,
DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE,
displayTimeZone
).toString()
));
++offset;
mHeaderPositionMap.put(globalPosition, true);
mPMap.put(globalPosition, offset);
++globalPosition;
}
mHeaderPositionMap.put(globalPosition, false);
mPMap.put(globalPosition, offset);
++globalPosition;
++position;
previousTime = time;
}
}
}
}