public class EasyTracker {
    public static EasyTracker getTracker() {
        return new EasyTracker();
    }

    public void trackView(String s) {
    }

    public void trackActivityStart(Activity activity) {
    }

    public void trackActivityStop(Activity activity) {
    }

    public void setContext(Context context) {
    }

    public void trackEvent(String s1, String s2, String s3, long l) {
    }

    public void dispatch() {
    }
} public class ScheduleContract {

    /**
     * Special value for {@link SyncColumns#UPDATED} indicating that an entry
     * has never been updated, or doesn't exist yet.
     */
    public static final long UPDATED_NEVER = -2;

    /**
     * Special value for {@link SyncColumns#UPDATED} indicating that the last
     * update time is unknown, usually when inserted from a local file source.
     */
    public static final long UPDATED_UNKNOWN = -1;

    public interface SyncColumns {
        /** Last time this entry was updated or synchronized. */
        String UPDATED = "updated";
    }

    interface BlocksColumns {
        /** Unique string identifying this block of time. */
        String BLOCK_ID = "block_id";
        /** Title describing this block of time. */
        String BLOCK_TITLE = "block_title";
        /** Time when this block starts. */
        String BLOCK_START = "block_start";
        /** Time when this block ends. */
        String BLOCK_END = "block_end";
        /** Type describing this block. */
        String BLOCK_TYPE = "block_type";
        /** Extra string metadata for the block. */
        String BLOCK_META = "block_meta";
    }

    interface TracksColumns {
        /** Unique string identifying this track. */
        String TRACK_ID = "track_id";
        /** Name describing this track. */
        String TRACK_NAME = "track_name";
        /** Color used to identify this track, in {@link Color#argb} format. */
        String TRACK_COLOR = "track_color";
        /** Body of text explaining this track in detail. */
        String TRACK_ABSTRACT = "track_abstract";
    }

    interface RoomsColumns {
        /** Unique string identifying this room. */
        String ROOM_ID = "room_id";
        /** Name describing this room. */
        String ROOM_NAME = "room_name";
        /** Building floor this room exists on. */
        String ROOM_FLOOR = "room_floor";
    }

    interface SessionsColumns {
        /** Unique string identifying this session. */
        String SESSION_ID = "session_id";
        /** The type of session (session, keynote, codelab, etc). */
        String SESSION_TYPE = "session_type";
        /** Difficulty level of the session. */
        String SESSION_LEVEL = "session_level";
        /** Title describing this track. */
        String SESSION_TITLE = "session_title";
        /** Body of text explaining this session in detail. */
        String SESSION_ABSTRACT = "session_abstract";
        /** Requirements that attendees should meet. */
        String SESSION_REQUIREMENTS = "session_requirements";
        /** Keywords/tags for this session. */
        String SESSION_TAGS = "session_keywords";
        /** Hashtag for this session. */
        String SESSION_HASHTAGS = "session_hashtag";
        /** Full URL to session online. */
        String SESSION_URL = "session_url";
        /** Full URL to YouTube. */
        String SESSION_YOUTUBE_URL = "session_youtube_url";
        /** Full URL to PDF. */
        String SESSION_PDF_URL = "session_pdf_url";
        /** Full URL to official session notes. */
        String SESSION_NOTES_URL = "session_notes_url";
        /** User-specific flag indicating starred status. */
        String SESSION_STARRED = "session_starred";
        /** Key for session Calendar event. (Used in ICS or above) */
        String SESSION_CAL_EVENT_ID = "session_cal_event_id";
        /** The YouTube live stream URL. */
        String SESSION_LIVESTREAM_URL = "session_livestream_url";
        String SESSION_START = "session_start";
        String SESSION_END = "session_end";
    }

    interface SpeakersColumns {
        /** Unique string identifying this speaker. */
        String SPEAKER_ID = "speaker_id";
        /** Name of this speaker. */
        String SPEAKER_NAME = "speaker_name";
        /** Profile photo of this speaker. */
        String SPEAKER_IMAGE_URL = "speaker_image_url";
        /** Company this speaker works for. */
        String SPEAKER_COMPANY = "speaker_company";
        /** Body of text describing this speaker in detail. */
        String SPEAKER_ABSTRACT = "speaker_abstract";
        /** Full URL to the speaker's profile. */
        String SPEAKER_URL = "speaker_url";
    }

    interface VendorsColumns {
        /** Unique string identifying this vendor. */
        String VENDOR_ID = "vendor_id";
        /** Name of this vendor. */
        String VENDOR_NAME = "vendor_name";
        /** Location or city this vendor is based in. */
        String VENDOR_LOCATION = "vendor_location";
        /** Body of text describing this vendor. */
        String VENDOR_DESC = "vendor_desc";
        /** Link to vendor online. */
        String VENDOR_URL = "vendor_url";
        /** Body of text describing the product of this vendor. */
        String VENDOR_PRODUCT_DESC = "vendor_product_desc";
        /** Link to vendor logo. */
        String VENDOR_LOGO_URL = "vendor_logo_url";
        /** User-specific flag indicating starred status. */
        String VENDOR_STARRED = "vendor_starred";
    }

    interface AnnouncementsColumns {
        /** Unique string identifying this announcment. */
        String ANNOUNCEMENT_ID = "announcement_id";
        /** Title of the announcement. */
        String ANNOUNCEMENT_TITLE = "announcement_title";
        /** Summary of the announcement. */
        String ANNOUNCEMENT_SUMMARY = "announcement_summary";
        /** Track announcement belongs to. */
        String ANNOUNCEMENT_TRACKS = "announcement_tracks";
        /** Full URL for the announcement. */
        String ANNOUNCEMENT_URL = "announcement_url";
        /** Date of the announcement. */
        String ANNOUNCEMENT_DATE = "announcement_date";
    }

    public static final String CONTENT_AUTHORITY = "no.java.schedule";

    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    private static final String PATH_BLOCKS = "blocks";
    private static final String PATH_AT = "at";
    private static final String PATH_BETWEEN = "between";
    private static final String PATH_TRACKS = "tracks";
    private static final String PATH_ROOMS = "rooms";
    private static final String PATH_SESSIONS = "sessions";
    private static final String PATH_WITH_TRACK = "with_track";
    private static final String PATH_STARRED = "starred";
    private static final String PATH_SPEAKERS = "speakers";
    private static final String PATH_VENDORS = "vendors";
    private static final String PATH_ANNOUNCEMENTS = "announcements";
    private static final String PATH_SEARCH = "search";
    private static final String PATH_SEARCH_SUGGEST = "search_suggest_query";

    /**
     * Blocks are generic timeslots that {@link Sessions} and other related
     * events fall into.
     */
    public static class Blocks implements BlocksColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_BLOCKS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.block";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.block";

        /** Count of {@link Sessions} inside given block. */
        public static final String SESSIONS_COUNT = "sessions_count";

        /**
         * Flag indicating the number of sessions inside this block that have
         * {@link Sessions#SESSION_STARRED} set.
         */
        public static final String NUM_STARRED_SESSIONS = "num_starred_sessions";

        /**
         * The {@link Sessions#SESSION_ID} of the first starred session in this
         * block.
         */
        public static final String STARRED_SESSION_ID = "starred_session_id";

        /**
         * The {@link Sessions#SESSION_TITLE} of the first starred session in
         * this block.
         */
        public static final String STARRED_SESSION_TITLE = "starred_session_title";

        /**
         * The {@link Sessions#SESSION_LIVESTREAM_URL} of the first starred
         * session in this block.
         */
        public static final String STARRED_SESSION_LIVESTREAM_URL =
                "starred_session_livestream_url";

        /**
         * The {@link Rooms#ROOM_NAME} of the first starred session in this
         * block.
         */
        public static final String STARRED_SESSION_ROOM_NAME = "starred_session_room_name";

        /**
         * The {@link Rooms#ROOM_ID} of the first starred session in this block.
         */
        public static final String STARRED_SESSION_ROOM_ID = "starred_session_room_id";

        /**
         * The {@link Sessions#SESSION_HASHTAGS} of the first starred session in
         * this block.
         */
        public static final String STARRED_SESSION_HASHTAGS = "starred_session_hashtags";

        /**
         * The {@link Sessions#SESSION_URL} of the first starred session in this
         * block.
         */
        public static final String STARRED_SESSION_URL = "starred_session_url";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START + " ASC, "
                + BlocksColumns.BLOCK_END + " ASC";

        public static final String EMPTY_SESSIONS_SELECTION = "(" + BLOCK_TYPE
                + " = '" + ParserUtils.BLOCK_TYPE_SESSION + "' OR " + BLOCK_TYPE
                + " = '" + ParserUtils.BLOCK_TYPE_CODE_LAB + "') AND "
                + SESSIONS_COUNT + " = 0";

        /** Build {@link Uri} for requested {@link #BLOCK_ID}. */
        public static Uri buildBlockUri(String blockId) {
            return CONTENT_URI.buildUpon().appendPath(blockId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Sessions} associated
         * with the requested {@link #BLOCK_ID}.
         */
        public static Uri buildSessionsUri(String blockId) {
            return CONTENT_URI.buildUpon().appendPath(blockId).appendPath(PATH_SESSIONS).build();
        }

        /**
         * Build {@link Uri} that references starred {@link Sessions} associated
         * with the requested {@link #BLOCK_ID}.
         */
        public static Uri buildStarredSessionsUri(String blockId) {
            return CONTENT_URI.buildUpon().appendPath(blockId).appendPath(PATH_SESSIONS)
                    .appendPath(PATH_STARRED).build();
        }

        /**
         * Build {@link Uri} that references any {@link Blocks} that occur
         * between the requested time boundaries.
         */
        public static Uri buildBlocksBetweenDirUri(long startTime, long endTime) {
            return CONTENT_URI.buildUpon().appendPath(PATH_BETWEEN).appendPath(
                    String.valueOf(startTime)).appendPath(String.valueOf(endTime)).build();
        }

        /** Read {@link #BLOCK_ID} from {@link Blocks} {@link Uri}. */
        public static String getBlockId(Uri uri) {
            return uri.getPathSegments().get(1);
        }

        /**
         * Generate a {@link #BLOCK_ID} that will always match the requested
         * {@link Blocks} details.
         */
        public static String generateBlockId(long startTime, long endTime) {
            startTime /= DateUtils.SECOND_IN_MILLIS;
            endTime /= DateUtils.SECOND_IN_MILLIS;
            return ParserUtils.sanitizeId(startTime + "-" + endTime);
        }
    }

    /**
     * Tracks are overall categories for {@link Sessions} and {@link Vendors},
     * such as "Android" or "Enterprise."
     */
    public static class Tracks implements TracksColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_TRACKS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.track";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.track";

        /** "All tracks" ID. */
        public static final String ALL_TRACK_ID = "all";
        public static final String CODELABS_TRACK_ID = generateTrackId("Code Labs");
        public static final String TECH_TALK_TRACK_ID = generateTrackId("Tech Talk");

        /** Count of {@link Sessions} inside given track. */
        public static final String SESSIONS_COUNT = "sessions_count";
        /** Count of {@link Vendors} inside given track. */
        public static final String VENDORS_COUNT = "vendors_count";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = TracksColumns.TRACK_NAME + " ASC";

        /** Build {@link Uri} for requested {@link #TRACK_ID}. */
        public static Uri buildTrackUri(String trackId) {
            return CONTENT_URI.buildUpon().appendPath(trackId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Sessions} associated
         * with the requested {@link #TRACK_ID}.
         */
        public static Uri buildSessionsUri(String trackId) {
            return CONTENT_URI.buildUpon().appendPath(trackId).appendPath(PATH_SESSIONS).build();
        }

        /**
         * Build {@link Uri} that references any {@link Vendors} associated with
         * the requested {@link #TRACK_ID}.
         */
        public static Uri buildVendorsUri(String trackId) {
            return CONTENT_URI.buildUpon().appendPath(trackId).appendPath(PATH_VENDORS).build();
        }

        /** Read {@link #TRACK_ID} from {@link Tracks} {@link Uri}. */
        public static String getTrackId(Uri uri) {
            return uri.getPathSegments().get(1);
        }

        /**
         * Generate a {@link #TRACK_ID} that will always match the requested
         * {@link Tracks} details.
         */
        public static String generateTrackId(String name) {
            return ParserUtils.sanitizeId(name);
        }
    }

    /**
     * Rooms are physical locations at the conference venue.
     */
    public static class Rooms implements RoomsColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_ROOMS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.room";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.room";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = RoomsColumns.ROOM_FLOOR + " ASC, "
                + RoomsColumns.ROOM_NAME + " COLLATE NOCASE ASC";

        /** Build {@link Uri} for requested {@link #ROOM_ID}. */
        public static Uri buildRoomUri(String roomId) {
            return CONTENT_URI.buildUpon().appendPath(roomId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Sessions} associated
         * with the requested {@link #ROOM_ID}.
         */
        public static Uri buildSessionsDirUri(String roomId) {
            return CONTENT_URI.buildUpon().appendPath(roomId).appendPath(PATH_SESSIONS).build();
        }

        /** Read {@link #ROOM_ID} from {@link Rooms} {@link Uri}. */
        public static String getRoomId(Uri uri) {
            return uri.getPathSegments().get(1);
        }
    }

    /**
     * Each session is a block of time that has a {@link Tracks}, a
     * {@link Rooms}, and zero or more {@link Speakers}.
     */
    public static class Sessions implements SessionsColumns, BlocksColumns, RoomsColumns,
            SyncColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_SESSIONS).build();
        public static final Uri CONTENT_STARRED_URI =
                CONTENT_URI.buildUpon().appendPath(PATH_STARRED).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.session";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.session";

        public static final String BLOCK_ID = "block_id";
        public static final String ROOM_ID = "room_id";
        public static final String START = "session_start";
        public static final String END = "session_end";


        public static final String SEARCH_SNIPPET = "search_snippet";

        // TODO: shortcut primary track to offer sub-sorting here
        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START + " ASC,"
                + SessionsColumns.SESSION_TITLE + " COLLATE NOCASE ASC";

        public static final String BLOCK_SESSION_SORT =
                              ROOM_NAME + " ASC,"+
                              SessionsColumns.SESSION_START + " ASC";

        public static final String LIVESTREAM_SELECTION =
                SESSION_LIVESTREAM_URL + " is not null AND " + SESSION_LIVESTREAM_URL + "!=''";

        // Used to fetch sessions for a particular time
        public static final String AT_TIME_SELECTION =
                BLOCK_START + " < ? and " + BLOCK_END + " " + "> ?";

        // Builds selectionArgs for {@link AT_TIME_SELECTION}
        public static String[] buildAtTimeSelectionArgs(long time) {
            final String timeString = String.valueOf(time);
            return new String[] { timeString, timeString };
        }

        // Used to fetch upcoming sessions
        public static final String UPCOMING_SELECTION =
                BLOCK_START + " = (select min(" + BLOCK_START + ") from " +
                ScheduleDatabase.Tables.BLOCKS_JOIN_SESSIONS + " where " + LIVESTREAM_SELECTION +
                " and " + BLOCK_START + " >" + " ?)";

        // Builds selectionArgs for {@link UPCOMING_SELECTION}
        public static String[] buildUpcomingSelectionArgs(long minTime) {
            return new String[] { String.valueOf(minTime) };
        }

        /** Build {@link Uri} for requested {@link #SESSION_ID}. */
        public static Uri buildSessionUri(String sessionId) {
            return CONTENT_URI.buildUpon().appendPath(sessionId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Speakers} associated
         * with the requested {@link #SESSION_ID}.
         */
        public static Uri buildSpeakersDirUri(String sessionId) {
            return CONTENT_URI.buildUpon().appendPath(sessionId).appendPath(PATH_SPEAKERS).build();
        }

        /**
         * Build {@link Uri} that includes track detail with list of sessions.
         */
        public static Uri buildWithTracksUri() {
            return CONTENT_URI.buildUpon().appendPath(PATH_WITH_TRACK).build();
        }

        /**
         * Build {@link Uri} that includes track detail for a specific session.
         */
        public static Uri buildWithTracksUri(String sessionId) {
            return CONTENT_URI.buildUpon().appendPath(sessionId)
                    .appendPath(PATH_WITH_TRACK).build();
        }

        /**
         * Build {@link Uri} that references any {@link Tracks} associated with
         * the requested {@link #SESSION_ID}.
         */
        public static Uri buildTracksDirUri(String sessionId) {
            return CONTENT_URI.buildUpon().appendPath(sessionId).appendPath(PATH_TRACKS).build();
        }

        public static Uri buildSessionsAtDirUri(long time) {
            return CONTENT_URI.buildUpon().appendPath(PATH_AT).appendPath(String.valueOf(time))
                    .build();
        }

        public static Uri buildSearchUri(String query) {
            return CONTENT_URI.buildUpon().appendPath(PATH_SEARCH).appendPath(query).build();
        }

        public static boolean isSearchUri(Uri uri) {
            List<String> pathSegments = uri.getPathSegments();
            return pathSegments.size() >= 2 && PATH_SEARCH.equals(pathSegments.get(1));
        }

        /** Read {@link #SESSION_ID} from {@link Sessions} {@link Uri}. */
        public static String getSessionId(Uri uri) {
            return uri.getPathSegments().get(1);
        }

        public static String getSearchQuery(Uri uri) {
            return uri.getPathSegments().get(2);
        }
    }

    /**
     * Speakers are individual people that lead {@link Sessions}.
     */
    public static class Speakers implements SpeakersColumns, SyncColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_SPEAKERS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.speaker";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.speaker";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = SpeakersColumns.SPEAKER_NAME
                + " COLLATE NOCASE ASC";

        /** Build {@link Uri} for requested {@link #SPEAKER_ID}. */
        public static Uri buildSpeakerUri(String speakerId) {
            return CONTENT_URI.buildUpon().appendPath(speakerId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Sessions} associated
         * with the requested {@link #SPEAKER_ID}.
         */
        public static Uri buildSessionsDirUri(String speakerId) {
            return CONTENT_URI.buildUpon().appendPath(speakerId).appendPath(PATH_SESSIONS).build();
        }

        /** Read {@link #SPEAKER_ID} from {@link Speakers} {@link Uri}. */
        public static String getSpeakerId(Uri uri) {
            return uri.getPathSegments().get(1);
        }
    }

    /**
     * Each vendor is a company appearing at the conference that may be
     * associated with a specific {@link Tracks}.
     */
    public static class Vendors implements VendorsColumns, SyncColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_VENDORS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.vendor";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.vendor";

        /** {@link Tracks#TRACK_ID} that this vendor belongs to. */
        public static final String TRACK_ID = "track_id";

        public static final String SEARCH_SNIPPET = "search_snippet";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = VendorsColumns.VENDOR_NAME
                + " COLLATE NOCASE ASC";

        /** Build {@link Uri} for requested {@link #VENDOR_ID}. */
        public static Uri buildVendorUri(String vendorId) {
            return CONTENT_URI.buildUpon().appendPath(vendorId).build();
        }

        public static Uri buildSearchUri(String query) {
            return CONTENT_URI.buildUpon().appendPath(PATH_SEARCH).appendPath(query).build();
        }

        public static boolean isSearchUri(Uri uri) {
            List<String> pathSegments = uri.getPathSegments();
            return pathSegments.size() >= 2 && PATH_SEARCH.equals(pathSegments.get(1));
        }

        /** Read {@link #VENDOR_ID} from {@link Vendors} {@link Uri}. */
        public static String getVendorId(Uri uri) {
            return uri.getPathSegments().get(1);
        }

        public static String getSearchQuery(Uri uri) {
            return uri.getPathSegments().get(2);
        }

        /**
         * Generate a {@link #VENDOR_ID} that will always match the requested
         * {@link Vendors} details.
         */
        public static String generateVendorId(String companyName) {
            return ParserUtils.sanitizeId(companyName);
        }
    }

    /**
     * Announcements of breaking news
     */
    public static class Announcements implements AnnouncementsColumns, BaseColumns {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_ANNOUNCEMENTS).build();

        public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.androidito-iosched.announcement";
        public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.androidito-iosched.announcement";

        /** Default "ORDER BY" clause. */
        public static final String DEFAULT_SORT = AnnouncementsColumns.ANNOUNCEMENT_DATE
                + " COLLATE NOCASE ASC";

        /** Build {@link Uri} for requested {@link #ANNOUNCEMENT_ID}. */
        public static Uri buildAnnouncementUri(String announcementId) {
            return CONTENT_URI.buildUpon().appendPath(announcementId).build();
        }

        /**
         * Build {@link Uri} that references any {@link Announcements}
         * associated with the requested {@link #ANNOUNCEMENT_ID}.
         */
        public static Uri buildAnnouncementsDirUri(String announcementId) {
            return CONTENT_URI.buildUpon().appendPath(announcementId)
                    .appendPath(PATH_ANNOUNCEMENTS).build();
        }

        /**
         * Read {@link #ANNOUNCEMENT_ID} from {@link Announcements} {@link Uri}.
         */
        public static String getAnnouncementId(Uri uri) {
            return uri.getPathSegments().get(1);
        }
    }

    public static class SearchSuggest {
        public static final Uri CONTENT_URI =
                BASE_CONTENT_URI.buildUpon().appendPath(PATH_SEARCH_SUGGEST).build();

        public static final String DEFAULT_SORT = SearchManager.SUGGEST_COLUMN_TEXT_1
                + " COLLATE NOCASE ASC";
    }

    public static Uri addCallerIsSyncAdapterParameter(Uri uri) {
        return uri.buildUpon().appendQueryParameter(
                ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
    }

    public static boolean hasCallerIsSyncAdapterParameter(Uri uri) {
        return TextUtils.equals("true",
                uri.getQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER));
    }

    private ScheduleContract() {
    }
} public final class SessionsHelper {

    private static final String TAG = makeLogTag(SessionsHelper.class);

    private final Activity mActivity;

    public SessionsHelper(Activity activity) {
        mActivity = activity;
    }

    public void startMapActivity(String roomId) {
        Intent intent = new Intent(mActivity.getApplicationContext(),
                UIUtils.getMapActivityClass(mActivity));
        intent.putExtra(MapFragment.EXTRA_ROOM, roomId);
        mActivity.startActivity(intent);
    }

    public Intent createShareIntent(int messageTemplateResId, String title, String hashtags,
            String url) {
        ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(mActivity)
                .setType("text/plain")
                .setText(mActivity.getString(messageTemplateResId,
                        title, UIUtils.getSessionHashtagsString(hashtags), url));
        return builder.getIntent();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void tryConfigureShareMenuItem(MenuItem menuItem, int messageTemplateResId,
            final String title, String hashtags, String url) {
        // TODO: uncomment pending ShareActionProvider fixes for split AB
//        if (UIUtils.hasICS()) {
//            ActionProvider itemProvider = menuItem.getActionProvider();
//            ShareActionProvider provider;
//            if (!(itemProvider instanceof ShareActionProvider)) {
//                provider = new ShareActionProvider(mActivity);
//            } else {
//                provider = (ShareActionProvider) itemProvider;
//            }
//            provider.setShareIntent(createShareIntent(messageTemplateResId, title, hashtags, url));
//            provider.setOnShareTargetSelectedListener(new OnShareTargetSelectedListener() {
//                @Override
//                public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
//                    EasyTracker.getTracker().trackEvent("Session", "Shared", title, 0L);
//                    LOGD("Tracker", "Shared: " + title);
//                    return false;
//                }
//            });
//
//            menuItem.setActionProvider(provider);
//        }
    }

    public void shareSession(Context context, int messageTemplateResId, String title,
            String hashtags, String url) {
        EasyTracker.getTracker().trackEvent("Session", "Shared", title, 0L);
        LOGD("Tracker", "Shared: " + title);
        context.startActivity(Intent.createChooser(
                createShareIntent(messageTemplateResId, title, hashtags, url),
                context.getString(R.string.title_share)));
    }

    public void setSessionStarred(Uri sessionUri, boolean starred, String title) {
        LOGD(TAG, "setSessionStarred uri=" + sessionUri + " starred=" +
                starred + " title=" + title);
        sessionUri = ScheduleContract.addCallerIsSyncAdapterParameter(sessionUri);
        final ContentValues values = new ContentValues();
        values.put(ScheduleContract.Sessions.SESSION_STARRED, starred);
        AsyncQueryHandler handler =
                new AsyncQueryHandler(mActivity.getContentResolver()) {
                };
        handler.startUpdate(-1, null, sessionUri, values, null, null);

        EasyTracker.getTracker().trackEvent(
                "Session", starred ? "Starred" : "Unstarred", title, 0L);

        // Because change listener is set to null during initialization, these
        // won't fire on pageview.
        final Intent refreshIntent = new Intent(mActivity, MyScheduleWidgetProvider.class);
        refreshIntent.setAction(MyScheduleWidgetProvider.REFRESH_ACTION);
        mActivity.sendBroadcast(refreshIntent);

        // Sync to the cloud.
        //final Intent updateServerIntent = new Intent(mActivity, ScheduleUpdaterService.class);
        //updateServerIntent.putExtra(ScheduleUpdaterService.EXTRA_SESSION_ID,
        //        ScheduleContract.Sessions.getSessionId(sessionUri));
        //updateServerIntent.putExtra(ScheduleUpdaterService.EXTRA_IN_SCHEDULE, starred);
        //mActivity.startService(updateServerIntent);
    }

    public void startSocialStream(String hashtags) {
        //Intent intent = new Intent(mActivity, SocialStreamActivity.class);
        //mActivity.startActivity(intent);
    }
} public class UIUtils {
    /**
     * Time zone to use when formatting all session times. To always use the
     * phone local time, use {@link TimeZone#getDefault()}.
     */
    public static final TimeZone CONFERENCE_TIME_ZONE = TimeZone.getTimeZone("Europe/Oslo");

    public static final long CONFERENCE_START_MILLIS = ParserUtils.parseTime(
            "2016-09-07T08:00:00.000+01:00");
    public static final long CONFERENCE_END_MILLIS = ParserUtils.parseTime(
            "2016-09-08T18:00:00.000+01:00");

    public static final String CONFERENCE_HASHTAG = "#javazone";

    private static final int SECOND_MILLIS = 1000;
    private static final int MINUTE_MILLIS = 60 * SECOND_MILLIS;
    private static final int HOUR_MILLIS = 60 * MINUTE_MILLIS;
    private static final int DAY_MILLIS = 24 * HOUR_MILLIS;

    /**
     * Flags used with {@link DateUtils#formatDateRange}.
     */
    private static final int TIME_FLAGS = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY | DateUtils.FORMAT_24HOUR;
    private static final int LIGHTNING_TALK_TIME_FLAGS = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;

    /**
     * {@link StringBuilder} used for formatting time block.
     */
    private static StringBuilder sBuilder = new StringBuilder(50);
    /**
     * {@link Formatter} used for formatting time block.
     */
    private static Formatter sFormatter = new Formatter(sBuilder, Locale.getDefault());

    private static StyleSpan sBoldSpan = new StyleSpan(Typeface.BOLD);

    private static CharSequence sEmptyRoomText;
    private static CharSequence sCodeLabRoomText;

    private static CharSequence sNowPlayingText;
    private static CharSequence sLivestreamNowText;
    private static CharSequence sLivestreamAvailableText;

    public static final String GOOGLE_PLUS_PACKAGE_NAME = "com.google.android.apps.plus";

    /**
     * Format and return the given {@link Blocks} and {@link Rooms} values using
     * {@link #CONFERENCE_TIME_ZONE}.
     */
    public static String formatSessionSubtitle(String sessionTitle,
                                               long blockStart, long blockEnd, long sessionStart, long sessionEnd, String roomName, String type, Context context) {
        if (sEmptyRoomText == null || sCodeLabRoomText == null) {
            sEmptyRoomText = context.getText(R.string.unknown_room);

        }

        //TODO JavaZone room name handling
        if (roomName == null) {
            roomName = "TBD";
            //return context.getString(R.string.session_subtitle,
            //        formatBlockTimeString(blockStart, blockEnd, context), sEmptyRoomText,type);
        }


        if (Constants.LIGHTNINGTALK.equals(type)) {
            //if (sessionStart != 0) {
            //    type = "(Lightning talk starts " + formatLightningTalkTimeString(sessionStart, context) + ")";
            //} else {
                type = "(Lightning talk)";
            //}
        } else {
            type = "";
        }

        return context.getString(
                R.string.session_subtitle,
                formatBlockTimeString(sessionStart, sessionEnd, context),
                roomName, type
        );
    }

    private static String formatLightningTalkTimeString(long sessionStart, Context context) {
        TimeZone.setDefault(CONFERENCE_TIME_ZONE);

        if (sessionStart == 0) {
            return "TBD";
        }

        return DateUtils.formatDateTime(context, sessionStart, LIGHTNING_TALK_TIME_FLAGS);
    }

    /**
     * Format and return the given {@link Blocks} values using
     * {@link #CONFERENCE_TIME_ZONE}.
     */
    public static String formatBlockTimeString(long blockStart, long blockEnd, Context context) {
        TimeZone.setDefault(CONFERENCE_TIME_ZONE);

        if (blockStart == 0) {
            return "TBD";
        }

        // NOTE: There is an efficient version of formatDateRange in Eclair and
        // beyond that allows you to recycle a StringBuilder.
        return DateUtils.formatDateRange(context, blockStart, blockEnd, TIME_FLAGS);
    }

    public static boolean isSameDay(long time1, long time2) {
        TimeZone.setDefault(CONFERENCE_TIME_ZONE);

        Calendar cal1 = Calendar.getInstance();
        Calendar cal2 = Calendar.getInstance();
        cal1.setTimeInMillis(time1);
        cal2.setTimeInMillis(time2);
        return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
    }

    public static String getTimeAgo(long time, Context ctx) {
        if (time < 1000000000000L) {
            // if timestamp given in seconds, convert to millis
            time *= 1000;
        }

        long now = getCurrentTime(ctx);
        if (time > now || time <= 0) {
            return null;
        }

        // TODO: localize
        final long diff = now - time;
        if (diff < MINUTE_MILLIS) {
            return "just now";
        } else if (diff < 2 * MINUTE_MILLIS) {
            return "a minute ago";
        } else if (diff < 50 * MINUTE_MILLIS) {
            return diff / MINUTE_MILLIS + " minutes ago";
        } else if (diff < 90 * MINUTE_MILLIS) {
            return "an hour ago";
        } else if (diff < 24 * HOUR_MILLIS) {
            return diff / HOUR_MILLIS + " hours ago";
        } else if (diff < 48 * HOUR_MILLIS) {
            return "yesterday";
        } else {
            return diff / DAY_MILLIS + " days ago";
        }
    }

    /**
     * Populate the given {@link TextView} with the requested text, formatting
     * through {@link Html#fromHtml(String)} when applicable. Also sets
     * {@link TextView#setMovementMethod} so inline links are handled.
     */
    public static void setTextMaybeHtml(TextView view, String text) {
        if (TextUtils.isEmpty(text)) {
            view.setText("");
            return;
        }
        if (text.contains("<") && text.contains(">")) {
            view.setText(Html.fromHtml(text));
            view.setMovementMethod(LinkMovementMethod.getInstance());
        } else {
            view.setText(text);
        }
    }

    public static void updateTimeAndLivestreamBlockUI(final Context context,
                                                      long blockStart, long blockEnd, boolean hasLivestream,
                                                      View backgroundView, TextView titleView, TextView subtitleView,
                                                      CharSequence subtitle) {
        long currentTimeMillis = getCurrentTime(context);

        boolean past = (currentTimeMillis > blockEnd && currentTimeMillis < CONFERENCE_END_MILLIS);
        boolean present = (blockStart <= currentTimeMillis && currentTimeMillis <= blockEnd);

        final Resources res = context.getResources();
        if (backgroundView != null) {
            backgroundView.setBackgroundColor(past
                    ? res.getColor(R.color.past_background_color)
                    : 0);
        }

        if (titleView != null) {
            titleView.setTypeface(Typeface.SANS_SERIF, past ? Typeface.NORMAL : Typeface.BOLD);
        }

        if (subtitleView != null) {
            boolean empty = true;
            SpannableStringBuilder sb = new SpannableStringBuilder(); // TODO: recycle
            if (subtitle != null) {
                sb.append(subtitle);
                empty = false;
            }

            if (present) {
                if (sNowPlayingText == null) {
                    sNowPlayingText = Html.fromHtml(context.getString(R.string.now_playing_badge));
                }
                if (!empty) {
                    sb.append("  ");
                }
                sb.append(sNowPlayingText);

                if (hasLivestream) {
                    if (sLivestreamNowText == null) {
                        sLivestreamNowText = Html.fromHtml("&nbsp;&nbsp;" +
                                context.getString(R.string.live_now_badge));
                    }
                    sb.append(sLivestreamNowText);
                }
            } else if (hasLivestream) {
                if (sLivestreamAvailableText == null) {
                    sLivestreamAvailableText = Html.fromHtml(
                            context.getString(R.string.live_available_badge));
                }
                if (!empty) {
                    sb.append("  ");
                }
                sb.append(sLivestreamAvailableText);
            }

            subtitleView.setText(sb);
        }
    }

    /**
     * Given a snippet string with matching segments surrounded by curly
     * braces, turn those areas into bold spans, removing the curly braces.
     */
    public static Spannable buildStyledSnippet(String snippet) {
        final SpannableStringBuilder builder = new SpannableStringBuilder(snippet);

        // Walk through string, inserting bold snippet spans
        int startIndex = -1, endIndex = -1, delta = 0;
        while ((startIndex = snippet.indexOf('{', endIndex)) != -1) {
            endIndex = snippet.indexOf('}', startIndex);

            // Remove braces from both sides
            builder.delete(startIndex - delta, startIndex - delta + 1);
            builder.delete(endIndex - delta - 1, endIndex - delta);

            // Insert bold style
            builder.setSpan(sBoldSpan, startIndex - delta, endIndex - delta - 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            delta += 2;
        }

        return builder;
    }

    public static void preferPackageForIntent(Context context, Intent intent, String packageName) {
        PackageManager pm = context.getPackageManager();
        for (ResolveInfo resolveInfo : pm.queryIntentActivities(intent, 0)) {
            if (resolveInfo.activityInfo.packageName.equals(packageName)) {
                intent.setPackage(packageName);
                break;
            }
        }
    }

    public static ImageFetcher getImageFetcher(final FragmentActivity activity) {
        // The ImageFetcher takes care of loading remote images into our ImageView
        ImageFetcher fetcher = new ImageFetcher(activity);
        fetcher.setImageCache(ImageCache.findOrCreateCache(activity, "imageFetcher"));
        return fetcher;
    }

    public static String getSessionHashtagsString(String hashtags) {
        if (!TextUtils.isEmpty(hashtags)) {
            if (!hashtags.startsWith("#")) {
                hashtags = "#" + hashtags;
            }

            if (hashtags.contains(CONFERENCE_HASHTAG)) {
                return hashtags;
            }
            return CONFERENCE_HASHTAG + " " + hashtags;
        } else {
            return CONFERENCE_HASHTAG;
        }
    }

    private static final int BRIGHTNESS_THRESHOLD = 130;

    /**
     * Calculate whether a color is light or dark, based on a commonly known
     * brightness formula.
     *
     * @see {@literal http://en.wikipedia.org/wiki/HSV_color_space%23Lightness}
     */
    public static boolean isColorDark(int color) {
        return Color.alpha(color) != 0 && (((30 * Color.red(color) +
                59 * Color.green(color) +
                11 * Color.blue(color)) / 100) <= BRIGHTNESS_THRESHOLD);
    }

    // Shows whether a notification was fired for a particular session time block. In the
    // event that notification has not been fired yet, return false and set the bit.
    public static boolean isNotificationFiredForBlock(Context context, String blockId) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        final String key = String.format("notification_fired_%s", blockId);
        boolean fired = sp.getBoolean(key, false);
        sp.edit().putBoolean(key, true).commit();
        return fired;
    }

    private static final long sAppLoadTime = System.currentTimeMillis();

    public static long getCurrentTime(final Context context) {
        if (BuildConfig.DEBUG) {
            return context.getSharedPreferences("mock_data", Context.MODE_PRIVATE)
                    .getLong("mock_current_time", System.currentTimeMillis())
                    + System.currentTimeMillis() - sAppLoadTime;
        } else {
            return System.currentTimeMillis();
        }
    }

    public static void safeOpenLink(Context context, Intent linkIntent) {
        try {
            context.startActivity(linkIntent);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(context, "Couldn't open link", Toast.LENGTH_SHORT).show();
        }
    }

    // TODO: use <meta-data> element instead
    private static final Class[] sPhoneActivities = new Class[]{
            MapActivity.class,
            SessionDetailActivity.class,
            SessionsActivity.class,
            TrackDetailActivity.class,
            VendorDetailActivity.class,
    };

    // TODO: use <meta-data> element instead
    private static final Class[] sTabletActivities = new Class[]{
            MapMultiPaneActivity.class,
            SessionsVendorsMultiPaneActivity.class,
    };

    public static void enableDisableActivities(final Context context) {
        boolean isHoneycombTablet = isHoneycombTablet(context);
        PackageManager pm = context.getPackageManager();

        // Enable/disable phone activities
        for (Class a : sPhoneActivities) {
            pm.setComponentEnabledSetting(new ComponentName(context, a),
                    isHoneycombTablet
                            ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                            : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP);
        }

        // Enable/disable tablet activities
        for (Class a : sTabletActivities) {
            pm.setComponentEnabledSetting(new ComponentName(context, a),
                    isHoneycombTablet
                            ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                            : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
        }
    }

    public static Class getMapActivityClass(Context context) {
        if (UIUtils.isHoneycombTablet(context)) {
            return MapMultiPaneActivity.class;
        }

        return MapActivity.class;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static void setActivatedCompat(View view, boolean activated) {
        if (hasHoneycomb()) {
            view.setActivated(activated);
        }
    }

    public static boolean isGoogleTV(Context context) {
        return context.getPackageManager().hasSystemFeature("com.google.android.tv");
    }

    public static boolean hasFroyo() {
        // Can use static final constants like FROYO, declared in later versions
        // of the OS since they are inlined at compile time. This is guaranteed behavior.
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    public static boolean hasGingerbread() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
    }

    public static boolean hasHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean hasHoneycombMR1() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
    }

    public static boolean hasICS() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    }

    public static boolean hasJellyBean() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    public static boolean hasLollipop() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    public static boolean isTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout
                & Configuration.SCREENLAYOUT_SIZE_MASK)
                >= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    public static boolean isHoneycombTablet(Context context) {
        return hasHoneycomb() && isTablet(context);
    }

    public static void colorImageViewArray(ImageView[] imageViews, int colorId) {
        for(ImageView imageView : imageViews) {
            imageView.setColorFilter(colorId, PorterDuff.Mode.SRC_IN);
        }
    }
} public abstract class ActionMode {
    private Object mTag;

    public static ActionMode start(FragmentActivity activity, Callback callback) {
        if (UIUtils.hasHoneycomb()) {
            return ActionModeHoneycomb.startInternal(activity, callback);
        } else {
            return ActionModeBase.startInternal(activity, callback);
        }
    }

    public static void setMultiChoiceMode(ListView listView, FragmentActivity activity,
            MultiChoiceModeListener listener) {
        if (UIUtils.hasHoneycomb()) {
            ActionModeHoneycomb.beginMultiChoiceMode(listView, activity, listener);
        } else {
            ActionModeBase.beginMultiChoiceMode(listView, activity, listener);
        }
    }

    /**
     * Set a tag object associated with this ActionMode.
     *
     * <p>Like the tag available to views, this allows applications to associate arbitrary
     * data with an ActionMode for later reference.
     *
     * @param tag Tag to associate with this ActionMode
     *
     * @see #getTag()
     */
    public void setTag(Object tag) {
        mTag = tag;
    }

    /**
     * Retrieve the tag object associated with this ActionMode.
     *
     * <p>Like the tag available to views, this allows applications to associate arbitrary
     * data with an ActionMode for later reference.
     *
     * @return Tag associated with this ActionMode
     *
     * @see #setTag(Object)
     */
    public Object getTag() {
        return mTag;
    }

    /**
     * Set the title of the action mode. This method will have no visible effect if
     * a custom view has been set.
     *
     * @param title Title string to set
     *
     * @see #setTitle(int)
     * @see #setCustomView(View)
     */
    public abstract void setTitle(CharSequence title);

    /**
     * Set the title of the action mode. This method will have no visible effect if
     * a custom view has been set.
     *
     * @param resId Resource ID of a string to set as the title
     *
     * @see #setTitle(CharSequence)
     * @see #setCustomView(View)
     */
    public abstract void setTitle(int resId);

    /**
     * Invalidate the action mode and refresh menu content. The mode's
     * {@link ActionMode.Callback} will have its
     * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
     * If it returns true the menu will be scanned for updated content and any relevant changes
     * will be reflected to the user.
     */
    public abstract void invalidate();

    /**
     * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
     * have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
     */
    public abstract void finish();

    /**
     * Returns the current title of this action mode.
     * @return Title text
     */
    public abstract CharSequence getTitle();

    /**
     * Returns a {@link MenuInflater} with the ActionMode's context.
     */
    public abstract MenuInflater getMenuInflater();

    /**
     * Returns whether the UI presenting this action mode can take focus or not.
     * This is used by internal components within the framework that would otherwise
     * present an action mode UI that requires focus, such as an EditText as a custom view.
     *
     * @return true if the UI used to show this action mode can take focus
     * @hide Internal use only
     */
    public boolean isUiFocusable() {
        return true;
    }


    /**
     * Callback interface for action modes. Supplied to
     * {@link View#startActionMode(Callback)}, a Callback
     * configures and handles events raised by a user's interaction with an action mode.
     *
     * <p>An action mode's lifecycle is as follows:
     * <ul>
     * <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
     * creation</li>
     * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
     * and any time the {@link ActionMode} is invalidated</li>
     * <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
     * contextual action button is clicked</li>
     * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
     * is closed</li>
     * </ul>
     */
    public interface Callback {
        /**
         * Called when action mode is first created. The menu supplied will be used to
         * generate action buttons for the action mode.
         *
         * @param mode ActionMode being created
         * @param menu Menu used to populate action buttons
         * @return true if the action mode should be created, false if entering this
         *              mode should be aborted.
         */
        public boolean onCreateActionMode(ActionMode mode, Menu menu);

        /**
         * Called to refresh an action mode's action menu whenever it is invalidated.
         *
         * @param mode ActionMode being prepared
         * @param menu Menu used to populate action buttons
         * @return true if the menu or action mode was updated, false otherwise.
         */
        public boolean onPrepareActionMode(ActionMode mode, Menu menu);

        /**
         * Called to report a user click on an action button.
         *
         * @param mode The current ActionMode
         * @param item The item that was clicked
         * @return true if this callback handled the event, false if the standard MenuItem
         *          invocation should continue.
         */
        public boolean onActionItemClicked(ActionMode mode, MenuItem item);

        /**
         * Called when an action mode is about to be exited and destroyed.
         *
         * @param mode The current ActionMode being destroyed
         */
        public void onDestroyActionMode(ActionMode mode);
    }
} public interface MultiChoiceModeListener extends ActionMode.Callback {

    /**
     * Called when an item is checked or unchecked during selection mode.
     *
     * @param mode     The {@link ActionMode} providing the selection mode
     * @param position Adapter position of the item that was checked or unchecked
     * @param id       Adapter ID of the item that was checked or unchecked
     * @param checked  <code>true</code> if the item is now checked, <code>false</code> if the
     *                 item is now unchecked.
     */
    public void onItemCheckedStateChanged(ActionMode mode,
            int position, long id, boolean checked);
} public static void LOGD(final String tag, String message) {
    if (Log.isLoggable(tag, Log.DEBUG)) {
        Log.d(tag, message);
    }
} public static void LOGV(final String tag, String message) {
    //noinspection PointlessBooleanExpression,ConstantConditions
    if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.VERBOSE)) {
        Log.v(tag, message);
    }
} public static void LOGW(final String tag, String message) {
    Log.w(tag, message);
} public static String makeLogTag(String str) {
    if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
        return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
    }

    return LOG_PREFIX + str;
} public static Spannable buildStyledSnippet(String snippet) {
    final SpannableStringBuilder builder = new SpannableStringBuilder(snippet);

    // Walk through string, inserting bold snippet spans
    int startIndex = -1, endIndex = -1, delta = 0;
    while ((startIndex = snippet.indexOf('{', endIndex)) != -1) {
        endIndex = snippet.indexOf('}', startIndex);

        // Remove braces from both sides
        builder.delete(startIndex - delta, startIndex - delta + 1);
        builder.delete(endIndex - delta - 1, endIndex - delta);

        // Insert bold style
        builder.setSpan(sBoldSpan, startIndex - delta, endIndex - delta - 1,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        delta += 2;
    }

    return builder;
} public static String formatSessionSubtitle(String sessionTitle,
                                           long blockStart, long blockEnd, long sessionStart, long sessionEnd, String roomName, String type, Context context) {
    if (sEmptyRoomText == null || sCodeLabRoomText == null) {
        sEmptyRoomText = context.getText(R.string.unknown_room);

    }

    //TODO JavaZone room name handling
    if (roomName == null) {
        roomName = "TBD";
        //return context.getString(R.string.session_subtitle,
        //        formatBlockTimeString(blockStart, blockEnd, context), sEmptyRoomText,type);
    }


    if (Constants.LIGHTNINGTALK.equals(type)) {
        //if (sessionStart != 0) {
        //    type = "(Lightning talk starts " + formatLightningTalkTimeString(sessionStart, context) + ")";
        //} else {
            type = "(Lightning talk)";
        //}
    } else {
        type = "";
    }

    return context.getString(
            R.string.session_subtitle,
            formatBlockTimeString(sessionStart, sessionEnd, context),
            roomName, type
    );
}
import com.google.analytics.tracking.android.EasyTracker;
import no.java.schedule.R;
import com.google.android.apps.iosched.provider.ScheduleContract;
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.google.android.apps.iosched.util.actionmodecompat.MultiChoiceModeListener;
import com.actionbarsherlock.app.SherlockListFragment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
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.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.text.Spannable;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.LinkedHashSet;
import static com.google.android.apps.iosched.util.LogUtils.LOGD;
import static com.google.android.apps.iosched.util.LogUtils.LOGV;
import static com.google.android.apps.iosched.util.LogUtils.LOGW;
import static com.google.android.apps.iosched.util.LogUtils.makeLogTag;
import static com.google.android.apps.iosched.util.UIUtils.buildStyledSnippet;
import static com.google.android.apps.iosched.util.UIUtils.formatSessionSubtitle;
/*
 * 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;






/**
 * A {@link ListFragment} showing a list of sessions. This fragment supports multiple-selection
 * using the contextual action bar (on API 11+ devices), and also supports a separate 'activated'
 * state for indicating the currently-opened detail view on tablet devices.
 */
public class SessionsFragment extends SherlockListFragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        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 boolean mHasSetEmptyText = false;
    private int mSessionQueryToken;

    private LinkedHashSet<Integer> mSelectedSessionPositions = new LinkedHashSet<Integer>();
    private Handler mHandler = new Handler();

    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);
        }

        reloadFromArguments(getArguments());
    }

    protected void reloadFromArguments(Bundle arguments) {
        // Teardown from previous arguments
        setListAdapter(null);

        // Load new arguments
        final Intent intent = BaseActivity.fragmentArgumentsToIntent(arguments);
        final Uri sessionsUri = intent.getData();

        if (sessionsUri == null) {
            return;
        }

        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 onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (!mHasSetEmptyText) {
            // Could be a bug, but calling this twice makes it become visible
            // when it shouldn't
            // be visible.
            setEmptyText(getString(R.string.empty_sessions));
            mHasSetEmptyText = true;
        }

        ActionMode.setMultiChoiceMode(getListView(), getActivity(), this);
    }

    @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);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
        getActivity().getContentResolver().unregisterContentObserver(mObserver);
    }

    @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);
        }
    }

    /** {@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);
        final Uri sessionsUri = intent.getData();
        Loader<Cursor> loader = null;
        if (id == SessionsQuery._TOKEN) {
            loader = new CursorLoader(getActivity(), sessionsUri, SessionsQuery.PROJECTION,
                    null, null, sessionsUri.toString().startsWith("content://no.java.schedule/blocks/") ? ScheduleContract.Sessions.BLOCK_SESSION_SORT :    ScheduleContract.Sessions.DEFAULT_SORT);  // TODO Clean up code and let user choose sorting
        } 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();
                    }
                    EasyTracker.getTracker().trackView("Session Block: " + title);
                    LOGD("Tracker", "Session Block: " + title);
                }
            }
        } else {
            LOGD(TAG, "Query complete, Not Actionable: " + token);
            cursor.close();
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }

    // MultiChoiceModeListener interface
    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        SessionsHelper helper = new SessionsHelper(getActivity());
        mode.finish();
        switch (item.getItemId()) {
            case R.id.menu_map: {
                // multiple selection not supported
                int position = mSelectedSessionPositions.iterator().next();
                Cursor cursor = (Cursor) mAdapter.getItem(position);
                String roomId = cursor.getString(SessionsQuery.ROOM_ID);
                helper.startMapActivity(roomId);

                String title = cursor.getString(SessionsQuery.TITLE);
                EasyTracker.getTracker().trackEvent(
                        "Session", "Mapped", title, 0L);
                LOGV(TAG, "Starred: " + title);
                
                return true;
            }
            case R.id.menu_star: {
                // multiple selection supported
                boolean starred = false;
                int numChanged = 0;
                for (int position : mSelectedSessionPositions) {
                    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;
                    EasyTracker.getTracker().trackEvent(
                            "Session", starred ? "Starred" : "Unstarred", title, 0L);
                    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 = mSelectedSessionPositions.iterator().next();
                // 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:
                StringBuilder hashtags = new StringBuilder();
                for (int position : mSelectedSessionPositions) {
                    Cursor cursor = (Cursor) mAdapter.getItem(position);
                    String term = cursor.getString(SessionsQuery.HASHTAGS);
                    if (!term.startsWith("#")) {
                        term = "#" + term;
                    }
                    if (hashtags.length() > 0) {
                        hashtags.append(" OR ");
                    }
                    hashtags.append(term);
                    
                    String title = cursor.getString(SessionsQuery.TITLE);
                    EasyTracker.getTracker().trackEvent(
                            "Session", "Mapped", title, 0L);
                    LOGV(TAG, "Starred: " + title);
                }

                helper.startSocialStream(hashtags.toString());
                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);
        mSelectedSessionPositions.clear();
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {}

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
        if (checked) {
            mSelectedSessionPositions.add(position);
        } else {
            mSelectedSessionPositions.remove(position);
        }

        int numSelectedSessions = mSelectedSessionPositions.size();
        mode.setTitle(getResources().getQuantityString(
                R.plurals.title_selected_sessions,
                numSelectedSessions, numSelectedSessions));

        if (numSelectedSessions == 1) {
            // activate all the menu item
            //mMapMenuItem.setVisible(true);
            mShareMenuItem.setVisible(true);
            //mSocialStreamMenuItem.setVisible(true);
            mStarredMenuItem.setVisible(true);
            position = mSelectedSessionPositions.iterator().next();
            Cursor cursor = (Cursor) mAdapter.getItem(position);
            boolean starred = cursor.getInt(SessionsQuery.STARRED) != 0;
            setSelectedSessionStarred(starred);
        } else {
            mMapMenuItem.setVisible(false);
            mShareMenuItem.setVisible(false);
            mSocialStreamMenuItem.setVisible(false);
            boolean allStarred = true;
            boolean allUnstarred = true;
            for (int pos : mSelectedSessionPositions) {
                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 ContentObserver mObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            if (getActivity() == null) {
                return;
            }

            Loader<Cursor> loader = getLoaderManager().getLoader(mSessionQueryToken);
            if (loader != null) {
                loader.forceLoad();
            }
        }
    };

    /**
     * {@link CursorAdapter} that renders a {@link SessionsQuery}.
     */
    private class SessionsAdapter extends CursorAdapter {

        public SessionsAdapter(Context context) {
            super(context, null, false);
        }

        /** {@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;
            }

            if (sessionId.equals(mSelectedSessionId)){
                UIUtils.setActivatedCompat(view, true);
            } else {
                UIUtils.setActivatedCompat(view, false);
            }
            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 long sessionStart = cursor.getLong(SessionsQuery.START);
            final long sessionEnd = cursor.getLong(SessionsQuery.END);
            final String roomName = cursor.getString(SessionsQuery.ROOM_NAME);
            final String type = cursor.getString(SessionsQuery.TYPE);
            final String subtitle = formatSessionSubtitle(
                    sessionTitle, blockStart,blockEnd,sessionStart, sessionEnd, roomName, type,context);

            final boolean starred = cursor.getInt(SessionsQuery.STARRED) != 0;
            view.findViewById(R.id.indicator_in_schedule).setVisibility(
                    starred ? View.VISIBLE : View.INVISIBLE);

            final boolean hasLivestream = !TextUtils.isEmpty(
                    cursor.getString(SessionsQuery.LIVESTREAM_URL));

            // Show past/present/future and livestream status for this block.
            UIUtils.updateTimeAndLivestreamBlockUI(context,
                    blockStart, blockEnd, hasLivestream,
                    view.findViewById(R.id.list_item_session), titleView, subtitleView, subtitle);
        }
    }

    /**
     * {@link CursorAdapter} that renders a {@link SearchQuery}.
     */
    private class SearchAdapter extends CursorAdapter {
        public SearchAdapter(Context context) {
            super(context, null, false);
        }

        /** {@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);