Java tutorial
/* * Copyright 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gdgdevfest.android.apps.devfestbcn.sync; import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.LOGD; import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.LOGI; import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.makeLogTag; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.List; import android.accounts.Account; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.Context; import android.content.OperationApplicationException; import android.content.SharedPreferences; import android.content.SyncResult; import android.net.ConnectivityManager; import android.os.Bundle; import android.os.RemoteException; import android.preference.PreferenceManager; import com.gdgdevfest.android.apps.devfestbcn.Config; import com.gdgdevfest.android.apps.devfestbcn.R; import com.gdgdevfest.android.apps.devfestbcn.io.BlocksHandler; import com.gdgdevfest.android.apps.devfestbcn.io.JSONHandler; import com.gdgdevfest.android.apps.devfestbcn.io.MapPropertyHandler; import com.gdgdevfest.android.apps.devfestbcn.io.RoomsHandler; import com.gdgdevfest.android.apps.devfestbcn.io.SearchSuggestHandler; import com.gdgdevfest.android.apps.devfestbcn.io.SessionsHandler; import com.gdgdevfest.android.apps.devfestbcn.io.SpeakersHandler; import com.gdgdevfest.android.apps.devfestbcn.io.TracksHandler; import com.gdgdevfest.android.apps.devfestbcn.io.map.model.Tile; import com.gdgdevfest.android.apps.devfestbcn.provider.ScheduleContract; import com.gdgdevfest.android.apps.devfestbcn.util.AccountUtils; import com.gdgdevfest.android.apps.devfestbcn.util.Lists; import com.gdgdevfest.android.apps.devfestbcn.util.NetUtils; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.services.CommonGoogleClientRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.googledevelopers.Googledevelopers; 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; /** * A helper class for dealing with sync and other remote persistence operations. * 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.class); public static final int FLAG_SYNC_LOCAL = 0x1; private static final int LOCAL_VERSION_CURRENT = 61; private static final String LOCAL_MAPVERSION_CURRENT = "\"vlh7Ig\""; private Context mContext; public SyncHelper(Context context) { mContext = context; } public static void requestManualSync(Account mChosenAccount) { Bundle b = new Bundle(); b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); ContentResolver.requestSync(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, b); } /** * Loads conference information (sessions, rooms, tracks, speakers, etc.) * from a local static cache data and then syncs down data from the * Conference API. * * @param syncResult Optional {@link SyncResult} object to populate. * @throws IOException */ public void performSync(SyncResult syncResult, int flags) throws IOException { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); final int localVersion = prefs.getInt("local_data_version", 0); // Bulk of sync work, performed by executing several fetches from // local and online sources. final ContentResolver resolver = mContext.getContentResolver(); ArrayList<ContentProviderOperation> batch = new ArrayList<ContentProviderOperation>(); LOGI(TAG, "Performing sync"); final long startLocal = System.currentTimeMillis(); final boolean localParse = localVersion < LOCAL_VERSION_CURRENT; LOGD(TAG, "found localVersion=" + localVersion + " and LOCAL_VERSION_CURRENT=" + LOCAL_VERSION_CURRENT); // Only run local sync if there's a newer version of data available // than what was last locally-sync'd. if (localParse) { int json_rooms = R.raw.rooms_es; int json_common_slots = R.raw.common_slots_es; int json_tracks = R.raw.tracks_es; int json_speakers = R.raw.speakers_es; int json_sessions = R.raw.sessions_es; int json_session_tracks = R.raw.session_tracks_es; int json_search_suggest = R.raw.search_suggest_es; int json_map = R.raw.map_es; try { Class res = R.raw.class; json_rooms = res.getField(mContext.getResources().getString(R.string.json_rooms)).getInt(null); json_common_slots = res.getField(mContext.getResources().getString(R.string.json_common_slots)) .getInt(null); json_tracks = res.getField(mContext.getResources().getString(R.string.json_tracks)).getInt(null); json_speakers = res.getField(mContext.getResources().getString(R.string.json_speakers)) .getInt(null); json_sessions = res.getField(mContext.getResources().getString(R.string.json_sessions)) .getInt(null); json_session_tracks = res.getField(mContext.getResources().getString(R.string.json_session_tracks)) .getInt(null); json_search_suggest = res.getField(mContext.getResources().getString(R.string.json_search_suggest)) .getInt(null); json_map = res.getField(mContext.getResources().getString(R.string.json_map)).getInt(null); } catch (Exception e) { LOGI(TAG, "Error al recuperar los raws localizados: " + e.getMessage()); } // Load static local data LOGI(TAG, "Local syncing rooms"); batch.addAll(new RoomsHandler(mContext).parse(JSONHandler.parseResource(mContext, json_rooms))); LOGI(TAG, "Local syncing blocks"); batch.addAll(new BlocksHandler(mContext).parse(JSONHandler.parseResource(mContext, json_common_slots))); LOGI(TAG, "Local syncing tracks"); batch.addAll(new TracksHandler(mContext).parse(JSONHandler.parseResource(mContext, json_tracks))); LOGI(TAG, "Local syncing speakers"); batch.addAll( new SpeakersHandler(mContext).parseString(JSONHandler.parseResource(mContext, json_speakers))); LOGI(TAG, "Local syncing sessions"); batch.addAll( new SessionsHandler(mContext).parseString(JSONHandler.parseResource(mContext, json_sessions), JSONHandler.parseResource(mContext, json_session_tracks))); LOGI(TAG, "Local syncing search suggestions"); batch.addAll(new SearchSuggestHandler(mContext) .parse(JSONHandler.parseResource(mContext, json_search_suggest))); LOGI(TAG, "Local syncing map"); MapPropertyHandler mapHandler = new MapPropertyHandler(mContext); batch.addAll(mapHandler.parse(JSONHandler.parseResource(mContext, json_map))); //need to sync tile files before data is updated in content provider syncMapTiles(mapHandler.getTiles()); prefs.edit().putInt("local_data_version", LOCAL_VERSION_CURRENT).commit(); prefs.edit().putString("local_mapdata_version", LOCAL_MAPVERSION_CURRENT).commit(); if (syncResult != null) { ++syncResult.stats.numUpdates; // TODO: better way of indicating progress? ++syncResult.stats.numEntries; } } LOGD(TAG, "Local sync took " + (System.currentTimeMillis() - startLocal) + "ms"); try { // Apply all queued up batch operations for local data. resolver.applyBatch(ScheduleContract.CONTENT_AUTHORITY, batch); } catch (RemoteException e) { throw new RuntimeException("Problem applying batch operation", e); } catch (OperationApplicationException e) { throw new RuntimeException("Problem applying batch operation", e); } batch = new ArrayList<ContentProviderOperation>(); } public void addOrRemoveSessionFromSchedule(Context context, String sessionId, boolean inSchedule) throws IOException { LOGI(TAG, "Updating session on user schedule: " + sessionId); 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); } } } private void sendScheduleUpdate(Googledevelopers conferenceAPI, Context context, String sessionId, boolean inSchedule) throws IOException { if (inSchedule) { conferenceAPI.users().events().sessions().update(Config.EVENT_ID, sessionId, null).execute(); } else { conferenceAPI.users().events().sessions().delete(Config.EVENT_ID, sessionId).execute(); } } private ArrayList<ContentProviderOperation> remoteSyncMapData(String urlString, SharedPreferences preferences) throws IOException { final String localVersion = preferences.getString("local_mapdata_version", null); ArrayList<ContentProviderOperation> batch = Lists.newArrayList(); BasicHttpClient httpClient = new BasicHttpClient(); httpClient.setRequestLogger(mQuietLogger); httpClient.addHeader("If-None-Match", localVersion); LOGD(TAG, "Local map version: " + localVersion); HttpResponse response = httpClient.get(urlString, null); final int status = response.getStatus(); if (status == HttpURLConnection.HTTP_OK) { // Data has been updated, otherwise would have received HTTP_NOT_MODIFIED LOGI(TAG, "Remote syncing map data"); final List<String> etag = response.getHeaders().get("ETag"); if (etag != null && etag.size() > 0) { MapPropertyHandler handler = new MapPropertyHandler(mContext); batch.addAll(handler.parse(response.getBodyAsString())); syncMapTiles(handler.getTiles()); // save new etag as version preferences.edit().putString("local_mapdata_version", etag.get(0)).commit(); } } //else: no update return batch; } private boolean isOnline() { ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnectedOrConnecting(); } /** * 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 syncMapTiles(Collection<Tile> collection) throws IOException, SVGParseException { } /** * Write the byte array directly to a file. * @throws IOException */ private void writeFile(byte[] data, File file) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, false)); bos.write(data); bos.close(); } /** * 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) { } }; private Googledevelopers getConferenceAPIClient() { HttpTransport httpTransport = new NetHttpTransport(); JsonFactory jsonFactory = new GsonFactory(); GoogleCredential credential = new GoogleCredential().setAccessToken(AccountUtils.getAuthToken(mContext)); // Note: The Googledevelopers API is unique, in that it requires an API key in addition to the client // ID normally embedded an an OAuth token. Most apps will use one or the other. return new Googledevelopers.Builder(httpTransport, jsonFactory, null) .setApplicationName(NetUtils.getUserAgent(mContext)) .setGoogleClientRequestInitializer(new CommonGoogleClientRequestInitializer(Config.API_KEY)) .setHttpRequestInitializer(credential).build(); } }