/*
* 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.content.Context;
import android.content.Intent;
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.settings.SettingsUtils;
import com.google.samples.apps.iosched.myschedule.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.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 mDefaultStartEndTimeColor;
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 MyScheduleDayAdapter: " + position);
return VIEW_TYPE_NORMAL;
}
ScheduleItem item = mScheduleItems.get(position);
long now = TimeUtils.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;
mDefaultStartEndTimeColor = R.color.body_text_2;
if (itemViewType == VIEW_TYPE_NOW) {
isNowPlaying = true;
mDefaultStartEndTimeColor = R.color.body_text_1;
}
rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_schedule_item);
if (itemPosition < 0 || itemPosition >= mScheduleItems.size()) {
LOGE(TAG, "Invalid view position passed to MyScheduleDayAdapter: " + position);
return rv;
}
long now = TimeUtils.getCurrentTime(mContext);
rv.setTextViewText(R.id.start_end_time, formatTime(now, item));
rv.setViewVisibility(R.id.live_now_badge, View.GONE);
// Set default colors to time indicators, in case they were overridden by conflict warning:
if (!isNowPlaying) {
rv.setTextColor(R.id.start_end_time, mContext.getResources().getColor(mDefaultStartEndTimeColor));
}
if (item.type == ScheduleItem.FREE) {
rv.setImageViewResource(R.id.icon, R.drawable.ic_browse);
rv.setTextViewText(R.id.slot_title, mContext.getText(R.string.browse_sessions));
rv.setTextColor(R.id.slot_title, mContext.getResources().getColor(R.color.flat_button_text));
rv.setTextViewText(R.id.slot_room, item.subtitle);
rv.setTextColor(R.id.slot_room, 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.icon, UIUtils.getBreakIcon(item.title));
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_room, item.room);
rv.setTextColor(R.id.slot_room, mContext.getResources().getColor(R.color.body_text_2));
} else if (item.type == ScheduleItem.SESSION) {
rv.setImageViewResource(R.id.icon, UIUtils.getSessionIcon(item.sessionType));
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_room, item.room);
rv.setTextColor(R.id.slot_room, mContext.getResources().getColor(R.color.body_text_2));
// 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);
if (showConflict && !isNowPlaying) {
int conflictColor = mContext.getResources().getColor(R.color.my_schedule_conflict);
rv.setTextColor(R.id.start_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 MyScheduleDayAdapter: " + 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 = SettingsUtils.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 <= TimeUtils.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;
}
}
private String formatTime(long now, ScheduleItem item) {
StringBuilder time = new StringBuilder();
if (item.startTime <= now) {
// session is happening now!
if (0 != (item.flags & ScheduleItem.FLAG_HAS_LIVESTREAM)) {
// session has live stream
time.append(mContext.getString(R.string.watch_now));
} else {
time.append(mContext.getString(R.string.session_now));
}
} else {
// session in the future
time.append(TimeUtils.formatShortTime(mContext, new Date(item.startTime)));
}
time.append(" - ");
time.append(TimeUtils.formatShortTime(mContext, new Date(item.endTime)));
return time.toString();
}
}
}