Java tutorial
/* * 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.conferenceengineer.android.iosched.ui; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; 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.os.SystemClock; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.app.ActionBarActivity; import android.support.v7.view.ActionMode; import android.text.Spannable; import android.text.TextUtils; import android.util.SparseBooleanArray; import android.view.*; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.conferenceengineer.android.iosched.conference686.R; import com.conferenceengineer.android.iosched.provider.ScheduleContract; import com.conferenceengineer.android.iosched.util.PrefUtils; import com.conferenceengineer.android.iosched.util.SessionsHelper; import com.conferenceengineer.android.iosched.util.UIUtils; import java.util.ArrayList; import java.util.List; import static com.conferenceengineer.android.iosched.util.LogUtils.*; import static com.conferenceengineer.android.iosched.util.UIUtils.buildStyledSnippet; import static com.conferenceengineer.android.iosched.util.UIUtils.formatSessionSubtitle; /** * A {@link ListFragment} showing a list of sessions. */ public class SessionsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>, MultiSelectionUtil.MultiChoiceModeListener { private static final String TAG = makeLogTag(SessionsFragment.class); private static final String STATE_SELECTED_ID = "selectedId"; private CursorAdapter mAdapter; private String mSelectedSessionId; private MenuItem mStarredMenuItem; //private MenuItem mMapMenuItem; private MenuItem mShareMenuItem; private MenuItem mSocialStreamMenuItem; private int mSessionQueryToken; private Handler mHandler = new Handler(); private MultiSelectionUtil.Controller mMultiSelectionController; // instance state while view is destroyed but fragment is alive private Bundle mViewDestroyedInstanceState; public interface Callbacks { /** Return true to select (activate) the session in the list, false otherwise. */ public boolean onSessionSelected(String sessionId); } private static Callbacks sDummyCallbacks = new Callbacks() { @Override public boolean onSessionSelected(String sessionId) { return true; } }; private Callbacks mCallbacks = sDummyCallbacks; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mSelectedSessionId = savedInstanceState.getString(STATE_SELECTED_ID); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // As of support library r12, 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 call reloadFromArguments (which calls restartLoader/initLoader) in onActivityCreated. reloadFromArguments(getArguments()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_list_with_empty_container_inset, container, false); TextView emptyView = new TextView(getActivity(), null, R.attr.emptyText); emptyView.setText(R.string.empty_sessions); ((ViewGroup) rootView.findViewById(android.R.id.empty)).addView(emptyView); mMultiSelectionController = MultiSelectionUtil.attachMultiSelectionController( (ListView) rootView.findViewById(android.R.id.list), (ActionBarActivity) getActivity(), this); if (savedInstanceState == null && isMenuVisible()) { savedInstanceState = mViewDestroyedInstanceState; } mMultiSelectionController.tryRestoreInstanceState(savedInstanceState); return rootView; } @Override public void onDestroyView() { super.onDestroyView(); if (mMultiSelectionController != null) { mMultiSelectionController.finish(); } mMultiSelectionController = null; } void reloadFromArguments(Bundle arguments) { // Teardown from previous arguments setListAdapter(null); // Load new arguments final Intent intent = BaseActivity.fragmentArgumentsToIntent(arguments); Uri sessionsUri = intent.getData(); if (sessionsUri == null) { sessionsUri = ScheduleContract.Sessions.CONTENT_URI; } if (!ScheduleContract.Sessions.isSearchUri(sessionsUri)) { mAdapter = new SessionsAdapter(getActivity()); mSessionQueryToken = SessionsQuery._TOKEN; } else { mAdapter = new SearchAdapter(getActivity()); mSessionQueryToken = SearchQuery._TOKEN; } setListAdapter(mAdapter); // Force start background query to load sessions getLoaderManager().restartLoader(mSessionQueryToken, arguments, this); } public void setSelectedSessionId(String id) { mSelectedSessionId = id; if (mAdapter != null) { mAdapter.notifyDataSetChanged(); } } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); view.setBackgroundColor(Color.WHITE); final ListView listView = getListView(); listView.setSelector(android.R.color.transparent); listView.setCacheColorHint(Color.WHITE); } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof Callbacks)) { throw new ClassCastException("Activity must implement fragment's callbacks."); } mCallbacks = (Callbacks) activity; activity.getContentResolver().registerContentObserver(ScheduleContract.Sessions.CONTENT_URI, true, mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(activity); sp.registerOnSharedPreferenceChangeListener(mPrefChangeListener); } @Override public void onDetach() { super.onDetach(); mCallbacks = sDummyCallbacks; getActivity().getContentResolver().unregisterContentObserver(mObserver); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); sp.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); } @Override public void onResume() { super.onResume(); mHandler.post(mRefreshSessionsRunnable); } @Override public void onPause() { super.onPause(); mHandler.removeCallbacks(mRefreshSessionsRunnable); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mSelectedSessionId != null) { outState.putString(STATE_SELECTED_ID, mSelectedSessionId); } if (mMultiSelectionController != null) { mMultiSelectionController.saveInstanceState(outState); } } @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (mMultiSelectionController == null) { return; } // Hide the action mode when the fragment becomes invisible if (!menuVisible) { Bundle bundle = new Bundle(); if (mMultiSelectionController.saveInstanceState(bundle)) { mViewDestroyedInstanceState = bundle; mMultiSelectionController.finish(); } } else if (mViewDestroyedInstanceState != null) { mMultiSelectionController.tryRestoreInstanceState(mViewDestroyedInstanceState); mViewDestroyedInstanceState = null; } } /** {@inheritDoc} */ @Override public void onListItemClick(ListView l, View v, int position, long id) { final Cursor cursor = (Cursor) mAdapter.getItem(position); String sessionId = cursor.getString(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_ID)); if (mCallbacks.onSessionSelected(sessionId)) { mSelectedSessionId = sessionId; mAdapter.notifyDataSetChanged(); } } // LoaderCallbacks interface @Override public Loader<Cursor> onCreateLoader(int id, Bundle data) { final Intent intent = BaseActivity.fragmentArgumentsToIntent(data); Uri sessionsUri = intent.getData(); if (sessionsUri == null) { sessionsUri = ScheduleContract.Sessions.CONTENT_URI; } Loader<Cursor> loader = null; if (id == SessionsQuery._TOKEN) { loader = new CursorLoader(getActivity(), sessionsUri, SessionsQuery.PROJECTION, null, null, ScheduleContract.Sessions.DEFAULT_SORT); } else if (id == SearchQuery._TOKEN) { loader = new CursorLoader(getActivity(), sessionsUri, SearchQuery.PROJECTION, null, null, ScheduleContract.Sessions.DEFAULT_SORT); } return loader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (getActivity() == null) { return; } int token = loader.getId(); if (token == SessionsQuery._TOKEN || token == SearchQuery._TOKEN) { mAdapter.changeCursor(cursor); Bundle arguments = getArguments(); if (arguments != null && arguments.containsKey("_uri")) { String uri = arguments.get("_uri").toString(); if (uri != null && uri.contains("blocks")) { String title = arguments.getString(Intent.EXTRA_TITLE); if (title == null) { title = (String) this.getActivity().getTitle(); } LOGD("Tracker", "Session Block: " + title); } } } else { LOGD(TAG, "Query complete, Not Actionable: " + token); cursor.close(); } mMultiSelectionController.tryRestoreInstanceState(); } @Override public void onLoaderReset(Loader<Cursor> loader) { } private List<Integer> getCheckedSessionPositions() { List<Integer> checkedSessionPositions = new ArrayList<Integer>(); ListView listView = getListView(); if (listView == null) { return checkedSessionPositions; } SparseBooleanArray checkedPositionsBool = listView.getCheckedItemPositions(); for (int i = 0; i < checkedPositionsBool.size(); i++) { if (checkedPositionsBool.valueAt(i)) { checkedSessionPositions.add(checkedPositionsBool.keyAt(i)); } } return checkedSessionPositions; } // MultiChoiceModeListener interface @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { List<Integer> checkedSessionPositions = getCheckedSessionPositions(); SessionsHelper helper = new SessionsHelper(getActivity()); mode.finish(); switch (item.getItemId()) { case R.id.menu_star: { // multiple selection supported boolean starred = false; int numChanged = 0; for (int position : checkedSessionPositions) { Cursor cursor = (Cursor) mAdapter.getItem(position); String title = cursor.getString(SessionsQuery.TITLE); String sessionId = cursor.getString(SessionsQuery.SESSION_ID); Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(sessionId); starred = cursor.getInt(SessionsQuery.STARRED) == 0; helper.setSessionStarred(sessionUri, starred, title); ++numChanged; LOGV(TAG, "Starred: " + title); } Toast.makeText(getActivity(), getResources().getQuantityString( starred ? R.plurals.toast_added_to_schedule : R.plurals.toast_removed_from_schedule, numChanged, numChanged), Toast.LENGTH_SHORT).show(); setSelectedSessionStarred(starred); return true; } case R.id.menu_share: { // multiple selection not supported int position = checkedSessionPositions.get(0); // On ICS+ devices, we normally won't reach this as ShareActionProvider will handle // sharing. Cursor cursor = (Cursor) mAdapter.getItem(position); new SessionsHelper(getActivity()).shareSession(getActivity(), R.string.share_template, cursor.getString(SessionsQuery.TITLE), cursor.getString(SessionsQuery.HASHTAGS), cursor.getString(SessionsQuery.URL)); return true; } case R.id.menu_social_stream: // multiple selection not supported int position = checkedSessionPositions.get(0); Cursor cursor = (Cursor) mAdapter.getItem(position); String hashtags = cursor.getString(SessionsQuery.HASHTAGS); helper.startSocialStream(hashtags); return true; default: LOGW(TAG, "CAB unknown selection=" + item.getItemId()); return false; } } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.sessions_context, menu); mStarredMenuItem = menu.findItem(R.id.menu_star); //mMapMenuItem = menu.findItem(R.id.menu_map); mShareMenuItem = menu.findItem(R.id.menu_share); mSocialStreamMenuItem = menu.findItem(R.id.menu_social_stream); return true; } @Override public void onDestroyActionMode(ActionMode mode) { } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return true; } @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { List<Integer> checkedSessionPositions = getCheckedSessionPositions(); int numSelectedSessions = checkedSessionPositions.size(); mode.setTitle(getResources().getQuantityString(R.plurals.title_selected_sessions, numSelectedSessions, numSelectedSessions)); if (numSelectedSessions == 1) { // activate all the menu items //mMapMenuItem.setVisible(true); mShareMenuItem.setVisible(true); mStarredMenuItem.setVisible(true); position = checkedSessionPositions.get(0); Cursor cursor = (Cursor) mAdapter.getItem(position); boolean starred = cursor.getInt(SessionsQuery.STARRED) != 0; String hashtags = cursor.getString(SessionsQuery.HASHTAGS); mSocialStreamMenuItem.setVisible(!TextUtils.isEmpty(hashtags)); setSelectedSessionStarred(starred); } else { //mMapMenuItem.setVisible(false); mShareMenuItem.setVisible(false); mSocialStreamMenuItem.setVisible(false); boolean allStarred = true; boolean allUnstarred = true; for (int pos : checkedSessionPositions) { Cursor cursor = (Cursor) mAdapter.getItem(pos); boolean starred = cursor.getInt(SessionsQuery.STARRED) != 0; allStarred = allStarred && starred; allUnstarred = allUnstarred && !starred; } if (allStarred) { setSelectedSessionStarred(true); mStarredMenuItem.setVisible(true); } else if (allUnstarred) { setSelectedSessionStarred(false); mStarredMenuItem.setVisible(true); } else { mStarredMenuItem.setVisible(false); } } } private void setSelectedSessionStarred(boolean starred) { mStarredMenuItem .setTitle(starred ? R.string.description_remove_schedule : R.string.description_add_schedule); mStarredMenuItem .setIcon(starred ? R.drawable.ic_action_remove_schedule : R.drawable.ic_action_add_schedule); } private final Runnable mRefreshSessionsRunnable = new Runnable() { public void run() { if (mAdapter != null) { // This is used to refresh session title colors. mAdapter.notifyDataSetChanged(); } // Check again on the next quarter hour, with some padding to // account for network // time differences. long nextQuarterHour = (SystemClock.uptimeMillis() / 900000 + 1) * 900000 + 5000; mHandler.postAtTime(mRefreshSessionsRunnable, nextQuarterHour); } }; private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { if (isAdded() && mAdapter != null) { if (PrefUtils.PREF_LOCAL_TIMES.equals(key)) { PrefUtils.isUsingLocalTime(getActivity(), true); // force update mAdapter.notifyDataSetInvalidated(); } } } }; private final ContentObserver mObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { if (!isAdded()) { return; } Loader<Cursor> loader = getLoaderManager().getLoader(mSessionQueryToken); if (loader != null) { loader.forceLoad(); } } }; /** * {@link CursorAdapter} that renders a {@link SessionsQuery}. */ private class SessionsAdapter extends CursorAdapter { StringBuilder mBuffer = new StringBuilder(); public SessionsAdapter(Context context) { super(context, null, 0); } /** {@inheritDoc} */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return getActivity().getLayoutInflater().inflate(R.layout.list_item_session, parent, false); } /** {@inheritDoc} */ @Override public void bindView(View view, Context context, Cursor cursor) { String sessionId = cursor.getString(SessionsQuery.SESSION_ID); if (sessionId == null) { return; } UIUtils.setActivatedCompat(view, sessionId.equals(mSelectedSessionId)); final TextView titleView = (TextView) view.findViewById(R.id.session_title); final TextView subtitleView = (TextView) view.findViewById(R.id.session_subtitle); final String sessionTitle = cursor.getString(SessionsQuery.TITLE); titleView.setText(sessionTitle); // Format time block this session occupies final long blockStart = cursor.getLong(SessionsQuery.BLOCK_START); final long blockEnd = cursor.getLong(SessionsQuery.BLOCK_END); final String roomName = cursor.getString(SessionsQuery.ROOM_NAME); final String subtitle = formatSessionSubtitle(sessionTitle, blockStart, blockEnd, roomName, mBuffer, context); final boolean starred = cursor.getInt(SessionsQuery.STARRED) != 0; view.findViewById(R.id.indicator_in_schedule).setVisibility(starred ? View.VISIBLE : View.INVISIBLE); // Show past/present/future and livestream status for this block. UIUtils.updateTimeBlockUI(context, blockStart, blockEnd, titleView, subtitleView, subtitle); } } /** * {@link CursorAdapter} that renders a {@link SearchQuery}. */ private class SearchAdapter extends CursorAdapter { public SearchAdapter(Context context) { super(context, null, 0); } /** {@inheritDoc} */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return getActivity().getLayoutInflater().inflate(R.layout.list_item_session, parent, false); } /** {@inheritDoc} */ @Override public void bindView(View view, Context context, Cursor cursor) { UIUtils.setActivatedCompat(view, cursor.getString(SessionsQuery.SESSION_ID).equals(mSelectedSessionId)); ((TextView) view.findViewById(R.id.session_title)).setText(cursor.getString(SearchQuery.TITLE)); final String snippet = cursor.getString(SearchQuery.SEARCH_SNIPPET); final Spannable styledSnippet = buildStyledSnippet(snippet); ((TextView) view.findViewById(R.id.session_subtitle)).setText(styledSnippet); final boolean starred = cursor.getInt(SearchQuery.STARRED) != 0; view.findViewById(R.id.indicator_in_schedule).setVisibility(starred ? View.VISIBLE : View.INVISIBLE); } } /** * {@link com.conferenceengineer.android.iosched.provider.ScheduleContract.Sessions} * query parameters. */ private interface SessionsQuery { int _TOKEN = 0x1; String[] PROJECTION = { BaseColumns._ID, ScheduleContract.Sessions.SESSION_ID, ScheduleContract.Sessions.SESSION_TITLE, ScheduleContract.Sessions.SESSION_STARRED, ScheduleContract.Blocks.BLOCK_START, ScheduleContract.Blocks.BLOCK_END, ScheduleContract.Rooms.ROOM_NAME, ScheduleContract.Rooms.ROOM_ID, ScheduleContract.Sessions.SESSION_HASHTAGS, ScheduleContract.Sessions.SESSION_URL, }; int _ID = 0; int SESSION_ID = 1; int TITLE = 2; int STARRED = 3; int BLOCK_START = 4; int BLOCK_END = 5; int ROOM_NAME = 6; int ROOM_ID = 7; int HASHTAGS = 8; int URL = 9; } /** * {@link com.conferenceengineer.android.iosched.provider.ScheduleContract.Sessions} * search query parameters. */ private interface SearchQuery { int _TOKEN = 0x3; String[] PROJECTION = { BaseColumns._ID, ScheduleContract.Sessions.SESSION_ID, ScheduleContract.Sessions.SESSION_TITLE, ScheduleContract.Sessions.SESSION_STARRED, ScheduleContract.Sessions.SEARCH_SNIPPET, }; int _ID = 0; int SESSION_ID = 1; int TITLE = 2; int STARRED = 3; int SEARCH_SNIPPET = 4; } }