/* * Copyright 2012 Google Inc. * * 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.android.apps.iosched.appwidget; import com.google.android.apps.iosched.R; import com.google.android.apps.iosched.provider.ScheduleContract; import com.google.android.apps.iosched.ui.HomeActivity; import com.google.android.apps.iosched.ui.SessionLivestreamActivity; import com.google.android.apps.iosched.ui.SimpleSectionedListAdapter; import com.google.android.apps.iosched.ui.SimpleSectionedListAdapter.Section; import com.google.android.apps.iosched.ui.TaskStackBuilderProxyActivity; import com.google.android.apps.iosched.util.AccountUtils; import com.google.android.apps.iosched.util.PrefUtils; import com.google.android.apps.iosched.util.UIUtils; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.os.Build; import android.provider.BaseColumns; import android.text.TextUtils; 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 java.util.ArrayList; import java.util.Formatter; import java.util.List; import java.util.Locale; import static com.google.android.apps.iosched.util.LogUtils.LOGV; import static com.google.android.apps.iosched.util.LogUtils.makeLogTag; /** * This is the service that provides the factory to be bound to the collection service. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ScheduleWidgetRemoteViewsService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new WidgetRemoveViewsFactory(this.getApplicationContext()); } /** * This is the factory that will provide data to the collection widget. */ private static class WidgetRemoveViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final String TAG = makeLogTag(WidgetRemoveViewsFactory.class); private Context mContext; private Cursor mCursor; private SparseIntArray mPMap; private List<SimpleSectionedListAdapter.Section> mSections; private SparseBooleanArray mHeaderPositionMap; StringBuilder mBuffer = new StringBuilder(); Formatter mFormatter = new Formatter(mBuffer, Locale.getDefault()); public WidgetRemoveViewsFactory(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() { if (mCursor != null) { mCursor.close(); } } public int getCount() { if (mCursor == null || !AccountUtils.isAuthenticated(mContext)) { return 0; } int size = mCursor.getCount() + mSections.size(); if (size < 10) { init(); size = mCursor.getCount() + mSections.size(); } LOGV(TAG, "size returned:" + size); return size; } public RemoteViews getViewAt(int position) { RemoteViews rv; boolean isSectionHeader = mHeaderPositionMap.get(position); int offset = mPMap.get(position); Intent homeIntent = new Intent(mContext, HomeActivity.class); if (isSectionHeader) { rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item_schedule_header); Section section = mSections.get(offset - 1); rv.setTextViewText(R.id.list_item_schedule_header_textview, section.getTitle()); } else { int cursorPosition = position - offset; mCursor.moveToPosition(cursorPosition); rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item_schedule_block_widget); final String type = mCursor.getString(BlocksQuery.BLOCK_TYPE); final String blockId = mCursor.getString(BlocksQuery.BLOCK_ID); final String blockTitle = mCursor.getString(BlocksQuery.BLOCK_TITLE); final String blockType = mCursor.getString(BlocksQuery.BLOCK_TYPE); final String blockMeta = mCursor.getString(BlocksQuery.BLOCK_META); final long blockStart = mCursor.getLong(BlocksQuery.BLOCK_START); final long blockEnd = mCursor.getLong(BlocksQuery.BLOCK_END); final Resources res = mContext.getResources(); rv.setTextViewText(R.id.block_endtime, null); if (ScheduleContract.Blocks.BLOCK_TYPE_SESSION.equals(type) || ScheduleContract.Blocks.BLOCK_TYPE_CODELAB.equals(type) || ScheduleContract.Blocks.BLOCK_TYPE_OFFICE_HOURS.equals(blockType)) { final int numStarredSessions = mCursor.getInt(BlocksQuery.NUM_STARRED_SESSIONS); final String starredSessionId = mCursor .getString(BlocksQuery.STARRED_SESSION_ID); if (numStarredSessions == 0) { // No sessions starred rv.setTextViewText(R.id.block_title, mContext.getString( R.string.schedule_empty_slot_title_template, TextUtils.isEmpty(blockTitle) ? "" : (" " + blockTitle.toLowerCase()))); rv.setTextColor(R.id.block_title, res.getColor(R.color.body_text_1_positive)); rv.setTextViewText(R.id.block_subtitle, mContext.getString( R.string.schedule_empty_slot_subtitle)); rv.setViewVisibility(R.id.extra_button, View.GONE); Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Blocks .buildSessionsUri(blockId))); rv.setOnClickFillInIntent(R.id.list_item_middle_container, fillIntent); } else if (numStarredSessions == 1) { // exactly 1 session starred final String starredSessionTitle = mCursor.getString(BlocksQuery.STARRED_SESSION_TITLE); String starredSessionSubtitle = mCursor.getString(BlocksQuery.STARRED_SESSION_ROOM_NAME); if (starredSessionSubtitle == null) { starredSessionSubtitle = mContext.getString(R.string.unknown_room); } // Determine if the session is in the past long currentTimeMillis = UIUtils.getCurrentTime(mContext); boolean conferenceEnded = currentTimeMillis > UIUtils.CONFERENCE_END_MILLIS; boolean blockEnded = currentTimeMillis > blockEnd; if (blockEnded && !conferenceEnded) { starredSessionSubtitle = mContext.getString(R.string.session_finished); } rv.setTextViewText(R.id.block_title, starredSessionTitle); rv.setTextColor(R.id.block_title, res.getColor(R.color.body_text_1)); rv.setTextViewText(R.id.block_subtitle, starredSessionSubtitle); rv.setViewVisibility(R.id.extra_button, View.VISIBLE); Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions .buildSessionUri(starredSessionId))); rv.setOnClickFillInIntent(R.id.list_item_middle_container, fillIntent); fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Blocks .buildSessionsUri(blockId))); rv.setOnClickFillInIntent(R.id.extra_button, fillIntent); } else { // 2 or more sessions starred rv.setTextViewText(R.id.block_title, mContext.getString(R.string.schedule_conflict_title, numStarredSessions)); rv.setTextColor(R.id.block_title, res.getColor(R.color.body_text_1)); rv.setTextViewText(R.id.block_subtitle, mContext.getString(R.string.schedule_conflict_subtitle)); rv.setViewVisibility(R.id.extra_button, View.VISIBLE); Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Blocks .buildStarredSessionsUri(blockId))); rv.setOnClickFillInIntent(R.id.list_item_middle_container, fillIntent); fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Blocks .buildSessionsUri(blockId))); rv.setOnClickFillInIntent(R.id.extra_button, fillIntent); } rv.setTextColor(R.id.block_subtitle, res.getColor(R.color.body_text_2)); } else if (ScheduleContract.Blocks.BLOCK_TYPE_KEYNOTE.equals(type)) { long currentTimeMillis = UIUtils.getCurrentTime(mContext); boolean past = (currentTimeMillis > blockEnd && currentTimeMillis < UIUtils.CONFERENCE_END_MILLIS); boolean present = !past && (currentTimeMillis >= blockStart); boolean canViewStream = present && UIUtils.hasHoneycomb(); final String starredSessionId = mCursor .getString(BlocksQuery.STARRED_SESSION_ID); final String starredSessionTitle = mCursor.getString(BlocksQuery.STARRED_SESSION_TITLE); rv.setTextViewText(R.id.block_title, starredSessionTitle); rv.setTextViewText(R.id.block_subtitle, res.getString(R.string.keynote_room)); rv.setTextColor(R.id.block_title, canViewStream ? res.getColor(R.color.body_text_1) : res.getColor(R.color.body_text_disabled)); rv.setTextColor(R.id.block_subtitle, canViewStream ? res.getColor(R.color.body_text_2) : res.getColor(R.color.body_text_disabled)); rv.setViewVisibility(R.id.extra_button, View.GONE); if (canViewStream) { Intent fillIntent = TaskStackBuilderProxyActivity.getFillIntent( homeIntent, new Intent(Intent.ACTION_VIEW, ScheduleContract.Sessions .buildSessionUri(starredSessionId)) .setClass(mContext, SessionLivestreamActivity.class)); rv.setOnClickFillInIntent(R.id.list_item_middle_container, fillIntent); } else { rv.setOnClickFillInIntent(R.id.list_item_middle_container, new Intent()); } } else { rv.setTextViewText(R.id.block_title, blockTitle); rv.setTextColor(R.id.block_title, res.getColor(R.color.body_text_disabled)); rv.setTextViewText(R.id.block_subtitle, blockMeta); rv.setTextColor(R.id.block_subtitle, res.getColor(R.color.body_text_disabled)); rv.setViewVisibility(R.id.extra_button, View.GONE); mBuffer.setLength(0); rv.setTextViewText(R.id.block_endtime, DateUtils.formatDateRange(mContext, mFormatter, blockEnd, blockEnd, DateUtils.FORMAT_SHOW_TIME, PrefUtils.getDisplayTimeZone(mContext).getID()).toString()); rv.setOnClickFillInIntent(R.id.list_item_middle_container, new Intent()); } mBuffer.setLength(0); rv.setTextViewText(R.id.block_time, DateUtils.formatDateRange(mContext, mFormatter, blockStart, blockStart, DateUtils.FORMAT_SHOW_TIME, PrefUtils.getDisplayTimeZone(mContext).getID()).toString()); } return rv; } public RemoteViews getLoadingView() { return null; } public int getViewTypeCount() { return 2; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } public void onDataSetChanged() { init(); } private void init() { if (mCursor != null) { mCursor.close(); } String liveStreamedOnlyBlocksSelection = "(" + (UIUtils.shouldShowLiveSessionsOnly(mContext) ? ScheduleContract.Blocks.BLOCK_TYPE + " NOT IN ('" + ScheduleContract.Blocks.BLOCK_TYPE_SESSION + "','" + ScheduleContract.Blocks.BLOCK_TYPE_CODELAB + "','" + ScheduleContract.Blocks.BLOCK_TYPE_OFFICE_HOURS + "','" + ScheduleContract.Blocks.BLOCK_TYPE_FOOD + "')" + " OR " + ScheduleContract.Blocks.NUM_LIVESTREAMED_SESSIONS + ">1 " : "1==1") + ")"; String onlyStarredOfficeHoursSelection = "(" + ScheduleContract.Blocks.BLOCK_TYPE + " != '" + ScheduleContract.Blocks.BLOCK_TYPE_OFFICE_HOURS + "' OR " + ScheduleContract.Blocks.NUM_STARRED_SESSIONS + ">0)"; String excludeSandbox = "("+ScheduleContract.Blocks.BLOCK_TYPE + " != '" + ScheduleContract.Blocks.BLOCK_TYPE_SANDBOX +"')"; mCursor = mContext.getContentResolver().query(ScheduleContract.Blocks.CONTENT_URI, BlocksQuery.PROJECTION, ScheduleContract.Blocks.BLOCK_END + " >= ? AND " + liveStreamedOnlyBlocksSelection + " AND " + onlyStarredOfficeHoursSelection + " AND " + excludeSandbox, new String[]{ Long.toString(UIUtils.getCurrentTime(mContext)) }, ScheduleContract.Blocks.DEFAULT_SORT); String displayTimeZone = PrefUtils.getDisplayTimeZone(mContext).getID(); mSections = new ArrayList<SimpleSectionedListAdapter.Section>(); mCursor.moveToFirst(); long previousTime = -1; long time; mPMap = new SparseIntArray(); mHeaderPositionMap = new SparseBooleanArray(); int offset = 0; int globalPosition = 0; while (!mCursor.isAfterLast()) { time = mCursor.getLong(BlocksQuery.BLOCK_START); if (!UIUtils.isSameDayDisplay(previousTime, time, mContext)) { mBuffer.setLength(0); mSections.add(new SimpleSectionedListAdapter.Section(mCursor.getPosition(), DateUtils.formatDateRange( mContext, mFormatter, time, time, DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY, displayTimeZone).toString())); ++offset; mHeaderPositionMap.put(globalPosition, true); mPMap.put(globalPosition, offset); ++globalPosition; } mHeaderPositionMap.put(globalPosition, false); mPMap.put(globalPosition, offset); ++globalPosition; previousTime = time; mCursor.moveToNext(); } LOGV(TAG, "Leaving init()"); } } public interface BlocksQuery { String[] PROJECTION = { BaseColumns._ID, ScheduleContract.Blocks.BLOCK_ID, ScheduleContract.Blocks.BLOCK_TITLE, ScheduleContract.Blocks.BLOCK_START, ScheduleContract.Blocks.BLOCK_END, ScheduleContract.Blocks.BLOCK_TYPE, ScheduleContract.Blocks.BLOCK_META, ScheduleContract.Blocks.NUM_STARRED_SESSIONS, ScheduleContract.Blocks.NUM_LIVESTREAMED_SESSIONS, ScheduleContract.Blocks.STARRED_SESSION_ID, ScheduleContract.Blocks.STARRED_SESSION_TITLE, ScheduleContract.Blocks.STARRED_SESSION_ROOM_NAME, }; int _ID = 0; int BLOCK_ID = 1; int BLOCK_TITLE = 2; int BLOCK_START = 3; int BLOCK_END = 4; int BLOCK_TYPE = 5; int BLOCK_META = 6; int NUM_STARRED_SESSIONS = 7; int NUM_LIVESTREAMED_SESSIONS = 8; int STARRED_SESSION_ID = 9; int STARRED_SESSION_TITLE = 10; int STARRED_SESSION_ROOM_NAME = 11; } }