com.conferenceengineer.android.iosched.io.SessionsHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.conferenceengineer.android.iosched.io.SessionsHandler.java

Source

/*
 * 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.io;

import android.content.ContentProviderOperation;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;

import com.conferenceengineer.android.iosched.Config;
import com.conferenceengineer.android.iosched.conference686.R;
import com.conferenceengineer.android.iosched.io.model.SessionResponse;
import com.conferenceengineer.android.iosched.io.model.SessionsResponse;
import com.conferenceengineer.android.iosched.io.model.TrackResponse;
import com.conferenceengineer.android.iosched.io.model.TracksResponse;
import com.conferenceengineer.android.iosched.provider.ScheduleContract;
import com.conferenceengineer.android.iosched.provider.ScheduleContract.Sessions;
import com.conferenceengineer.android.iosched.provider.ScheduleContract.SyncColumns;
import com.conferenceengineer.android.iosched.provider.ScheduleDatabase;
import com.conferenceengineer.android.iosched.util.Lists;
import com.conferenceengineer.android.iosched.util.ParserUtils;
import com.conferenceengineer.android.iosched.util.PrefUtils;

import java.io.IOException;
import java.util.*;

import org.json.JSONException;
import org.json.JSONObject;

import static com.conferenceengineer.android.iosched.provider.ScheduleDatabase.SessionsSpeakers;
import static com.conferenceengineer.android.iosched.util.LogUtils.*;
import static com.conferenceengineer.android.iosched.util.ParserUtils.sanitizeId;

public class SessionsHandler {

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

    private static final String EVENT_TYPE_KEYNOTE = Sessions.SESSION_TYPE_KEYNOTE;
    private static final String EVENT_TYPE_OFFICE_HOURS = Sessions.SESSION_TYPE_OFFICE_HOURS;
    private static final String EVENT_TYPE_CODELAB = Sessions.SESSION_TYPE_CODELAB;
    private static final String EVENT_TYPE_SANDBOX = Sessions.SESSION_TYPE_SANDBOX;

    private static final int PARSE_FLAG_FORCE_SCHEDULE_REMOVE = 1;
    private static final int PARSE_FLAG_FORCE_SCHEDULE_ADD = 2;

    private Context mContext;

    public SessionsHandler(Context context) {
        mContext = context;
    }

    public ArrayList<ContentProviderOperation> fetchAndParse(ConferenceAPI conferenceAPI) throws IOException {
        // Set up the HTTP transport and JSON factory
        SessionsResponse sessions;
        SessionsResponse starredSessions = null;
        TracksResponse tracks;

        try {
            sessions = conferenceAPI.events().sessions().list(Config.EVENT_ID).setLimit(9999L).execute();
            tracks = conferenceAPI.events().tracks().list(Config.EVENT_ID).execute();

            if (sessions == null || sessions.getSessions() == null) {
                throw new HandlerException("Sessions list was null.");
            }
            if (tracks == null || tracks.getTracks() == null) {
                throw new HandlerException("trackDetails list was null.");
            }
        } catch (HandlerException e) {
            LOGE(TAG, "Fatal: error fetching sessions/tracks", e);
            return Lists.newArrayList();
        }

        final boolean profileAvailableBefore = PrefUtils.isDevsiteProfileAvailable(mContext);
        boolean profileAvailableNow = false;
        /*
            TODO: Look at remote sync
                try {
        starredSessions = conferenceAPI.users().events().sessions().list(Config.EVENT_ID).execute();
            
        // If this succeeded, the user has a DevSite profile
        PrefUtils.markDevSiteProfileAvailable(mContext, true);
        profileAvailableNow = true;
            
                } catch (GoogleJsonResponseException e) {
        LOGE(TAG, "User does not have a developers.google.com profile. Not syncing remote " +
                "personalized schedule.");
        */ starredSessions = null;
        PrefUtils.markDevSiteProfileAvailable(mContext, false);
        //        }

        /*
                TODO: Look at remote sync for starred events
                if (profileAvailableNow && !profileAvailableBefore) {
        LOGI(TAG, "developers.google.com mode change: DEVSITE_PROFILE_AVAILABLE=false -> true");
        // User's DevSite profile has come into existence. Re-upload tracks.
        ContentResolver cr = mContext.getContentResolver();
        String[] projection = new String[] {ScheduleContract.Sessions.SESSION_ID, Sessions.SESSION_TITLE};
        Cursor c = cr.query(ScheduleContract.BASE_CONTENT_URI.buildUpon().
                appendPath("sessions").appendPath("starred").build(), projection, null, null, null);
        if (c != null) {
            c.moveToFirst();
            while (!c.isAfterLast()) {
                String id = c.getString(0);
                String title = c.getString(1);
                LOGI(TAG, "Adding session: (" + id + ") " + title);
                Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(id);
                SessionsHelper.uploadStarredSession(mContext, sessionUri, true);
                c.moveToNext();
            }
        }
        // Hack: Use local starred sessions for now, to give the new sessions time to take effect
        // TODO(trevorjohns): Upload starred sessions should be synchronous to avoid this hack
        starredSessions = null;
                }
        */
        starredSessions = null;
        return buildContentProviderOperations(sessions, starredSessions, tracks);
    }

    public ArrayList<ContentProviderOperation> parseString(String sessionsJson, String tracksJson) {
        try {
            SessionsResponse sessions = new SessionsResponse();
            sessions.fromJSON(new JSONObject(sessionsJson));

            TracksResponse tracks = new TracksResponse();
            tracks.fromJSON(new JSONObject(tracksJson));
            return buildContentProviderOperations(sessions, null, tracks);
        } catch (JSONException e) {
            LOGE(TAG, "Error reading speakers from packaged data", e);
            return Lists.newArrayList();
        }
    }

    private ArrayList<ContentProviderOperation> buildContentProviderOperations(SessionsResponse sessions,
            SessionsResponse starredSessions, TracksResponse tracks) {

        // If there was no starred sessions response (e.g. there was an auth issue,
        // or this is a local sync), keep all the locally starred sessions.
        boolean retainLocallyStarredSessions = (starredSessions == null);

        final ArrayList<ContentProviderOperation> batch = Lists.newArrayList();

        // Build lookup table for starredSessions mappings
        HashSet<String> starredSessionsMap = new HashSet<String>();
        if (starredSessions != null) {
            List<SessionResponse> starredSessionList = starredSessions.getSessions();
            if (starredSessionList != null) {
                for (SessionResponse session : starredSessionList) {
                    String sessionId = session.getId();
                    starredSessionsMap.add(sessionId);
                }
            }
        }

        // Build lookup table for track mappings
        // Assumes that sessions can only have one track. Not guarenteed by the Conference API,
        // but is being enforced by conference organizer policy.
        HashMap<String, TrackResponse> trackMap = new HashMap<String, TrackResponse>();
        if (tracks != null) {
            for (TrackResponse track : tracks.getTracks()) {
                List<String> sessionIds = track.getSessions();
                if (sessionIds != null) {
                    for (String sessionId : sessionIds) {
                        trackMap.put(sessionId, track);
                    }
                }
            }
        }

        if (sessions != null) {
            List<SessionResponse> sessionList = sessions.getSessions();
            int numSessions = sessionList.size();

            if (numSessions > 0) {
                LOGI(TAG, "Updating sessions data");

                Set<String> starredSessionIds = new HashSet<String>();
                if (retainLocallyStarredSessions) {
                    Cursor starredSessionsCursor = mContext.getContentResolver().query(Sessions.CONTENT_STARRED_URI,
                            new String[] { ScheduleContract.Sessions.SESSION_ID }, null, null, null);
                    while (starredSessionsCursor.moveToNext()) {
                        starredSessionIds.add(starredSessionsCursor.getString(0));
                    }
                    starredSessionsCursor.close();
                }

                // Clear out existing sessions
                batch.add(ContentProviderOperation
                        .newDelete(ScheduleContract.addCallerIsSyncAdapterParameter(Sessions.CONTENT_URI)).build());

                // Maintain a list of created session block IDs
                Set<String> blockIds = new HashSet<String>();

                // Maintain a map of insert operations for sandbox-only blocks
                HashMap<String, ContentProviderOperation> sandboxBlocks = new HashMap<String, ContentProviderOperation>();

                for (SessionResponse session : sessionList) {
                    int flags = 0;
                    String sessionId = session.getId();
                    if (retainLocallyStarredSessions) {
                        flags = (starredSessionIds.contains(sessionId) ? PARSE_FLAG_FORCE_SCHEDULE_ADD
                                : PARSE_FLAG_FORCE_SCHEDULE_REMOVE);
                    }
                    if (session.getFlags() != 0) {
                        // Allow data set flags to override locally
                        // set ones (e.g. single talk slot additions).
                        flags = session.getFlags();
                    }

                    if (TextUtils.isEmpty(sessionId)) {
                        LOGW(TAG, "Found session with empty ID in API response.");
                        continue;
                    }

                    // Session title
                    String sessionTitle = session.getTitle();
                    String sessionSubtype = session.getSubtype();
                    if (EVENT_TYPE_CODELAB.equals(sessionSubtype)) {
                        sessionTitle = mContext.getString(R.string.codelab_title_template, sessionTitle);
                    }

                    // Whether or not it's in the schedule
                    boolean inSchedule = starredSessionsMap.contains(sessionId);
                    if ((flags & PARSE_FLAG_FORCE_SCHEDULE_ADD) != 0
                            || (flags & PARSE_FLAG_FORCE_SCHEDULE_REMOVE) != 0) {
                        inSchedule = (flags & PARSE_FLAG_FORCE_SCHEDULE_ADD) != 0;
                    }

                    if (EVENT_TYPE_KEYNOTE.equals(sessionSubtype)) {
                        // Keynotes are always in your schedule.
                        inSchedule = true;
                    }

                    // Clean up session abstract
                    String sessionAbstract = session.getDescription();
                    if (sessionAbstract != null) {
                        sessionAbstract = sessionAbstract.replace('\r', '\n');
                    }

                    // Hashtags
                    TrackResponse track = trackMap.get(sessionId);
                    String hashtag = null;
                    if (track != null) {
                        hashtag = ParserUtils.sanitizeId(track.getTitle());
                    }

                    // Get block id
                    long sessionStartTime = session.getStartTimestamp().longValue() * 1000;
                    long sessionEndTime = session.getEndTimestamp().longValue() * 1000;
                    String blockId = ScheduleContract.Blocks.generateBlockId(sessionStartTime, sessionEndTime);

                    if (!blockIds.contains(blockId) && !EVENT_TYPE_SANDBOX.equals(sessionSubtype)) {
                        // New non-sandbox block
                        if (sandboxBlocks.containsKey(blockId)) {
                            sandboxBlocks.remove(blockId);
                        }
                        String blockType;
                        String blockTitle;
                        if (EVENT_TYPE_KEYNOTE.equals(sessionSubtype)) {
                            blockType = ScheduleContract.Blocks.BLOCK_TYPE_KEYNOTE;
                            blockTitle = mContext.getString(R.string.schedule_block_title_keynote);
                        } else if (EVENT_TYPE_CODELAB.equals(sessionSubtype)) {
                            blockType = ScheduleContract.Blocks.BLOCK_TYPE_CODELAB;
                            blockTitle = mContext.getString(R.string.schedule_block_title_code_labs);
                        } else if (EVENT_TYPE_OFFICE_HOURS.equals(sessionSubtype)) {
                            blockType = ScheduleContract.Blocks.BLOCK_TYPE_OFFICE_HOURS;
                            blockTitle = mContext.getString(R.string.schedule_block_title_office_hours);
                        } else {
                            blockType = ScheduleContract.Blocks.BLOCK_TYPE_SESSION;
                            blockTitle = mContext.getString(R.string.schedule_block_title_sessions);
                        }

                        batch.add(ContentProviderOperation.newInsert(ScheduleContract.Blocks.CONTENT_URI)
                                .withValue(ScheduleContract.Blocks.BLOCK_ID, blockId)
                                .withValue(ScheduleContract.Blocks.BLOCK_TYPE, blockType)
                                .withValue(ScheduleContract.Blocks.BLOCK_TITLE, blockTitle)
                                .withValue(ScheduleContract.Blocks.BLOCK_START, sessionStartTime)
                                .withValue(ScheduleContract.Blocks.BLOCK_END, sessionEndTime).build());
                        blockIds.add(blockId);

                    } else if (!sandboxBlocks.containsKey(blockId) && !blockIds.contains(blockId)
                            && EVENT_TYPE_SANDBOX.equals(sessionSubtype)) {
                        // New sandbox-only block, add insert operation to map
                        String blockType = ScheduleContract.Blocks.BLOCK_TYPE_SANDBOX;
                        String blockTitle = mContext.getString(R.string.schedule_block_title_sandbox);
                        sandboxBlocks.put(blockId,
                                ContentProviderOperation.newInsert(ScheduleContract.Blocks.CONTENT_URI)
                                        .withValue(ScheduleContract.Blocks.BLOCK_ID, blockId)
                                        .withValue(ScheduleContract.Blocks.BLOCK_TYPE, blockType)
                                        .withValue(ScheduleContract.Blocks.BLOCK_TITLE, blockTitle)
                                        .withValue(ScheduleContract.Blocks.BLOCK_START, sessionStartTime)
                                        .withValue(ScheduleContract.Blocks.BLOCK_END, sessionEndTime).build());

                    }

                    // Insert session info
                    final ContentProviderOperation.Builder builder;
                    if (EVENT_TYPE_SANDBOX.equals(sessionSubtype)) {
                        // Sandbox companies go in the special sandbox table

                        builder = ContentProviderOperation
                                .newInsert(ScheduleContract
                                        .addCallerIsSyncAdapterParameter(ScheduleContract.Sandbox.CONTENT_URI))
                                .withValue(SyncColumns.UPDATED, System.currentTimeMillis())
                                .withValue(ScheduleContract.Sandbox.COMPANY_ID, sessionId)
                                .withValue(ScheduleContract.Sandbox.COMPANY_NAME, sessionTitle)
                                .withValue(ScheduleContract.Sandbox.COMPANY_DESC, sessionAbstract)
                                .withValue(ScheduleContract.Sandbox.COMPANY_URL, session.getWebLink())
                                .withValue(ScheduleContract.Sandbox.COMPANY_LOGO_URL, session.getIconUrl())
                                .withValue(ScheduleContract.Sandbox.ROOM_ID, sanitizeId(session.getLocation()))
                                .withValue(ScheduleContract.Sandbox.TRACK_ID,
                                        (track != null ? track.getId() : null))
                                .withValue(ScheduleContract.Sandbox.BLOCK_ID, blockId);
                        batch.add(builder.build());

                    } else {
                        // All other fields go in the normal sessions table
                        builder = ContentProviderOperation
                                .newInsert(ScheduleContract.addCallerIsSyncAdapterParameter(Sessions.CONTENT_URI))
                                .withValue(SyncColumns.UPDATED, System.currentTimeMillis())
                                .withValue(Sessions.SESSION_ID, sessionId)
                                .withValue(Sessions.SESSION_TYPE, sessionSubtype)
                                .withValue(Sessions.SESSION_LEVEL, null) // Not available
                                .withValue(Sessions.SESSION_TITLE, sessionTitle)
                                .withValue(Sessions.SESSION_ABSTRACT, sessionAbstract)
                                .withValue(Sessions.SESSION_HASHTAGS, hashtag)
                                .withValue(Sessions.SESSION_TAGS, null) // Not available
                                .withValue(Sessions.SESSION_URL, session.getWebLink())
                                .withValue(Sessions.SESSION_MODERATOR_URL, null) // Not available
                                .withValue(Sessions.SESSION_REQUIREMENTS, null) // Not available
                                .withValue(Sessions.SESSION_STARRED, inSchedule)
                                .withValue(Sessions.SESSION_YOUTUBE_URL, null) // Not available
                                .withValue(Sessions.SESSION_PDF_URL, null) // Not available
                                .withValue(Sessions.SESSION_NOTES_URL, null) // Not available
                                .withValue(Sessions.ROOM_ID, sanitizeId(session.getLocation()))
                                .withValue(Sessions.BLOCK_ID, blockId);

                        batch.add(builder.build());
                    }

                    // Replace all session speakers
                    final Uri sessionSpeakersUri = Sessions.buildSpeakersDirUri(sessionId);
                    batch.add(ContentProviderOperation
                            .newDelete(ScheduleContract.addCallerIsSyncAdapterParameter(sessionSpeakersUri))
                            .build());

                    List<String> presenterIds = session.getPresenterIds();
                    if (presenterIds != null) {
                        for (String presenterId : presenterIds) {
                            batch.add(ContentProviderOperation.newInsert(sessionSpeakersUri)
                                    .withValue(SessionsSpeakers.SESSION_ID, sessionId)
                                    .withValue(SessionsSpeakers.SPEAKER_ID, presenterId).build());
                        }
                    }

                    // Add track mapping
                    if (track != null) {
                        String trackId = track.getId();
                        if (trackId != null) {
                            final Uri sessionTracksUri = ScheduleContract.addCallerIsSyncAdapterParameter(
                                    ScheduleContract.Sessions.buildTracksDirUri(sessionId));
                            batch.add(ContentProviderOperation.newInsert(sessionTracksUri)
                                    .withValue(ScheduleDatabase.SessionsTracks.SESSION_ID, sessionId)
                                    .withValue(ScheduleDatabase.SessionsTracks.TRACK_ID, trackId).build());
                        }
                    }

                    // Codelabs: Add mapping to codelab table
                    if (EVENT_TYPE_CODELAB.equals(sessionSubtype)) {
                        final Uri sessionTracksUri = ScheduleContract.addCallerIsSyncAdapterParameter(
                                ScheduleContract.Sessions.buildTracksDirUri(sessionId));
                        batch.add(ContentProviderOperation.newInsert(sessionTracksUri)
                                .withValue(ScheduleDatabase.SessionsTracks.SESSION_ID, sessionId)
                                .withValue(ScheduleDatabase.SessionsTracks.TRACK_ID, "CODE_LABS").build());
                    }
                }

                // Insert sandbox-only blocks
                batch.addAll(sandboxBlocks.values());
            }
        }

        return batch;
    }

}