(); // Clear out the list
- }
- }
-
- // NearbyDevice scan callback.
- private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
- @Override
- public void onLeScan(final BluetoothDevice device, final int RSSI, byte[] scanRecord) {
- Log.i(TAG, String.format("onLeScan: %s, RSSI: %d", device.getName(), RSSI));
-
- if (device.getName() == null) {
- return;
- }
- NearbyDevice candidateNearbyDevice = new NearbyDevice(device, RSSI);
- handleDeviceFound(candidateNearbyDevice);
- }
- };
-
- private void handleDeviceFound(NearbyDevice candidateNearbyDevice) {
- NearbyDevice nearbyDevice = mNearbyDeviceAdapter.getExistingDevice(candidateNearbyDevice);
-
- // Check if this is a new device.
- if (nearbyDevice != null) {
- // For existing devices, update their RSSI.
- nearbyDevice.updateLastSeen(candidateNearbyDevice.getLastRSSI());
- mNearbyDeviceAdapter.updateListUI();
- } else {
- // For new devices, add the device to the adapter.
- nearbyDevice = candidateNearbyDevice;
- if (nearbyDevice.isBroadcastingUrl()) {
- if (!mIsQueuing) {
- mIsQueuing = true;
- // We wait QUERY_PERIOD ms to see if any other devices are discovered so we can batch.
- mQueryHandler.postAtTime(mBatchMetadataRunnable, QUERY_PERIOD);
- }
- // Add the device to the queue of devices to look for.
- mDeviceBatchList.add(nearbyDevice);
- mNearbyDeviceAdapter.addDevice(nearbyDevice);
- }
- }
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java
deleted file mode 100644
index 5e71f5927d..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleContract.java
+++ /dev/null
@@ -1,1012 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.provider;
-
-import android.app.SearchManager;
-import android.content.Context;
-import android.net.Uri;
-import android.provider.BaseColumns;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.google.samples.apps.iosched.util.AccountUtils;
-import com.google.samples.apps.iosched.util.ParserUtils;
-
-import java.util.List;
-
-/**
- * Contract class for interacting with {@link ScheduleProvider}. Unless
- * otherwise noted, all time-based fields are milliseconds since epoch and can
- * be compared against {@link System#currentTimeMillis()}.
- *
- * The backing {@link android.content.ContentProvider} assumes that {@link Uri}
- * are generated using stronger {@link String} identifiers, instead of
- * {@code int} {@link BaseColumns#_ID} values, which are prone to shuffle during
- * sync.
- */
-public class ScheduleContract {
- /**
- * Query parameter to create a distinct query.
- */
- public static final String QUERY_PARAMETER_DISTINCT = "distinct";
- public static final String OVERRIDE_ACCOUNTNAME_PARAMETER = "overrideAccount";
-
- 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 subtitle for the block. */
- String BLOCK_SUBTITLE = "block_subtitle";
- }
-
- interface TagsColumns {
- /** Unique string identifying this tag. For example, "TOPIC_ANDROID", "TYPE_CODELAB" */
- String TAG_ID = "tag_id";
- /**
- * Tag category. For example, the tags that identify what topic a session pertains
- * to might belong to the "TOPIC" category; the tags that identify what type a session
- * is (codelab, office hours, etc) might belong to the "TYPE" category.
- */
- String TAG_CATEGORY = "tag_category";
- /** Tag name. For example, "Android" */
- String TAG_NAME = "tag_name";
- /** Tag's order in its category (for sorting). */
- String TAG_ORDER_IN_CATEGORY = "tag_order_in_category";
- /** Tag's color, in integer format. */
- String TAG_COLOR = "tag_color";
- /** Tag abstract. Short summary describing tag. */
- String TAG_ABSTRACT = "tag_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 MyScheduleColumns {
- String SESSION_ID = SessionsColumns.SESSION_ID;
- /** Account name for which the session is starred (in my schedule) */
- String MY_SCHEDULE_ACCOUNT_NAME = "account_name";
- /** Indicate if last operation was "add" (true) or "remove" (false). Since uniqueness is
- * given by seesion_id+account_name, this field can be used as a way to find removals and
- * sync them with the cloud */
- String MY_SCHEDULE_IN_SCHEDULE = "in_schedule";
- /** Flag to indicate if the corresponding in_my_schedule item needs to be synced */
- String MY_SCHEDULE_DIRTY_FLAG = "dirty";
- }
-
- interface SessionsColumns {
- /** Unique string identifying this session. */
- String SESSION_ID = "session_id";
- /** Difficulty level of the session. */
- String SESSION_LEVEL = "session_level";
- /** Start time of this track. */
- String SESSION_START = "session_start";
- /** End time of this track. */
- String SESSION_END = "session_end";
- /** 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";
- /** Kewords/tags for this session. */
- String SESSION_KEYWORDS = "session_keywords";
- /** Hashtag for this session. */
- String SESSION_HASHTAG = "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_IN_MY_SCHEDULE = "session_in_my_schedule";
- /** 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";
- /** The Moderator URL. */
- String SESSION_MODERATOR_URL = "session_moderator_url";
- /** The set of tags the session has. This is a comma-separated list of tags.*/
- String SESSION_TAGS = "session_tags";
- /** The names of the speakers on this session, formatted for display. */
- String SESSION_SPEAKER_NAMES = "session_speaker_names";
- /** The order (for sorting) of this session's type. */
- String SESSION_GROUPING_ORDER = "session_grouping_order";
- /** The hashcode of the data used to create this record. */
- String SESSION_IMPORT_HASHCODE = "session_import_hashcode";
- /** The session's main tag. */
- String SESSION_MAIN_TAG = "session_main_tag";
- /** The session's branding color */
- String SESSION_COLOR = "session_color";
- /** The session's captions URL (for livestreamed sessions). */
- String SESSION_CAPTIONS_URL = "session_captions_url";
- /** The session interval when using the interval counter query. */
- String SESSION_INTERVAL_COUNT= "session_interval_count";
- /** The session's photo URL. */
- String SESSION_PHOTO_URL = "session_photo_url";
- /** The session's related content (videos and call to action links). */
- String SESSION_RELATED_CONTENT = "session_related_content";
- }
-
- 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";
- /** The hashcode of the data used to create this record. */
- String SPEAKER_IMPORT_HASHCODE = "speaker_import_hashcode";
- }
-
- interface AnnouncementsColumns {
- /** Unique string identifying this announcment. */
- String ANNOUNCEMENT_ID = "announcement_id";
- /** Title of the announcement. */
- String ANNOUNCEMENT_TITLE = "announcement_title";
- /** Google+ activity JSON for the announcement. */
- String ANNOUNCEMENT_ACTIVITY_JSON = "announcement_activity_json";
- /** Full URL for the announcement. */
- String ANNOUNCEMENT_URL = "announcement_url";
- /** Date of the announcement. */
- String ANNOUNCEMENT_DATE = "announcement_date";
- }
-
- interface MapMarkerColumns {
- /** Unique string identifying this marker. */
- String MARKER_ID = "map_marker_id";
- /** Type of marker. */
- String MARKER_TYPE = "map_marker_type";
- /** Latitudinal position of marker. */
- String MARKER_LATITUDE = "map_marker_latitude";
- /** Longitudinal position of marker. */
- String MARKER_LONGITUDE = "map_marker_longitude";
- /** Label (title) for this marker. */
- String MARKER_LABEL = "map_marker_label";
- /** Building floor this marker is on. */
- String MARKER_FLOOR = "map_marker_floor";
- }
-
- interface FeedbackColumns {
- String SESSION_ID = "session_id";
- String SESSION_RATING = "feedback_session_rating";
- String ANSWER_RELEVANCE = "feedback_answer_q1";
- String ANSWER_CONTENT = "feedback_answer_q2";
- String ANSWER_SPEAKER = "feedback_answer_q3";
- String COMMENTS = "feedback_comments";
- String SYNCED = "synced";
- }
-
- interface MapTileColumns {
- /** Floor **/
- String TILE_FLOOR = "map_tile_floor";
- /** Filename **/
- String TILE_FILE = "map_tile_file";
- /** Url **/
- String TILE_URL = "map_tile_url";
- }
-
- interface ExpertsColumns {
- /** Unique string identifying this expert. */
- String EXPERT_ID = "expert_id";
- /** Name of this expert. */
- String EXPERT_NAME = "expert_name";
- /** Profile photo of this expert. */
- String EXPERT_IMAGE_URL = "expert_image_url";
- /** Title of this expert. */
- String EXPERT_TITLE = "expert_title";
- /** Body of text describing this expert in detail. */
- String EXPERT_ABSTRACT = "expert_abstract";
- /** Full URL to the expert's profile. */
- String EXPERT_URL = "expert_url";
- /** Country code of this expert. */
- String EXPERT_COUNTRY = "expert_country";
- /** City of this expert. */
- String EXPERT_CITY = "expert_city";
- /** Whether the expert is attending the I/O this year. */
- String EXPERT_ATTENDING = "expert_attending";
- /** The hashcode of the data used to create this record. */
- String EXPERT_IMPORT_HASHCODE = "expert_import_hashcode";
- }
-
- interface PartnersColumns {
- /** Unique string identifying this partner. */
- String PARTNER_ID = "partner_id";
- /** Name of this partner. */
- String PARTNER_NAME = "partner_name";
- /** Description of this partner. */
- String PARTNER_DESC = "partner_desc";
- /** Website URL for this partner. */
- String PARTNER_WEBSITE_URL = "partner_website_url";
- /** Logo URL for this partner. */
- String PARTNER_LOGO_URL = "partner_logo_url";
- }
-
- interface HashtagColumns {
- /** Hashtags */
- String HASHTAG_NAME = "hashtag_name";
- /** Description about this hashtag */
- String HASHTAG_DESCRIPTION = "hashtag_description";
- /** Text color for this hashtag */
- String HASHTAG_COLOR = "hashtag_color";
- /** Ordering of this hashtag */
- String HASHTAG_ORDER = "hashtag_order";
- }
-
- interface PeopleIveMetColumns {
- /** Google+ ID of the person */
- String PERSON_ID = "person_id";
- /** Time when the badge of this person was scanned */
- String PERSON_TIMESTAMP = "person_timestamp";
- /** Name of the person */
- String PERSON_NAME = "person_name";
- /** URL of profile icon of this person */
- String PERSON_IMAGE_URL = "person_image_url";
- /** Note about this person */
- String PERSON_NOTE = "person_note";
- }
-
- interface VideoColumns {
- /** Unique string identifying this video. */
- String VIDEO_ID = "video_id";
- /** Year of the video (e.g. 2014, 2013, ...). */
- String VIDEO_YEAR = "video_year";
- /** Title of the video. */
- String VIDEO_TITLE = "video_title";
- /** Description of the video. */
- String VIDEO_DESC = "video_desc";
- /** Youtube video ID (just the alphanumeric string, not the whole URL). */
- String VIDEO_VID = "video_vid";
- /** Topic (e.g. "Android"). */
- String VIDEO_TOPIC = "video_topic";
- /** Speaker(s) (e.g. "Lauren Ipsum"). */
- String VIDEO_SPEAKERS = "video_speakers";
- /** Thumbnail url. */
- String VIDEO_THUMBNAIL_URL = "video_thumbnail_url";
- /** Import hashcode */
- String VIDEO_IMPORT_HASHCODE = "video_import_hashcode";
- }
-
- public static final String CONTENT_AUTHORITY = "com.google.samples.apps.iosched";
-
- public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
-
- private static final String PATH_BLOCKS = "blocks";
- private static final String PATH_AFTER = "after";
- private static final String PATH_TAGS = "tags";
- private static final String PATH_ROOM = "room";
- private static final String PATH_UNSCHEDULED = "unscheduled";
- private static final String PATH_ROOMS = "rooms";
- private static final String PATH_SESSIONS = "sessions";
- private static final String PATH_FEEDBACK = "feedback";
- private static final String PATH_MY_SCHEDULE = "my_schedule";
- private static final String PATH_SESSIONS_COUNTER = "counter";
- private static final String PATH_SPEAKERS = "speakers";
- private static final String PATH_ANNOUNCEMENTS = "announcements";
- private static final String PATH_MAP_MARKERS = "mapmarkers";
- private static final String PATH_MAP_FLOOR = "floor";
- private static final String PATH_MAP_TILES= "maptiles";
- private static final String PATH_HASHTAGS = "hashtags";
- private static final String PATH_VIDEOS = "videos";
- private static final String PATH_SEARCH = "search";
- private static final String PATH_SEARCH_SUGGEST = "search_suggest_query";
- private static final String PATH_SEARCH_INDEX = "search_index";
- private static final String PATH_EXPERTS = "experts";
- private static final String PATH_PARTNERS = "partners";
- private static final String PATH_PEOPLE_IVE_MET = "people_ive_met";
-
- public static final String[] TOP_LEVEL_PATHS = {
- PATH_BLOCKS,
- PATH_TAGS,
- PATH_ROOMS,
- PATH_SESSIONS,
- PATH_FEEDBACK,
- PATH_MY_SCHEDULE,
- PATH_SPEAKERS,
- PATH_ANNOUNCEMENTS,
- PATH_MAP_MARKERS,
- PATH_MAP_FLOOR,
- PATH_MAP_MARKERS,
- PATH_MAP_TILES,
- PATH_HASHTAGS,
- PATH_VIDEOS,
- PATH_EXPERTS,
- PATH_PARTNERS,
- PATH_PEOPLE_IVE_MET
- };
-
- public static final String[] USER_DATA_RELATED_PATHS = {
- PATH_SESSIONS,
- PATH_MY_SCHEDULE
- };
-
- /**
- * Blocks are generic timeslots.
- */
- public static class Blocks implements BlocksColumns, BaseColumns {
- public static final String BLOCK_TYPE_FREE = "free";
- public static final String BLOCK_TYPE_BREAK = "break";
- public static final String BLOCK_TYPE_KEYNOTE = "keynote";
-
- public static final boolean isValidBlockType(String type) {
- return BLOCK_TYPE_FREE.equals(type) || BLOCK_TYPE_BREAK.equals(type)
- || BLOCK_TYPE_KEYNOTE.equals(type);
- }
-
- 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.iosched2014.block";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.block";
-
- /** "ORDER BY" clauses. */
- public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START + " ASC, "
- + BlocksColumns.BLOCK_END + " ASC";
-
- /** Build {@link Uri} for requested {@link #BLOCK_ID}. */
- public static Uri buildBlockUri(String blockId) {
- return CONTENT_URI.buildUpon().appendPath(blockId).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.
- * @param startTime the block start time, in milliseconds since Epoch UTC
- * @param endTime the block end time, in milliseconds since Epoch UTF
- */
- public static String generateBlockId(long startTime, long endTime) {
- startTime /= DateUtils.SECOND_IN_MILLIS;
- endTime /= DateUtils.SECOND_IN_MILLIS;
- return ParserUtils.sanitizeId(startTime + "-" + endTime);
- }
- }
-
- /**
- * Tags represent Session classifications. A session can have many tags. Tags can indicate,
- * for example, what product a session pertains to (Android, Chrome, ...), what type
- * of session it is (session, codelab, office hours, ...) and what overall event theme
- * it falls under (Design, Develop, Distribute), amongst others.
- */
- public static class Tags implements TagsColumns, BaseColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_TAGS).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.tag";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.tag";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = TagsColumns.TAG_ORDER_IN_CATEGORY;
-
- /**
- * Build {@link Uri} that references all tags.
- */
- public static Uri buildTagsUri() {
- return CONTENT_URI;
- }
-
- /** Build a {@link Uri} that references a given tag. */
- public static Uri buildTagUri(String tagId) {
- return CONTENT_URI.buildUpon().appendPath(tagId).build();
- }
-
- /** Read {@link #TAG_ID} from {@link Tags} {@link Uri}. */
- public static String getTagId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- /**
- * MySchedule represent the sessions that the user has starred/added to the "my schedule".
- * Each row of MySchedule represents one session in one account's my schedule.
- */
- public static class MySchedule implements MyScheduleColumns, BaseColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_MY_SCHEDULE).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.myschedule";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.myschedule";
-
- /**
- * Build {@link Uri} that references all My Schedule for the current user.
- */
- public static Uri buildMyScheduleUri(Context context) {
- return buildMyScheduleUri(context, null);
- }
- public static Uri buildMyScheduleUri(Context context, String accountName) {
- if (accountName == null) {
- accountName = AccountUtils.getActiveAccountName(context);
- }
- return addOverrideAccountName(CONTENT_URI, accountName);
- }
-
- }
-
- /**
- * 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.iosched2014.room";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.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);
- }
- }
-
- public static class Feedback implements BaseColumns, FeedbackColumns, SyncColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_FEEDBACK).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.session_feedback";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.session_feedback";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = BaseColumns._ID + " ASC, ";
-
- /** Build {@link Uri} to feedback for given session. */
- public static Uri buildFeedbackUri(String sessionId) {
- return CONTENT_URI.buildUpon().appendPath(sessionId).build();
- }
-
- /** Read {@link #SESSION_ID} from {@link Feedback} {@link Uri}. */
- public static String getSessionId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- /**
- * Each session has zero or more {@link Tags}, a {@link Rooms},
- * zero or more {@link Speakers}.
- */
- public static class Sessions implements SessionsColumns, RoomsColumns,
- SyncColumns, BaseColumns {
- public static final String QUERY_PARAMETER_TAG_FILTER = "filter";
-
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_SESSIONS).build();
- public static final Uri CONTENT_MY_SCHEDULE_URI =
- CONTENT_URI.buildUpon().appendPath(PATH_MY_SCHEDULE).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.session";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.session";
-
- public static final String ROOM_ID = "room_id";
-
- public static final String SEARCH_SNIPPET = "search_snippet";
-
- public static final String HAS_GIVEN_FEEDBACK = "has_given_feedback";
-
- // ORDER BY clauses
- public static final String SORT_BY_TYPE_THEN_TIME = SESSION_GROUPING_ORDER + " ASC,"
- + SESSION_START + " ASC," + SESSION_TITLE + " COLLATE NOCASE ASC";
- public static final String SORT_BY_TIME = SESSION_START + " ASC,"
- + SESSION_TITLE + " COLLATE NOCASE ASC";
-
- public static final String LIVESTREAM_SELECTION =
- SESSION_LIVESTREAM_URL + " is not null AND " + SESSION_LIVESTREAM_URL + "!=''";
-
- // Used to fetch sessions starting within a specific time interval
- public static final String STARTING_AT_TIME_INTERVAL_SELECTION =
- SESSION_START + " >= ? and " + SESSION_START + " <= ?";
-
- // Used to fetch sessions for a particular time
- public static final String AT_TIME_SELECTION =
- SESSION_START + " <= ? and " + SESSION_END + " >= ?";
-
- // Builds selectionArgs for {@link STARTING_AT_TIME_INTERVAL_SELECTION}
- public static String[] buildAtTimeIntervalArgs(long intervalStart, long intervalEnd) {
- return new String[] { String.valueOf(intervalStart), String.valueOf(intervalEnd) };
- }
-
- // 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_LIVE_SELECTION = SESSION_START + " > ?";
-
- // Builds selectionArgs for {@link UPCOMING_LIVE_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 references any {@link Tags} associated with
- * the requested {@link #SESSION_ID}.
- */
- public static Uri buildTagsDirUri(String sessionId) {
- return CONTENT_URI.buildUpon().appendPath(sessionId).appendPath(PATH_TAGS).build();
- }
-
- /**
- * Build {@link Uri} that references sessions that match the query. The query can be
- * multiple words separated with spaces.
- *
- * @param query The query. Can be multiple words separated by spaces.
- * @return {@link Uri} to the sessions
- */
- public static Uri buildSearchUri(String query) {
- if (null == query) {
- query = "";
- }
- // convert "lorem ipsum dolor sit" to "lorem* ipsum* dolor* sit*"
- query = query.replaceAll(" +", " *") + "*";
- return CONTENT_URI.buildUpon()
- .appendPath(PATH_SEARCH).appendPath(query).build();
- }
-
- public static boolean isSearchUri(Uri uri) {
- List pathSegments = uri.getPathSegments();
- return pathSegments.size() >= 2 && PATH_SEARCH.equals(pathSegments.get(1));
- }
-
- /** Build {@link Uri} that references sessions in a room that have begun after the requested time **/
- public static Uri buildSessionsInRoomAfterUri(String room, long time) {
- return CONTENT_URI.buildUpon().appendPath(PATH_ROOM).appendPath(room).appendPath(PATH_AFTER)
- .appendPath(String.valueOf(time)).build();
- }
-
- /** Build {@link Uri} that references sessions not in user's schedule that happen in the specified interval **/
- public static Uri buildUnscheduledSessionsInInterval(long start, long end) {
- String interval = start+"-"+end;
- return CONTENT_URI.buildUpon().appendPath(PATH_UNSCHEDULED).appendPath(interval).build();
- }
-
- public static boolean isUnscheduledSessionsInInterval(Uri uri) {
- return uri != null && uri.toString().startsWith(
- CONTENT_URI.buildUpon().appendPath(PATH_UNSCHEDULED).toString());
- }
-
- public static long[] getInterval(Uri uri) {
- if (uri == null) {
- return null;
- }
- List segments = uri.getPathSegments();
- if (segments.size() == 3 && segments.get(2).indexOf('-') > 0 ) {
- String[] interval = segments.get(2).split("-");
- return new long[]{Long.parseLong(interval[0]), Long.parseLong(interval[1])};
- }
- return null;
- }
-
- public static String getRoom(Uri uri){
- return uri.getPathSegments().get(2);
- }
-
- public static String getAfter(Uri uri){
- return uri.getPathSegments().get(4);
- }
-
-
- /** 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) {
- List segments = uri.getPathSegments();
- if (2 < segments.size()) {
- return segments.get(2);
- }
- return null;
- }
-
- public static boolean hasFilterParam(Uri uri) {
- return uri != null && uri.getQueryParameter(QUERY_PARAMETER_TAG_FILTER) != null;
- }
-
- /** Build {@link Uri} that references all sessions that have ALL of the indicated tags. */
- public static Uri buildTagFilterUri(String[] requiredTags) {
- StringBuilder sb = new StringBuilder();
- for (String tag : requiredTags) {
- if (TextUtils.isEmpty(tag)) continue;
- if (sb.length() > 0) {
- sb.append(",");
- }
- sb.append(tag.trim());
- }
- if (sb.length() == 0) {
- // equivalent to "all sessions"
- return CONTENT_URI;
- } else {
- // filter by the given set of tags
- return CONTENT_URI.buildUpon().appendQueryParameter(QUERY_PARAMETER_TAG_FILTER,
- sb.toString()).build();
- }
- }
-
- /** Build {@link Uri} that counts sessions by start/end intervals. */
- public static Uri buildCounterByIntervalUri() {
- return CONTENT_URI.buildUpon().appendPath(PATH_SESSIONS_COUNTER).build();
- }
- }
-
- /**
- * 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.iosched2014.speaker";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.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);
- }
- }
-
- /**
- * 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.iosched2014.announcement";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.announcement";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = AnnouncementsColumns.ANNOUNCEMENT_DATE
- + " COLLATE NOCASE DESC";
-
- /** Build {@link Uri} for requested {@link #ANNOUNCEMENT_ID}. */
- public static Uri buildAnnouncementUri(String announcementId) {
- return CONTENT_URI.buildUpon().appendPath(announcementId).build();
- }
-
- /**
- * Read {@link #ANNOUNCEMENT_ID} from {@link Announcements} {@link Uri}.
- */
- public static String getAnnouncementId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- /**
- * TileProvider entries are used to create an overlay provider for the map.
- */
- public static class MapTiles implements MapTileColumns, BaseColumns {
- public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
- .appendPath(PATH_MAP_TILES).build();
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.maptiles";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.maptiles";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = MapTileColumns.TILE_FLOOR + " ASC";
-
-
- /** Build {@link Uri} for all overlay zoom entries */
- public static Uri buildUri() {
- return CONTENT_URI;
- }
-
- /** Build {@link Uri} for requested floor. */
- public static Uri buildFloorUri(String floor) {
- return CONTENT_URI.buildUpon()
- .appendPath(String.valueOf(floor)).build();
- }
-
- /** Read floor from {@link MapMarkers} {@link Uri}. */
- public static String getFloorId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- /**
- * Markers refer to marked positions on the map.
- */
- public static class MapMarkers implements MapMarkerColumns, BaseColumns {
- public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
- .appendPath(PATH_MAP_MARKERS).build();
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.mapmarker";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.mapmarker";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = MapMarkerColumns.MARKER_FLOOR
- + " ASC, " + MapMarkerColumns.MARKER_ID + " ASC";
-
- /** Build {@link Uri} for requested {@link #MARKER_ID}. */
- public static Uri buildMarkerUri(String markerId) {
- return CONTENT_URI.buildUpon().appendPath(markerId).build();
- }
-
- /** Build {@link Uri} for all markers */
- public static Uri buildMarkerUri() {
- return CONTENT_URI;
- }
-
- /** Build {@link Uri} for requested {@link #MARKER_ID}. */
- public static Uri buildFloorUri(int floor) {
- return CONTENT_URI.buildUpon().appendPath(PATH_MAP_FLOOR)
- .appendPath("" + floor).build();
- }
-
- /** Read {@link #MARKER_ID} from {@link MapMarkers} {@link Uri}. */
- public static String getMarkerId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
-
- /** Read FLOOR from {@link MapMarkers} {@link Uri}. */
- public static String getMarkerFloor(Uri uri) {
- return uri.getPathSegments().get(2);
- }
-
- }
-
- /**
- * Hashtags are used for Google+ search. This model is independent from other models.
- */
- public static class Hashtags implements HashtagColumns, BaseColumns {
- public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
- .appendPath(PATH_HASHTAGS).build();
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.hashtags";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.hashtags";
-
- /** Build {@link Uri} for requested hashtag. */
- public static Uri buildHashtagUri(String hashtag) {
- return CONTENT_URI.buildUpon().appendPath(hashtag).build();
- }
-
- /** Read hashtag from {@link Hashtags} {@link Uri}. */
- public static String getHashtagName(Uri uri) {
- return uri.getPathSegments().get(1);
- }
-
- }
-
- /**
- * Videos are displayed in the Video Library. They are links to Youtube plus metadata.
- */
- public static class Videos implements VideoColumns, BaseColumns {
- public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
- .appendPath(PATH_VIDEOS).build();
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.iosched2014.videos";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.iosched2014.videos";
-
- public static final String DEFAULT_SORT = VideoColumns.VIDEO_YEAR + " DESC, "
- + VideoColumns.VIDEO_TOPIC + " ASC, " + VideoColumns.VIDEO_TITLE + " ASC";
-
- /** Build {@link Uri} for given video */
- public static Uri buildVideoUri(String videoId) {
- return CONTENT_URI.buildUpon().appendPath(videoId).build();
- }
-
- /** Return video ID given URI */
- public static String getVideoId(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 class SearchIndex {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_SEARCH_INDEX).build();
- }
-
- /**
- * Experts are individual people. Independent from the other data models.
- */
- public static class Experts implements ExpertsColumns, SyncColumns, BaseColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_EXPERTS).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.expert";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.expert";
-
- /** Default "ORDER BY" clause. */
- public static final String DEFAULT_SORT = ExpertsColumns.EXPERT_NAME
- + " COLLATE NOCASE ASC";
-
- /** Build {@link Uri} for requested {@link #EXPERT_ID}. */
- public static Uri buildExpertUri(String expertId) {
- return CONTENT_URI.buildUpon().appendPath(expertId).build();
- }
-
- public static String getExpertId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- /**
- * Partners are companies presenting at IO. This model is independent from other data models.
- */
- public static class Partners implements PartnersColumns, SyncColumns, BaseColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_PARTNERS).build();
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.partner";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.partner";
-
- /** Build {@link Uri} for requested {@link #PARTNER_ID}. */
- public static Uri buildPartnerUri(String partnerId) {
- return CONTENT_URI.buildUpon().appendPath(partnerId).build();
- }
-
- public static String getPartnerId(Uri uri) { return uri.getPathSegments().get(1); }
- }
-
- /**
- * Each record of PeopleIveMet is collected by scanning their badges. This model is independent
- * from other models.
- */
- public static class PeopleIveMet implements PeopleIveMetColumns, BaseColumns {
- public static final Uri CONTENT_URI =
- BASE_CONTENT_URI.buildUpon().appendPath(PATH_PEOPLE_IVE_MET).build();
-
- public static final String DEFAULT_SORT = PeopleIveMetColumns.PERSON_TIMESTAMP + " DESC";
-
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.iosched2014.people_ive_met";
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.iosched2014.people_ive_met";
-
- public static Uri buildPersonUri(String personId) {
- return CONTENT_URI.buildUpon().appendPath(personId).build();
- }
-
- public static String getPersonId(Uri uri) {
- return uri.getPathSegments().get(1);
- }
- }
-
- 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));
- }
-
- /**
- * Adds an account override parameter to the URI.
- * The override parameter instructs the Content Provider to ignore the currently logged in
- * account and use the provided account when fetching account-specific data
- * (such as sessions in My Schedule).
- *
- */
- public static Uri addOverrideAccountName(Uri uri, String accountName) {
- return uri.buildUpon().appendQueryParameter(
- OVERRIDE_ACCOUNTNAME_PARAMETER, accountName).build();
- }
-
- public static String getOverrideAccountName(Uri uri) {
- return uri.getQueryParameter(OVERRIDE_ACCOUNTNAME_PARAMETER);
- }
-
- private ScheduleContract() {
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.java
deleted file mode 100644
index 79f59ea10c..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleDatabase.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.provider;
-
-import android.accounts.Account;
-import android.app.SearchManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.provider.BaseColumns;
-
-import com.google.samples.apps.iosched.provider.ScheduleContract.*;
-import com.google.samples.apps.iosched.sync.ConferenceDataHandler;
-import com.google.samples.apps.iosched.sync.SyncHelper;
-import com.google.samples.apps.iosched.util.AccountUtils;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Helper for managing {@link SQLiteDatabase} that stores data for
- * {@link ScheduleProvider}.
- */
-public class ScheduleDatabase extends SQLiteOpenHelper {
- private static final String TAG = makeLogTag(ScheduleDatabase.class);
-
- private static final String DATABASE_NAME = "schedule.db";
-
- // NOTE: carefully update onUpgrade() when bumping database versions to make
- // sure user data is saved.
-
- private static final int VER_2014_RELEASE_A = 122; // app version 2.0.0, 2.0.1
- private static final int VER_2014_RELEASE_C = 207; // app version 2.1.x
- private static final int CUR_DATABASE_VERSION = VER_2014_RELEASE_C;
-
- private final Context mContext;
-
- interface Tables {
- String BLOCKS = "blocks";
- String TAGS = "tags";
- String ROOMS = "rooms";
- String SESSIONS = "sessions";
- String MY_SCHEDULE = "myschedule";
- String SPEAKERS = "speakers";
- String SESSIONS_TAGS = "sessions_tags";
- String SESSIONS_SPEAKERS = "sessions_speakers";
- String ANNOUNCEMENTS = "announcements";
- String MAPMARKERS = "mapmarkers";
- String MAPTILES = "mapoverlays";
- String HASHTAGS = "hashtags";
- String FEEDBACK = "feedback";
- String EXPERTS = "experts";
- String PEOPLE_IVE_MET = "people_ive_met";
- String VIDEOS = "videos";
- String PARTNERS = "partners";
-
- String SESSIONS_SEARCH = "sessions_search";
-
- String SEARCH_SUGGEST = "search_suggest";
-
- String SESSIONS_JOIN_MYSCHEDULE = "sessions "
- + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id "
- + "AND myschedule.account_name=? ";
-
- String SESSIONS_JOIN_ROOMS_TAGS = "sessions "
- + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id "
- + "AND myschedule.account_name=? "
- + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id "
- + "LEFT OUTER JOIN sessions_tags ON sessions.session_id=sessions_tags.session_id";
-
- String SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE = "sessions "
- + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id "
- + "AND myschedule.account_name=? "
- + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id "
- + "LEFT OUTER JOIN sessions_tags ON sessions.session_id=sessions_tags.session_id "
- + "LEFT OUTER JOIN feedback ON sessions.session_id=feedback.session_id";
-
- String SESSIONS_JOIN_ROOMS = "sessions "
- + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id "
- + "AND myschedule.account_name=? "
- + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
-
- String SESSIONS_SPEAKERS_JOIN_SPEAKERS = "sessions_speakers "
- + "LEFT OUTER JOIN speakers ON sessions_speakers.speaker_id=speakers.speaker_id";
-
- String SESSIONS_TAGS_JOIN_TAGS = "sessions_tags "
- + "LEFT OUTER JOIN tags ON sessions_tags.tag_id=tags.tag_id";
-
- String SESSIONS_SPEAKERS_JOIN_SESSIONS_ROOMS = "sessions_speakers "
- + "LEFT OUTER JOIN sessions ON sessions_speakers.session_id=sessions.session_id "
- + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
-
- String SESSIONS_SEARCH_JOIN_SESSIONS_ROOMS = "sessions_search "
- + "LEFT OUTER JOIN sessions ON sessions_search.session_id=sessions.session_id "
- + "LEFT OUTER JOIN myschedule ON sessions.session_id=myschedule.session_id "
- + "AND myschedule.account_name=? "
- + "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
-
- // When tables get deprecated, add them to this list (so they get correctly deleted
- // on database upgrades)
- interface DeprecatedTables {
- String TRACKS = "tracks";
- String SESSIONS_TRACKS = "sessions_tracks";
- String SANDBOX = "sandbox";
- };
-
- }
-
- private interface Triggers {
- // Deletes from dependent tables when corresponding sessions are deleted.
- String SESSIONS_TAGS_DELETE = "sessions_tags_delete";
- String SESSIONS_SPEAKERS_DELETE = "sessions_speakers_delete";
- String SESSIONS_MY_SCHEDULE_DELETE = "sessions_myschedule_delete";
- String SESSIONS_FEEDBACK_DELETE = "sessions_feedback_delete";
-
- // When triggers get deprecated, add them to this list (so they get correctly deleted
- // on database upgrades)
- interface DeprecatedTriggers {
- String SESSIONS_TRACKS_DELETE = "sessions_tracks_delete";
- };
- }
-
- public interface SessionsSpeakers {
- String SESSION_ID = "session_id";
- String SPEAKER_ID = "speaker_id";
- }
-
- public interface SessionsTags {
- String SESSION_ID = "session_id";
- String TAG_ID = "tag_id";
- }
-
- interface SessionsSearchColumns {
- String SESSION_ID = "session_id";
- String BODY = "body";
- }
-
- /** Fully-qualified field names. */
- private interface Qualified {
- String SESSIONS_SEARCH = Tables.SESSIONS_SEARCH + "(" + SessionsSearchColumns.SESSION_ID
- + "," + SessionsSearchColumns.BODY + ")";
-
- String SESSIONS_TAGS_SESSION_ID = Tables.SESSIONS_TAGS + "."
- + SessionsTags.SESSION_ID;
-
- String SESSIONS_SPEAKERS_SESSION_ID = Tables.SESSIONS_SPEAKERS+ "."
- + SessionsSpeakers.SESSION_ID;
-
- String SESSIONS_SPEAKERS_SPEAKER_ID = Tables.SESSIONS_SPEAKERS+ "."
- + SessionsSpeakers.SPEAKER_ID;
-
- String SPEAKERS_SPEAKER_ID = Tables.SPEAKERS + "." + Speakers.SPEAKER_ID;
-
- String FEEDBACK_SESSION_ID = Tables.FEEDBACK + "." + FeedbackColumns.SESSION_ID;
- }
-
- /** {@code REFERENCES} clauses. */
- private interface References {
- String BLOCK_ID = "REFERENCES " + Tables.BLOCKS + "(" + Blocks.BLOCK_ID + ")";
- String TAG_ID = "REFERENCES " + Tables.TAGS + "(" + Tags.TAG_ID + ")";
- String ROOM_ID = "REFERENCES " + Tables.ROOMS + "(" + Rooms.ROOM_ID + ")";
- String SESSION_ID = "REFERENCES " + Tables.SESSIONS + "(" + Sessions.SESSION_ID + ")";
- String SPEAKER_ID = "REFERENCES " + Tables.SPEAKERS + "(" + Speakers.SPEAKER_ID + ")";
- }
-
- public ScheduleDatabase(Context context) {
- super(context, DATABASE_NAME, null, CUR_DATABASE_VERSION);
- mContext = context;
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + Tables.BLOCKS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + BlocksColumns.BLOCK_ID + " TEXT NOT NULL,"
- + BlocksColumns.BLOCK_TITLE + " TEXT NOT NULL,"
- + BlocksColumns.BLOCK_START + " INTEGER NOT NULL,"
- + BlocksColumns.BLOCK_END + " INTEGER NOT NULL,"
- + BlocksColumns.BLOCK_TYPE + " TEXT,"
- + BlocksColumns.BLOCK_SUBTITLE + " TEXT,"
- + "UNIQUE (" + BlocksColumns.BLOCK_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.TAGS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + TagsColumns.TAG_ID + " TEXT NOT NULL,"
- + TagsColumns.TAG_CATEGORY + " TEXT NOT NULL,"
- + TagsColumns.TAG_NAME + " TEXT NOT NULL,"
- + TagsColumns.TAG_ORDER_IN_CATEGORY + " INTEGER,"
- + TagsColumns.TAG_COLOR + " TEXT NOT NULL,"
- + TagsColumns.TAG_ABSTRACT + " TEXT NOT NULL,"
- + "UNIQUE (" + TagsColumns.TAG_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.ROOMS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + RoomsColumns.ROOM_ID + " TEXT NOT NULL,"
- + RoomsColumns.ROOM_NAME + " TEXT,"
- + RoomsColumns.ROOM_FLOOR + " TEXT,"
- + "UNIQUE (" + RoomsColumns.ROOM_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.SESSIONS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SyncColumns.UPDATED + " INTEGER NOT NULL,"
- + SessionsColumns.SESSION_ID + " TEXT NOT NULL,"
- + Sessions.ROOM_ID + " TEXT " + References.ROOM_ID + ","
- + SessionsColumns.SESSION_START + " INTEGER NOT NULL,"
- + SessionsColumns.SESSION_END + " INTEGER NOT NULL,"
- + SessionsColumns.SESSION_LEVEL + " TEXT,"
- + SessionsColumns.SESSION_TITLE + " TEXT,"
- + SessionsColumns.SESSION_ABSTRACT + " TEXT,"
- + SessionsColumns.SESSION_REQUIREMENTS + " TEXT,"
- + SessionsColumns.SESSION_KEYWORDS + " TEXT,"
- + SessionsColumns.SESSION_HASHTAG + " TEXT,"
- + SessionsColumns.SESSION_URL + " TEXT,"
- + SessionsColumns.SESSION_YOUTUBE_URL + " TEXT,"
- + SessionsColumns.SESSION_MODERATOR_URL + " TEXT,"
- + SessionsColumns.SESSION_PDF_URL + " TEXT,"
- + SessionsColumns.SESSION_NOTES_URL + " TEXT,"
- + SessionsColumns.SESSION_CAL_EVENT_ID + " INTEGER,"
- + SessionsColumns.SESSION_LIVESTREAM_URL + " TEXT,"
- + SessionsColumns.SESSION_TAGS + " TEXT,"
- + SessionsColumns.SESSION_GROUPING_ORDER + " INTEGER,"
- + SessionsColumns.SESSION_SPEAKER_NAMES + " TEXT,"
- + SessionsColumns.SESSION_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT '',"
- + SessionsColumns.SESSION_MAIN_TAG + " TEXT,"
- + SessionsColumns.SESSION_COLOR + " INTEGER,"
- + SessionsColumns.SESSION_CAPTIONS_URL + " TEXT,"
- + SessionsColumns.SESSION_PHOTO_URL + " TEXT,"
- + SessionsColumns.SESSION_RELATED_CONTENT + " TEXT,"
- + "UNIQUE (" + SessionsColumns.SESSION_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.SPEAKERS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SyncColumns.UPDATED + " INTEGER NOT NULL,"
- + SpeakersColumns.SPEAKER_ID + " TEXT NOT NULL,"
- + SpeakersColumns.SPEAKER_NAME + " TEXT,"
- + SpeakersColumns.SPEAKER_IMAGE_URL + " TEXT,"
- + SpeakersColumns.SPEAKER_COMPANY + " TEXT,"
- + SpeakersColumns.SPEAKER_ABSTRACT + " TEXT,"
- + SpeakersColumns.SPEAKER_URL + " TEXT,"
- + SpeakersColumns.SPEAKER_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT '',"
- + "UNIQUE (" + SpeakersColumns.SPEAKER_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.MY_SCHEDULE + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + MySchedule.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + ","
- + MySchedule.MY_SCHEDULE_ACCOUNT_NAME + " TEXT NOT NULL ,"
- + MySchedule.MY_SCHEDULE_DIRTY_FLAG + " INTEGER NOT NULL DEFAULT 1,"
- + MySchedule.MY_SCHEDULE_IN_SCHEDULE + " INTEGER NOT NULL DEFAULT 1,"
- + "UNIQUE (" + MySchedule.SESSION_ID + ","
- + MySchedule.MY_SCHEDULE_ACCOUNT_NAME + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.SESSIONS_SPEAKERS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SessionsSpeakers.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + ","
- + SessionsSpeakers.SPEAKER_ID + " TEXT NOT NULL " + References.SPEAKER_ID + ","
- + "UNIQUE (" + SessionsSpeakers.SESSION_ID + ","
- + SessionsSpeakers.SPEAKER_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.SESSIONS_TAGS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SessionsTags.SESSION_ID + " TEXT NOT NULL " + References.SESSION_ID + ","
- + SessionsTags.TAG_ID + " TEXT NOT NULL " + References.TAG_ID + ","
- + "UNIQUE (" + SessionsTags.SESSION_ID + ","
- + SessionsTags.TAG_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.ANNOUNCEMENTS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SyncColumns.UPDATED + " INTEGER NOT NULL,"
- + AnnouncementsColumns.ANNOUNCEMENT_ID + " TEXT,"
- + AnnouncementsColumns.ANNOUNCEMENT_TITLE + " TEXT NOT NULL,"
- + AnnouncementsColumns.ANNOUNCEMENT_ACTIVITY_JSON + " BLOB,"
- + AnnouncementsColumns.ANNOUNCEMENT_URL + " TEXT,"
- + AnnouncementsColumns.ANNOUNCEMENT_DATE + " INTEGER NOT NULL)");
-
- db.execSQL("CREATE TABLE " + Tables.MAPTILES + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + MapTileColumns.TILE_FLOOR+ " INTEGER NOT NULL,"
- + MapTileColumns.TILE_FILE+ " TEXT NOT NULL,"
- + MapTileColumns.TILE_URL+ " TEXT NOT NULL,"
- + "UNIQUE (" + MapTileColumns.TILE_FLOOR+ ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.FEEDBACK + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SyncColumns.UPDATED + " INTEGER NOT NULL,"
- + Sessions.SESSION_ID + " TEXT " + References.SESSION_ID + ","
- + FeedbackColumns.SESSION_RATING + " INTEGER NOT NULL,"
- + FeedbackColumns.ANSWER_RELEVANCE + " INTEGER NOT NULL,"
- + FeedbackColumns.ANSWER_CONTENT + " INTEGER NOT NULL,"
- + FeedbackColumns.ANSWER_SPEAKER + " INTEGER NOT NULL,"
- + FeedbackColumns.COMMENTS + " TEXT,"
- + FeedbackColumns.SYNCED + " INTEGER NOT NULL DEFAULT 0)");
-
- db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_FEEDBACK_DELETE + " AFTER DELETE ON "
- + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.FEEDBACK + " "
- + " WHERE " + Qualified.FEEDBACK_SESSION_ID + "=old." + Sessions.SESSION_ID
- + ";" + " END;");
-
- db.execSQL("CREATE TABLE " + Tables.MAPMARKERS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + MapMarkerColumns.MARKER_ID+ " TEXT NOT NULL,"
- + MapMarkerColumns.MARKER_TYPE+ " TEXT NOT NULL,"
- + MapMarkerColumns.MARKER_LATITUDE+ " DOUBLE NOT NULL,"
- + MapMarkerColumns.MARKER_LONGITUDE+ " DOUBLE NOT NULL,"
- + MapMarkerColumns.MARKER_LABEL+ " TEXT,"
- + MapMarkerColumns.MARKER_FLOOR+ " INTEGER NOT NULL,"
- + "UNIQUE (" + MapMarkerColumns.MARKER_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.HASHTAGS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + HashtagColumns.HASHTAG_NAME + " TEXT NOT NULL,"
- + HashtagColumns.HASHTAG_DESCRIPTION + " TEXT NOT NULL,"
- + HashtagColumns.HASHTAG_COLOR + " INTEGER NOT NULL,"
- + HashtagColumns.HASHTAG_ORDER + " INTEGER NOT NULL,"
- + "UNIQUE (" + HashtagColumns.HASHTAG_NAME + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.VIDEOS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + VideoColumns.VIDEO_ID + " TEXT NOT NULL,"
- + VideoColumns.VIDEO_YEAR + " INTEGER NOT NULL,"
- + VideoColumns.VIDEO_TITLE + " TEXT,"
- + VideoColumns.VIDEO_DESC + " TEXT,"
- + VideoColumns.VIDEO_VID + " TEXT,"
- + VideoColumns.VIDEO_TOPIC + " TEXT,"
- + VideoColumns.VIDEO_SPEAKERS + " TEXT,"
- + VideoColumns.VIDEO_THUMBNAIL_URL + " TEXT,"
- + VideoColumns.VIDEO_IMPORT_HASHCODE + " TEXT NOT NULL,"
- + "UNIQUE (" + VideoColumns.VIDEO_ID + ") ON CONFLICT REPLACE)");
-
- // Full-text search index. Update using updateSessionSearchIndex method.
- // Use the porter tokenizer for simple stemming, so that "frustration" matches "frustrated."
- db.execSQL("CREATE VIRTUAL TABLE " + Tables.SESSIONS_SEARCH + " USING fts3("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SessionsSearchColumns.BODY + " TEXT NOT NULL,"
- + SessionsSearchColumns.SESSION_ID
- + " TEXT NOT NULL " + References.SESSION_ID + ","
- + "UNIQUE (" + SessionsSearchColumns.SESSION_ID + ") ON CONFLICT REPLACE,"
- + "tokenize=porter)");
-
- // Search suggestions
- db.execSQL("CREATE TABLE " + Tables.SEARCH_SUGGEST + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + SearchManager.SUGGEST_COLUMN_TEXT_1 + " TEXT NOT NULL)");
-
- // Session deletion triggers
- db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_TAGS_DELETE + " AFTER DELETE ON "
- + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.SESSIONS_TAGS + " "
- + " WHERE " + Qualified.SESSIONS_TAGS_SESSION_ID + "=old." + Sessions.SESSION_ID
- + ";" + " END;");
-
- db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_SPEAKERS_DELETE + " AFTER DELETE ON "
- + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.SESSIONS_SPEAKERS + " "
- + " WHERE " + Qualified.SESSIONS_SPEAKERS_SESSION_ID + "=old." + Sessions.SESSION_ID
- + ";" + " END;");
-
- db.execSQL("CREATE TRIGGER " + Triggers.SESSIONS_MY_SCHEDULE_DELETE + " AFTER DELETE ON "
- + Tables.SESSIONS + " BEGIN DELETE FROM " + Tables.MY_SCHEDULE + " "
- + " WHERE " + Tables.MY_SCHEDULE + "." + MySchedule.SESSION_ID +
- "=old." + Sessions.SESSION_ID
- + ";" + " END;");
-
- upgradeAtoC(db);
- }
-
- private void upgradeAtoC(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + Tables.EXPERTS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + SyncColumns.UPDATED + " INTEGER NOT NULL, "
- + ExpertsColumns.EXPERT_ID + " TEXT NOT NULL, "
- + ExpertsColumns.EXPERT_NAME + " TEXT, "
- + ExpertsColumns.EXPERT_IMAGE_URL + " TEXT, "
- + ExpertsColumns.EXPERT_TITLE + " TEXT, "
- + ExpertsColumns.EXPERT_ABSTRACT + " TEXT, "
- + ExpertsColumns.EXPERT_URL + " TEXT, "
- + ExpertsColumns.EXPERT_COUNTRY + " TEXT, "
- + ExpertsColumns.EXPERT_CITY + " TEXT, "
- + ExpertsColumns.EXPERT_ATTENDING + " BOOLEAN, "
- + ExpertsColumns.EXPERT_IMPORT_HASHCODE + " TEXT NOT NULL DEFAULT '', "
- + "UNIQUE (" + ExpertsColumns.EXPERT_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.PEOPLE_IVE_MET + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + ScheduleContract.PeopleIveMetColumns.PERSON_ID + " TEXT NOT NULL, "
- + ScheduleContract.PeopleIveMetColumns.PERSON_TIMESTAMP + " INTEGER NOT NULL, "
- + ScheduleContract.PeopleIveMetColumns.PERSON_NAME + " TEXT, "
- + ScheduleContract.PeopleIveMetColumns.PERSON_IMAGE_URL + " TEXT, "
- + ScheduleContract.PeopleIveMetColumns.PERSON_NOTE + " TEXT, "
- + "UNIQUE (" + ScheduleContract.PeopleIveMetColumns.PERSON_ID + ") ON CONFLICT REPLACE)");
-
- db.execSQL("CREATE TABLE " + Tables.PARTNERS + " ("
- + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + PartnersColumns.PARTNER_ID + " TEXT NOT NULL,"
- + PartnersColumns.PARTNER_NAME + " TEXT NOT NULL,"
- + PartnersColumns.PARTNER_DESC + " TEXT NOT NULL,"
- + PartnersColumns.PARTNER_WEBSITE_URL + " TEXT NOT NULL,"
- + PartnersColumns.PARTNER_LOGO_URL + " TEXT NOT NULL,"
- + "UNIQUE (" + PartnersColumns.PARTNER_ID + ") ON CONFLICT REPLACE)");
- }
-
- /**
- * Updates the session search index. This should be done sparingly, as the queries are rather
- * complex.
- */
- static void updateSessionSearchIndex(SQLiteDatabase db) {
- db.execSQL("DELETE FROM " + Tables.SESSIONS_SEARCH);
-
- db.execSQL("INSERT INTO " + Qualified.SESSIONS_SEARCH
- + " SELECT s." + Sessions.SESSION_ID + ",("
-
- // Full text body
- + Sessions.SESSION_TITLE + "||'; '||"
- + Sessions.SESSION_ABSTRACT + "||'; '||"
- + "IFNULL(GROUP_CONCAT(t." + Speakers.SPEAKER_NAME + ",' '),'')||'; '||"
- + "'')"
-
- + " FROM " + Tables.SESSIONS + " s "
- + " LEFT OUTER JOIN"
-
- // Subquery resulting in session_id, speaker_id, speaker_name
- + "(SELECT " + Sessions.SESSION_ID + "," + Qualified.SPEAKERS_SPEAKER_ID
- + "," + Speakers.SPEAKER_NAME
- + " FROM " + Tables.SESSIONS_SPEAKERS
- + " INNER JOIN " + Tables.SPEAKERS
- + " ON " + Qualified.SESSIONS_SPEAKERS_SPEAKER_ID + "="
- + Qualified.SPEAKERS_SPEAKER_ID
- + ") t"
-
- // Grand finale
- + " ON s." + Sessions.SESSION_ID + "=t." + Sessions.SESSION_ID
- + " GROUP BY s." + Sessions.SESSION_ID);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- LOGD(TAG, "onUpgrade() from " + oldVersion + " to " + newVersion);
-
- // Cancel any sync currently in progress
- Account account = AccountUtils.getActiveAccount(mContext);
- if (account != null) {
- LOGI(TAG, "Cancelling any pending syncs for account");
- ContentResolver.cancelSync(account, ScheduleContract.CONTENT_AUTHORITY);
- }
-
- // Current DB version. We update this variable as we perform upgrades to reflect
- // the current version we are in.
- int version = oldVersion;
-
- // Indicates whether the data we currently have should be invalidated as a
- // result of the db upgrade. Default is true (invalidate); if we detect that this
- // is a trivial DB upgrade, we set this to false.
- boolean dataInvalidated = true;
-
- // Check if we can upgrade from release A to release C
- if (version == VER_2014_RELEASE_A) {
- // release A format can be upgraded to release C format
- LOGD(TAG, "Upgrading database from 2014 release A to 2014 release C.");
- upgradeAtoC(db);
- version = VER_2014_RELEASE_C;
- }
-
- LOGD(TAG, "After upgrade logic, at version " + version);
-
- // at this point, we ran out of upgrade logic, so if we are still at the wrong
- // version, we have no choice but to delete everything and create everything again.
- if (version != CUR_DATABASE_VERSION) {
- LOGW(TAG, "Upgrade unsuccessful -- destroying old data during upgrade");
-
- db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_TAGS_DELETE);
- db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_SPEAKERS_DELETE);
- db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_FEEDBACK_DELETE);
- db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.SESSIONS_MY_SCHEDULE_DELETE);
- db.execSQL("DROP TRIGGER IF EXISTS " + Triggers.DeprecatedTriggers.SESSIONS_TRACKS_DELETE);
-
- db.execSQL("DROP TABLE IF EXISTS " + Tables.BLOCKS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.ROOMS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.TAGS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SPEAKERS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.MY_SCHEDULE);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_SPEAKERS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_TAGS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.ANNOUNCEMENTS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.FEEDBACK);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SESSIONS_SEARCH);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_SUGGEST);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.MAPMARKERS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.MAPTILES);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.EXPERTS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.HASHTAGS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.PEOPLE_IVE_MET);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.VIDEOS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.PARTNERS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.TRACKS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.SESSIONS_TRACKS);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.DeprecatedTables.SANDBOX);
-
- onCreate(db);
- version = CUR_DATABASE_VERSION;
- }
-
- if (dataInvalidated) {
- LOGD(TAG, "Data invalidated; resetting our data timestamp.");
- ConferenceDataHandler.resetDataTimestamp(mContext);
- if (account != null) {
- LOGI(TAG, "DB upgrade complete. Requesting resync.");
- SyncHelper.requestManualSync(account);
- }
- }
- }
-
- public static void deleteDatabase(Context context) {
- context.deleteDatabase(DATABASE_NAME);
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java b/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java
deleted file mode 100644
index d33f3fabc0..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/provider/ScheduleProvider.java
+++ /dev/null
@@ -1,1048 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.provider;
-
-import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Announcements;
-import com.google.samples.apps.iosched.provider.ScheduleContract.PeopleIveMet;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Blocks;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Experts;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Feedback;
-import com.google.samples.apps.iosched.provider.ScheduleContract.MapMarkers;
-import com.google.samples.apps.iosched.provider.ScheduleContract.MapTiles;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Rooms;
-import com.google.samples.apps.iosched.provider.ScheduleContract.SearchSuggest;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Sessions;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Speakers;
-import com.google.samples.apps.iosched.provider.ScheduleContract.Tags;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSearchColumns;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSpeakers;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.Tables;
-import com.google.samples.apps.iosched.util.SelectionBuilder;
-
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.*;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.BaseColumns;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.google.samples.apps.iosched.Config;
-import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider;
-import com.google.samples.apps.iosched.provider.ScheduleContract.*;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSearchColumns;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.SessionsSpeakers;
-import com.google.samples.apps.iosched.provider.ScheduleDatabase.Tables;
-import com.google.samples.apps.iosched.util.AccountUtils;
-import com.google.samples.apps.iosched.util.SelectionBuilder;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.LOGV;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * Provider that stores {@link ScheduleContract} data. Data is usually inserted
- * by {@link com.google.samples.apps.iosched.sync.SyncHelper}, and queried by various
- * {@link Activity} instances.
- */
-public class ScheduleProvider extends ContentProvider {
- private static final String TAG = makeLogTag(ScheduleProvider.class);
-
- private ScheduleDatabase mOpenHelper;
-
- private static final UriMatcher sUriMatcher = buildUriMatcher();
-
- private static final int BLOCKS = 100;
- private static final int BLOCKS_BETWEEN = 101;
- private static final int BLOCKS_ID = 102;
-
- private static final int TAGS = 200;
- private static final int TAGS_ID = 201;
-
- private static final int ROOMS = 300;
- private static final int ROOMS_ID = 301;
- private static final int ROOMS_ID_SESSIONS = 302;
-
- private static final int SESSIONS = 400;
- private static final int SESSIONS_MY_SCHEDULE = 401;
- private static final int SESSIONS_SEARCH = 403;
- private static final int SESSIONS_AT = 404;
- private static final int SESSIONS_ID = 405;
- private static final int SESSIONS_ID_SPEAKERS = 406;
- private static final int SESSIONS_ID_TAGS = 407;
- private static final int SESSIONS_ROOM_AFTER = 408;
- private static final int SESSIONS_UNSCHEDULED = 409;
- private static final int SESSIONS_COUNTER = 410;
-
- private static final int SPEAKERS = 500;
- private static final int SPEAKERS_ID = 501;
- private static final int SPEAKERS_ID_SESSIONS = 502;
-
- private static final int MY_SCHEDULE = 600;
-
- private static final int ANNOUNCEMENTS = 700;
- private static final int ANNOUNCEMENTS_ID = 701;
-
- private static final int SEARCH_SUGGEST = 800;
- private static final int SEARCH_INDEX = 801;
-
- private static final int MAPMARKERS = 900;
- private static final int MAPMARKERS_FLOOR = 901;
- private static final int MAPMARKERS_ID = 902;
-
- private static final int MAPTILES = 1000;
- private static final int MAPTILES_FLOOR = 1001;
-
- private static final int FEEDBACK_ALL = 1002;
- private static final int FEEDBACK_FOR_SESSION = 1003;
-
- private static final int EXPERTS = 1100;
- private static final int EXPERTS_ID = 1101;
- private static final int HASHTAGS = 1200;
- private static final int HASHTAGS_NAME = 1201;
-
- private static final int PEOPLE_IVE_MET = 1250;
- private static final int PEOPLE_IVE_MET_ID = 1251;
-
- private static final int VIDEOS = 1300;
- private static final int VIDEOS_ID = 1301;
-
- private static final int PARTNERS = 1400;
- private static final int PARTNERS_ID = 1401;
-
-
- /**
- * Build and return a {@link UriMatcher} that catches all {@link Uri}
- * variations supported by this {@link ContentProvider}.
- */
- private static UriMatcher buildUriMatcher() {
- final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
- final String authority = ScheduleContract.CONTENT_AUTHORITY;
-
- matcher.addURI(authority, "blocks", BLOCKS);
- matcher.addURI(authority, "blocks/between/*/*", BLOCKS_BETWEEN);
- matcher.addURI(authority, "blocks/*", BLOCKS_ID);
-
- matcher.addURI(authority, "tags", TAGS);
- matcher.addURI(authority, "tags/*", TAGS_ID);
-
- matcher.addURI(authority, "rooms", ROOMS);
- matcher.addURI(authority, "rooms/*", ROOMS_ID);
- matcher.addURI(authority, "rooms/*/sessions", ROOMS_ID_SESSIONS);
-
- matcher.addURI(authority, "sessions", SESSIONS);
- matcher.addURI(authority, "sessions/my_schedule", SESSIONS_MY_SCHEDULE);
- matcher.addURI(authority, "sessions/search/*", SESSIONS_SEARCH);
- matcher.addURI(authority, "sessions/at/*", SESSIONS_AT);
- matcher.addURI(authority, "sessions/unscheduled/*", SESSIONS_UNSCHEDULED);
- matcher.addURI(authority, "sessions/room/*/after/*", SESSIONS_ROOM_AFTER);
- matcher.addURI(authority, "sessions/counter", SESSIONS_COUNTER);
- matcher.addURI(authority, "sessions/*", SESSIONS_ID);
- matcher.addURI(authority, "sessions/*/speakers", SESSIONS_ID_SPEAKERS);
- matcher.addURI(authority, "sessions/*/tags", SESSIONS_ID_TAGS);
-
- matcher.addURI(authority, "my_schedule", MY_SCHEDULE);
-
- matcher.addURI(authority, "speakers", SPEAKERS);
- matcher.addURI(authority, "speakers/*", SPEAKERS_ID);
- matcher.addURI(authority, "speakers/*/sessions", SPEAKERS_ID_SESSIONS);
-
- matcher.addURI(authority, "announcements", ANNOUNCEMENTS);
- matcher.addURI(authority, "announcements/*", ANNOUNCEMENTS_ID);
-
- matcher.addURI(authority, "search_suggest_query", SEARCH_SUGGEST);
- matcher.addURI(authority, "search_index", SEARCH_INDEX); // 'update' only
-
- matcher.addURI(authority, "mapmarkers", MAPMARKERS);
- matcher.addURI(authority, "mapmarkers/floor/*", MAPMARKERS_FLOOR);
- matcher.addURI(authority, "mapmarkers/*", MAPMARKERS_ID);
-
- matcher.addURI(authority, "maptiles", MAPTILES);
- matcher.addURI(authority, "maptiles/*", MAPTILES_FLOOR);
-
- matcher.addURI(authority, "feedback/*", FEEDBACK_FOR_SESSION);
- matcher.addURI(authority, "feedback*", FEEDBACK_ALL);
- matcher.addURI(authority, "feedback", FEEDBACK_ALL);
-
- matcher.addURI(authority, "experts/", EXPERTS);
- matcher.addURI(authority, "experts/*", EXPERTS_ID);
-
- matcher.addURI(authority, "hashtags", HASHTAGS);
- matcher.addURI(authority, "hashtags/*", HASHTAGS_NAME);
-
- matcher.addURI(authority, "people_ive_met/", PEOPLE_IVE_MET);
- matcher.addURI(authority, "people_ive_met/*", PEOPLE_IVE_MET_ID);
-
- matcher.addURI(authority, "videos", VIDEOS);
- matcher.addURI(authority, "videos/*", VIDEOS_ID);
-
- matcher.addURI(authority, "partners", PARTNERS);
- matcher.addURI(authority, "partners/*", PARTNERS_ID);
-
- return matcher;
- }
-
- @Override
- public boolean onCreate() {
- mOpenHelper = new ScheduleDatabase(getContext());
- return true;
- }
-
- private void deleteDatabase() {
- // TODO: wait for content provider operations to finish, then tear down
- mOpenHelper.close();
- Context context = getContext();
- ScheduleDatabase.deleteDatabase(context);
- mOpenHelper = new ScheduleDatabase(getContext());
- }
-
- /** {@inheritDoc} */
- @Override
- public String getType(Uri uri) {
- final int match = sUriMatcher.match(uri);
- switch (match) {
- case BLOCKS:
- return Blocks.CONTENT_TYPE;
- case BLOCKS_BETWEEN:
- return Blocks.CONTENT_TYPE;
- case BLOCKS_ID:
- return Blocks.CONTENT_ITEM_TYPE;
- case TAGS:
- return Tags.CONTENT_TYPE;
- case TAGS_ID:
- return Tags.CONTENT_TYPE;
- case ROOMS:
- return Rooms.CONTENT_TYPE;
- case ROOMS_ID:
- return Rooms.CONTENT_ITEM_TYPE;
- case ROOMS_ID_SESSIONS:
- return Sessions.CONTENT_TYPE;
- case SESSIONS:
- return Sessions.CONTENT_TYPE;
- case SESSIONS_MY_SCHEDULE:
- return Sessions.CONTENT_TYPE;
- case SESSIONS_UNSCHEDULED:
- return Sessions.CONTENT_TYPE;
- case SESSIONS_SEARCH:
- return Sessions.CONTENT_TYPE;
- case SESSIONS_AT:
- return Sessions.CONTENT_TYPE;
- case SESSIONS_ID:
- return Sessions.CONTENT_ITEM_TYPE;
- case SESSIONS_ID_SPEAKERS:
- return Speakers.CONTENT_TYPE;
- case SESSIONS_ID_TAGS:
- return Tags.CONTENT_TYPE;
- case SESSIONS_ROOM_AFTER:
- return Sessions.CONTENT_TYPE;
- case MY_SCHEDULE:
- return MySchedule.CONTENT_TYPE;
- case SPEAKERS:
- return Speakers.CONTENT_TYPE;
- case SPEAKERS_ID:
- return Speakers.CONTENT_ITEM_TYPE;
- case SPEAKERS_ID_SESSIONS:
- return Sessions.CONTENT_TYPE;
- case ANNOUNCEMENTS:
- return Announcements.CONTENT_TYPE;
- case ANNOUNCEMENTS_ID:
- return Announcements.CONTENT_ITEM_TYPE;
- case MAPMARKERS:
- return MapMarkers.CONTENT_TYPE;
- case MAPMARKERS_FLOOR:
- return MapMarkers.CONTENT_TYPE;
- case MAPMARKERS_ID:
- return MapMarkers.CONTENT_ITEM_TYPE;
- case MAPTILES:
- return MapTiles.CONTENT_TYPE;
- case MAPTILES_FLOOR:
- return MapTiles.CONTENT_ITEM_TYPE;
- case FEEDBACK_FOR_SESSION:
- return Feedback.CONTENT_ITEM_TYPE;
- case FEEDBACK_ALL:
- return Feedback.CONTENT_TYPE;
- case EXPERTS:
- return Experts.CONTENT_TYPE;
- case EXPERTS_ID:
- return Experts.CONTENT_ITEM_TYPE;
- case PEOPLE_IVE_MET:
- return ScheduleContract.PeopleIveMet.CONTENT_TYPE;
- case PEOPLE_IVE_MET_ID:
- return ScheduleContract.PeopleIveMet.CONTENT_ITEM_TYPE;
- case HASHTAGS:
- return Hashtags.CONTENT_TYPE;
- case HASHTAGS_NAME:
- return Hashtags.CONTENT_ITEM_TYPE;
- case VIDEOS:
- return Videos.CONTENT_TYPE;
- case VIDEOS_ID:
- return Videos.CONTENT_ITEM_TYPE;
- case PARTNERS:
- return Partners.CONTENT_TYPE;
- case PARTNERS_ID:
- return Partners.CONTENT_ITEM_TYPE;
-
- default:
- throw new UnsupportedOperationException("Unknown uri: " + uri);
- }
- }
-
- /** Returns a tuple of question marks. For example, if count is 3, returns "(?,?,?)". */
- private String makeQuestionMarkTuple(int count) {
- if (count < 1) {
- return "()";
- }
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("(?");
- for (int i = 1; i < count; i++) {
- stringBuilder.append(",?");
- }
- stringBuilder.append(")");
- return stringBuilder.toString();
- }
-
- /** Adds the tags filter query parameter to the given builder. */
- private void addTagsFilter(SelectionBuilder builder, String tagsFilter) {
- // Note: for context, remember that session queries are done on a join of sessions
- // and the sessions_tags relationship table, and are GROUP'ed BY the session ID.
- String[] requiredTags = tagsFilter.split(",");
- if (requiredTags.length == 0) {
- // filtering by 0 tags -- no-op
- return;
- } else if (requiredTags.length == 1) {
- // filtering by only one tag, so a simple WHERE clause suffices
- builder.where(Tags.TAG_ID + "=?", requiredTags[0]);
- } else {
- // Filtering by multiple tags, so we must add a WHERE clause with an IN operator,
- // and add a HAVING statement to exclude groups that fall short of the number
- // of required tags. For example, if requiredTags is { "X", "Y", "Z" }, and a certain
- // session only has tags "X" and "Y", it will be excluded by the HAVING statement.
- String questionMarkTuple = makeQuestionMarkTuple(requiredTags.length);
- builder.where(Tags.TAG_ID + " IN " + questionMarkTuple, requiredTags);
- builder.having("COUNT(" + Qualified.SESSIONS_SESSION_ID + ") >= " + requiredTags.length);
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
-
- final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-
- String tagsFilter = uri.getQueryParameter(Sessions.QUERY_PARAMETER_TAG_FILTER);
- final int match = sUriMatcher.match(uri);
-
- // avoid the expensive string concatenation below if not loggable
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- LOGV(TAG, "uri=" + uri + " match=" + match + " proj=" + Arrays.toString(projection) +
- " selection=" + selection + " args=" + Arrays.toString(selectionArgs) + ")");
- }
-
-
- switch (match) {
- default: {
- // Most cases are handled with simple SelectionBuilder
- final SelectionBuilder builder = buildExpandedSelection(uri, match);
-
- // If a special filter was specified, try to apply it
- if (!TextUtils.isEmpty(tagsFilter)) {
- addTagsFilter(builder, tagsFilter);
- }
-
- boolean distinct = !TextUtils.isEmpty(
- uri.getQueryParameter(ScheduleContract.QUERY_PARAMETER_DISTINCT));
-
- Cursor cursor = builder
- .where(selection, selectionArgs)
- .query(db, distinct, projection, sortOrder, null);
- Context context = getContext();
- if (null != context) {
- cursor.setNotificationUri(context.getContentResolver(), uri);
- }
- return cursor;
- }
- case SEARCH_SUGGEST: {
- final SelectionBuilder builder = new SelectionBuilder();
-
- // Adjust incoming query to become SQL text match
- selectionArgs[0] = selectionArgs[0] + "%";
- builder.table(Tables.SEARCH_SUGGEST);
- builder.where(selection, selectionArgs);
- builder.map(SearchManager.SUGGEST_COLUMN_QUERY,
- SearchManager.SUGGEST_COLUMN_TEXT_1);
-
- projection = new String[] {
- BaseColumns._ID,
- SearchManager.SUGGEST_COLUMN_TEXT_1,
- SearchManager.SUGGEST_COLUMN_QUERY
- };
-
- final String limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT);
- return builder.query(db, false, projection, SearchSuggest.DEFAULT_SORT, limit);
- }
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- LOGV(TAG, "insert(uri=" + uri + ", values=" + values.toString()
- + ", account=" + getCurrentAccountName(uri, false) + ")");
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- final int match = sUriMatcher.match(uri);
- boolean syncToNetwork = !ScheduleContract.hasCallerIsSyncAdapterParameter(uri);
- switch (match) {
- case BLOCKS: {
- db.insertOrThrow(Tables.BLOCKS, null, values);
- notifyChange(uri);
- return Blocks.buildBlockUri(values.getAsString(Blocks.BLOCK_ID));
- }
- case TAGS: {
- db.insertOrThrow(Tables.TAGS, null, values);
- notifyChange(uri);
- return Tags.buildTagUri(values.getAsString(Tags.TAG_ID));
- }
- case ROOMS: {
- db.insertOrThrow(Tables.ROOMS, null, values);
- notifyChange(uri);
- return Rooms.buildRoomUri(values.getAsString(Rooms.ROOM_ID));
- }
- case SESSIONS: {
- db.insertOrThrow(Tables.SESSIONS, null, values);
- notifyChange(uri);
- return Sessions.buildSessionUri(values.getAsString(Sessions.SESSION_ID));
- }
- case SESSIONS_ID_SPEAKERS: {
- db.insertOrThrow(Tables.SESSIONS_SPEAKERS, null, values);
- notifyChange(uri);
- return Speakers.buildSpeakerUri(values.getAsString(SessionsSpeakers.SPEAKER_ID));
- }
- case SESSIONS_ID_TAGS: {
- db.insertOrThrow(Tables.SESSIONS_TAGS, null, values);
- notifyChange(uri);
- return Tags.buildTagUri(values.getAsString(Tags.TAG_ID));
- }
- case MY_SCHEDULE: {
- values.put(MySchedule.MY_SCHEDULE_ACCOUNT_NAME, getCurrentAccountName(uri, false));
- db.insertOrThrow(Tables.MY_SCHEDULE, null, values);
- notifyChange(uri);
- return Sessions.buildSessionUri(values.getAsString(
- ScheduleContract.MyScheduleColumns.SESSION_ID));
- }
- case SPEAKERS: {
- db.insertOrThrow(Tables.SPEAKERS, null, values);
- notifyChange(uri);
- return Speakers.buildSpeakerUri(values.getAsString(Speakers.SPEAKER_ID));
- }
- case ANNOUNCEMENTS: {
- db.insertOrThrow(Tables.ANNOUNCEMENTS, null, values);
- notifyChange(uri);
- return Announcements.buildAnnouncementUri(values
- .getAsString(Announcements.ANNOUNCEMENT_ID));
- }
- case SEARCH_SUGGEST: {
- db.insertOrThrow(Tables.SEARCH_SUGGEST, null, values);
- notifyChange(uri);
- return SearchSuggest.CONTENT_URI;
- }
- case MAPMARKERS: {
- db.insertOrThrow(Tables.MAPMARKERS, null, values);
- notifyChange(uri);
- return MapMarkers.buildMarkerUri(values.getAsString(MapMarkers.MARKER_ID));
- }
- case MAPTILES: {
- db.insertOrThrow(Tables.MAPTILES, null, values);
- notifyChange(uri);
- return MapTiles.buildFloorUri(values.getAsString(MapTiles.TILE_FLOOR));
- }
- case FEEDBACK_FOR_SESSION: {
- db.insertOrThrow(Tables.FEEDBACK, null, values);
- notifyChange(uri);
- return Feedback.buildFeedbackUri(values.getAsString(Feedback.SESSION_ID));
- }
- case EXPERTS: {
- db.insertOrThrow(Tables.EXPERTS, null, values);
- notifyChange(uri);
- return Experts.buildExpertUri(values.getAsString(Experts.EXPERT_ID));
- }
- case HASHTAGS: {
- db.insertOrThrow(Tables.HASHTAGS, null, values);
- notifyChange(uri);
- return Hashtags.buildHashtagUri(values.getAsString(Hashtags.HASHTAG_NAME));
- }
- case PEOPLE_IVE_MET: {
- db.insertOrThrow(Tables.PEOPLE_IVE_MET, null, values);
- notifyChange(uri);
- return ScheduleContract.PeopleIveMet.buildPersonUri(values.getAsString(PeopleIveMet.PERSON_ID));
- }
- case VIDEOS: {
- db.insertOrThrow(Tables.VIDEOS, null, values);
- notifyChange(uri);
- return Videos.buildVideoUri(values.getAsString(Videos.VIDEO_ID));
- }
- case PARTNERS: {
- db.insertOrThrow(Tables.PARTNERS, null, values);
- notifyChange(uri);
- return Partners.buildPartnerUri(values.getAsString(Partners.PARTNER_ID));
- }
- default: {
- throw new UnsupportedOperationException("Unknown insert uri: " + uri);
- }
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- String accountName = getCurrentAccountName(uri, false);
- LOGV(TAG, "update(uri=" + uri + ", values=" + values.toString()
- + ", account=" + accountName + ")");
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- final int match = sUriMatcher.match(uri);
- if (match == SEARCH_INDEX) {
- // update the search index
- ScheduleDatabase.updateSessionSearchIndex(db);
- return 1;
- }
-
- final SelectionBuilder builder = buildSimpleSelection(uri);
- if (match == MY_SCHEDULE) {
- values.remove(MySchedule.MY_SCHEDULE_ACCOUNT_NAME);
- builder.where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", accountName);
- }
- int retVal = builder.where(selection, selectionArgs).update(db, values);
- notifyChange(uri);
- return retVal;
- }
-
- /** {@inheritDoc} */
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- String accountName = getCurrentAccountName(uri, false);
- LOGV(TAG, "delete(uri=" + uri + ", account=" + accountName + ")");
- if (uri == ScheduleContract.BASE_CONTENT_URI) {
- // Handle whole database deletes (e.g. when signing out)
- deleteDatabase();
- notifyChange(uri);
- return 1;
- }
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- final SelectionBuilder builder = buildSimpleSelection(uri);
- final int match = sUriMatcher.match(uri);
- if (match == MY_SCHEDULE) {
- builder.where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", accountName);
- }
- int retVal = builder.where(selection, selectionArgs).delete(db);
- notifyChange(uri);
- return retVal;
- }
-
- private void notifyChange(Uri uri) {
- // We only notify changes if the caller is not the sync adapter.
- // The sync adapter has the responsibility of notifying changes (it can do so
- // more intelligently than we can -- for example, doing it only once at the end
- // of the sync instead of issuing thousands of notifications for each record).
- if (!ScheduleContract.hasCallerIsSyncAdapterParameter(uri)) {
- Context context = getContext();
- context.getContentResolver().notifyChange(uri, null);
-
- // Widgets can't register content observers so we refresh widgets separately.
- context.sendBroadcast(ScheduleWidgetProvider.getRefreshBroadcastIntent(context, false));
- }
- }
-
- /**
- * Apply the given set of {@link ContentProviderOperation}, executing inside
- * a {@link SQLiteDatabase} transaction. All changes will be rolled back if
- * any single one fails.
- */
- @Override
- public ContentProviderResult[] applyBatch(ArrayList operations)
- throws OperationApplicationException {
- final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- final int numOperations = operations.size();
- final ContentProviderResult[] results = new ContentProviderResult[numOperations];
- for (int i = 0; i < numOperations; i++) {
- results[i] = operations.get(i).apply(this, results, i);
- }
- db.setTransactionSuccessful();
- return results;
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * Build a simple {@link SelectionBuilder} to match the requested
- * {@link Uri}. This is usually enough to support {@link #insert},
- * {@link #update}, and {@link #delete} operations.
- */
- private SelectionBuilder buildSimpleSelection(Uri uri) {
- final SelectionBuilder builder = new SelectionBuilder();
- final int match = sUriMatcher.match(uri);
- switch (match) {
- case BLOCKS: {
- return builder.table(Tables.BLOCKS);
- }
- case BLOCKS_ID: {
- final String blockId = Blocks.getBlockId(uri);
- return builder.table(Tables.BLOCKS)
- .where(Blocks.BLOCK_ID + "=?", blockId);
- }
- case TAGS: {
- return builder.table(Tables.TAGS);
- }
- case TAGS_ID: {
- final String tagId = Tags.getTagId(uri);
- return builder.table(Tables.TAGS)
- .where(Tags.TAG_ID + "=?", tagId);
- }
- case ROOMS: {
- return builder.table(Tables.ROOMS);
- }
- case ROOMS_ID: {
- final String roomId = Rooms.getRoomId(uri);
- return builder.table(Tables.ROOMS)
- .where(Rooms.ROOM_ID + "=?", roomId);
- }
- case SESSIONS: {
- return builder.table(Tables.SESSIONS);
- }
- case SESSIONS_ID: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS)
- .where(Sessions.SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_ID_SPEAKERS: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS_SPEAKERS)
- .where(Sessions.SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_ID_TAGS: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS_TAGS)
- .where(Sessions.SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_MY_SCHEDULE: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.MY_SCHEDULE)
- .where(ScheduleContract.MyScheduleColumns.SESSION_ID + "=?", sessionId);
- }
- case MY_SCHEDULE: {
- return builder.table(Tables.MY_SCHEDULE)
- .where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", getCurrentAccountName(uri, false));
- }
- case SPEAKERS: {
- return builder.table(Tables.SPEAKERS);
- }
- case SPEAKERS_ID: {
- final String speakerId = Speakers.getSpeakerId(uri);
- return builder.table(Tables.SPEAKERS)
- .where(Speakers.SPEAKER_ID + "=?", speakerId);
- }
- case ANNOUNCEMENTS: {
- return builder.table(Tables.ANNOUNCEMENTS);
- }
- case ANNOUNCEMENTS_ID: {
- final String announcementId = Announcements.getAnnouncementId(uri);
- return builder.table(Tables.ANNOUNCEMENTS)
- .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId);
- }
- case MAPMARKERS: {
- return builder.table(Tables.MAPMARKERS);
- }
- case MAPMARKERS_FLOOR: {
- final String floor = MapMarkers.getMarkerFloor(uri);
- return builder.table(Tables.MAPMARKERS)
- .where(MapMarkers.MARKER_FLOOR+ "=?", floor);
- }
- case MAPMARKERS_ID: {
- final String markerId = MapMarkers.getMarkerId(uri);
- return builder.table(Tables.MAPMARKERS)
- .where(MapMarkers.MARKER_ID + "=?", markerId);
- }
- case MAPTILES: {
- return builder.table(Tables.MAPTILES);
- }
- case MAPTILES_FLOOR: {
- final String floor = MapTiles.getFloorId(uri);
- return builder.table(Tables.MAPTILES)
- .where(MapTiles.TILE_FLOOR+ "=?", floor);
- }
- case SEARCH_SUGGEST: {
- return builder.table(Tables.SEARCH_SUGGEST);
- }
- case FEEDBACK_FOR_SESSION: {
- final String session_id = Feedback.getSessionId(uri);
- return builder.table(Tables.FEEDBACK)
- .where(Feedback.SESSION_ID + "=?", session_id);
- }
- case FEEDBACK_ALL: {
- return builder.table(Tables.FEEDBACK);
- }
- case EXPERTS: {
- return builder.table(Tables.EXPERTS);
- }
- case EXPERTS_ID: {
- String expertId = Experts.getExpertId(uri);
- return builder.table(Tables.EXPERTS)
- .where(Experts.EXPERT_ID + "= ?", expertId);
- }
- case HASHTAGS: {
- return builder.table(Tables.HASHTAGS);
- }
- case HASHTAGS_NAME: {
- final String hashtagName = Hashtags.getHashtagName(uri);
- return builder.table(Tables.HASHTAGS)
- .where(Hashtags.HASHTAG_NAME + "=?", hashtagName);
- }
- case PEOPLE_IVE_MET: {
- return builder.table(Tables.PEOPLE_IVE_MET);
- }
- case PEOPLE_IVE_MET_ID: {
- String personId = ScheduleContract.PeopleIveMet.getPersonId(uri);
- return builder.table(Tables.PEOPLE_IVE_MET)
- .where(PeopleIveMet.PERSON_ID + "=?", personId);
- }
- case VIDEOS: {
- return builder.table(Tables.VIDEOS);
- }
- case VIDEOS_ID: {
- final String videoId = Videos.getVideoId(uri);
- return builder.table(Tables.VIDEOS).where(Videos.VIDEO_ID + "=?", videoId);
- }
- case PARTNERS: {
- return builder.table(Tables.PARTNERS);
- }
- case PARTNERS_ID: {
- final String partnerId = Partners.getPartnerId(uri);
- return builder.table(Tables.PARTNERS).where(Partners.PARTNER_ID + "=?", partnerId);
- }
- default: {
- throw new UnsupportedOperationException("Unknown uri for " + match + ": " + uri);
- }
- }
- }
-
- private String getCurrentAccountName(Uri uri, boolean sanitize) {
- String accountName = ScheduleContract.getOverrideAccountName(uri);
- if (accountName == null) {
- accountName = AccountUtils.getActiveAccountName(getContext());
- }
- if (sanitize) {
- // sanitize accountName when concatenating (http://xkcd.com/327/)
- accountName = (accountName != null) ? accountName.replace("'", "''") : null;
- }
- return accountName;
- }
-
- /**
- * Build an advanced {@link SelectionBuilder} to match the requested
- * {@link Uri}. This is usually only used by {@link #query}, since it
- * performs table joins useful for {@link Cursor} data.
- */
- private SelectionBuilder buildExpandedSelection(Uri uri, int match) {
- final SelectionBuilder builder = new SelectionBuilder();
- switch (match) {
- case BLOCKS: {
- return builder.table(Tables.BLOCKS);
- }
- case BLOCKS_BETWEEN: {
- final List segments = uri.getPathSegments();
- final String startTime = segments.get(2);
- final String endTime = segments.get(3);
- return builder.table(Tables.BLOCKS)
- .where(Blocks.BLOCK_START + ">=?", startTime)
- .where(Blocks.BLOCK_START + "<=?", endTime);
- }
- case BLOCKS_ID: {
- final String blockId = Blocks.getBlockId(uri);
- return builder.table(Tables.BLOCKS)
- .where(Blocks.BLOCK_ID + "=?", blockId);
- }
- case TAGS: {
- return builder.table(Tables.TAGS);
- }
- case TAGS_ID: {
- final String tagId = Tags.getTagId(uri);
- return builder.table(Tables.TAGS)
- .where(Tags.TAG_ID + "=?", tagId);
- }
- case ROOMS: {
- return builder.table(Tables.ROOMS);
- }
- case ROOMS_ID: {
- final String roomId = Rooms.getRoomId(uri);
- return builder.table(Tables.ROOMS)
- .where(Rooms.ROOM_ID + "=?", roomId);
- }
- case ROOMS_ID_SESSIONS: {
- final String roomId = Rooms.getRoomId(uri);
- return builder.table(Tables.SESSIONS_JOIN_ROOMS)
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .where(Qualified.SESSIONS_ROOM_ID + "=?", roomId);
- }
- case SESSIONS: {
- // We query sessions on the joined table of sessions with rooms and tags.
- // Since there may be more than one tag per session, we GROUP BY session ID.
- // The starred sessions ("my schedule") are associated with a user, so we
- // use the current user to select them properly
- return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .groupBy(Qualified.SESSIONS_SESSION_ID);
- }
- case SESSIONS_COUNTER: {
- return builder.table(Tables.SESSIONS_JOIN_MYSCHEDULE, getCurrentAccountName(uri, true))
- .map(Sessions.SESSION_INTERVAL_COUNT, "count(1)")
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .groupBy(Sessions.SESSION_START + ", " + Sessions.SESSION_END);
- }
- case SESSIONS_MY_SCHEDULE: {
- return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .map(Sessions.HAS_GIVEN_FEEDBACK, Subquery.SESSION_HAS_GIVEN_FEEDBACK)
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .where("( " + Sessions.SESSION_IN_MY_SCHEDULE + "=1 OR " +
- Sessions.SESSION_TAGS +
- " LIKE '%" + Config.Tags.SPECIAL_KEYNOTE + "%' )")
- .groupBy(Qualified.SESSIONS_SESSION_ID);
- }
- case SESSIONS_UNSCHEDULED: {
- final long[] interval = Sessions.getInterval(uri);
- return builder.table(Tables.SESSIONS_JOIN_ROOMS_TAGS_FEEDBACK_MYSCHEDULE, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .where(Sessions.SESSION_IN_MY_SCHEDULE + "=0")
- .where(Sessions.SESSION_START + ">=?", String.valueOf(interval[0]))
- .where(Sessions.SESSION_START + "", String.valueOf(interval[1]))
- .groupBy(Qualified.SESSIONS_SESSION_ID);
- }
- case SESSIONS_SEARCH: {
- final String query = Sessions.getSearchQuery(uri);
- return builder.table(Tables.SESSIONS_SEARCH_JOIN_SESSIONS_ROOMS, getCurrentAccountName(uri, true))
- .map(Sessions.SEARCH_SNIPPET, Subquery.SESSIONS_SNIPPET)
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .where(SessionsSearchColumns.BODY + " MATCH ?", query);
- }
- case SESSIONS_AT: {
- final List segments = uri.getPathSegments();
- final String time = segments.get(2);
- return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .where(Sessions.SESSION_START + "<=?", time)
- .where(Sessions.SESSION_END + ">=?", time);
- }
- case SESSIONS_ID: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .map(Sessions.SESSION_IN_MY_SCHEDULE, "IFNULL(in_schedule, 0)")
- .where(Qualified.SESSIONS_SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_ID_SPEAKERS: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SPEAKERS)
- .mapToTable(Speakers._ID, Tables.SPEAKERS)
- .mapToTable(Speakers.SPEAKER_ID, Tables.SPEAKERS)
- .where(Qualified.SESSIONS_SPEAKERS_SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_ID_TAGS: {
- final String sessionId = Sessions.getSessionId(uri);
- return builder.table(Tables.SESSIONS_TAGS_JOIN_TAGS)
- .mapToTable(Tags._ID, Tables.TAGS)
- .mapToTable(Tags.TAG_ID, Tables.TAGS)
- .where(Qualified.SESSIONS_TAGS_SESSION_ID + "=?", sessionId);
- }
- case SESSIONS_ROOM_AFTER: {
- final String room = Sessions.getRoom(uri);
- final String time = Sessions.getAfter(uri);
- return builder.table(Tables.SESSIONS_JOIN_ROOMS, getCurrentAccountName(uri, true))
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .where(Qualified.SESSIONS_ROOM_ID+ "=?", room)
- .where("("+Sessions.SESSION_START + "<= ? AND "+Sessions.SESSION_END+
- " >= ?) OR ("+Sessions.SESSION_START + " >= ?)", time,time,time);
- }
- case SPEAKERS: {
- return builder.table(Tables.SPEAKERS);
- }
- case MY_SCHEDULE: {
- // force a where condition to avoid leaking schedule info to another account
- // Note that, since SelectionBuilder always join multiple where calls using AND,
- // even if malicious code specifying additional conditions on account_name won't
- // be able to fetch data from a different account.
- return builder.table(Tables.MY_SCHEDULE)
- .where(MySchedule.MY_SCHEDULE_ACCOUNT_NAME + "=?", getCurrentAccountName(uri, true));
- }
- case SPEAKERS_ID: {
- final String speakerId = Speakers.getSpeakerId(uri);
- return builder.table(Tables.SPEAKERS)
- .where(Speakers.SPEAKER_ID + "=?", speakerId);
- }
- case SPEAKERS_ID_SESSIONS: {
- final String speakerId = Speakers.getSpeakerId(uri);
- return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SESSIONS_ROOMS)
- .mapToTable(Sessions._ID, Tables.SESSIONS)
- .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS)
- .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS)
- .where(Qualified.SESSIONS_SPEAKERS_SPEAKER_ID + "=?", speakerId);
- }
- case ANNOUNCEMENTS: {
- return builder.table(Tables.ANNOUNCEMENTS);
- }
- case ANNOUNCEMENTS_ID: {
- final String announcementId = Announcements.getAnnouncementId(uri);
- return builder.table(Tables.ANNOUNCEMENTS)
- .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId);
- }
- case MAPMARKERS: {
- return builder.table(Tables.MAPMARKERS);
- }
- case MAPMARKERS_FLOOR: {
- final String floor = MapMarkers.getMarkerFloor(uri);
- return builder.table(Tables.MAPMARKERS)
- .where(MapMarkers.MARKER_FLOOR + "=?", floor);
- }
- case MAPMARKERS_ID: {
- final String roomId = MapMarkers.getMarkerId(uri);
- return builder.table(Tables.MAPMARKERS)
- .where(MapMarkers.MARKER_ID + "=?", roomId);
- }
- case MAPTILES: {
- return builder.table(Tables.MAPTILES);
- }
- case MAPTILES_FLOOR: {
- final String floor = MapTiles.getFloorId(uri);
- return builder.table(Tables.MAPTILES)
- .where(MapTiles.TILE_FLOOR + "=?", floor);
- }
- case FEEDBACK_FOR_SESSION: {
- final String sessionId = Feedback.getSessionId(uri);
- return builder.table(Tables.FEEDBACK)
-
- .where(Feedback.SESSION_ID + "=?", sessionId);
- }
- case FEEDBACK_ALL: {
- return builder.table(Tables.FEEDBACK);
- }
- case EXPERTS: {
- return builder.table(Tables.EXPERTS);
- }
- case EXPERTS_ID: {
- String expertId = Experts.getExpertId(uri);
- return builder.table(Tables.EXPERTS)
- .where(Experts.EXPERT_ID + "= ?", expertId);
- }
- case HASHTAGS: {
- return builder.table(Tables.HASHTAGS);
- }
- case HASHTAGS_NAME: {
- final String hashtagName = Hashtags.getHashtagName(uri);
- return builder.table(Tables.HASHTAGS)
- .where(HashtagColumns.HASHTAG_NAME + "=?", hashtagName);
- }
- case PEOPLE_IVE_MET: {
- return builder.table(Tables.PEOPLE_IVE_MET);
- }
- case PEOPLE_IVE_MET_ID: {
- String personId = ScheduleContract.PeopleIveMet.getPersonId(uri);
- return builder.table(Tables.PEOPLE_IVE_MET)
- .where(PeopleIveMet.PERSON_ID + "=?", personId);
- }
- case VIDEOS: {
- return builder.table(Tables.VIDEOS);
- }
- case VIDEOS_ID: {
- final String videoId = Videos.getVideoId(uri);
- return builder.table(Tables.VIDEOS)
- .where(VideoColumns.VIDEO_ID + "=?", videoId);
- }
- case PARTNERS: {
- return builder.table(Tables.PARTNERS);
- }
- case PARTNERS_ID: {
- final String partnerId = Partners.getPartnerId(uri);
- return builder.table(Tables.PARTNERS).where(Partners.PARTNER_ID + "=?", partnerId);
- }
- default: {
- throw new UnsupportedOperationException("Unknown uri: " + uri);
- }
- }
- }
-
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- final int match = sUriMatcher.match(uri);
- switch (match) {
- default: {
- throw new UnsupportedOperationException("Unknown uri: " + uri);
- }
- }
- }
-
- private interface Subquery {
-
- String SESSION_HAS_GIVEN_FEEDBACK = "(SELECT COUNT(1) FROM "
- + Tables.FEEDBACK + " WHERE " + Qualified.FEEDBACK_SESSION_ID + "="
- + Qualified.SESSIONS_SESSION_ID + ")";
- String SESSIONS_SNIPPET = "snippet(" + Tables.SESSIONS_SEARCH + ",'{','}','\u2026')";
- }
-
- /**
- * {@link ScheduleContract} fields that are fully qualified with a specific
- * parent {@link Tables}. Used when needed to work around SQL ambiguity.
- */
- private interface Qualified {
- String SESSIONS_SESSION_ID = Tables.SESSIONS + "." + Sessions.SESSION_ID;
- String SESSIONS_ROOM_ID = Tables.SESSIONS + "." + Sessions.ROOM_ID;
-
- String SESSIONS_TAGS_SESSION_ID = Tables.SESSIONS_TAGS + "."
- + ScheduleDatabase.SessionsTags.SESSION_ID;
-
- String SESSIONS_SPEAKERS_SESSION_ID = Tables.SESSIONS_SPEAKERS + "."
- + SessionsSpeakers.SESSION_ID;
- String SESSIONS_SPEAKERS_SPEAKER_ID = Tables.SESSIONS_SPEAKERS + "."
- + SessionsSpeakers.SPEAKER_ID;
- String FEEDBACK_SESSION_ID = Tables.FEEDBACK + "." + Feedback.SESSION_ID;
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java b/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java
deleted file mode 100644
index 79f5d7bbc1..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/receiver/SessionAlarmReceiver.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.receiver;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import com.google.samples.apps.iosched.service.SessionAlarmService;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * {@link BroadcastReceiver} to reinitialize {@link android.app.AlarmManager} for all starred
- * session blocks.
- */
-public class SessionAlarmReceiver extends BroadcastReceiver {
- public static final String TAG = makeLogTag(SessionAlarmReceiver.class);
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Intent scheduleIntent = new Intent(
- SessionAlarmService.ACTION_SCHEDULE_ALL_STARRED_BLOCKS,
- null, context, SessionAlarmService.class);
- context.startService(scheduleIntent);
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java b/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java
deleted file mode 100644
index 3c15dec5bb..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/service/FeedbackListenerService.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.service;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.wearable.DataApi;
-import com.google.android.gms.wearable.DataEvent;
-import com.google.android.gms.wearable.DataEventBuffer;
-import com.google.android.gms.wearable.DataMap;
-import com.google.android.gms.wearable.DataMapItem;
-import com.google.android.gms.wearable.PutDataMapRequest;
-import com.google.android.gms.wearable.Wearable;
-import com.google.android.gms.wearable.WearableListenerService;
-import com.google.samples.apps.iosched.util.FeedbackUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.app.Service;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.NotificationManagerCompat;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.concurrent.TimeUnit;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.LOGE;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * A {@link com.google.android.gms.wearable.WearableListenerService} service to receive the session
- * feedback from the wearable device and handle dismissal of notifications by deleting the
- * associated Data Items.
- */
-public class FeedbackListenerService extends WearableListenerService
- implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
-
- private static final String TAG = makeLogTag(FeedbackListenerService.class);
- public static final String PATH_RESPONSE = "/iowear/response";
- private GoogleApiClient mGoogleApiClient;
- private boolean mConnected = false;
- private static final long TIMEOUT_S = 10; // how long wait for APi Client connection, in seconds
-
- @Override
- public void onCreate() {
- super.onCreate();
- mGoogleApiClient = new GoogleApiClient.Builder(this)
- .addApi(Wearable.API)
- .addConnectionCallbacks(this)
- .addOnConnectionFailedListener(this)
- .build();
- mGoogleApiClient.connect();
- }
-
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (null != intent) {
- String action = intent.getAction();
- if (SessionAlarmService.ACTION_NOTIFICATION_DISMISSAL.equals(action)) {
- String sessionId = intent.getStringExtra(SessionAlarmService.KEY_SESSION_ID);
- LOGD(TAG, "onStartCommand(): Action = ACTION_NOTIFICATION_DISMISSAL Session: "
- + sessionId);
- dismissWearableNotification(sessionId);
- }
- }
- return Service.START_NOT_STICKY;
- }
-
- /**
- * Removes the Data Item that was used to create a notification on the watch. By deleting the
- * data item, a {@link com.google.android.gms.wearable.WearableListenerService} on the watch
- * will be notified and the notification on the watch will be removed.
- *
- * Since connection to the Google API client is asynchronous, we spawn a thread and wait for
- * the connection to be established before attempting to use the Google API client.
- *
- * @param sessionId The Session ID of the notification that should be removed
- */
- private void dismissWearableNotification(final String sessionId) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- if (!mConnected) {
- mGoogleApiClient.blockingConnect(TIMEOUT_S, TimeUnit.SECONDS);
- }
- if (!mConnected) {
- Log.e(TAG, "Failed to connect to mGoogleApiClient within " + TIMEOUT_S
- + " seconds");
- return;
- }
- LOGD(TAG, "dismissWearableNotification(): Attempting to dismiss wearable "
- + "notification");
- PutDataMapRequest putDataMapRequest = PutDataMapRequest
- .create(FeedbackUtils.getFeedbackPath(sessionId));
- if (mGoogleApiClient.isConnected()) {
- Wearable.DataApi.deleteDataItems(mGoogleApiClient, putDataMapRequest.getUri())
- .setResultCallback(new ResultCallback() {
- @Override
- public void onResult(
- DataApi.DeleteDataItemsResult deleteDataItemsResult) {
- if (!deleteDataItemsResult.getStatus().isSuccess()) {
- LOGD(TAG, "dismissWearableNotification(): failed to delete"
- + " the data item");
- }
- }
- });
- } else {
- Log.e(TAG, "dismissWearableNotification()): No Google API Client connection");
- }
- }
- }).start();
- }
-
- @Override
- public void onDataChanged(DataEventBuffer dataEvents) {
- LOGD(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName());
-
- for (DataEvent event : dataEvents) {
- LOGD(TAG, "Uri is: " + event.getDataItem().getUri());
- DataMapItem mapItem = DataMapItem.fromDataItem(event.getDataItem());
- String path = event.getDataItem().getUri().getPath();
- if (event.getType() == DataEvent.TYPE_CHANGED) {
- if (PATH_RESPONSE.equals(path)) {
- // we have a response
- DataMap data = mapItem.getDataMap();
- String jsonString = data.getString("response");
- if (TextUtils.isEmpty(jsonString)) {
- return;
- }
- LOGD(TAG, "jsonString is: " + jsonString);
- saveFeedback(jsonString);
- }
- } else if (event.getType() == DataEvent.TYPE_DELETED) {
- if (path.startsWith(SessionAlarmService.PATH_FEEDBACK)) {
- Uri uri = event.getDataItem().getUri();
- dismissLocalNotification(uri.getLastPathSegment());
- }
- }
- }
- }
-
- /**
- * Dismisses the local notification for the given session
- */
- private void dismissLocalNotification(String sessionId) {
- LOGD(TAG, "dismissLocalNotification: sessionId=" + sessionId);
- NotificationManagerCompat.from(this)
- .cancel(sessionId, SessionAlarmService.FEEDBACK_NOTIFICATION_ID);
- }
-
- /**
- * Persisting the feedback in the database. The input is the JSON string that represents the
- * response from the user on the paired wear device. The format of a typical response is:
- * [{"s":"sessionId-1234"},{"q":1,"a":2},{"q":0,"a":1},{"q":3,"a":1},{"q":2,"a":1}]
- */
- private void saveFeedback(String jsonString) {
- try {
- JSONArray jsonArray = new JSONArray(jsonString);
- if (null != jsonArray) {
- JSONObject sessionObj = (JSONObject) jsonArray.get(0);
- String sessionId = sessionObj.getString("s");
- StringBuffer result = new StringBuffer("Session Id: " + sessionId + "\n");
- int[] answers = new int[4];
- for (int i = 0; i < answers.length; i++) {
- answers[i] = -1;
- }
- for (int i = 1; i < jsonArray.length(); i++) {
- JSONObject answerObj = (JSONObject) jsonArray.get(i);
- int question = answerObj.getInt("q");
- int answer = answerObj.getInt("a") + 1;
- answers[question] = answer;
- result.append("Question: " + question + " ---> Answer: " + answer + "\n");
- }
- LOGD(TAG, "Feedback answers received from the wear: " + result.toString());
- FeedbackUtils.saveSessionFeedback(this, sessionId, answers[0], answers[1],
- answers[2], answers[3], null);
- }
-
- } catch (JSONException e) {
- LOGE(TAG, "Failed to parse the json received from the wear", e);
- }
- }
-
- @Override
- public void onConnected(Bundle bundle) {
- mConnected = true;
- }
-
- @Override
- public void onConnectionSuspended(int i) {
- mConnected = false;
- }
-
- @Override
- public void onConnectionFailed(ConnectionResult connectionResult) {
- Log.e(TAG, "Failed to connect to the Google API client");
- mConnected = false;
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java b/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java
deleted file mode 100644
index 948e97f88f..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/service/SessionAlarmService.java
+++ /dev/null
@@ -1,669 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.service;
-
-import android.app.*;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.TaskStackBuilder;
-import android.util.Log;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.wearable.DataApi;
-import com.google.android.gms.wearable.PutDataMapRequest;
-import com.google.android.gms.wearable.PutDataRequest;
-import com.google.android.gms.wearable.Wearable;
-import com.google.samples.apps.iosched.R;
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.ui.BrowseSessionsActivity;
-import com.google.samples.apps.iosched.ui.MapFragment;
-import com.google.samples.apps.iosched.ui.MyScheduleActivity;
-import com.google.samples.apps.iosched.ui.SessionFeedbackActivity;
-import com.google.samples.apps.iosched.ui.phone.MapActivity;
-import com.google.samples.apps.iosched.util.FeedbackUtils;
-import com.google.samples.apps.iosched.util.PrefUtils;
-import com.google.samples.apps.iosched.util.UIUtils;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * Background service to handle scheduling of starred session notification via
- * {@link android.app.AlarmManager}.
- */
-public class SessionAlarmService extends IntentService
- implements GoogleApiClient.ConnectionCallbacks,
- GoogleApiClient.OnConnectionFailedListener {
-
- private static final String TAG = makeLogTag(SessionAlarmService.class);
-
- public static final String ACTION_NOTIFY_SESSION =
- "com.google.samples.apps.iosched.action.NOTIFY_SESSION";
- public static final String ACTION_NOTIFY_SESSION_FEEDBACK =
- "com.google.samples.apps.iosched.action.NOTIFY_SESSION_FEEDBACK";
- public static final String ACTION_SCHEDULE_FEEDBACK_NOTIFICATION =
- "com.google.samples.apps.iosched.action.SCHEDULE_FEEDBACK_NOTIFICATION";
- public static final String ACTION_SCHEDULE_STARRED_BLOCK =
- "com.google.samples.apps.iosched.action.SCHEDULE_STARRED_BLOCK";
- public static final String ACTION_SCHEDULE_ALL_STARRED_BLOCKS =
- "com.google.samples.apps.iosched.action.SCHEDULE_ALL_STARRED_BLOCKS";
- public static final String EXTRA_SESSION_START =
- "com.google.samples.apps.iosched.extra.SESSION_START";
- public static final String EXTRA_SESSION_END =
- "com.google.samples.apps.iosched.extra.SESSION_END";
- public static final String EXTRA_SESSION_ALARM_OFFSET =
- "com.google.samples.apps.iosched.extra.SESSION_ALARM_OFFSET";
- public static final String EXTRA_SESSION_ID =
- "com.google.samples.apps.iosched.extra.SESSION_ID";
- public static final String EXTRA_SESSION_TITLE =
- "com.google.samples.apps.iosched.extra.SESSION_TITLE";
- public static final String EXTRA_SESSION_ROOM =
- "com.google.samples.apps.iosched.extra.SESSION_ROOM";
- public static final String EXTRA_SESSION_SPEAKERS =
- "com.google.samples.apps.iosched.extra.SESSION_SPEAKERS";
-
- public static final int NOTIFICATION_ID = 100;
- public static final int FEEDBACK_NOTIFICATION_ID = 101;
-
- // pulsate every 1 second, indicating a relatively high degree of urgency
- private static final int NOTIFICATION_LED_ON_MS = 100;
- private static final int NOTIFICATION_LED_OFF_MS = 1000;
- private static final int NOTIFICATION_ARGB_COLOR = 0xff0088ff; // cyan
-
- private static final long MILLI_TEN_MINUTES = 600000;
- private static final long MILLI_FIVE_MINUTES = 300000;
- private static final long MILLI_ONE_MINUTE = 60000;
-
- private static final long UNDEFINED_ALARM_OFFSET = -1;
- private static final long UNDEFINED_VALUE = -1;
- public static final String ACTION_NOTIFICATION_DISMISSAL
- = "com.google.sample.apps.iosched.ACTION_NOTIFICATION_DISMISSAL";
- private GoogleApiClient mGoogleApiClient;
- public static final String KEY_SESSION_ID = "session-id";
- private static final String KEY_SESSION_NAME = "session-name";
- private static final String KEY_SPEAKER_NAME = "speaker-name";
- private static final String KEY_SESSION_ROOM = "session-room";
- public static final String PATH_FEEDBACK = "/iowear/feedback";
-
- // special session ID that identifies a debug notification
- public static final String DEBUG_SESSION_ID = "debug-session-id";
-
- public SessionAlarmService() {
- super(TAG);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mGoogleApiClient = new GoogleApiClient.Builder(this)
- .addApi(Wearable.API)
- .addConnectionCallbacks(this)
- .addOnConnectionFailedListener(this)
- .build();
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- mGoogleApiClient.blockingConnect(2000, TimeUnit.MILLISECONDS);
- final String action = intent.getAction();
-
- LOGD(TAG, "SessionAlarmService handling " + action);
-
- if (ACTION_SCHEDULE_ALL_STARRED_BLOCKS.equals(action)) {
- LOGD(TAG, "Scheduling all starred blocks.");
- scheduleAllStarredBlocks();
- scheduleAllStarredSessionFeedbacks();
- return;
- }
-
- final long sessionEnd = intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_END,
- UNDEFINED_VALUE);
- if (sessionEnd == UNDEFINED_VALUE) {
- LOGD(TAG, "IGNORING ACTION -- missing sessionEnd parameter");
- return;
- }
-
- final long sessionAlarmOffset =
- intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET,
- UNDEFINED_ALARM_OFFSET);
- LOGD(TAG, "Session alarm offset is: " + sessionAlarmOffset);
-
- // Feedback notifications have a slightly different set of extras.
- if (ACTION_SCHEDULE_FEEDBACK_NOTIFICATION.equals(action) ||
- ACTION_NOTIFY_SESSION_FEEDBACK.equals(action)) {
- final String sessionId = intent.getStringExtra(SessionAlarmService.EXTRA_SESSION_ID);
- final String sessionTitle = intent.getStringExtra(
- SessionAlarmService.EXTRA_SESSION_TITLE);
- final String sessionRoom = intent.getStringExtra(
- SessionAlarmService.EXTRA_SESSION_ROOM);
- final String sessionSpeakers = intent.getStringExtra(
- SessionAlarmService.EXTRA_SESSION_SPEAKERS);
- if (sessionTitle == null || sessionEnd == UNDEFINED_VALUE ||
- sessionId == null) {
- Log.e(TAG,
- "Attempted to schedule or notify for feedback without providing extras.");
- return;
- }
- if (ACTION_SCHEDULE_FEEDBACK_NOTIFICATION.equals(action)) {
- LOGD(TAG, "Scheduling feedback alarm for session: " + sessionTitle);
- scheduleFeedbackAlarm(sessionId, sessionEnd, sessionAlarmOffset, sessionTitle,
- sessionRoom, sessionSpeakers);
- } else {
- LOGD(TAG, "Notifying for feedback on session: " + sessionTitle);
- notifySessionFeedback(sessionId, sessionEnd, sessionTitle, sessionRoom,
- sessionSpeakers);
- }
- return;
- }
-
- final long sessionStart =
- intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_START, UNDEFINED_VALUE);
- if (sessionStart == UNDEFINED_VALUE) {
- LOGD(TAG, "IGNORING ACTION -- no session start parameter.");
- return;
- }
-
- if (ACTION_NOTIFY_SESSION.equals(action)) {
- LOGD(TAG, "Notifying about sessions starting at " +
- sessionStart + " = " + (new Date(sessionStart)).toString());
- LOGD(TAG, "-> Alarm offset: " + sessionAlarmOffset);
- notifySession(sessionStart, sessionAlarmOffset);
- } else if (ACTION_SCHEDULE_STARRED_BLOCK.equals(action)) {
- LOGD(TAG, "Scheduling session alarm.");
- LOGD(TAG, "-> Session start: " + sessionStart + " = " + (new Date(sessionStart))
- .toString());
- LOGD(TAG, "-> Session end: " + sessionEnd + " = " + (new Date(sessionEnd)).toString());
- LOGD(TAG, "-> Alarm offset: " + sessionAlarmOffset);
- scheduleAlarm(sessionStart, sessionEnd, sessionAlarmOffset);
- }
- }
-
- public void scheduleFeedbackAlarm(final String sessionId, final long sessionEnd,
- final long alarmOffset, final String sessionTitle, String sessionRoom,
- String sessionSpeakers) {
- // By default, feedback alarms fire 5 minutes before session end time. If alarm offset is
- // provided, alarm is set to go off that much time from now (useful for testing).
- long alarmTime;
- if (alarmOffset == UNDEFINED_ALARM_OFFSET) {
- alarmTime = sessionEnd - MILLI_FIVE_MINUTES;
- } else {
- alarmTime = UIUtils.getCurrentTime(this) + alarmOffset;
- }
-
- LOGD(TAG, "Scheduling session feedback alarm for session '" + sessionTitle + "'");
- LOGD(TAG, " -> end time: " + sessionEnd + " = " + (new Date(sessionEnd)).toString());
- LOGD(TAG, " -> alarm time: " + alarmTime + " = " + (new Date(alarmTime)).toString());
- LOGD(TAG, " -> room name: " + sessionRoom);
- LOGD(TAG, " -> speakers: " + sessionSpeakers);
-
- final Intent feedbackIntent = new Intent(
- ACTION_NOTIFY_SESSION_FEEDBACK,
- null,
- this,
- SessionAlarmService.class);
- feedbackIntent.setData(
- new Uri.Builder().authority("com.google.samples.apps.iosched")
- .path(sessionId).build()
- );
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd);
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, alarmOffset);
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ID, sessionId);
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_TITLE, sessionTitle);
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_SPEAKERS, sessionSpeakers);
- feedbackIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ROOM, sessionRoom);
- PendingIntent pi = PendingIntent.getService(
- this, 1, feedbackIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- am.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
- }
-
- private void scheduleAlarm(final long sessionStart,
- final long sessionEnd, final long alarmOffset) {
-
- NotificationManager nm =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.cancel(NOTIFICATION_ID);
- final long currentTime = UIUtils.getCurrentTime(this);
- // If the session is already started, do not schedule system notification.
- if (currentTime > sessionStart) {
- LOGD(TAG, "Not scheduling alarm because target time is in the past: " + sessionStart);
- return;
- }
-
- // By default, sets alarm to go off at 10 minutes before session start time. If alarm
- // offset is provided, alarm is set to go off by that much time from now.
- long alarmTime;
- if (alarmOffset == UNDEFINED_ALARM_OFFSET) {
- alarmTime = sessionStart - MILLI_TEN_MINUTES;
- } else {
- alarmTime = currentTime + alarmOffset;
- }
-
- LOGD(TAG, "Scheduling alarm for " + alarmTime + " = " + (new Date(alarmTime)).toString());
-
- final Intent notifIntent = new Intent(
- ACTION_NOTIFY_SESSION,
- null,
- this,
- SessionAlarmService.class);
- // Setting data to ensure intent's uniqueness for different session start times.
- notifIntent.setData(
- new Uri.Builder().authority("com.google.samples.apps.iosched")
- .path(String.valueOf(sessionStart)).build()
- );
- notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart);
- LOGD(TAG, "-> Intent extra: session start " + sessionStart);
- notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd);
- LOGD(TAG, "-> Intent extra: session end " + sessionEnd);
- notifIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, alarmOffset);
- LOGD(TAG, "-> Intent extra: session alarm offset " + alarmOffset);
- PendingIntent pi = PendingIntent.getService(this,
- 0,
- notifIntent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- // Schedule an alarm to be fired to notify user of added sessions are about to begin.
- LOGD(TAG, "-> Scheduling RTC_WAKEUP alarm at " + alarmTime);
- am.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
- }
-
- // A starred session is about to end; notify the user to provide session feedback.
- // Constructs and triggers a system notification. Does nothing if the session has already
- // concluded.
- private void notifySessionFeedback(final String sessionId, final long sessionEnd,
- final String sessionTitle, final String sessionRoom, final String sessionSpeakers) {
- LOGD(TAG, "Considering firing notification for feedback for session: " + sessionTitle);
- boolean isDebug = DEBUG_SESSION_ID.equals(sessionId);
-
- if (isDebug) {
- LOGD(TAG, "Note: this is a debug notification.");
- }
-
- // Don't fire notification if this feature is disabled in settings
- if (!PrefUtils.shouldShowSessionFeedbackReminders(this)) {
- LOGD(TAG, "Skipping session feedback notification for session " + sessionId + " ("
- + sessionTitle + "). Disabled in settings.");
- return;
- }
-
- // Avoid repeated notifications.
- if (!isDebug && UIUtils.isFeedbackNotificationFiredForSession(this, sessionId)) {
- LOGD(TAG, "Skipping repeated session feedback notification for session '"
- + sessionTitle + "'");
- return;
- }
-
- // If the session is no longer is MY_SCHEDULE, don't notify for it.
- final Uri myScheduleUri = ScheduleContract.MySchedule.buildMyScheduleUri(this);
- final Cursor c = getContentResolver().query(
- myScheduleUri, MySessionsExistenceQuery.PROJECTION,
- MySessionsExistenceQuery.WHERE_CLAUSE, new String[]{sessionId}, null);
- if (!isDebug && (c == null || !c.moveToFirst())) {
- // no longer in MY_SCHEDULE
- return;
- }
-
- LOGD(TAG, "Going forward with session feedback notification for: " + sessionTitle);
- final Uri sessionUri = ScheduleContract.Sessions.buildSessionUri(sessionId);
-
- final Resources res = getResources();
- String contentText = res.getString(R.string.session_feedback_notification_text,
- sessionTitle);
-
- PendingIntent pi = TaskStackBuilder.create(this)
- .addNextIntent(new Intent(this, MyScheduleActivity.class))
- .addNextIntent(new Intent(Intent.ACTION_VIEW, sessionUri, this,
- SessionFeedbackActivity.class))
- .getPendingIntent(1, PendingIntent.FLAG_CANCEL_CURRENT);
-
- // this is used to synchronize deletion of notifications on phone and wear
- Intent dismissalIntent = new Intent(ACTION_NOTIFICATION_DISMISSAL);
- dismissalIntent.putExtra(KEY_SESSION_ID, sessionId);
- PendingIntent dismissalPendingIntent = PendingIntent
- .getService(this, (int) new Date().getTime(), dismissalIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this)
- .setContentTitle(sessionTitle)
- .setContentText(contentText)
- //.setColor(getResources().getColor(R.color.theme_primary))
- // Note: setColor() is available in the support lib v21+.
- // We commented it out because we want the source to compile
- // against support lib v20. If you are using support lib
- // v21 or above on Android L, uncomment this line.
- .setTicker(res.getString(R.string.session_feedback_notification_ticker))
- .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
- .setLights(
- SessionAlarmService.NOTIFICATION_ARGB_COLOR,
- SessionAlarmService.NOTIFICATION_LED_ON_MS,
- SessionAlarmService.NOTIFICATION_LED_OFF_MS)
- .setSmallIcon(R.drawable.ic_stat_notification)
- .setContentIntent(pi)
- .setPriority(Notification.PRIORITY_MAX)
- .setLocalOnly(true) // make it local to the phone
- .setDeleteIntent(dismissalPendingIntent)
- .setAutoCancel(true);
- NotificationManager nm = (NotificationManager) getSystemService(
- Context.NOTIFICATION_SERVICE);
- LOGD(TAG, "Now showing session feedback notification!");
- nm.notify(sessionId, FEEDBACK_NOTIFICATION_ID, notifBuilder.build());
- setupNotificationOnWear(sessionId, sessionRoom, sessionTitle, sessionSpeakers);
- }
-
- /**
- * Builds corresponding notification for the Wear device that is paired to this handset. This
- * is done by adding a Data Item to teh Data Store; the Wear device will be notified to build a
- * local notification.
- */
- private void setupNotificationOnWear(String sessionId, String sessionRoom, String sessionName,
- String speaker) {
- if (!mGoogleApiClient.isConnected()) {
- Log.e(TAG, "setupNotificationOnWear(): Failed to send data item since there was no "
- + "connectivity to Google API Client");
- return;
- }
- PutDataMapRequest putDataMapRequest = PutDataMapRequest
- .create(FeedbackUtils.getFeedbackPath(sessionId));
- putDataMapRequest.getDataMap().putLong("time", new Date().getTime());
- putDataMapRequest.getDataMap().putString(KEY_SESSION_ID, sessionId);
- putDataMapRequest.getDataMap().putString(KEY_SESSION_NAME, sessionName);
- putDataMapRequest.getDataMap().putString(KEY_SPEAKER_NAME, speaker);
- putDataMapRequest.getDataMap().putString(KEY_SESSION_ROOM, sessionRoom);
-
- PutDataRequest request = putDataMapRequest.asPutDataRequest();
-
- Wearable.DataApi.putDataItem(mGoogleApiClient, request)
- .setResultCallback(new ResultCallback() {
- @Override
- public void onResult(DataApi.DataItemResult dataItemResult) {
- LOGD(TAG, "setupNotificationOnWear(): Sending notification result success:"
- + dataItemResult.getStatus().isSuccess()
- );
- }
- });
- }
-
- // Starred sessions are about to begin. Constructs and triggers system notification.
- private void notifySession(final long sessionStart, final long alarmOffset) {
- long currentTime = UIUtils.getCurrentTime(this);
- final long intervalEnd = sessionStart + MILLI_TEN_MINUTES;
- LOGD(TAG, "Considering notifying for time interval.");
- LOGD(TAG, " Interval start: " + sessionStart + "=" + (new Date(sessionStart)).toString());
- LOGD(TAG, " Interval end: " + intervalEnd + "=" + (new Date(intervalEnd)).toString());
- LOGD(TAG, " Current time is: " + currentTime + "=" + (new Date(currentTime)).toString());
- if (sessionStart < currentTime) {
- LOGD(TAG, "Skipping session notification (too late -- time interval already started)");
- return;
- }
-
- if (!PrefUtils.shouldShowSessionReminders(this)) {
- // skip if disabled in settings
- LOGD(TAG, "Skipping session notification for sessions. Disabled in settings.");
- return;
- }
-
- // Avoid repeated notifications.
- if (alarmOffset == UNDEFINED_ALARM_OFFSET && UIUtils.isNotificationFiredForBlock(
- this, ScheduleContract.Blocks.generateBlockId(sessionStart, intervalEnd))) {
- LOGD(TAG, "Skipping session notification (already notified)");
- return;
- }
-
- final ContentResolver cr = getContentResolver();
-
- LOGD(TAG, "Looking for sessions in interval " + sessionStart + " - " + intervalEnd);
- Cursor c = cr.query(
- ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI,
- SessionDetailQuery.PROJECTION,
- ScheduleContract.Sessions.STARTING_AT_TIME_INTERVAL_SELECTION,
- ScheduleContract.Sessions.buildAtTimeIntervalArgs(sessionStart, intervalEnd),
- null);
- int starredCount = c.getCount();
- LOGD(TAG, "# starred sessions in that interval: " + c.getCount());
- String singleSessionId = null;
- String singleSessionRoomId = null;
- ArrayList starredSessionTitles = new ArrayList();
- while (c.moveToNext()) {
- singleSessionId = c.getString(SessionDetailQuery.SESSION_ID);
- singleSessionRoomId = c.getString(SessionDetailQuery.ROOM_ID);
- starredSessionTitles.add(c.getString(SessionDetailQuery.SESSION_TITLE));
- LOGD(TAG, "-> Title: " + c.getString(SessionDetailQuery.SESSION_TITLE));
- }
- if (starredCount < 1) {
- return;
- }
-
- // Generates the pending intent which gets fired when the user taps on the notification.
- // NOTE: Use TaskStackBuilder to comply with Android's design guidelines
- // related to navigation from notifications.
- Intent baseIntent = new Intent(this, MyScheduleActivity.class);
- baseIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- TaskStackBuilder taskBuilder = TaskStackBuilder.create(this)
- .addNextIntent(baseIntent);
-
- // For a single session, tapping the notification should open the session details (b/15350787)
- if (starredCount == 1) {
- taskBuilder.addNextIntent(new Intent(Intent.ACTION_VIEW,
- ScheduleContract.Sessions.buildSessionUri(singleSessionId)));
- }
-
- PendingIntent pi = taskBuilder.getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT);
-
- final Resources res = getResources();
- String contentText;
- int minutesLeft = (int) (sessionStart - currentTime + 59000) / 60000;
- if (minutesLeft < 1) {
- minutesLeft = 1;
- }
-
- if (starredCount == 1) {
- contentText = res.getString(R.string.session_notification_text_1, minutesLeft);
- } else {
- contentText = res.getQuantityString(R.plurals.session_notification_text,
- starredCount - 1,
- minutesLeft,
- starredCount - 1);
- }
-
- NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(this)
- .setContentTitle(starredSessionTitles.get(0))
- .setContentText(contentText)
- //.setColor(getResources().getColor(R.color.theme_primary))
- // Note: setColor() is available in the support lib v21+.
- // We commented it out because we want the source to compile
- // against support lib v20. If you are using support lib
- // v21 or above on Android L, uncomment this line.
- .setTicker(res.getQuantityString(R.plurals.session_notification_ticker,
- starredCount,
- starredCount))
- .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
- .setLights(
- SessionAlarmService.NOTIFICATION_ARGB_COLOR,
- SessionAlarmService.NOTIFICATION_LED_ON_MS,
- SessionAlarmService.NOTIFICATION_LED_OFF_MS)
- .setSmallIcon(R.drawable.ic_stat_notification)
- .setContentIntent(pi)
- .setPriority(Notification.PRIORITY_MAX)
- .setAutoCancel(true);
- if (minutesLeft > 5) {
- notifBuilder.addAction(R.drawable.ic_alarm_holo_dark,
- String.format(res.getString(R.string.snooze_x_min), 5),
- createSnoozeIntent(sessionStart, intervalEnd, 5));
- }
- if (starredCount == 1 && PrefUtils.isAttendeeAtVenue(this)) {
- notifBuilder.addAction(R.drawable.ic_map_holo_dark,
- res.getString(R.string.title_map),
- createRoomMapIntent(singleSessionRoomId));
- }
- String bigContentTitle;
- if (starredCount == 1 && starredSessionTitles.size() > 0) {
- bigContentTitle = starredSessionTitles.get(0);
- } else {
- bigContentTitle = res.getQuantityString(R.plurals.session_notification_title,
- starredCount,
- minutesLeft,
- starredCount);
- }
- NotificationCompat.InboxStyle richNotification = new NotificationCompat.InboxStyle(
- notifBuilder)
- .setBigContentTitle(bigContentTitle);
-
- // Adds starred sessions starting at this time block to the notification.
- for (int i = 0; i < starredCount; i++) {
- richNotification.addLine(starredSessionTitles.get(i));
- }
- NotificationManager nm = (NotificationManager) getSystemService(
- Context.NOTIFICATION_SERVICE);
- LOGD(TAG, "Now showing notification.");
- nm.notify(NOTIFICATION_ID, richNotification.build());
- }
-
- private PendingIntent createSnoozeIntent(final long sessionStart, final long sessionEnd,
- final int snoozeMinutes) {
- Intent scheduleIntent = new Intent(
- SessionAlarmService.ACTION_SCHEDULE_STARRED_BLOCK,
- null, this, SessionAlarmService.class);
- scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart);
- scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd);
- scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET,
- snoozeMinutes * MILLI_ONE_MINUTE);
- return PendingIntent.getService(this, 0, scheduleIntent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- private PendingIntent createRoomMapIntent(final String roomId) {
- Intent mapIntent = new Intent(getApplicationContext(),
- UIUtils.getMapActivityClass(getApplicationContext()));
- mapIntent.putExtra(MapFragment.EXTRA_ROOM, roomId);
- mapIntent.putExtra(MapActivity.EXTRA_DETACHED_MODE, true);
- return TaskStackBuilder
- .create(getApplicationContext())
- .addNextIntent(new Intent(this, BrowseSessionsActivity.class))
- .addNextIntent(mapIntent)
- .getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- private void scheduleAllStarredBlocks() {
- final ContentResolver cr = getContentResolver();
- final Cursor c = cr.query(ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI,
- new String[]{"distinct " + ScheduleContract.Sessions.SESSION_START,
- ScheduleContract.Sessions.SESSION_END,
- ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE},
- null,
- null,
- null
- );
- if (c == null) {
- return;
- }
-
- while (c.moveToNext()) {
- final long sessionStart = c.getLong(0);
- final long sessionEnd = c.getLong(1);
- scheduleAlarm(sessionStart, sessionEnd, UNDEFINED_ALARM_OFFSET);
- }
- }
-
- // Schedules feedback alarms for all starred sessions.
- private void scheduleAllStarredSessionFeedbacks() {
- final ContentResolver cr = getContentResolver();
- // TODO: Should we also check that SESSION_IN_MY_SCHEDULE is true?
- final Cursor c = cr.query(ScheduleContract.Sessions.CONTENT_MY_SCHEDULE_URI,
- new String[]{
- ScheduleContract.Sessions.SESSION_ID,
- ScheduleContract.Sessions.SESSION_TITLE,
- ScheduleContract.Sessions.SESSION_END,
- ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE,
- ScheduleContract.Sessions.ROOM_NAME,
- ScheduleContract.Sessions.SESSION_SPEAKER_NAMES,
- },
- null,
- null,
- null
- );
- if (c == null) {
- return;
- }
- while (c.moveToNext()) {
- final String sessionId = c.getString(0);
- final String sessionTitle = c.getString(1);
- final long sessionEnd = c.getLong(2);
- final String sessionRoom = c.getString(3);
- final String sessionSpeakers = c.getString(4);
- scheduleFeedbackAlarm(sessionId, sessionEnd, UNDEFINED_ALARM_OFFSET, sessionTitle,
- sessionRoom, sessionSpeakers);
- }
- }
-
- public interface SessionDetailQuery {
-
- String[] PROJECTION = {
- ScheduleContract.Sessions.SESSION_ID,
- ScheduleContract.Sessions.SESSION_TITLE,
- ScheduleContract.Sessions.ROOM_ID,
- ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE
- };
-
- int SESSION_ID = 0;
- int SESSION_TITLE = 1;
- int ROOM_ID = 2;
- }
-
- public interface MySessionsExistenceQuery {
-
- String[] PROJECTION = {
- ScheduleContract.MySchedule.SESSION_ID
- };
-
- int SESSION_ID = 0;
-
- public static final String WHERE_CLAUSE =
- ScheduleContract.MySchedule.SESSION_ID + "=?";
- }
-
- @Override
- public void onConnected(Bundle connectionHint) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Connected to Google Api Service");
- }
- }
-
- @Override
- public void onConnectionSuspended(int cause) {
- // Ignore
- }
-
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Disconnected from Google Api Service");
- }
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java b/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java
deleted file mode 100644
index f523cf26a6..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/service/SessionCalendarService.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.service;
-
-import android.util.Log;
-import com.google.samples.apps.iosched.Config;
-import com.google.samples.apps.iosched.R;
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.util.AccountUtils;
-
-import android.annotation.TargetApi;
-import android.app.IntentService;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.CalendarContract;
-import android.text.TextUtils;
-import com.google.samples.apps.iosched.util.PrefUtils;
-
-import java.util.ArrayList;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGE;
-import static com.google.samples.apps.iosched.util.LogUtils.LOGW;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * Background {@link android.app.Service} that adds or removes session Calendar events through
- * the {@link CalendarContract} API available in Android 4.0 or above.
- */
-public class SessionCalendarService extends IntentService {
- private static final String TAG = makeLogTag(SessionCalendarService.class);
-
- public static final String ACTION_ADD_SESSION_CALENDAR =
- "com.google.samples.apps.iosched.action.ADD_SESSION_CALENDAR";
- public static final String ACTION_REMOVE_SESSION_CALENDAR =
- "com.google.samples.apps.iosched.action.REMOVE_SESSION_CALENDAR";
- public static final String ACTION_UPDATE_ALL_SESSIONS_CALENDAR =
- "com.google.samples.apps.iosched.action.UPDATE_ALL_SESSIONS_CALENDAR";
- public static final String ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED =
- "com.google.samples.apps.iosched.action.UPDATE_CALENDAR_COMPLETED";
- public static final String ACTION_CLEAR_ALL_SESSIONS_CALENDAR =
- "com.google.samples.apps.iosched.action.CLEAR_ALL_SESSIONS_CALENDAR";
- public static final String EXTRA_ACCOUNT_NAME =
- "com.google.samples.apps.iosched.extra.ACCOUNT_NAME";
- public static final String EXTRA_SESSION_START =
- "com.google.samples.apps.iosched.extra.SESSION_BLOCK_START";
- public static final String EXTRA_SESSION_END =
- "com.google.samples.apps.iosched.extra.SESSION_BLOCK_END";
- public static final String EXTRA_SESSION_TITLE =
- "com.google.samples.apps.iosched.extra.SESSION_TITLE";
- public static final String EXTRA_SESSION_ROOM =
- "com.google.samples.apps.iosched.extra.SESSION_ROOM";
-
- private static final long INVALID_CALENDAR_ID = -1;
-
- // TODO: localize
- private static final String CALENDAR_CLEAR_SEARCH_LIKE_EXPRESSION =
- "%added by Google I/O Android app%";
-
- public SessionCalendarService() {
- super(TAG);
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- final String action = intent.getAction();
- Log.d(TAG, "Received intent: " + action);
-
- final ContentResolver resolver = getContentResolver();
-
- boolean isAddEvent = false;
-
- if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {
- isAddEvent = true;
-
- } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {
- isAddEvent = false;
-
- } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action) &&
- PrefUtils.shouldSyncCalendar(this)) {
- try {
- getContentResolver().applyBatch(CalendarContract.AUTHORITY,
- processAllSessionsCalendar(resolver, getCalendarId(intent)));
- sendBroadcast(new Intent(
- SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED));
- } catch (RemoteException e) {
- LOGE(TAG, "Error adding all sessions to Google Calendar", e);
- } catch (OperationApplicationException e) {
- LOGE(TAG, "Error adding all sessions to Google Calendar", e);
- }
-
- } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {
- try {
- getContentResolver().applyBatch(CalendarContract.AUTHORITY,
- processClearAllSessions(resolver, getCalendarId(intent)));
- } catch (RemoteException e) {
- LOGE(TAG, "Error clearing all sessions from Google Calendar", e);
- } catch (OperationApplicationException e) {
- LOGE(TAG, "Error clearing all sessions from Google Calendar", e);
- }
-
- } else {
- return;
- }
-
- final Uri uri = intent.getData();
- final Bundle extras = intent.getExtras();
- if (uri == null || extras == null || !PrefUtils.shouldSyncCalendar(this)) {
- return;
- }
-
- try {
- resolver.applyBatch(CalendarContract.AUTHORITY,
- processSessionCalendar(resolver, getCalendarId(intent), isAddEvent, uri,
- extras.getLong(EXTRA_SESSION_START),
- extras.getLong(EXTRA_SESSION_END),
- extras.getString(EXTRA_SESSION_TITLE),
- extras.getString(EXTRA_SESSION_ROOM)));
- } catch (RemoteException e) {
- LOGE(TAG, "Error adding session to Google Calendar", e);
- } catch (OperationApplicationException e) {
- LOGE(TAG, "Error adding session to Google Calendar", e);
- }
- }
-
- /**
- * Gets the currently-logged in user's Google Calendar, or the Google Calendar for the user
- * specified in the given intent's {@link #EXTRA_ACCOUNT_NAME}.
- */
- private long getCalendarId(Intent intent) {
- final String accountName;
- if (intent != null && intent.hasExtra(EXTRA_ACCOUNT_NAME)) {
- accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
- } else {
- accountName = AccountUtils.getActiveAccountName(this);
- }
-
- if (TextUtils.isEmpty(accountName)) {
- return INVALID_CALENDAR_ID;
- }
-
- // TODO: The calendar ID should be stored in shared preferences upon choosing an account.
- Cursor calendarsCursor = getContentResolver().query(
- CalendarContract.Calendars.CONTENT_URI,
- new String[]{"_id"},
- // TODO: What if the calendar is not displayed or not sync'd?
- "account_name = ownerAccount and account_name = ?",
- new String[]{accountName},
- null);
-
- long calendarId = INVALID_CALENDAR_ID;
- if (calendarsCursor != null && calendarsCursor.moveToFirst()) {
- calendarId = calendarsCursor.getLong(0);
- calendarsCursor.close();
- }
-
- return calendarId;
- }
-
- private String makeCalendarEventTitle(String sessionTitle) {
- return sessionTitle + getResources().getString(R.string.session_calendar_suffix);
- }
-
- /**
- * Processes all sessions in the
- * {@link com.google.samples.apps.iosched.provider.ScheduleProvider}, adding or removing
- * calendar events to/from the specified Google Calendar depending on whether a session is
- * in the user's schedule or not.
- */
- private ArrayList processAllSessionsCalendar(ContentResolver resolver,
- final long calendarId) {
-
- ArrayList batch = new ArrayList();
-
- // Unable to find the Calendar associated with the user. Stop here.
- if (calendarId == INVALID_CALENDAR_ID) {
- return batch;
- }
-
- // Retrieves all sessions. For each session, add to Calendar if starred and attempt to
- // remove from Calendar if unstarred.
- Cursor cursor = resolver.query(
- ScheduleContract.Sessions.CONTENT_URI,
- SessionsQuery.PROJECTION,
- null, null, null);
-
- if (cursor != null) {
- while (cursor.moveToNext()) {
- Uri uri = ScheduleContract.Sessions.buildSessionUri(
- Long.valueOf(cursor.getLong(0)).toString());
- boolean isAddEvent = (cursor.getInt(SessionsQuery.SESSION_IN_MY_SCHEDULE) == 1);
- if (isAddEvent) {
- batch.addAll(processSessionCalendar(resolver,
- calendarId, isAddEvent, uri,
- cursor.getLong(SessionsQuery.SESSION_START),
- cursor.getLong(SessionsQuery.SESSION_END),
- cursor.getString(SessionsQuery.SESSION_TITLE),
- cursor.getString(SessionsQuery.ROOM_NAME)));
- }
- }
- cursor.close();
- }
-
- return batch;
- }
-
- /**
- * Adds or removes a single session to/from the specified Google Calendar.
- */
- private ArrayList processSessionCalendar(
- final ContentResolver resolver,
- final long calendarId, final boolean isAddEvent,
- final Uri sessionUri, final long sessionBlockStart, final long sessionBlockEnd,
- final String sessionTitle, final String sessionRoom) {
- ArrayList batch = new ArrayList();
-
- // Unable to find the Calendar associated with the user. Stop here.
- if (calendarId == INVALID_CALENDAR_ID) {
- return batch;
- }
-
- final String calendarEventTitle = makeCalendarEventTitle(sessionTitle);
-
- Cursor cursor;
- ContentValues values = new ContentValues();
-
- // Add Calendar event.
- if (isAddEvent) {
- if (sessionBlockStart == 0L || sessionBlockEnd == 0L || sessionTitle == null) {
- LOGW(TAG, "Unable to add a Calendar event due to insufficient input parameters.");
- return batch;
- }
-
- // Check if the calendar event exists first. If it does, we don't want to add a
- // duplicate one.
- cursor = resolver.query(
- CalendarContract.Events.CONTENT_URI, // URI
- new String[] {CalendarContract.Events._ID}, // Projection
- CalendarContract.Events.CALENDAR_ID + "=? and " // Selection
- + CalendarContract.Events.TITLE + "=? and "
- + CalendarContract.Events.DTSTART + ">=? and "
- + CalendarContract.Events.DTEND + "<=?",
- new String[]{ // Selection args
- Long.valueOf(calendarId).toString(),
- calendarEventTitle,
- Long.toString(Config.CONFERENCE_START_MILLIS),
- Long.toString(Config.CONFERENCE_END_MILLIS)
- },
- null);
-
- long newEventId = -1;
-
- if (cursor != null && cursor.moveToFirst()) {
- // Calendar event already exists for this session.
- newEventId = cursor.getLong(0);
- cursor.close();
-
- // Data fix (workaround):
- batch.add(
- ContentProviderOperation.newUpdate(CalendarContract.Events.CONTENT_URI)
- .withValue(CalendarContract.Events.EVENT_TIMEZONE,
- Config.CONFERENCE_TIMEZONE.getID())
- .withSelection(CalendarContract.Events._ID + "=?",
- new String[]{Long.valueOf(newEventId).toString()})
- .build()
- );
- // End data fix.
-
- } else {
- // Calendar event doesn't exist, create it.
-
- // NOTE: we can't use batch processing here because we need the result of
- // the insert.
- values.clear();
- values.put(CalendarContract.Events.DTSTART, sessionBlockStart);
- values.put(CalendarContract.Events.DTEND, sessionBlockEnd);
- values.put(CalendarContract.Events.EVENT_LOCATION, sessionRoom);
- values.put(CalendarContract.Events.TITLE, calendarEventTitle);
- values.put(CalendarContract.Events.CALENDAR_ID, calendarId);
- values.put(CalendarContract.Events.EVENT_TIMEZONE,
- Config.CONFERENCE_TIMEZONE.getID());
- Uri eventUri = resolver.insert(CalendarContract.Events.CONTENT_URI, values);
- String eventId = eventUri.getLastPathSegment();
- if (eventId == null) {
- return batch; // Should be empty at this point
- }
-
- newEventId = Long.valueOf(eventId);
- // Since we're adding session reminder to system notification, we're not creating
- // Calendar event reminders. If we were to create Calendar event reminders, this
- // is how we would do it.
- //values.put(CalendarContract.Reminders.EVENT_ID, Integer.valueOf(eventId));
- //values.put(CalendarContract.Reminders.MINUTES, 10);
- //values.put(CalendarContract.Reminders.METHOD,
- // CalendarContract.Reminders.METHOD_ALERT); // Or default?
- //cr.insert(CalendarContract.Reminders.CONTENT_URI, values);
- //values.clear();
- }
-
- // Update the session in our own provider with the newly created calendar event ID.
- values.clear();
- values.put(ScheduleContract.Sessions.SESSION_CAL_EVENT_ID, newEventId);
- resolver.update(sessionUri, values, null, null);
-
- } else {
- // Remove Calendar event, if exists.
-
- // Get the event calendar id.
- cursor = resolver.query(sessionUri,
- new String[] {ScheduleContract.Sessions.SESSION_CAL_EVENT_ID},
- null, null, null);
- long calendarEventId = -1;
- if (cursor != null && cursor.moveToFirst()) {
- calendarEventId = cursor.getLong(0);
- cursor.close();
- }
-
- // Try to remove the Calendar Event based on key. If successful, move on;
- // otherwise, remove the event based on Event title.
- int affectedRows = 0;
- if (calendarEventId != -1) {
- affectedRows = resolver.delete(
- CalendarContract.Events.CONTENT_URI,
- CalendarContract.Events._ID + "=?",
- new String[]{Long.valueOf(calendarEventId).toString()});
- }
-
- if (affectedRows == 0) {
- resolver.delete(CalendarContract.Events.CONTENT_URI,
- String.format("%s=? and %s=? and %s=? and %s=?",
- CalendarContract.Events.CALENDAR_ID,
- CalendarContract.Events.TITLE,
- CalendarContract.Events.DTSTART,
- CalendarContract.Events.DTEND),
- new String[]{Long.valueOf(calendarId).toString(),
- calendarEventTitle,
- Long.valueOf(sessionBlockStart).toString(),
- Long.valueOf(sessionBlockEnd).toString()});
- }
-
- // Remove the session and calendar event association.
- values.clear();
- values.put(ScheduleContract.Sessions.SESSION_CAL_EVENT_ID, (Long) null);
- resolver.update(sessionUri, values, null, null);
- }
-
- return batch;
- }
-
- /**
- * Removes all calendar entries associated with Google I/O 2013.
- */
- private ArrayList processClearAllSessions(
- ContentResolver resolver, long calendarId) {
-
- ArrayList batch = new ArrayList();
-
- // Unable to find the Calendar associated with the user. Stop here.
- if (calendarId == INVALID_CALENDAR_ID) {
- Log.e(TAG, "Unable to find Calendar for user");
- return batch;
- }
-
- // Delete all calendar entries matching the given title within the given time period
- batch.add(ContentProviderOperation
- .newDelete(CalendarContract.Events.CONTENT_URI)
- .withSelection(
- CalendarContract.Events.CALENDAR_ID + " = ? and "
- + CalendarContract.Events.TITLE + " LIKE ? and "
- + CalendarContract.Events.DTSTART + ">= ? and "
- + CalendarContract.Events.DTEND + "<= ?",
- new String[]{
- Long.toString(calendarId),
- CALENDAR_CLEAR_SEARCH_LIKE_EXPRESSION,
- Long.toString(Config.CONFERENCE_START_MILLIS),
- Long.toString(Config.CONFERENCE_END_MILLIS)
- }
- )
- .build());
-
- return batch;
- }
-
- private interface SessionsQuery {
- String[] PROJECTION = {
- ScheduleContract.Sessions._ID,
- ScheduleContract.Sessions.SESSION_START,
- ScheduleContract.Sessions.SESSION_END,
- ScheduleContract.Sessions.SESSION_TITLE,
- ScheduleContract.Sessions.ROOM_NAME,
- ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE,
- };
-
- int _ID = 0;
- int SESSION_START = 1;
- int SESSION_END = 2;
- int SESSION_TITLE = 3;
- int ROOM_NAME = 4;
- int SESSION_IN_MY_SCHEDULE = 5;
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java b/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java
deleted file mode 100644
index 7ac3f54786..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/ConferenceDataHandler.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-
-import com.google.samples.apps.iosched.io.*;
-import com.google.samples.apps.iosched.io.map.model.Tile;
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.util.FileUtils;
-import com.google.samples.apps.iosched.util.Lists;
-import com.google.samples.apps.iosched.util.MapUtils;
-import com.google.gson.JsonParser;
-import com.google.gson.stream.JsonReader;
-
-import java.io.*;
-import java.net.HttpURLConnection;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-
-import com.larvalabs.svgandroid.SVG;
-import com.larvalabs.svgandroid.SVGBuilder;
-import com.larvalabs.svgandroid.SVGParseException;
-import com.turbomanage.httpclient.BasicHttpClient;
-import com.turbomanage.httpclient.ConsoleRequestLogger;
-import com.turbomanage.httpclient.HttpResponse;
-import com.turbomanage.httpclient.RequestLogger;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Helper class that parses conference data and imports them into the app's
- * Content Provider.
- */
-public class ConferenceDataHandler {
- private static final String TAG = makeLogTag(SyncHelper.class);
-
- // Shared preferences key under which we store the timestamp that corresponds to
- // the data we currently have in our content provider.
- private static final String SP_KEY_DATA_TIMESTAMP = "data_timestamp";
-
- // symbolic timestamp to use when we are missing timestamp data (which means our data is
- // really old or nonexistent)
- private static final String DEFAULT_TIMESTAMP = "Sat, 1 Jan 2000 00:00:00 GMT";
-
- private static final String DATA_KEY_ROOMS = "rooms";
- private static final String DATA_KEY_BLOCKS = "blocks";
- private static final String DATA_KEY_TAGS = "tags";
- private static final String DATA_KEY_SPEAKERS = "speakers";
- private static final String DATA_KEY_SESSIONS = "sessions";
- private static final String DATA_KEY_SEARCH_SUGGESTIONS = "search_suggestions";
- private static final String DATA_KEY_MAP = "map";
- private static final String DATA_KEY_HASHTAGS = "hashtags";
- private static final String DATA_KEY_EXPERTS = "experts";
- private static final String DATA_KEY_VIDEOS = "video_library";
- private static final String DATA_KEY_PARTNERS = "partners";
-
- private static final String[] DATA_KEYS_IN_ORDER = {
- DATA_KEY_ROOMS,
- DATA_KEY_BLOCKS,
- DATA_KEY_TAGS,
- DATA_KEY_SPEAKERS,
- DATA_KEY_SESSIONS,
- DATA_KEY_SEARCH_SUGGESTIONS,
- DATA_KEY_MAP,
- DATA_KEY_HASHTAGS,
- DATA_KEY_EXPERTS,
- DATA_KEY_VIDEOS,
- DATA_KEY_PARTNERS
- };
-
- Context mContext = null;
-
- // Handlers for each entity type:
- RoomsHandler mRoomsHandler = null;
- BlocksHandler mBlocksHandler = null;
- TagsHandler mTagsHandler = null;
- SpeakersHandler mSpeakersHandler = null;
- SessionsHandler mSessionsHandler = null;
- SearchSuggestHandler mSearchSuggestHandler = null;
- MapPropertyHandler mMapPropertyHandler = null;
- ExpertsHandler mExpertsHandler = null;
- HashtagsHandler mHashtagsHandler = null;
- VideosHandler mVideosHandler = null;
- PartnersHandler mPartnersHandler = null;
-
- // Convenience map that maps the key name to its corresponding handler (e.g.
- // "blocks" to mBlocksHandler (to avoid very tedious if-elses)
- HashMap mHandlerForKey = new HashMap();
-
- // Tally of total content provider operations we carried out (for statistical purposes)
- private int mContentProviderOperationsDone = 0;
-
- public ConferenceDataHandler(Context ctx) {
- mContext = ctx;
- }
-
- /**
- * Parses the conference data in the given objects and imports the data into the
- * content provider. The format of the data is documented at https://code.google.com/p/iosched.
- *
- * @param dataBodies The collection of JSON objects to parse and import.
- * @param dataTimestamp The timestamp of the data. This should be in RFC1123 format.
- * @param downloadsAllowed Whether or not we are supposed to download data from the internet if needed.
- * @throws IOException If there is a problem parsing the data.
- */
- public void applyConferenceData(String[] dataBodies, String dataTimestamp,
- boolean downloadsAllowed) throws IOException {
- LOGD(TAG, "Applying data from " + dataBodies.length + " files, timestamp " + dataTimestamp);
-
- // create handlers for each data type
- mHandlerForKey.put(DATA_KEY_ROOMS, mRoomsHandler = new RoomsHandler(mContext));
- mHandlerForKey.put(DATA_KEY_BLOCKS, mBlocksHandler = new BlocksHandler(mContext));
- mHandlerForKey.put(DATA_KEY_TAGS, mTagsHandler = new TagsHandler(mContext));
- mHandlerForKey.put(DATA_KEY_SPEAKERS, mSpeakersHandler = new SpeakersHandler(mContext));
- mHandlerForKey.put(DATA_KEY_SESSIONS, mSessionsHandler = new SessionsHandler(mContext));
- mHandlerForKey.put(DATA_KEY_SEARCH_SUGGESTIONS, mSearchSuggestHandler =
- new SearchSuggestHandler(mContext));
- mHandlerForKey.put(DATA_KEY_MAP, mMapPropertyHandler = new MapPropertyHandler(mContext));
- mHandlerForKey.put(DATA_KEY_EXPERTS, mExpertsHandler = new ExpertsHandler(mContext));
- mHandlerForKey.put(DATA_KEY_HASHTAGS, mHashtagsHandler = new HashtagsHandler(mContext));
- mHandlerForKey.put(DATA_KEY_VIDEOS, mVideosHandler = new VideosHandler(mContext));
- mHandlerForKey.put(DATA_KEY_PARTNERS, mPartnersHandler = new PartnersHandler(mContext));
-
- // process the jsons. This will call each of the handlers when appropriate to deal
- // with the objects we see in the data.
- LOGD(TAG, "Processing " + dataBodies.length + " JSON objects.");
- for (int i = 0; i < dataBodies.length; i++) {
- LOGD(TAG, "Processing json object #" + (i + 1) + " of " + dataBodies.length);
- processDataBody(dataBodies[i]);
- }
-
- // the sessions handler needs to know the tag and speaker maps to process sessions
- mSessionsHandler.setTagMap(mTagsHandler.getTagMap());
- mSessionsHandler.setSpeakerMap(mSpeakersHandler.getSpeakerMap());
-
- // produce the necessary content provider operations
- ArrayList batch = new ArrayList();
- for (String key : DATA_KEYS_IN_ORDER) {
- LOGD(TAG, "Building content provider operations for: " + key);
- mHandlerForKey.get(key).makeContentProviderOperations(batch);
- LOGD(TAG, "Content provider operations so far: " + batch.size());
- }
- LOGD(TAG, "Total content provider operations: " + batch.size());
-
- // download or process local map tile overlay files (SVG files)
- LOGD(TAG, "Processing map overlay files");
- processMapOverlayFiles(mMapPropertyHandler.getTileOverlays(), downloadsAllowed);
-
- // finally, push the changes into the Content Provider
- LOGD(TAG, "Applying " + batch.size() + " content provider operations.");
- try {
- int operations = batch.size();
- if (operations > 0) {
- mContext.getContentResolver().applyBatch(ScheduleContract.CONTENT_AUTHORITY, batch);
- }
- LOGD(TAG, "Successfully applied " + operations + " content provider operations.");
- mContentProviderOperationsDone += operations;
- } catch (RemoteException ex) {
- LOGE(TAG, "RemoteException while applying content provider operations.");
- throw new RuntimeException("Error executing content provider batch operation", ex);
- } catch (OperationApplicationException ex) {
- LOGE(TAG, "OperationApplicationException while applying content provider operations.");
- throw new RuntimeException("Error executing content provider batch operation", ex);
- }
-
- // notify all top-level paths
- LOGD(TAG, "Notifying changes on all top-level paths on Content Resolver.");
- ContentResolver resolver = mContext.getContentResolver();
- for (String path : ScheduleContract.TOP_LEVEL_PATHS) {
- Uri uri = ScheduleContract.BASE_CONTENT_URI.buildUpon().appendPath(path).build();
- resolver.notifyChange(uri, null);
- }
-
-
- // update our data timestamp
- setDataTimestamp(dataTimestamp);
- LOGD(TAG, "Done applying conference data.");
- }
-
- public int getContentProviderOperationsDone() {
- return mContentProviderOperationsDone;
- }
-
- /**
- * Processes a conference data body and calls the appropriate data type handlers
- * to process each of the objects represented therein.
- *
- * @param dataBody The body of data to process
- * @throws IOException If there is an error parsing the data.
- */
- private void processDataBody(String dataBody) throws IOException {
- JsonReader reader = new JsonReader(new StringReader(dataBody));
- JsonParser parser = new JsonParser();
- try {
- reader.setLenient(true); // To err is human
-
- // the whole file is a single JSON object
- reader.beginObject();
-
- while (reader.hasNext()) {
- // the key is "rooms", "speakers", "tracks", etc.
- String key = reader.nextName();
- if (mHandlerForKey.containsKey(key)) {
- // pass the value to the corresponding handler
- mHandlerForKey.get(key).process(parser.parse(reader));
- } else {
- LOGW(TAG, "Skipping unknown key in conference data json: " + key);
- reader.skipValue();
- }
- }
- reader.endObject();
- } finally {
- reader.close();
- }
- }
-
- /**
- * Synchronise the map overlay files either from the local assets (if available) or from a remote url.
- *
- * @param collection Set of tiles containing a local filename and remote url.
- * @throws IOException
- */
- private void processMapOverlayFiles(Collection collection, boolean downloadAllowed) throws IOException, SVGParseException {
- // clear the tile cache on disk if any tiles have been updated
- boolean shouldClearCache = false;
- // keep track of used files, unused files are removed
- ArrayList usedTiles = Lists.newArrayList();
- for (Tile tile : collection) {
- final String filename = tile.filename;
- final String url = tile.url;
-
- usedTiles.add(filename);
-
- if (!MapUtils.hasTile(mContext, filename)) {
- shouldClearCache = true;
- // copy or download the tile if it is not stored yet
- if (MapUtils.hasTileAsset(mContext, filename)) {
- // file already exists as an asset, copy it
- MapUtils.copyTileAsset(mContext, filename);
- } else if (downloadAllowed && !TextUtils.isEmpty(url)) {
- try {
- // download the file only if downloads are allowed and url is not empty
- File tileFile = MapUtils.getTileFile(mContext, filename);
- BasicHttpClient httpClient = new BasicHttpClient();
- httpClient.setRequestLogger(mQuietLogger);
- HttpResponse httpResponse = httpClient.get(url, null);
- FileUtils.writeFile(httpResponse.getBody(), tileFile);
-
- // ensure the file is valid SVG
- InputStream is = new FileInputStream(tileFile);
- SVG svg = new SVGBuilder().readFromInputStream(is).build();
- is.close();
- } catch (IOException ex) {
- LOGE(TAG, "FAILED downloading map overlay tile "+url+
- ": " + ex.getMessage(), ex);
- } catch (SVGParseException ex) {
- LOGE(TAG, "FAILED parsing map overlay tile "+url+
- ": " + ex.getMessage(), ex);
- }
- } else {
- LOGD(TAG, "Skipping download of map overlay tile" +
- " (since downloadsAllowed=false)");
- }
- }
- }
-
- if (shouldClearCache) {
- MapUtils.clearDiskCache(mContext);
- }
-
- MapUtils.removeUnusedTiles(mContext, usedTiles);
- }
-
- // Returns the timestamp of the data we have in the content provider.
- public String getDataTimestamp() {
- return PreferenceManager.getDefaultSharedPreferences(mContext).getString(
- SP_KEY_DATA_TIMESTAMP, DEFAULT_TIMESTAMP);
- }
-
- // Sets the timestamp of the data we have in the content provider.
- public void setDataTimestamp(String timestamp) {
- LOGD(TAG, "Setting data timestamp to: " + timestamp);
- PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(
- SP_KEY_DATA_TIMESTAMP, timestamp).commit();
- }
-
- // Reset the timestamp of the data we have in the content provider
- public static void resetDataTimestamp(final Context context) {
- LOGD(TAG, "Resetting data timestamp to default (to invalidate our synced data)");
- PreferenceManager.getDefaultSharedPreferences(context).edit().remove(
- SP_KEY_DATA_TIMESTAMP).commit();
- }
-
- /**
- * A type of ConsoleRequestLogger that does not log requests and responses.
- */
- private RequestLogger mQuietLogger = new ConsoleRequestLogger(){
- @Override
- public void logRequest(HttpURLConnection uc, Object content) throws IOException { }
-
- @Override
- public void logResponse(HttpResponse res) { }
- };
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java b/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java
deleted file mode 100644
index 965010329e..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/EventFeedbackApi.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.content.Context;
-
-import com.google.samples.apps.iosched.Config;
-
-import java.net.HttpURLConnection;
-import java.util.List;
-
-import com.turbomanage.httpclient.BasicHttpClient;
-import com.turbomanage.httpclient.HttpResponse;
-import com.turbomanage.httpclient.ParameterMap;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Created by lwray on 5/7/14.
- */
-public class EventFeedbackApi {
- private static final String TAG = makeLogTag(EventFeedbackApi.class);
-
-
- private static final String PARAMETER_EVENT_CODE = "code";
- private static final String PARAMETER_API_KEY = "apikey";
-
- private static final String PARAMETER_SESSION_ID = "objectid";
- private static final String PARAMETER_SURVEY_ID = "surveyId";
- private static final String PARAMETER_REGISTRANT_ID = "registrantKey";
- private final Context mContext;
- private final String mUrl;
-
- public EventFeedbackApi(Context context) {
- mContext = context;
- mUrl = Config.FEEDBACK_URL;
- }
-
- /**
- * Posts a session to the event server.
- *
- * @param sessionId The ID of the session that was reviewed.
- * @return whether or not updating succeeded
- */
- public boolean sendSessionToServer(String sessionId, List questions) {
-
- BasicHttpClient httpClient = new BasicHttpClient();
- httpClient.addHeader(PARAMETER_EVENT_CODE, Config.FEEDBACK_API_CODE);
- httpClient.addHeader(PARAMETER_API_KEY, Config.FEEDBACK_API_KEY);
-
- ParameterMap parameterMap = httpClient.newParams();
- parameterMap.add(PARAMETER_SESSION_ID, sessionId);
- parameterMap.add(PARAMETER_SURVEY_ID, Config.FEEDBACK_SURVEY_ID);
- parameterMap.add(PARAMETER_REGISTRANT_ID, Config.FEEDBACK_DUMMY_REGISTRANT_ID);
- int i = 1;
- for (String question : questions) {
- parameterMap.add("q" + i, question);
- i++;
- }
-
- HttpResponse response = httpClient.get(mUrl, parameterMap);
-
- if (response != null && response.getStatus() == HttpURLConnection.HTTP_OK) {
- LOGD(TAG, "Server returned HTTP_OK, so session posting was successful.");
- return true;
- } else {
- LOGE(TAG, "Error posting session: HTTP status " + response.getStatus());
- return false;
- }
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java
deleted file mode 100644
index 77df3da2e2..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/FeedbackSyncHelper.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Created by lwray on 5/8/14.
- */
-public class FeedbackSyncHelper {
- private static final String TAG = makeLogTag(FeedbackSyncHelper.class);
-
-
- Context mContext;
- EventFeedbackApi mEventFeedbackApi;
-
- FeedbackSyncHelper(Context context) {
- mContext = context;
- mEventFeedbackApi = new EventFeedbackApi(context);
-
- }
-
- public void sync() {
- final ContentResolver cr = mContext.getContentResolver();
- final Uri newFeedbackUri = ScheduleContract.Feedback.CONTENT_URI;
- Cursor c = cr.query(newFeedbackUri,
- null,
- ScheduleContract.Feedback.SYNCED + " = 0",
- null,
- null);
- LOGD(TAG, "Number of unsynced feedbacks: " + c.getCount());
- List questions = new ArrayList();
- List updatedSessions = new ArrayList();
-
- while (c.moveToNext()) {
- String sessionId = c.getString(c.getColumnIndex(ScheduleContract.Feedback.SESSION_ID));
-
- questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.SESSION_RATING)));
- questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_RELEVANCE)));
- questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_CONTENT)));
- questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.ANSWER_SPEAKER)));
- questions.add(c.getString(c.getColumnIndex(ScheduleContract.Feedback.COMMENTS)));
-
- if (mEventFeedbackApi.sendSessionToServer(sessionId, questions)) {
- LOGI(TAG, "Successfully updated session " + sessionId);
- updatedSessions.add(sessionId);
- } else {
- LOGE(TAG, "Couldn't update session " + sessionId);
- }
- }
-
- c.close();
-
- // Flip the "synced" flag to true for any successfully updated sessions, but leave them
- // in the database to prevent duplicate feedback
- ContentValues contentValues = new ContentValues();
- contentValues.put(ScheduleContract.Feedback.SYNCED, 1);
- for (String sessionId : updatedSessions) {
- cr.update(ScheduleContract.Feedback.buildFeedbackUri(sessionId), contentValues, null, null);
- }
-
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java b/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java
deleted file mode 100644
index 38f4c02c37..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/RemoteConferenceDataFetcher.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.content.Context;
-import android.text.TextUtils;
-
-import com.google.samples.apps.iosched.Config;
-import com.google.gson.Gson;
-import com.google.samples.apps.iosched.R;
-import com.google.samples.apps.iosched.io.model.DataManifest;
-import com.google.samples.apps.iosched.util.FileUtils;
-import com.google.samples.apps.iosched.util.HashUtils;
-import com.google.samples.apps.iosched.util.TimeUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.util.HashSet;
-import java.util.List;
-
-import com.turbomanage.httpclient.BasicHttpClient;
-import com.turbomanage.httpclient.ConsoleRequestLogger;
-import com.turbomanage.httpclient.HttpResponse;
-import com.turbomanage.httpclient.RequestLogger;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Helper class that fetches conference data from the remote server.
- */
-public class RemoteConferenceDataFetcher {
- private static final String TAG = makeLogTag(SyncHelper.class);
-
- // The directory under which we cache our downloaded files
- private static String CACHE_DIR = "data_cache";
-
- private Context mContext = null;
-
- // name of URL override file used for debug purposes
- private static final String URL_OVERRIDE_FILE_NAME = "iosched_manifest_url_override.txt";
-
- // URL of the remote manifest file
- private String mManifestUrl = null;
-
- // timestamp of the manifest file on the server
- private String mServerTimestamp = null;
-
- // the set of cache files we have used -- we use this for cache cleanup.
- private HashSet mCacheFilesToKeep = new HashSet();
-
- // total # of bytes downloaded (approximate)
- private long mBytesDownloaded = 0;
-
- // total # of bytes read from cache hits (approximate)
- private long mBytesReadFromCache = 0;
-
- public RemoteConferenceDataFetcher(Context context) {
- mContext = context;
- mManifestUrl = getManifestUrl();
- }
-
- /**
- * Fetches data from the remote server.
- *
- * @param refTimestamp The timestamp of the data to use as a reference; if the remote data
- * is not newer than this timestamp, no data will be downloaded and
- * this method will return null.
- *
- * @return The data downloaded, or null if there is no data to download
- * @throws IOException if an error occurred during download.
- */
- public String[] fetchConferenceDataIfNewer(String refTimestamp) throws IOException {
- if (TextUtils.isEmpty(mManifestUrl)) {
- LOGW(TAG, "Manifest URL is empty (remote sync disabled!).");
- return null;
- }
-
- BasicHttpClient httpClient = new BasicHttpClient();
- httpClient.setRequestLogger(mQuietLogger);
-
- // Only download if data is newer than refTimestamp
- // Cloud Storage is very picky with the If-Modified-Since format. If it's in a wrong
- // format, it refuses to serve the file, returning 400 HTTP error. So, if the
- // refTimestamp is in a wrong format, we simply ignore it. But pay attention to this
- // warning in the log, because it might mean unnecessary data is being downloaded.
- if (!TextUtils.isEmpty(refTimestamp)) {
- if (TimeUtils.isValidFormatForIfModifiedSinceHeader(refTimestamp)) {
- httpClient.addHeader("If-Modified-Since", refTimestamp);
- } else {
- LOGW(TAG, "Could not set If-Modified-Since HTTP header. Potentially downloading " +
- "unnecessary data. Invalid format of refTimestamp argument: "+refTimestamp);
- }
- }
-
- HttpResponse response = httpClient.get(mManifestUrl, null);
- if (response == null) {
- LOGE(TAG, "Request for manifest returned null response.");
- throw new IOException("Request for data manifest returned null response.");
- }
-
- int status = response.getStatus();
- if (status == HttpURLConnection.HTTP_OK) {
- LOGD(TAG, "Server returned HTTP_OK, so new data is available.");
- mServerTimestamp = getLastModified(response);
- LOGD(TAG, "Server timestamp for new data is: " + mServerTimestamp);
- String body = response.getBodyAsString();
- if (TextUtils.isEmpty(body)) {
- LOGE(TAG, "Request for manifest returned empty data.");
- throw new IOException("Error fetching conference data manifest: no data.");
- }
- LOGD(TAG, "Manifest "+mManifestUrl+" read, contents: " + body);
- mBytesDownloaded += body.getBytes().length;
- return processManifest(body);
- } else if (status == HttpURLConnection.HTTP_NOT_MODIFIED) {
- // data on the server is not newer than our data
- LOGD(TAG, "HTTP_NOT_MODIFIED: data has not changed since " + refTimestamp);
- return null;
- } else {
- LOGE(TAG, "Error fetching conference data: HTTP status " + status);
- throw new IOException("Error fetching conference data: HTTP status " + status);
- }
- }
-
- // Returns the timestamp of the data downloaded from the server
- public String getServerDataTimestamp() {
- return mServerTimestamp;
- }
-
- /**
- * Returns the remote manifest file's URL. This is stored as a resource in the app,
- * but can be overriden by a file in the filesystem for debug purposes.
- * @return The URL of the remote manifest file.
- */
- private String getManifestUrl() {
-
- String manifestUrl = Config.MANIFEST_URL;
-
- // check for an override file
- File urlOverrideFile = new File(mContext.getFilesDir(), URL_OVERRIDE_FILE_NAME);
- if (urlOverrideFile.exists()) {
- try {
- String overrideUrl = FileUtils.readFileAsString(urlOverrideFile).trim();
- LOGW(TAG, "Debug URL override active: " + overrideUrl);
- return overrideUrl;
- } catch (IOException ex) {
- return manifestUrl;
- }
- } else {
- return manifestUrl;
- }
- }
-
- /**
- * Fetches a file from the cache/network, from an absolute or relative URL. If the
- * file is available in our cache, we read it from there; if not, we will
- * download it from the network and cache it.
- *
- * @param url The URL to fetch the file from. The URL may be absolute or relative; if
- * relative, it will be considered to be relative to the manifest URL.
- * @return The contents of the file.
- * @throws IOException If an error occurs.
- */
- private String fetchFile(String url) throws IOException {
- // If this is a relative url, consider it relative to the manifest URL
- if (!url.contains("://")) {
- if (TextUtils.isEmpty(mManifestUrl) || !mManifestUrl.contains("/")) {
- LOGE(TAG, "Could not build relative URL based on manifest URL.");
- return null;
- }
- int i = mManifestUrl.lastIndexOf('/');
- url = mManifestUrl.substring(0, i) + "/" + url;
- }
-
- LOGD(TAG, "Attempting to fetch: " + sanitizeUrl(url));
-
- // Check if we have it in our cache first
- String body = null;
- try {
- body = loadFromCache(url);
- if (!TextUtils.isEmpty(body)) {
- // cache hit
- mBytesReadFromCache += body.getBytes().length;
- mCacheFilesToKeep.add(getCacheKey(url));
- return body;
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- LOGE(TAG, "IOException getting file from cache.");
- // proceed anyway to attempt to download it from the network
- }
-
- // We don't have the file on cache, so download it
- LOGD(TAG, "Cache miss. Downloading from network: " + sanitizeUrl(url));
- BasicHttpClient client = new BasicHttpClient();
- client.setRequestLogger(mQuietLogger);
- HttpResponse response = client.get(url, null);
-
- if (response == null) {
- throw new IOException("Request for URL " + sanitizeUrl(url) + " returned null response.");
- }
-
- LOGD(TAG, "HTTP response " + response.getStatus());
- if (response.getStatus() == HttpURLConnection.HTTP_OK) {
- body = response.getBodyAsString();
- if (TextUtils.isEmpty(body)) {
- throw new IOException("Got empty response when attempting to fetch " +
- sanitizeUrl(url));
- }
- LOGD(TAG, "Successfully downloaded from network: " + sanitizeUrl(url));
- mBytesDownloaded += body.getBytes().length;
- writeToCache(url, body);
- mCacheFilesToKeep.add(getCacheKey(url));
- return body;
- } else {
- LOGE(TAG, "Failed to fetch from network: " + sanitizeUrl(url));
- throw new IOException("Request for URL " + sanitizeUrl(url) +
- " failed with HTTP error " + response.getStatus());
- }
- }
-
- /**
- * Returns the cache file where we store our cache of the response of the given URL.
- * @param url The URL for which to return the cache file.
- * @return The cache file.
- */
- private File getCacheFile(String url) {
- String cacheKey = getCacheKey(url);
- return new File(mContext.getCacheDir() + File.separator + CACHE_DIR + File.separator +
- cacheKey);
- }
-
- // Creates the cache directory, if it doesn't exist yet
- private void createCacheDir() throws IOException {
- File dir = new File(mContext.getCacheDir() + File.separator + CACHE_DIR);
- if (!dir.exists() && !dir.mkdir()) {
- throw new IOException("Failed to mkdir: " + dir);
- }
- }
-
-
- /**
- * Loads our cached content corresponding to the given URL.
- * @param url The URL for which to load the cached response.
- * @return The cached response corresponding to the URL; or null if the given URL
- * does not exist in our cache.
- * @throws IOException If there is an error reading the cache.
- */
- private String loadFromCache(String url) throws IOException {
- String cacheKey = getCacheKey(url);
- File cacheFile = getCacheFile(url);
- if (cacheFile.exists()) {
- LOGD(TAG, "Cache hit " + cacheKey + " for " + sanitizeUrl(url));
- return FileUtils.readFileAsString(cacheFile);
- } else {
- LOGD(TAG, "Cache miss " + cacheKey + " for " + sanitizeUrl(url));
- return null;
- }
- }
-
- /**
- * Writes a file to the cache.
- * @param url The URL from which the contents were retrieved.
- * @param body The contents retrieved from the given URL.
- * @throws IOException If there is a problem writing the file.
- */
- private void writeToCache(String url, String body) throws IOException {
- String cacheKey = getCacheKey(url);
- File cacheFile = getCacheFile(url);
- createCacheDir();
- FileUtils.writeFile(body, cacheFile);
- LOGD(TAG, "Wrote to cache " + cacheKey + " --> " + sanitizeUrl(url));
- }
-
- /**
- * Returns the cache key to be used to store the given URL. The cache key is the
- * file name under which the contents of the URL are stored.
- * @param url The URL.
- * @return The cache key (guaranteed to be a valid filename)
- */
- private String getCacheKey(String url) {
- return HashUtils.computeWeakHash(url.trim()) + String.format("%04x", url.length());
- }
-
- // Sanitize a URL for logging purposes (only the last component is left visible).
- private String sanitizeUrl(String url) {
- int i = url.lastIndexOf('/');
- if (i >= 0 && i < url.length()) {
- return url.substring(0, i).replaceAll("[A-za-z]", "*") +
- url.substring(i);
- }
- else return url.replaceAll("[A-za-z]", "*");
- }
-
- private static final String MANIFEST_FORMAT = "iosched-json-v1";
-
- /**
- * Process the data manifest and download data files referenced from it.
- * @param manifestJson The JSON of the manifest file.
- * @return The contents of the set of files referenced from the manifest, or null
- * if none could be retrieved.
- * @throws IOException If an error occurs while retrieving information.
- */
- private String[] processManifest(String manifestJson) throws IOException {
- LOGD(TAG, "Processing data manifest, length " + manifestJson.length());
-
- DataManifest manifest = new Gson().fromJson(manifestJson, DataManifest.class);
- if (manifest.format == null || !manifest.format.equals(MANIFEST_FORMAT)) {
- LOGE(TAG, "Manifest has invalid format spec: " + manifest.format);
- throw new IOException("Invalid format spec on manifest:" + manifest.format);
- }
-
- if (manifest.data_files == null || manifest.data_files.length == 0) {
- LOGW(TAG, "Manifest does not list any files. Nothing done.");
- return null;
- }
-
- LOGD(TAG, "Manifest lists " + manifest.data_files.length + " data files.");
- String[] jsons = new String[manifest.data_files.length];
- for (int i = 0; i < manifest.data_files.length; i++) {
- String url = manifest.data_files[i];
- LOGD(TAG, "Processing data file: " + sanitizeUrl(url));
- jsons[i] = fetchFile(url);
- if (TextUtils.isEmpty(jsons[i])) {
- LOGE(TAG, "Failed to fetch data file: " + sanitizeUrl(url));
- throw new IOException("Failed to fetch data file " + sanitizeUrl(url));
- }
- }
-
- LOGD(TAG, "Got " + jsons.length + " data files.");
- cleanUpCache();
- return jsons;
- }
-
- // Delete unnecessary files from our cache
- private void cleanUpCache() {
- LOGD(TAG, "Starting cache cleanup, " + mCacheFilesToKeep.size() + " URLs to keep.");
- File dir = new File(mContext.getCacheDir() + File.separator + CACHE_DIR);
- if (!dir.exists()) {
- LOGD(TAG, "Cleanup complete (there is no cache).");
- return;
- }
-
- int deleted = 0, kept = 0;
- for (File file : dir.listFiles()) {
- if (mCacheFilesToKeep.contains(file.getName())) {
- LOGD(TAG, "Cache cleanup: KEEEPING " + file.getName());
- ++kept;
- } else {
- LOGD(TAG, "Cache cleanup: DELETING " + file.getName());
- file.delete();
- ++deleted;
- }
- }
-
- LOGD(TAG, "End of cache cleanup. " + kept + " files kept, " + deleted + " deleted.");
- }
-
- public long getTotalBytesDownloaded() {
- return mBytesDownloaded;
- }
-
- public long getTotalBytesReadFromCache() {
- return mBytesReadFromCache;
- }
-
- private String getLastModified(HttpResponse resp) {
- if (!resp.getHeaders().containsKey("Last-Modified")) {
- return "";
- }
-
- List s = resp.getHeaders().get("Last-Modified");
- return s.isEmpty() ? "" : s.get(0);
- }
-
- /**
- * A type of ConsoleRequestLogger that does not log requests and responses.
- */
- private RequestLogger mQuietLogger = new ConsoleRequestLogger(){
- @Override
- public void logRequest(HttpURLConnection uc, Object content) throws IOException { }
-
- @Override
- public void logResponse(HttpResponse res) { }
- };
-
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.java
deleted file mode 100755
index 9c16ee6041..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncAdapter.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.accounts.Account;
-import android.content.*;
-import android.os.Bundle;
-
-import com.google.samples.apps.iosched.BuildConfig;
-
-import java.util.regex.Pattern;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * Sync adapter for Google I/O data
- */
-public class SyncAdapter extends AbstractThreadedSyncAdapter {
- private static final String TAG = makeLogTag(SyncAdapter.class);
-
- private static final Pattern sSanitizeAccountNamePattern = Pattern.compile("(.).*?(.?)@");
- public static final String EXTRA_SYNC_USER_DATA_ONLY = "com.google.samples.apps.iosched.EXTRA_SYNC_USER_DATA_ONLY";;
-
- private final Context mContext;
-
- public SyncAdapter(Context context, boolean autoInitialize) {
- super(context, autoInitialize);
- mContext = context;
-
- //noinspection ConstantConditions,PointlessBooleanExpression
- if (!BuildConfig.DEBUG) {
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(Thread thread, Throwable throwable) {
- LOGE(TAG, "Uncaught sync exception, suppressing UI in release build.",
- throwable);
- }
- });
- }
- }
-
- @Override
- public void onPerformSync(final Account account, Bundle extras, String authority,
- final ContentProviderClient provider, final SyncResult syncResult) {
- final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
- final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- final boolean initialize = extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- final boolean userDataOnly = extras.getBoolean(EXTRA_SYNC_USER_DATA_ONLY, false);
-
- final String logSanitizedAccountName = sSanitizeAccountNamePattern
- .matcher(account.name).replaceAll("$1...$2@");
-
- if (uploadOnly) {
- return;
- }
-
- LOGI(TAG, "Beginning sync for account " + logSanitizedAccountName + "," +
- " uploadOnly=" + uploadOnly +
- " manualSync=" + manualSync +
- " userDataOnly =" + userDataOnly +
- " initialize=" + initialize);
-
- // Sync from bootstrap and remote data, as needed
- new SyncHelper(mContext).performSync(syncResult, account, extras);
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java
deleted file mode 100644
index 380e2226eb..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncHelper.java
+++ /dev/null
@@ -1,372 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.accounts.Account;
-import android.content.*;
-import android.net.ConnectivityManager;
-import android.os.Bundle;
-
-import com.google.samples.apps.iosched.Config;
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.service.SessionAlarmService;
-import com.google.samples.apps.iosched.service.SessionCalendarService;
-import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper;
-import com.google.samples.apps.iosched.sync.userdata.UserDataSyncHelperFactory;
-import com.google.samples.apps.iosched.util.AccountUtils;
-import com.google.samples.apps.iosched.util.PrefUtils;
-import com.google.samples.apps.iosched.util.UIUtils;
-
-import java.io.IOException;
-
-import static com.google.samples.apps.iosched.util.LogUtils.*;
-
-/**
- * A helper class for dealing with conference data synchronization.
- * All operations occur on the thread they're called from, so it's best to wrap
- * calls in an {@link android.os.AsyncTask}, or better yet, a
- * {@link android.app.Service}.
- */
-public class SyncHelper {
- private static final String TAG = makeLogTag("SyncHelper");
-
- private Context mContext;
- private ConferenceDataHandler mConferenceDataHandler;
- private RemoteConferenceDataFetcher mRemoteDataFetcher;
-
- public SyncHelper(Context context) {
- mContext = context;
- mConferenceDataHandler = new ConferenceDataHandler(mContext);
- mRemoteDataFetcher = new RemoteConferenceDataFetcher(mContext);
- }
-
- public static void requestManualSync(Account mChosenAccount) {
- requestManualSync(mChosenAccount, false);
- }
- public static void requestManualSync(Account mChosenAccount, boolean userDataSyncOnly) {
- if (mChosenAccount != null) {
- LOGD(TAG, "Requesting manual sync for account " + mChosenAccount.name
- +" userDataSyncOnly="+userDataSyncOnly);
- Bundle b = new Bundle();
- b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
- b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
- if (userDataSyncOnly) {
- b.putBoolean(SyncAdapter.EXTRA_SYNC_USER_DATA_ONLY, true);
- }
- ContentResolver.setSyncAutomatically(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, true);
- ContentResolver.setIsSyncable(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, 1);
-
- boolean pending = ContentResolver.isSyncPending(mChosenAccount,
- ScheduleContract.CONTENT_AUTHORITY);
- if (pending) {
- LOGD(TAG, "Warning: sync is PENDING. Will cancel.");
- }
- boolean active = ContentResolver.isSyncActive(mChosenAccount,
- ScheduleContract.CONTENT_AUTHORITY);
- if (active) {
- LOGD(TAG, "Warning: sync is ACTIVE. Will cancel.");
- }
-
- if (pending || active) {
- LOGD(TAG, "Cancelling previously pending/active sync.");
- ContentResolver.cancelSync(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY);
- }
-
- LOGD(TAG, "Requesting sync now.");
- ContentResolver.requestSync(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, b);
- } else {
- LOGD(TAG, "Can't request manual sync -- no chosen account.");
- }
- }
-
- /**
- * Attempts to perform conference data synchronization. The data comes from the remote URL
- * configured in {@link com.google.samples.apps.iosched.Config#MANIFEST_URL}. The remote URL
- * must point to a manifest file that, in turn, can reference other files. For more details
- * about conference data synchronization, refer to the documentation at
- * http://code.google.com/p/iosched.
- *
- * @param syncResult (optional) the sync result object to update with statistics.
- * @param account the account associated with this sync
- * @return Whether or not the synchronization made any changes to the data.
- */
- public boolean performSync(SyncResult syncResult, Account account, Bundle extras) {
- boolean dataChanged = false;
-
- if (!PrefUtils.isDataBootstrapDone(mContext)) {
- LOGD(TAG, "Sync aborting (data bootstrap not done yet)");
- return false;
- }
-
- long lastAttemptTime = PrefUtils.getLastSyncAttemptedTime(mContext);
- long now = UIUtils.getCurrentTime(mContext);
- long timeSinceAttempt = now - lastAttemptTime;
- final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- final boolean userDataOnly = extras.getBoolean(SyncAdapter.EXTRA_SYNC_USER_DATA_ONLY, false);
-
- if (!manualSync && timeSinceAttempt >= 0 && timeSinceAttempt < Config.MIN_INTERVAL_BETWEEN_SYNCS) {
- /*
- Code removed because it was causing a runaway sync; probably because we are setting
- syncResult.delayUntil incorrectly.
-
- Random r = new Random();
- long toWait = 10000 + r.nextInt(30000) // random jitter between 10 - 40 seconds
- + Config.MIN_INTERVAL_BETWEEN_SYNCS - timeSinceAttempt;
- LOGW(TAG, "Sync throttled!! Another sync was attempted just " + timeSinceAttempt
- + "ms ago. Requesting delay of " + toWait + "ms.");
- syncResult.fullSyncRequested = true;
- syncResult.delayUntil = (System.currentTimeMillis() + toWait) / 1000L;
- return false;*/
- }
-
- LOGI(TAG, "Performing sync for account: " + account);
- PrefUtils.markSyncAttemptedNow(mContext);
- long opStart;
- long remoteSyncDuration, choresDuration;
-
- opStart = System.currentTimeMillis();
-
- // remote sync consists of these operations, which we try one by one (and tolerate
- // individual failures on each)
- final int OP_REMOTE_SYNC = 0;
- final int OP_USER_SCHEDULE_SYNC = 1;
- final int OP_USER_FEEDBACK_SYNC = 2;
-
- int[] opsToPerform = userDataOnly ?
- new int[] { OP_USER_SCHEDULE_SYNC } :
- new int[] { OP_REMOTE_SYNC, OP_USER_SCHEDULE_SYNC, OP_USER_FEEDBACK_SYNC};
-
-
- for (int op : opsToPerform) {
- try {
- switch (op) {
- case OP_REMOTE_SYNC:
- dataChanged |= doRemoteSync();
- break;
- case OP_USER_SCHEDULE_SYNC:
- dataChanged |= doUserScheduleSync(account.name);
- break;
- case OP_USER_FEEDBACK_SYNC:
- doUserFeedbackSync();
- break;
- }
- } catch (AuthException ex) {
- syncResult.stats.numAuthExceptions++;
-
- // if we have a token, try to refresh it
- if (AccountUtils.hasToken(mContext, account.name)) {
- AccountUtils.refreshAuthToken(mContext);
- } else {
- LOGW(TAG, "No auth token yet for this account. Skipping remote sync.");
- }
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- LOGE(TAG, "Error performing remote sync.");
- increaseIoExceptions(syncResult);
- }
- }
- remoteSyncDuration = System.currentTimeMillis() - opStart;
-
- // If data has changed, there are a few chores we have to do
- opStart = System.currentTimeMillis();
- if (dataChanged) {
- try {
- performPostSyncChores(mContext);
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- LOGE(TAG, "Error performing post sync chores.");
- }
- }
- clearExpertsIfNecessary();
- choresDuration = System.currentTimeMillis() - opStart;
-
- int operations = mConferenceDataHandler.getContentProviderOperationsDone();
- if (syncResult != null && syncResult.stats != null) {
- syncResult.stats.numEntries += operations;
- syncResult.stats.numUpdates += operations;
- }
-
- if (dataChanged) {
- long totalDuration = choresDuration + remoteSyncDuration;
- LOGD(TAG, "SYNC STATS:\n" +
- " * Account synced: " + (account == null ? "null" : account.name) + "\n" +
- " * Content provider operations: " + operations + "\n" +
- " * Remote sync took: " + remoteSyncDuration + "ms\n" +
- " * Post-sync chores took: " + choresDuration + "ms\n" +
- " * Total time: " + totalDuration + "ms\n" +
- " * Total data read from cache: \n" +
- (mRemoteDataFetcher.getTotalBytesReadFromCache() / 1024) + "kB\n" +
- " * Total data downloaded: \n" +
- (mRemoteDataFetcher.getTotalBytesDownloaded() / 1024) + "kB");
- }
-
- LOGI(TAG, "End of sync (" + (dataChanged ? "data changed" : "no data change") + ")");
-
- updateSyncInterval(mContext, account);
-
- return dataChanged;
- }
-
- public static void performPostSyncChores(final Context context) {
- // Update search index
- LOGD(TAG, "Updating search index.");
- context.getContentResolver().update(ScheduleContract.SearchIndex.CONTENT_URI,
- new ContentValues(), null, null);
-
- // Sync calendars
- LOGD(TAG, "Session data changed. Syncing starred sessions with Calendar.");
- syncCalendar(context);
- }
-
- private static void syncCalendar(Context context) {
- Intent intent = new Intent(SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR);
- intent.setClass(context, SessionCalendarService.class);
- context.startService(intent);
- }
-
- private void doUserFeedbackSync() {
- LOGD(TAG, "Syncing feedback");
- new FeedbackSyncHelper(mContext).sync();
- }
-
- /**
- * Checks if the remote server has new data that we need to import. If so, download
- * the new data and import it into the database.
- *
- * @return Whether or not data was changed.
- * @throws IOException if there is a problem downloading or importing the data.
- */
- private boolean doRemoteSync() throws IOException {
- if (!isOnline()) {
- LOGD(TAG, "Not attempting remote sync because device is OFFLINE");
- return false;
- }
-
- LOGD(TAG, "Starting remote sync.");
-
- // Fetch the remote data files via RemoteConferenceDataFetcher
- String[] dataFiles = mRemoteDataFetcher.fetchConferenceDataIfNewer(
- mConferenceDataHandler.getDataTimestamp());
-
- if (dataFiles != null) {
- LOGI(TAG, "Applying remote data.");
- // save the remote data to the database
- mConferenceDataHandler.applyConferenceData(dataFiles,
- mRemoteDataFetcher.getServerDataTimestamp(), true);
- LOGI(TAG, "Done applying remote data.");
-
- // mark that conference data sync succeeded
- PrefUtils.markSyncSucceededNow(mContext);
- return true;
- } else {
- // no data to process (everything is up to date)
-
- // mark that conference data sync succeeded
- PrefUtils.markSyncSucceededNow(mContext);
- return false;
- }
- }
-
- /**
- * Checks if there are changes on MySchedule to sync with/from remote AppData folder.
- *
- * @return Whether or not data was changed.
- * @throws IOException if there is a problem uploading the data.
- */
- private boolean doUserScheduleSync(String accountName) throws IOException {
- if (!isOnline()) {
- LOGD(TAG, "Not attempting myschedule sync because device is OFFLINE");
- return false;
- }
-
- LOGD(TAG, "Starting user data (myschedule) sync.");
-
- AbstractUserDataSyncHelper helper = UserDataSyncHelperFactory.buildSyncHelper(
- mContext, accountName);
- boolean modified = helper.sync();
- if (modified) {
- // schedule notifications for the starred sessions
- Intent scheduleIntent = new Intent(
- SessionAlarmService.ACTION_SCHEDULE_ALL_STARRED_BLOCKS,
- null, mContext, SessionAlarmService.class);
- mContext.startService(scheduleIntent);
- }
- return modified;
- }
-
- // Returns whether we are connected to the internet.
- private boolean isOnline() {
- ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- return cm.getActiveNetworkInfo() != null &&
- cm.getActiveNetworkInfo().isConnectedOrConnecting();
- }
-
- private void increaseIoExceptions(SyncResult syncResult) {
- if (syncResult != null && syncResult.stats != null) {
- ++syncResult.stats.numIoExceptions;
- }
- }
-
- private void increaseSuccesses(SyncResult syncResult) {
- if (syncResult != null && syncResult.stats != null) {
- ++syncResult.stats.numEntries;
- ++syncResult.stats.numUpdates;
- }
- }
-
- private boolean clearExpertsIfNecessary() {
- if (Config.hasExpertsDirectoryExpired()) {
- return 0 < mContext.getContentResolver()
- .delete(ScheduleContract.Experts.CONTENT_URI, null, null);
- }
- return false;
- }
-
- public static class AuthException extends RuntimeException {
- }
-
-
- public static long calculateRecommendedSyncInterval(final Context context) {
- long now = UIUtils.getCurrentTime(context);
- long aroundConferenceStart = Config.CONFERENCE_START_MILLIS - Config.AUTO_SYNC_AROUND_CONFERENCE_THRESH;
- if (now < aroundConferenceStart) {
- return Config.AUTO_SYNC_INTERVAL_LONG_BEFORE_CONFERENCE;
- } else if (now <= Config.CONFERENCE_END_MILLIS) {
- return Config.AUTO_SYNC_INTERVAL_AROUND_CONFERENCE;
- } else {
- return Config.AUTO_SYNC_INTERVAL_AFTER_CONFERENCE;
- }
- }
-
- public static void updateSyncInterval(final Context context, final Account account) {
- LOGD(TAG, "Checking sync interval for " + account);
- long recommended = calculateRecommendedSyncInterval(context);
- long current = PrefUtils.getCurSyncInterval(context);
- LOGD(TAG, "Recommended sync interval " + recommended + ", current " + current);
- if (recommended != current) {
- LOGD(TAG, "Setting up sync for account " + account + ", interval " + recommended + "ms");
- ContentResolver.setIsSyncable(account, ScheduleContract.CONTENT_AUTHORITY, 1);
- ContentResolver.setSyncAutomatically(account, ScheduleContract.CONTENT_AUTHORITY, true);
- ContentResolver.addPeriodicSync(account, ScheduleContract.CONTENT_AUTHORITY,
- new Bundle(), recommended / 1000L);
- PrefUtils.setCurSyncInterval(context, recommended);
- } else {
- LOGD(TAG, "No need to update sync interval.");
- }
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java b/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java
deleted file mode 100755
index 5ca6c1f14c..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/SyncService.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Service that handles sync. It simply instantiates a SyncAdapter and returns its IBinder.
- */
-public class SyncService extends Service {
- private static final Object sSyncAdapterLock = new Object();
- private static SyncAdapter sSyncAdapter = null;
-
- @Override
- public void onCreate() {
- synchronized (sSyncAdapterLock) {
- if (sSyncAdapter == null) {
- sSyncAdapter = new SyncAdapter(getApplicationContext(), false);
- }
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return sSyncAdapter.getSyncAdapterBinder();
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java b/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java
deleted file mode 100644
index bfb6630281..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/TriggerSyncReceiver.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.accounts.Account;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.util.AccountUtils;
-
-/**
- * A simple {@link BroadcastReceiver} that triggers a sync. This is used by the GCM code to trigger
- * jittered syncs using {@link android.app.AlarmManager}.
- */
-public class TriggerSyncReceiver extends BroadcastReceiver {
- public static final String EXTRA_USER_DATA_SYNC_ONLY = "com.google.samples.apps.iosched.EXTRA_USER_DATA_SYNC_ONLY";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String accountName = AccountUtils.getActiveAccountName(context);
- if (TextUtils.isEmpty(accountName)) {
- return;
- }
- Account account = AccountUtils.getActiveAccount(context);
- if (account != null) {
- if (intent.getBooleanExtra(EXTRA_USER_DATA_SYNC_ONLY, false) ) {
- // this is a request to sync user data only, so do a manual sync right now
- // with the userDataOnly == true.
- SyncHelper.requestManualSync(account, true);
- } else {
- // this is a request to sync everything
- ContentResolver.requestSync(account, ScheduleContract.CONTENT_AUTHORITY, new Bundle());
- }
- }
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java
deleted file mode 100644
index 5be93e098e..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/UserScheduleSyncHelper.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync;
-
-import android.content.Context;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGI;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-
-public class UserScheduleSyncHelper {
- private Context mContext;
- private static final String TAG = makeLogTag(SyncHelper.class);
-
- public UserScheduleSyncHelper(Context ctx) {
- mContext = ctx;
- }
-
- public void updateUserSchedule(Context context, ArrayList sessionsToAdd,
- ArrayList sessionsToRemove) throws IOException {
- LOGI(TAG, "Updating session on user schedule: add "+sessionsToAdd.size()+
- " and remove "+sessionsToRemove.size()+" sessions");
- /*
- Googledevelopers conferenceAPI = getConferenceAPIClient();
- try {
- sendScheduleUpdate(conferenceAPI, context, sessionId, inSchedule);
- } catch (GoogleJsonResponseException e) {
- if (e.getDetails().getCode() == 401) {
- LOGI(TAG, "Unauthorized; getting a new auth token.", e);
- AccountUtils.refreshAuthToken(mContext);
- // Try request one more time with new credentials before giving up
- conferenceAPI = getConferenceAPIClient();
- sendScheduleUpdate(conferenceAPI, context, sessionId, inSchedule);
- }
- }
- */
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java
deleted file mode 100644
index 449f4c8bcf..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/AbstractUserDataSyncHelper.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata;
-
-import android.content.*;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-
-import com.google.samples.apps.iosched.appwidget.ScheduleWidgetProvider;
-import com.google.samples.apps.iosched.gcm.ServerUtilities;
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.provider.ScheduleContract.MySchedule;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.LOGW;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-
-/**
- * Helper class that syncs starred sessions data in a Drive's AppData folder.
- *
- * Protocode:
- *
- * // when user clicks on "star":
- * session UI: run updateSession()
- * this.updateSession():
- * send addstar/removestar to contentProvider
- * send broadcast to update any dependent UI
- * save user actions as pending in shared preferences
- *
- * // on sync
- * syncadapter: call this.sync()
- * this.sync():
- * fetch remote content
- * if pending actions:
- * apply to content and update remote
- * if modified content != last synced content:
- * update contentProvider
- * send broadcast to update any dependent UI
- *
- *
- */
-public abstract class AbstractUserDataSyncHelper {
- private static final String TAG = makeLogTag(AbstractUserDataSyncHelper.class);
-
- protected Context mContext;
- protected String mAccountName;
-
- public AbstractUserDataSyncHelper(Context context, String accountName) {
- this.mContext = context;
- this.mAccountName = accountName;
- }
-
- protected abstract boolean syncImpl(List actions, boolean hasPendingLocalData);
-
- /**
- * Create a copy of current pending actions and delegate the
- * proper sync'ing to the concrete subclass on the method syncImpl.
- *
- */
- public boolean sync() {
- // get data pending sync:
- Cursor scheduleData = mContext.getContentResolver().query(
- MySchedule.buildMyScheduleUri(mContext, mAccountName), MyScheduleQuery.PROJECTION,
- null, null, null);
-
- if (scheduleData == null) {
- return false;
- }
-
- // Although we have a dirty flag per item, we need all schedule to sync, because it's all
- // sync'ed at once to a file on AppData folder. We only use the dirty flag to decide if
- // the local content was changed or not. If it was, we replace the remote content.
- boolean hasPendingLocalData = false;
- ArrayList actions = new ArrayList();
- while (scheduleData.moveToNext()) {
-
- UserAction userAction = new UserAction();
- userAction.sessionId = scheduleData.getString(MyScheduleQuery.SESSION_ID);
- Integer inSchedule = scheduleData.getInt(MyScheduleQuery.IN_SCHEDULE);
- if (inSchedule == 0) {
- userAction.type = UserAction.TYPE.REMOVE_STAR;
- } else {
- userAction.type = UserAction.TYPE.ADD_STAR;
- }
- userAction.requiresSync = scheduleData.getInt(MyScheduleQuery.DIRTY_FLAG) == 1;
- actions.add(userAction);
- if (!hasPendingLocalData && userAction.requiresSync) {
- hasPendingLocalData = true;
- }
- }
- scheduleData.close();
-
- Log.d(TAG, "Starting Drive AppData sync. hasPendingData = " + hasPendingLocalData);
-
- boolean dataChanged = syncImpl(actions, hasPendingLocalData);
-
- if (hasPendingLocalData) {
- resetDirtyFlag(actions);
-
- // Notify other devices via GCM
- ServerUtilities.notifyUserDataChanged(mContext);
- }
- if (dataChanged) {
- LOGD(TAG, "Notifying changes on paths related to user data on Content Resolver.");
- ContentResolver resolver = mContext.getContentResolver();
- for (String path : ScheduleContract.USER_DATA_RELATED_PATHS) {
- Uri uri = ScheduleContract.BASE_CONTENT_URI.buildUpon().appendPath(path).build();
- resolver.notifyChange(uri, null);
- }
- mContext.sendBroadcast(ScheduleWidgetProvider.getRefreshBroadcastIntent(mContext, false));
- }
- return dataChanged;
- }
-
- private void resetDirtyFlag(ArrayList actions) {
- ArrayList ops = new ArrayList();
- for (UserAction action: actions) {
- ContentProviderOperation op = ContentProviderOperation.newUpdate(
- ScheduleContract.addCallerIsSyncAdapterParameter(
- MySchedule.buildMyScheduleUri(mContext, mAccountName)))
- .withSelection(MySchedule.SESSION_ID + "=? AND " +
- MySchedule.MY_SCHEDULE_IN_SCHEDULE + "=?",
- new String[]{action.sessionId,
- action.type == UserAction.TYPE.ADD_STAR ? "1" : "0"})
- .withValue(MySchedule.MY_SCHEDULE_DIRTY_FLAG, 0)
- .build();
- LOGD(TAG, op.toString());
- ops.add(op);
- }
- try {
- ContentProviderResult[] result = mContext.getContentResolver().applyBatch(
- ScheduleContract.CONTENT_AUTHORITY, ops);
- LOGD(TAG, "Result of cleaning dirty flags is "+ Arrays.toString(result));
- } catch (Exception ex) {
- LOGW(TAG, "Could not update dirty flags. Ignoring.", ex);
- }
- }
-
- private interface MyScheduleQuery {
-
- String[] PROJECTION = {
- MySchedule.SESSION_ID,
- MySchedule.MY_SCHEDULE_IN_SCHEDULE,
- MySchedule.MY_SCHEDULE_DIRTY_FLAG,
- };
-
- int SESSION_ID = 0;
- int IN_SCHEDULE= 1;
- int DIRTY_FLAG = 2;
- }
-
-}
\ No newline at end of file
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java
deleted file mode 100644
index d1378beb2d..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/OnSuccessListener.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata;
-
-public interface OnSuccessListener {
- void onSuccess();
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java
deleted file mode 100644
index bd8640be09..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserAction.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata;
-
-public class UserAction {
- public enum TYPE {
- ADD_STAR, REMOVE_STAR;
- };
-
- public UserAction() {
- }
-
- public UserAction(TYPE type, String sessionId) {
- this.type = type;
- this.sessionId = sessionId;
- }
-
- public TYPE type;
- public String sessionId;
- public String accountName;
- public boolean requiresSync;
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java
deleted file mode 100644
index 71a3ded368..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/UserDataSyncHelperFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata;
-
-import android.content.Context;
-
-import com.google.samples.apps.iosched.sync.userdata.http.HTTPUserDataSyncHelper;
-
-
-/**
- * A simple factory to isolate the decision of which synchelper should be used.
- *
- * Currently, the HTTP sync helper is always returned, because of the early stage of
- * the GMS version. In the future, this can be changed.
- *
-**/
-
-public class UserDataSyncHelperFactory {
- public static AbstractUserDataSyncHelper buildSyncHelper(Context context, String accountName) {
- return new HTTPUserDataSyncHelper(context, accountName);
- }
-}
\ No newline at end of file
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java
deleted file mode 100644
index 7e1ad718e7..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/ApiClientAsyncTask.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.gms;
-
-import com.google.samples.apps.iosched.util.AccountUtils;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
-import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
-import com.google.android.gms.drive.Drive;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.IntentSender;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * An AsyncTask that maintains a connected client.
- */
-public abstract class ApiClientAsyncTask
- extends AsyncTask {
-
- private static final String TAG = makeLogTag(ApiClientAsyncTask.class);
- private static final int REQUEST_CODE_RESOLUTION = 1;
-
- private GoogleApiClient mClient;
- private Context mContext;
- private String lastUsedAccountName;
-
- public ApiClientAsyncTask(Context context) {
- this.mContext = context;
- }
-
- @Override
- protected final Result doInBackground(Params... params) {
- Log.d(TAG, "doInBackground of ApiClientAsyncTask");
-
- getGoogleApiClient();
-
- final CountDownLatch latch = new CountDownLatch(1);
- mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
- @Override
- public void onConnectionSuspended(int cause) {
- }
-
- @Override
- public void onConnected(Bundle arg0) {
- Log.d(TAG, "ApiClientAsyncTask onConnected");
- latch.countDown();
- }
- });
- mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
- @Override
- public void onConnectionFailed(ConnectionResult result) {
- Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
- if (!result.hasResolution()) {
- // show the localized error dialog.
- GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
- (Activity) ApiClientAsyncTask.this.getContext(), 0).show();
- return;
- }
- try {
- result.startResolutionForResult((Activity) ApiClientAsyncTask.this.getContext(), REQUEST_CODE_RESOLUTION);
- } catch (IntentSender.SendIntentException e) {
- Log.e(TAG, "Exception while starting resolution activity", e);
- }
- latch.countDown();
- }
- });
- mClient.connect();
- try {
- latch.await();
- } catch (InterruptedException e) {
- return null;
- }
- if (!mClient.isConnected()) {
- return null;
- }
- try {
- return doInBackgroundConnected(params);
- } catch (RuntimeException e) {
- Log.e(TAG, "ApiClientAsyncTask exception on doInBackgroundConnected!", e);
- throw e;
- } finally {
- mClient.disconnect();
- }
- }
-
- /**
- * Override this method to perform a computation on a background thread, while the client is
- * connected.
- */
- protected abstract Result doInBackgroundConnected(Params... params);
-
- /**
- * Gets the GoogleApliClient owned by this async task.
- */
- protected GoogleApiClient getGoogleApiClient() {
- String currentAccountName = AccountUtils.getActiveAccountName(mContext);
- if (lastUsedAccountName != null &&
- !lastUsedAccountName.equals(currentAccountName)) {
- if (mClient != null && mClient.isConnected()) {
- mClient.disconnect();
- }
- mClient = null;
- lastUsedAccountName = currentAccountName;
- }
- if (mClient == null) {
- GoogleApiClient.Builder builder = new GoogleApiClient.Builder(mContext)
- .addApi(Drive.API)
- .setAccountName(currentAccountName)
- .addScope(Drive.SCOPE_APPFOLDER);
- mClient = builder.build();
- }
- mClient.connect();
- return mClient;
- }
-
- public Context getContext() {
- return mContext;
- }
-}
\ No newline at end of file
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java
deleted file mode 100644
index 64c9cc8ade..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveAppAsyncTask.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.gms;
-
-import android.content.Context;
-
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.drive.*;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- *
- * Async task that syncs a file with the Drive AppData folder.
- *
- **/
-public class DriveAppAsyncTask extends ApiClientAsyncTask {
-
- public DriveAppAsyncTask(Context context) {
- super(context);
- }
-
- /*
- private static final String TAG = makeLogTag(DriveAppAsyncTask.class);
- private SyncParams mParams;
-
- protected String getDriveID() {
- return mParams == null ? null : mParams.getDriveId();
- }
-
- protected Date getLastModifiedDate() {
- return mParams == null ? null : mParams.getLastModifiedDate();
- }
-
-*/
- @Override
- protected Boolean doInBackgroundConnected(Void... params) {
- return true;
- }
- /*
- Log.d(TAG, "on DriveAppAsyncTask");
- this.mParams = params[0];
- GoogleApiClient apiClient = getGoogleApiClient();
- Set ourContent = UserDataHelper.getLocalStarredSessionIDs(getContext());
-
- boolean requiresUIRefresh = false;
-
- DriveId currentDriveId = mParams.getDriveId() == null ? null : DriveId.decodeFromString(mParams.getDriveId());
- DriveFile file = DriveHelper.lookupDriveFile(currentDriveId, apiClient);
- mParams.setDriveId(file == null ? null : file.getDriveId().encodeToString());
-
-
- try {
- if (file == null) {
- // File doesn't exist in Drive
- Log.d(TAG, "Creating file on Drive");
- DriveHelper.createNewDriveFile(mParams, ourContent, apiClient);
-
- } else {
- // File exists in Drive
- boolean requiresCloudUpdate = false;
-
- /** It seems that, due to a bug on AppData GMS implementation, the metadata is not
- * being updated correctly when something changes in the cloud. So, we are removing
- * the fancy logic and keeping it to the bare minimum.
- // Compare last modified date
- Date lastModifiedCloud = metadata.getModifiedDate();
- Log.d(TAG, "Found file in Drive ID="+file.getDriveId()+
- " cloud_last_modified="+lastModifiedCloud+
- " local_last_modified="+param.getLastModifiedDate());
- int dataCmp = lastModifiedCloud.compareTo(param.getLastModifiedDate());
- Log.d(TAG, "dataCmp="+dataCmp+" hasPendingActions="+param.hasPendingActions());
-
- if (dataCmp > 0) {
- * * /
- // If cloud file is newer than ours, merge our content there
- Log.d(TAG, "File in cloud is newer than ours. Maybe merging.");
- Set cloudContent = DriveHelper.loadFromCloud(file, apiClient);
- if (mParams.hasPendingActions()) {
- // apply our pending actions on top of Drive contents
- Log.d(TAG, "Local pending actions, applying to the remote file.");
- if (!cloudContent.equals(ourContent)) {
- for (UserAction action: mParams.getPendingActions()) {
- Log.d(TAG, "Applying action "+action);
- if (action.type == UserAction.TYPE.ADD_STAR) {
- cloudContent.add(action.sessionId);
- } else {
- cloudContent.remove(action.sessionId);
- }
- }
- requiresCloudUpdate = true;
- }
- }
-
- if (!cloudContent.equals(ourContent)) {
- ourContent = cloudContent;
- UserDataHelper.setLocalStarredSessions(getContext(), cloudContent);
- requiresUIRefresh = true;
- }
-
- /**
- } else if (dataCmp < 0 || ( dataCmp == 0 && param.hasPendingActions())) {
- // Replace Drive contents with our content
- Log.d(TAG, "File in cloud is behind ours. Will replace it.");
- requiresCloudUpdate = true;
- }
- * * /
- if (requiresCloudUpdate) {
- DriveHelper.saveToDrive(file, ourContent, apiClient);
- //lastModifiedCloud = getLastModifiedDate(file, apiClient);
- }
- // param.setLastModifiedDate(lastModifiedCloud);
- mParams.setLastModifiedDate(new Date());
- }
-
- } catch (IOException e) {
- Log.e(TAG, "IOException while setting content to the new file", e);
- throw new RuntimeException("IOException while setting content to the new file", e);
- }
-
- return requiresUIRefresh;
- }
-
- private Date getLastModifiedDate(DriveFile file, GoogleApiClient apiClient) {
- DriveResource.MetadataResult result = file.getMetadata(apiClient).await();
- DriveHelper.checkStatus("Getting last modified date", result.getStatus());
- return result.getMetadata().getModifiedDate();
- }
-
-*/
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java
deleted file mode 100644
index d5c74798e9..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/DriveHelper.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.gms;
-
-import android.util.Log;
-
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.drive.*;
-import com.google.api.client.util.Charsets;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-import static com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper.*;
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-public class DriveHelper {
-
- private static final String TAG = makeLogTag(DriveHelper.class);
-
- // Constants related to the JSON serialization:
- private static final String MIMETYPE_JSON = "application/json";
- public static final String DRIVE_FILENAME = "starred_sessions.json";
-
- static void saveToDrive(DriveFile file, Set contents,
- GoogleApiClient apiClient) throws IOException {
- DriveApi.ContentsResult contentsResult = file.openContents(apiClient,
- DriveFile.MODE_WRITE_ONLY, null).await();
- checkStatus("Open file for writing", contentsResult.getStatus());
- FileOutputStream os = new FileOutputStream(contentsResult.getContents()
- .getParcelFileDescriptor().getFileDescriptor());
- byte[] serializedContents = toByteArray(contents);
- Log.d(TAG, "Saving contents to drive file: "+new String(serializedContents));
- os.write(serializedContents);
- com.google.android.gms.common.api.Status status =
- file.commitAndCloseContents(apiClient, contentsResult.getContents()).await();
- checkStatus("Commit file contents", status);
- }
-
- static public DriveFile lookupDriveFile(DriveId driveId, GoogleApiClient apiClient) {
- DriveFile result = null;
-
- // First, check if ID is valid
- if (driveId != null) {
- Log.d(TAG, "DriveID passed is not null, trying to get the corresponding file");
- try {
- result = Drive.DriveApi.getFile(apiClient, driveId);
- if (result != null) {
- // check if metadata is ok. For example, if the file has been directly removed from
- // the server, the getFile can return a file that is actually not valid. Hopefully
- // the metadata will get the correct info
- try {
- DriveResource.MetadataResult metadataResult = result.getMetadata(apiClient).await();
- if (!metadataResult.getStatus().isSuccess()) {
- result = null;
- }
- } catch (Exception ex) {
- result = null;
- }
- }
- } catch (Exception e) {
- Log.d(TAG, "Saved drive ID "+driveId+" seems to be invalid (message: " +
- e.getMessage()+"). Ignoring it");
- result = null;
- }
- }
-
- if (result == null) {
- // search for a file with the expected name (and get the most recent one, if many)
- Log.d(TAG, "DriveID passed is null, looking up for a file named "+DRIVE_FILENAME);
- Metadata metaOfMostRecent = null;
- MetadataBuffer buffer = Drive.DriveApi.getAppFolder(apiClient)
- .listChildren(apiClient).await().getMetadataBuffer();
- Log.d(TAG, "Found "+buffer.getCount()+" files");
- for (Metadata metadata: buffer) {
- if (metaOfMostRecent != null) {
- Log.w(TAG, "Warning, found more than one file named "+DRIVE_FILENAME+
- " in AppData folder. Using the most recently modified.");
- }
- if (metaOfMostRecent == null || metaOfMostRecent
- .getModifiedDate().compareTo(metadata.getModifiedDate())<0) {
- metaOfMostRecent = metadata;
- }
- }
- if (metaOfMostRecent != null) {
- driveId = metaOfMostRecent.getDriveId();
- result = Drive.DriveApi.getFile(apiClient, driveId);
- }
- buffer.close();
- }
-
- return result;
- }
-
- static void createNewDriveFile(Set contents,
- GoogleApiClient apiClient) throws IOException {
-
- DriveApi.ContentsResult contentsResult = Drive.DriveApi.newContents(apiClient).await();
- checkStatus("creating new file", contentsResult.getStatus());
-
- // query Drive for an AppFolder reference (might be slow: ~4s in my tests)
- DriveFolder appDataFolder = Drive.DriveApi.getAppFolder(apiClient);
-
- // create a new file in AppFolder
- MetadataChangeSet metadataChangeSet =
- new MetadataChangeSet.Builder()
- .setMimeType(MIMETYPE_JSON)
- .setTitle(DRIVE_FILENAME)
- .build();
- Contents contentsObj = contentsResult.getContents();
-
- FileOutputStream os = new FileOutputStream(contentsObj.getParcelFileDescriptor().getFileDescriptor());
- os.write(toByteArray(contents));
-
- DriveFolder.DriveFileResult fileResult = appDataFolder.createFile(
- apiClient, metadataChangeSet, contentsResult.getContents()).await();
-
- Log.d(TAG, "Content saved to new Drive file: "+new String(toByteArray(contents),
- Charsets.UTF_8));
- checkStatus("saving contents to new file", fileResult.getStatus());
-
- // DON'T DO THIS: It seems that a bug makes this driveID being unusable later:
- // params.setDriveId(fileResult.getDriveFile().getDriveId());
- }
-
- static public void checkStatus(String message, com.google.android.gms.common.api.Status status) {
- if (!status.isSuccess()) {
- throw new RuntimeException("Error "+status.getStatusCode()+" on "+message);
- }
- }
-
- static public Set loadFromCloud(DriveFile file, GoogleApiClient apiClient)
- throws IOException {
- DriveApi.ContentsResult contentsResult = file.openContents(apiClient,
- DriveFile.MODE_READ_ONLY, null).await();
- checkStatus("Open file for reading", contentsResult.getStatus());
-
- HashSet result = new HashSet();
- try {
- FileInputStream is = new FileInputStream(contentsResult.getContents()
- .getParcelFileDescriptor().getFileDescriptor());
- String contents = fromStreamToString(is);
- file.discardContents(apiClient, contentsResult.getContents());
-
- LOGD(TAG, "Contents in the cloud file: [" + contents + "]");
-
- return fromString(contents);
-
- } catch (Exception ex) {
- Log.w(TAG, "Ignoring invalid remote content.", ex);
- return null;
- }
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java
deleted file mode 100644
index bbb315d3af..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/gms/GMSUserDataSyncHelper.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.gms;
-
-import android.content.Context;
-
-import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper;
-import com.google.samples.apps.iosched.sync.userdata.UserAction;
-
-import java.util.List;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-
-/**
- * Helper class that syncs starred sessions data with Drive's AppData folder using GMS
- * support for Drive AppData folder.
- */
-public class GMSUserDataSyncHelper extends AbstractUserDataSyncHelper {
- private static final String TAG = makeLogTag(GMSUserDataSyncHelper.class);
-
- public GMSUserDataSyncHelper(Context context, String accountName) {
- super(context, accountName);
- }
-
- @Override
- protected boolean syncImpl(List actions, boolean hasPendingLocalData) {
- /*
- DriveAppAsyncTask task = new DriveAppAsyncTask(getContext()) {
- @Override
- protected void onPostExecute(Boolean requiresUIRefresh) {
- Log.d(TAG, "Finished DriveAppAsyncTask. RequiresUIRefresh = " + requiresUIRefresh);
- updateSharedPreferences(getDriveID(), getLastModifiedDate());
- if (requiresUIRefresh != null && requiresUIRefresh) {
- notifyDataHasChanged();
- }
- if (callback != null) {
- callback.onSuccess();
- }
- }
- };
- task.execute(params);*/
- throw new RuntimeException("Unsupported implementation of GMSUserDataSyncHelper");
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java
deleted file mode 100644
index b105c7af5a..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/DriveTask.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.http;
-
-import android.util.Log;
-
-import com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper;
-import com.google.api.client.http.ByteArrayContent;
-import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpResponse;
-import com.google.api.services.drive.Drive;
-import com.google.api.services.drive.model.File;
-import com.google.api.services.drive.model.FileList;
-import com.google.api.services.drive.model.ParentReference;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * Provides fundamental base abstractions for handling files in App Data
- *
- */
-public abstract class DriveTask {
-
- private static final String TAG = makeLogTag(DriveTask.class);
- private Drive mDriveService = null;
-
- final public static String FILE_NAME = "starred_sessions.json";
- final public static String FILE_MIME_TYPE = "application/json";
-
- /**
- * Constructs an object.
- * @param driveService
- */
- public DriveTask(Drive driveService) {
- mDriveService = driveService;
- }
-
- /**
- * Getter for the Drive service.
- */
- public Drive getDriveService() {
- return mDriveService;
- }
-
- /**
- * Inserts preferences file into the appdata folder.
- * @param content The application context.
- * @return Inserted file.
- * @throws IOException
- */
- public File insertPreferencesFile(String content) throws IOException {
- File metadata = new File();
- metadata.setTitle(FILE_NAME);
- metadata.setParents(Arrays.asList(new ParentReference().setId("appdata")));
- ByteArrayContent c =
- ByteArrayContent.fromString(FILE_MIME_TYPE, content);
- return mDriveService.files().insert(metadata, c).execute();
- }
-
- /**
- * Updates the preferences file with content.
- * @param file File metadata.
- * @param content File content in JSON.
- * @return Updated file.
- * @throws IOException
- */
- public File updatePreferencesFile(File file, String content)
- throws IOException {
- Log.d(TAG, "Saving content to remote drive "+file.getId()+" : [" + content + "]");
- ByteArrayContent c =
- ByteArrayContent.fromString(FILE_MIME_TYPE, content);
- return mDriveService.files().update(file.getId(), file, c).execute();
- }
-
- /**
- * Retrieves the preferences file from the appdata folder.
- * @return Retrieved preferences file or {@code null}.
- * @throws IOException
- */
- public File getOrCreateFile() throws IOException {
- // TODO: fix the contains query once title querying bug is being resolved.
- String query =
- "title contains '" + FILE_NAME + "' and 'appdata' in parents";
- FileList list = mDriveService.files().list().setQ(query).execute();
- if (list != null && list.getItems().size() > 0) {
- return list.getItems().get(0);
- } else {
- // create a new preferences file
- return insertPreferencesFile("{\"starred_sessions\": []}");
- }
- }
-
- /**
- * Downloads the file contents.
- * @param file File to download.
- * @return The file content.l
- * @throws IOException
- */
- public String downloadFile(File file) throws IOException {
- HttpResponse res = mDriveService.getRequestFactory()
- .buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute();
- return UserDataHelper.fromStreamToString(res.getContent());
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java
deleted file mode 100644
index ecd636819b..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/GetOrCreateFIleDriveTask.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.http;
-
-import java.io.IOException;
-
-import com.google.api.services.drive.Drive;
-import com.google.api.services.drive.model.File;
-
-/**
- * Gets or creates a file on user's appdata folder.
- *
- */
-public class GetOrCreateFIleDriveTask extends DriveTask {
-
- /**
- * Constructs a new get or create task.
- * @param driveService A drive service.
- */
- public GetOrCreateFIleDriveTask(Drive driveService) {
- super(driveService);
- }
-
- /**
- * Executes the request..
- * @return Remote file's content.
- * @throws IOException
- */
- public String execute() throws IOException {
- File file = getOrCreateFile();
- if (file.getDownloadUrl() != null) {
- // retrieve the content
- return downloadFile(file);
- }
- return null;
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java
deleted file mode 100644
index 6a0ac4eab7..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/HTTPUserDataSyncHelper.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/**
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.http;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.google.api.client.extensions.android.http.AndroidHttp;
-import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
-import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
-import com.google.api.client.json.gson.GsonFactory;
-import com.google.api.services.drive.Drive;
-import com.google.api.services.drive.DriveScopes;
-import com.google.samples.apps.iosched.sync.SyncHelper;
-import com.google.samples.apps.iosched.sync.userdata.AbstractUserDataSyncHelper;
-import com.google.samples.apps.iosched.sync.userdata.UserAction;
-import com.google.samples.apps.iosched.sync.userdata.util.UserDataHelper;
-import com.google.samples.apps.iosched.util.AccountUtils;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-/**
- * Helper class that syncs starred sessions data with Drive's AppData folder using direct
- * HTTP Drive API through google-api-client library.
- *
- * Based on https://github.com/googledrive/appdatapreferences-android
- */
-public class HTTPUserDataSyncHelper extends AbstractUserDataSyncHelper {
- private static final String GCM_KEY_PREFIX = "GCM:";
-
- private GoogleAccountCredential mCredentials;
-
- /**
- * Private {@code HTTPUserDataSyncHelper} constructor.
- * @param context Context of the application
- */
- public HTTPUserDataSyncHelper(Context context, String accountName) {
- super(context, accountName);
- mCredentials = GoogleAccountCredential.usingOAuth2(mContext,
- java.util.Arrays.asList(DriveScopes.DRIVE_APPDATA));
- mCredentials.setSelectedAccountName(mAccountName);
- }
-
- private String extractGcmKey(Set remote) {
- String remoteGcmKey = null;
- Set toRemove = new HashSet();
- for (String s : remote) {
- if (s.startsWith(GCM_KEY_PREFIX)) {
- toRemove.add(s);
- remoteGcmKey = s.substring(GCM_KEY_PREFIX.length());
- LOGD(TAG, "Remote data came with GCM key: "
- + AccountUtils.sanitizeGcmKey(remoteGcmKey));
- }
- }
- for (String s : toRemove) {
- remote.remove(s);
- }
- return remoteGcmKey;
- }
-
- /**
- * Syncs the preferences file with an appdata preferences file.
- *
- * Synchronization steps:
- * 1. If there are local changes, sync the latest local version with remote
- * and ignore merge conflicts. The last write wins.
- * 2. If there are no local changes, fetch the latest remote version. If
- * it includes changes, notify that preferences have changed.
- */
- protected boolean syncImpl(List actions, boolean hasPendingLocalData) {
- try {
- LOGD(TAG, "Now syncing user data.");
- Set remote = UserDataHelper.fromString(fetchRemote());
- Set local = UserDataHelper.getSessionIDs(actions);
-
- String remoteGcmKey = extractGcmKey(remote);
- String localGcmKey = AccountUtils.getGcmKey(mContext, mAccountName);
- LOGD(TAG, "Local GCM key: " + AccountUtils.sanitizeGcmKey(localGcmKey));
- LOGD(TAG, "Remote GCM key: " + (remoteGcmKey == null ? "(null)"
- : AccountUtils.sanitizeGcmKey(remoteGcmKey)));
-
- // if the remote data came with a GCM key, it should override ours
- if (!TextUtils.isEmpty(remoteGcmKey)) {
- if (remoteGcmKey.equals(localGcmKey)) {
- LOGD(TAG, "Remote GCM key is the same as local, so no action necessary.");
- } else {
- LOGD(TAG, "Remote GCM key is different from local. OVERRIDING local.");
- localGcmKey = remoteGcmKey;
- AccountUtils.setGcmKey(mContext, mAccountName, localGcmKey);
- }
- }
-
- // If remote data is the same as local, and the remote end already has a GCM key,
- // there is nothing we need to do.
- if (remote.equals(local) && !TextUtils.isEmpty(remoteGcmKey)) {
- LOGD(TAG, "Update is not needed (local is same as remote, and remote has key)");
- return false;
- }
-
- Set merged;
- if (hasPendingLocalData || TextUtils.isEmpty(remoteGcmKey)) {
- // merge local dirty actions into remote content
- if (hasPendingLocalData) {
- LOGD(TAG, "Has pending local data, merging.");
- merged = mergeDirtyActions(actions, remote);
- } else {
- LOGD(TAG, "No pending local data, just updating remote GCM key.");
- merged = remote;
- }
- // add the GCM key special item
- merged.add(GCM_KEY_PREFIX + localGcmKey);
- // save to remote
- LOGD(TAG, "Sending user data to Drive, gcm key "
- + AccountUtils.sanitizeGcmKey(localGcmKey));
- new UpdateFileDriveTask(getDriveService()).execute(
- UserDataHelper.toSessionsString(merged));
- } else {
- merged = remote;
- }
-
- UserDataHelper.setLocalStarredSessions(mContext, merged, mAccountName);
- return true;
- } catch (IOException e) {
- handleException(e);
- }
- return false;
- }
-
- /**
- * Constructs a Drive service in the current context and with the
- * credentials use to initiate AppdataPreferences instance.
- * @return Drive service instance.
- */
- public Drive getDriveService() {
- Drive service = new Drive.Builder(
- AndroidHttp.newCompatibleTransport(),
- new GsonFactory(), mCredentials)
- .setApplicationName(mContext.getApplicationInfo().name)
- .build();
- return service;
- }
-
- /**
- * Updates the remote preferences file with the given JSON content.
- * @throws IOException
- */
- private Set mergeDirtyActions(List actions, Set starredSessions)
- throws IOException {
- // apply "dirty" actions:
- for (UserAction action: actions) {
- if (action.requiresSync) {
- if (UserAction.TYPE.ADD_STAR.equals(action.type)) {
- starredSessions.add(action.sessionId);
- } else {
- starredSessions.remove(action.sessionId);
- }
- }
- }
- return starredSessions;
- }
-
- /**
- * Fetches the remote file.
- * @throws IOException
- */
- private String fetchRemote() throws IOException {
- String json = new GetOrCreateFIleDriveTask(getDriveService()).execute();
- Log.v(TAG, "Got this content from remote myschedule: ["+json+"]");
- return json;
- }
-
-
- /**
- * Handles API exceptions and notifies OnExceptionListener
- * if given exception is a UserRecoverableAuthIOException.
- * @param exception Exception to handle
- */
- private void handleException(Exception exception) {
- Log.e(TAG, "Could not sync myschedule", exception);
- if (exception != null && exception instanceof UserRecoverableAuthIOException) {
- throw new SyncHelper.AuthException();
- }
- }
-
- private static final String TAG = makeLogTag(HTTPUserDataSyncHelper.class);
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java
deleted file mode 100644
index e3abdff639..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/http/UpdateFileDriveTask.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.http;
-
-import java.io.IOException;
-
-import com.google.api.services.drive.Drive;
-import com.google.api.services.drive.model.File;
-
-/**
- * Updates a string content to a file on appdata folder.
- *
- */
-public class UpdateFileDriveTask extends DriveTask {
-
- /**
- * Constructs a new task.
- * @param driveService A drive service.
- */
- public UpdateFileDriveTask(Drive driveService) {
- super(driveService);
- }
-
- /**
- * Executes the request.
- * @param content The new file content.
- * @throws IOException
- */
- public void execute(String content) throws IOException {
- // updates the existing preferences file with
- // the preferences
- File preferences = getOrCreateFile();
- updatePreferencesFile(preferences, content);
- }
-
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java
deleted file mode 100644
index 84404f5e0b..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserActionHelper.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.util;
-
-import android.content.ContentProviderOperation;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.sync.userdata.UserAction;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.stream.JsonReader;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-public class UserActionHelper {
- private static final String TAG = makeLogTag(UserActionHelper.class);
-
- static public void updateContentProvider(Context context, List userActions, String account) {
- ArrayList batch = new ArrayList();
- for (UserAction action: userActions) {
- batch.add(createUpdateOperation(context, action, account));
- }
- try {
- context.getContentResolver().applyBatch(ScheduleContract.CONTENT_AUTHORITY, batch);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not apply operations", e);
- } catch (OperationApplicationException e) {
- Log.e(TAG, "Could not apply operations", e);
- }
- }
-
- static private ContentProviderOperation createUpdateOperation(Context context, UserAction action, String account) {
- if (action.type == UserAction.TYPE.ADD_STAR) {
- return ContentProviderOperation
- .newInsert(
- ScheduleContract.addOverrideAccountName(
- ScheduleContract.MySchedule.CONTENT_URI, account))
- .withValue(ScheduleContract.MySchedule.MY_SCHEDULE_DIRTY_FLAG, "0")
- .withValue(ScheduleContract.MySchedule.SESSION_ID, action.sessionId)
- .build();
- } else {
- return ContentProviderOperation
- .newDelete(
- ScheduleContract.addOverrideAccountName(
- ScheduleContract.MySchedule.CONTENT_URI, account))
- .withSelection(
- ScheduleContract.MySchedule.SESSION_ID + " = ? AND " +
- ScheduleContract.MySchedule.MY_SCHEDULE_ACCOUNT_NAME + " = ? ",
- new String[]{action.sessionId, account}
- )
- .build();
- }
- }
-
- public static String serializeUserActions(List actions) {
- JsonArray array = new JsonArray();
- for (UserAction action: actions) {
- JsonObject obj = new JsonObject();
- obj.add("type", new JsonPrimitive(action.type.name()));
- obj.add("id", new JsonPrimitive(action.sessionId));
- array.add(obj);
- }
- return array.toString();
- }
-
- public static List deserializeUserActions(String str) {
- try {
- ArrayList actions = new ArrayList();
- JsonReader reader = new JsonReader(new StringReader(str));
- reader.beginArray();
- while (reader.hasNext()) {
- reader.beginObject();
- UserAction action = new UserAction();
- while (reader.hasNext()) {
- String key = reader.nextName();
- if ("type".equals(key)) {
- action.type = UserAction.TYPE.valueOf(reader.nextString());
- } else if ("id".equals(key)) {
- action.sessionId = reader.nextString();
- } else {
- throw new RuntimeException("Invalid key "+key+" in serialized UserAction: "+str);
- }
- }
- reader.endObject();
- actions.add(action);
- }
- reader.endArray();
- return actions;
- } catch (IOException ex) {
- throw new RuntimeException("Error deserializing UserActions: "+str, ex);
- }
- }
-}
diff --git a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java b/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java
deleted file mode 100644
index 2dd1bd2be2..0000000000
--- a/android/src/main/java/com/google/samples/apps/iosched/sync/userdata/util/UserDataHelper.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2014 Google Inc. All rights reserved.
- *
- * 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.samples.apps.iosched.sync.userdata.util;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-
-import com.google.samples.apps.iosched.provider.ScheduleContract;
-import com.google.samples.apps.iosched.sync.userdata.UserAction;
-import com.google.api.client.util.Charsets;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.stream.JsonReader;
-
-import java.io.*;
-import java.util.*;
-
-import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
-
-public class UserDataHelper {
-
- private static final String TAG = makeLogTag(UserDataHelper.class);
-
- // Constants related to the JSON serialization:
- static final String JSON_STARRED_SESSIONS_KEY = "starred_sessions";
-
- static public String toSessionsString(Set sessionIds) {
- JsonArray array = new JsonArray();
- for (String sessionId: sessionIds) {
- array.add(new JsonPrimitive(sessionId));
- }
- JsonObject obj = new JsonObject();
- obj.add(JSON_STARRED_SESSIONS_KEY, array);
- return obj.toString();
- }
-
- static public byte[] toByteArray(Set