/* * 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.ui; import com.google.android.apps.iosched.R; import com.google.android.apps.iosched.provider.ScheduleContract; import com.google.android.apps.iosched.ui.tablet.SessionsVendorsMultiPaneActivity; import com.google.android.apps.iosched.ui.widget.SimpleSectionedListAdapter; import com.google.android.apps.iosched.util.ParserUtils; import com.google.android.apps.iosched.util.SessionsHelper; import com.google.android.apps.iosched.util.UIUtils; import com.google.android.apps.iosched.util.actionmodecompat.ActionMode; import com.actionbarsherlock.app.SherlockListFragment; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.BaseColumns; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.ImageButton; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import static com.google.android.apps.iosched.util.LogUtils.LOGW; import static com.google.android.apps.iosched.util.LogUtils.makeLogTag; /** * A fragment that shows the user's customized schedule, including sessions that she has chosen, * and common conference items such as keynote sessions, lunch breaks, after hours party, etc. */ public class MyScheduleFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor>, ActionMode.Callback { private static final String TAG = makeLogTag(MyScheduleFragment.class); private SimpleSectionedListAdapter mAdapter; private MyScheduleAdapter mScheduleAdapter; private SparseArray<String> mLongClickedItemData; private View mLongClickedView; private ActionMode mActionMode; private boolean mScrollToNow; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // The MyScheduleAdapter is wrapped in a SimpleSectionedListAdapter so that // we can show list headers separating out the different days of the conference // (Wednesday/Thursday/Friday). mScheduleAdapter = new MyScheduleAdapter(getActivity()); mAdapter = new SimpleSectionedListAdapter(getActivity(), R.layout.list_item_schedule_header, mScheduleAdapter); setListAdapter(mAdapter); if (savedInstanceState == null) { mScrollToNow = true; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup root = (ViewGroup) inflater.inflate( R.layout.fragment_list_with_empty_container, container, false); inflater.inflate(R.layout.empty_waiting_for_sync, (ViewGroup) root.findViewById(android.R.id.empty), true); root.setBackgroundColor(Color.WHITE); ListView listView = (ListView) root.findViewById(android.R.id.list); listView.setItemsCanFocus(true); listView.setCacheColorHint(Color.WHITE); listView.setSelector(android.R.color.transparent); //listView.setEmptyView(root.findViewById(android.R.id.empty)); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // In support library r8, calling initLoader for a fragment in a // FragmentPagerAdapter in the fragment's onCreate may cause the same LoaderManager to be // dealt to multiple fragments because their mIndex is -1 (haven't been added to the // activity yet). Thus, we do this in onActivityCreated. getLoaderManager().initLoader(0, null, this); } private final ContentObserver mObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { if (getActivity() == null) { return; } Loader<Cursor> loader = getLoaderManager().getLoader(0); if (loader != null) { loader.forceLoad(); } } }; @Override public void onAttach(Activity activity) { super.onAttach(activity); activity.getContentResolver().registerContentObserver( ScheduleContract.Sessions.CONTENT_URI, true, mObserver); } @Override public void onDetach() { super.onDetach(); getActivity().getContentResolver().unregisterContentObserver(mObserver); } // LoaderCallbacks @Override public Loader<Cursor> onCreateLoader(int id, Bundle data) { return new CursorLoader(getActivity(), ScheduleContract.Blocks.CONTENT_URI, BlocksQuery.PROJECTION, null, null, ScheduleContract.Blocks.DEFAULT_SORT); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (getActivity() == null) { return; } long currentTime = UIUtils.getCurrentTime(getActivity()); int firstNowPosition = ListView.INVALID_POSITION; List<SimpleSectionedListAdapter.Section> sections = new ArrayList<SimpleSectionedListAdapter.Section>(); cursor.moveToFirst(); long previousBlockStart = -1; long blockStart, blockEnd; while (!cursor.isAfterLast()) { blockStart = cursor.getLong(BlocksQuery.BLOCK_START); blockEnd = cursor.getLong(BlocksQuery.BLOCK_END); if (!UIUtils.isSameDay(previousBlockStart, blockStart)) { sections.add(new SimpleSectionedListAdapter.Section(cursor.getPosition(), DateUtils.formatDateTime(getActivity(), blockStart, DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY))); } if (mScrollToNow && firstNowPosition == ListView.INVALID_POSITION // if we're currently in this block, or we're not in a block // and this // block is in the future, then this is the scroll position && ((blockStart < currentTime && currentTime < blockEnd) || blockStart > currentTime)) { firstNowPosition = cursor.getPosition(); } previousBlockStart = blockStart; cursor.moveToNext(); } mScheduleAdapter.changeCursor(cursor); SimpleSectionedListAdapter.Section[] dummy = new SimpleSectionedListAdapter.Section[sections.size()]; mAdapter.setSections(sections.toArray(dummy)); if (mScrollToNow && firstNowPosition != ListView.INVALID_POSITION) { firstNowPosition = mAdapter.positionToSectionedPosition(firstNowPosition); getListView().setSelectionFromTop(firstNowPosition, getResources().getDimensionPixelSize(R.dimen.list_scroll_top_offset)); mScrollToNow = false; } } @Override public void onLoaderReset(Loader<Cursor> loader) { } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { SessionsHelper helper = new SessionsHelper(getActivity()); String title = mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_TITLE); String hashtags = mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_HASHTAGS); String url = mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_URL); boolean handled = false; switch (item.getItemId()) { case R.id.menu_map: String roomId = mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_ROOM_ID); helper.startMapActivity(roomId); handled = true; break; case R.id.menu_star: String sessionId = mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_ID); Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(sessionId); helper.setSessionStarred(sessionUri, false, title); handled = true; break; case R.id.menu_share: // On ICS+ devices, we normally won't reach this as ShareActionProvider will handle // sharing. helper.shareSession(getActivity(), R.string.share_template, title, hashtags, url); handled = true; break; case R.id.menu_social_stream: helper.startSocialStream(hashtags); handled = true; break; default: LOGW(TAG, "Unknown action taken"); } mActionMode.finish(); return handled; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.sessions_context, menu); MenuItem starMenuItem = menu.findItem(R.id.menu_star); starMenuItem.setTitle(R.string.description_remove_schedule); starMenuItem.setIcon(R.drawable.ic_action_remove_schedule); return true; } @Override public void onDestroyActionMode(ActionMode mode) { mActionMode = null; if (mLongClickedView != null) { UIUtils.setActivatedCompat(mLongClickedView, false); } } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } /** * A list adapter that shows schedule blocks as list items. It handles a number of different * cases, such as empty blocks where the user has not chosen a session, blocks with conflicts * (i.e. multiple sessions chosen), non-session blocks, etc. */ private class MyScheduleAdapter extends CursorAdapter { public MyScheduleAdapter(Context context) { super(context, null, false); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return getActivity().getLayoutInflater().inflate(R.layout.list_item_schedule_block, parent, false); } @Override public void bindView(View view, Context context, final Cursor cursor) { final String type = cursor.getString(BlocksQuery.BLOCK_TYPE); final String blockId = cursor.getString(BlocksQuery.BLOCK_ID); final String blockTitle = cursor.getString(BlocksQuery.BLOCK_TITLE); final long blockStart = cursor.getLong(BlocksQuery.BLOCK_START); final long blockEnd = cursor.getLong(BlocksQuery.BLOCK_END); final String blockMeta = cursor.getString(BlocksQuery.BLOCK_META); final String blockTimeString = UIUtils.formatBlockTimeString(blockStart, blockEnd, context); final TextView timeView = (TextView) view.findViewById(R.id.block_time); final TextView titleView = (TextView) view.findViewById(R.id.block_title); final TextView subtitleView = (TextView) view.findViewById(R.id.block_subtitle); final ImageButton extraButton = (ImageButton) view.findViewById(R.id.extra_button); final View primaryTouchTargetView = view.findViewById(R.id.list_item_middle_container); final Resources res = getResources(); String subtitle; boolean isLiveStreamed = false; primaryTouchTargetView.setOnLongClickListener(null); UIUtils.setActivatedCompat(primaryTouchTargetView, false); if (ParserUtils.BLOCK_TYPE_SESSION.equals(type) || ParserUtils.BLOCK_TYPE_CODE_LAB.equals(type)) { final int numStarredSessions = cursor.getInt(BlocksQuery.NUM_STARRED_SESSIONS); final String starredSessionId = cursor.getString(BlocksQuery.STARRED_SESSION_ID); View.OnClickListener allSessionsListener = new View.OnClickListener() { @Override public void onClick(View view) { final Uri sessionsUri = ScheduleContract.Blocks.buildSessionsUri(blockId); final Intent intent = new Intent(Intent.ACTION_VIEW, sessionsUri); intent.putExtra(Intent.EXTRA_TITLE, blockTimeString); startActivity(intent); } }; if (numStarredSessions == 0) { // 0 sessions starred titleView.setText(getString(R.string.schedule_empty_slot_title_template, TextUtils.isEmpty(blockTitle) ? "" : (" " + blockTitle.toLowerCase()))); titleView.setTextColor(res.getColorStateList(R.color.body_text_1_positive)); subtitle = getString(R.string.schedule_empty_slot_subtitle); extraButton.setVisibility(View.GONE); primaryTouchTargetView.setOnClickListener(allSessionsListener); } else if (numStarredSessions == 1) { // exactly 1 session starred final String starredSessionTitle = cursor.getString(BlocksQuery.STARRED_SESSION_TITLE); titleView.setText(starredSessionTitle); titleView.setTextColor(res.getColorStateList(R.color.body_text_1)); subtitle = cursor.getString(BlocksQuery.STARRED_SESSION_ROOM_NAME); if (subtitle == null) { // TODO: remove this WAR for API not returning rooms for code labs subtitle = getString( starredSessionTitle.contains("Code Lab") ? R.string.codelab_room : R.string.unknown_room); } isLiveStreamed = !TextUtils.isEmpty( cursor.getString(BlocksQuery.STARRED_SESSION_LIVESTREAM_URL)); extraButton.setVisibility(View.VISIBLE); extraButton.setOnClickListener(allSessionsListener); if (mLongClickedItemData != null && mActionMode != null && mLongClickedItemData.get(BlocksQuery.STARRED_SESSION_ID, "").equals( starredSessionId)) { UIUtils.setActivatedCompat(primaryTouchTargetView, true); mLongClickedView = primaryTouchTargetView; } primaryTouchTargetView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final Uri sessionUri = ScheduleContract.Sessions.buildSessionUri( starredSessionId); final Intent intent = new Intent(Intent.ACTION_VIEW, sessionUri); intent.putExtra(SessionsVendorsMultiPaneActivity.EXTRA_MASTER_URI, ScheduleContract.Blocks.buildSessionsUri(blockId)); intent.putExtra(Intent.EXTRA_TITLE, blockTimeString); startActivity(intent); } }); primaryTouchTargetView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mActionMode != null) { // CAB already displayed, ignore return true; } mLongClickedView = primaryTouchTargetView; String hashtags = cursor.getString(BlocksQuery.STARRED_SESSION_HASHTAGS); String url = cursor.getString(BlocksQuery.STARRED_SESSION_URL); String roomId = cursor.getString(BlocksQuery.STARRED_SESSION_ROOM_ID); mLongClickedItemData = new SparseArray<String>(); mLongClickedItemData.put(BlocksQuery.STARRED_SESSION_ID, starredSessionId); mLongClickedItemData.put(BlocksQuery.STARRED_SESSION_TITLE, starredSessionTitle); mLongClickedItemData.put(BlocksQuery.STARRED_SESSION_HASHTAGS, hashtags); mLongClickedItemData.put(BlocksQuery.STARRED_SESSION_URL, url); mLongClickedItemData.put(BlocksQuery.STARRED_SESSION_ROOM_ID, roomId); mActionMode = ActionMode.start(getActivity(), MyScheduleFragment.this); UIUtils.setActivatedCompat(primaryTouchTargetView, true); return true; } }); } else { // 2 or more sessions starred titleView.setText(getString(R.string.schedule_conflict_title, numStarredSessions)); titleView.setTextColor(res.getColorStateList(R.color.body_text_1)); subtitle = getString(R.string.schedule_conflict_subtitle); extraButton.setVisibility(View.VISIBLE); extraButton.setOnClickListener(allSessionsListener); primaryTouchTargetView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final Uri sessionsUri = ScheduleContract.Blocks .buildStarredSessionsUri( blockId); final Intent intent = new Intent(Intent.ACTION_VIEW, sessionsUri); intent.putExtra(Intent.EXTRA_TITLE, blockTimeString); startActivity(intent); } }); } subtitleView.setTextColor(res.getColorStateList(R.color.body_text_2)); primaryTouchTargetView.setEnabled(true); } else if (ParserUtils.BLOCK_TYPE_KEYNOTE.equals(type)) { final String starredSessionId = cursor.getString(BlocksQuery.STARRED_SESSION_ID); final String starredSessionTitle = cursor.getString(BlocksQuery.STARRED_SESSION_TITLE); long currentTimeMillis = UIUtils.getCurrentTime(context); boolean past = (currentTimeMillis > blockEnd && currentTimeMillis < UIUtils.CONFERENCE_END_MILLIS); boolean present = !past && (currentTimeMillis >= blockStart); boolean canViewStream = present && UIUtils.hasHoneycomb(); isLiveStreamed = true; titleView.setTextColor(canViewStream ? res.getColorStateList(R.color.body_text_1) : res.getColorStateList(R.color.body_text_disabled)); subtitleView.setTextColor(canViewStream ? res.getColorStateList(R.color.body_text_2) : res.getColorStateList(R.color.body_text_disabled)); subtitle = getString(R.string.keynote_room); titleView.setText(starredSessionTitle); extraButton.setVisibility(View.GONE); primaryTouchTargetView.setEnabled(canViewStream); primaryTouchTargetView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final Uri sessionUri = ScheduleContract.Sessions.buildSessionUri( starredSessionId); Intent livestreamIntent = new Intent(Intent.ACTION_VIEW, sessionUri); livestreamIntent.setClass(getActivity(), SessionLivestreamActivity.class); startActivity(livestreamIntent); } }); } else { titleView.setTextColor(res.getColorStateList(R.color.body_text_disabled)); subtitleView.setTextColor(res.getColorStateList(R.color.body_text_disabled)); subtitle = blockMeta; titleView.setText(blockTitle); extraButton.setVisibility(View.GONE); primaryTouchTargetView.setEnabled(false); primaryTouchTargetView.setOnClickListener(null); } timeView.setText(DateUtils.formatDateTime(context, blockStart, DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_12HOUR)); // Show past/present/future and livestream status for this block. UIUtils.updateTimeAndLivestreamBlockUI(context, blockStart, blockEnd, isLiveStreamed, view, titleView, subtitleView, subtitle); } } private 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.SESSIONS_COUNT, ScheduleContract.Blocks.NUM_STARRED_SESSIONS, ScheduleContract.Blocks.STARRED_SESSION_ID, ScheduleContract.Blocks.STARRED_SESSION_TITLE, ScheduleContract.Blocks.STARRED_SESSION_ROOM_NAME, ScheduleContract.Blocks.STARRED_SESSION_ROOM_ID, ScheduleContract.Blocks.STARRED_SESSION_HASHTAGS, ScheduleContract.Blocks.STARRED_SESSION_URL, ScheduleContract.Blocks.STARRED_SESSION_LIVESTREAM_URL, }; 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 SESSIONS_COUNT = 7; int NUM_STARRED_SESSIONS = 8; int STARRED_SESSION_ID = 9; int STARRED_SESSION_TITLE = 10; int STARRED_SESSION_ROOM_NAME = 11; int STARRED_SESSION_ROOM_ID = 12; int STARRED_SESSION_HASHTAGS = 13; int STARRED_SESSION_URL = 14; int STARRED_SESSION_LIVESTREAM_URL = 15; } }