package net.udrunk.model; import java.io.IOException; import java.net.ContentHandler; import java.net.URLStreamHandlerFactory; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; import net.udrunk.domain.Checkin; import net.udrunk.domain.Login; import net.udrunk.domain.Place; import net.udrunk.domain.Point; import net.udrunk.domain.User; import net.udrunk.domain.dto.AllPlacesDto; import net.udrunk.infra.DataBaseHelper; import net.udrunk.infra.JamendoCache; import net.udrunk.infra.MyLocation; import net.udrunk.infra.MyLocation.LocationResult; import net.udrunk.infra.PointSerializer; import net.udrunk.infra.TimeUtil; import net.udrunk.infra.resttemplate.UdrunkJsonHttpMessageConverter; import net.udrunk.services.CheckinService; import net.udrunk.services.UdrunkClient; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.location.Location; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; import com.google.android.imageloader.BitmapContentHandler; import com.google.android.imageloader.ImageLoader; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.googlecode.androidannotations.annotations.AfterInject; import com.googlecode.androidannotations.annotations.Background; import com.googlecode.androidannotations.annotations.EBean; import com.googlecode.androidannotations.annotations.RootContext; import com.googlecode.androidannotations.annotations.Trace; import com.googlecode.androidannotations.annotations.UiThread; import com.googlecode.androidannotations.annotations.rest.RestService; import com.googlecode.androidannotations.api.Scope; import com.j256.ormlite.android.apptools.OpenHelperManager; @EBean(scope = Scope.Singleton) public class Model extends Observable { public static final int LOGIN_SUCCESS = 0; public static final int LOGIN_FAILED = 1; public static final int USER_CREATION_SUCCESS = 10; public static final int USER_CREATION_FAILED = 11; public static final int CHECKINS_UPDATING = 20; public static final int CHECKINS_UPDATED = 21; public static final int PLACES_UPDATING = 30; public static final int PLACES_UPDATED = 31; @RootContext protected Context context; @RestService protected UdrunkClient restClient; protected DataBaseHelper databaseHelper; protected List<Place> places; protected boolean connected; protected MyLocation myLocation = new MyLocation(); public Location currentLocation; @AfterInject protected void afterInjection() { imageLoader = createImageLoader(context); List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>(); GsonBuilder gsonb = new GsonBuilder(); gsonb.registerTypeHierarchyAdapter(Point.class, new PointSerializer()); Gson gson = gsonb.create(); messageConverters.add(new UdrunkJsonHttpMessageConverter(gson)); restClient.getRestTemplate().setMessageConverters(messageConverters); } public Login getCurrentLogin() { SharedPreferences settings = context.getSharedPreferences("udrunk", 0); String loginStr = settings.getString("login", null); Gson gson = new Gson(); Login result = gson.fromJson(loginStr, Login.class); return result; } public User getCurrentUser() { User result; try { result = getDBHelper().getUserDao().queryForId( getCurrentLogin().getId()); } catch (SQLException e) { result = new User(); result.setId(getCurrentLogin().getId()); } return result; } public List<Place> getPlaces() { return places; } public void setPlaces(List<Place> places) { this.places = places; } public List<Checkin> getCheckins() { try { return getDBHelper().getCheckinDao().queryBuilder() .orderBy("added", false).query(); } catch (SQLException e) { e.printStackTrace(); return null; } } protected DataBaseHelper getDBHelper() { if (databaseHelper == null) { databaseHelper = OpenHelperManager.getHelper(context, DataBaseHelper.class); } return databaseHelper; } public boolean checkinsLoading; private boolean placesLoading; public synchronized boolean isPlacesLoading() { return placesLoading; } public synchronized void setPlacesLoading(boolean placesLoading) { this.placesLoading = placesLoading; } @AfterInject public void doSomethingAfterInjection() { doBindService(); } /** * * INIT AUTHENTIFICATION * */ protected void initAuth() { // This should of course be extracted into a separate class ClientHttpRequestInterceptor authInterceptor = new ClientHttpRequestInterceptor() { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); String username = "valentin"; String key = "valentin"; headers.set("Authorization", "ApiKey " + username + ":" + key); return execution.execute(request, body); } }; RestTemplate rt = restClient.getRestTemplate(); ClientHttpRequestInterceptor[] interceptors = { authInterceptor }; rt.setInterceptors(interceptors); } /** * * LOGIN SERVICE * */ @Background public void login(String login, String pass) { try { Log.d("Login", login + " " + pass); Login currentLogin = restClient.login(login, pass).getLogin(); if (currentLogin != null) { Login previousLogin = getCurrentLogin(); SharedPreferences settings = context.getSharedPreferences( "udrunk", 0); Gson gson = new Gson(); settings.edit().putString("login", gson.toJson(currentLogin)) .commit(); onLoginSucess(); // If previous login different, clear all checkins in bdd if (previousLogin != null && !currentLogin.getUsername().equals( previousLogin.getUsername())) { try { databaseHelper.getCheckinDao().deleteBuilder().delete(); } catch (SQLException e) { e.printStackTrace(); } } } else { onLoginFailed(); } } catch (RestClientException e) { e.printStackTrace(); showErrorToast("Login : " + e.getMessage()); onLoginFailed(); } } public void logout() { Login previousLogin = getCurrentLogin(); previousLogin.setApi_key(null); SharedPreferences settings = context.getSharedPreferences("udrunk", 0); Gson gson = new Gson(); settings.edit().putString("login", gson.toJson(previousLogin)).commit(); } @UiThread protected void onLoginSucess() { initAuth(); notifyObservers(LOGIN_SUCCESS); } @UiThread protected void onLoginFailed() { notifyObservers(LOGIN_FAILED); } @Background public void createUser(String username, String password, String email) { User user = new User(); user.setUsername(username); user.setEmail(email); try { restClient.insertUser(user); onUserCreationSuccess(); } catch (RestClientException e) { onUserCreationFailed(); } } @UiThread protected void onUserCreationSuccess() { notifyObservers(USER_CREATION_SUCCESS); } @UiThread protected void onUserCreationFailed() { notifyObservers(USER_CREATION_FAILED); } /** * * PLACE SERVICE * */ private long lastPlaceRetrievedTime = 0; private static long PLACES_MAX_TIME_DIFF = TimeUtil.SECOND * 15; public boolean arePlacesPassed() { if ((TimeUtil.getCurrentTime() - lastPlaceRetrievedTime) > PLACES_MAX_TIME_DIFF) { return true; } return false; } public void deletePlacesIfNecessary() { if (arePlacesPassed()) { setPlaces(null); notifyObservers(PLACES_UPDATED); } } @Trace public void retrievePlaces() { if (!isPlacesLoading()) { if (arePlacesPassed()) { setPlacesLoading(true); notifyObservers(PLACES_UPDATING); myLocation.getLocation(context, locationResult); } } } LocationResult locationResult = new LocationResult() { @Override public void gotLocation(Location location) { currentLocation = location; retrievePlacesBackground(location.getLatitude(), location.getLongitude()); } }; @Trace @Background protected void retrievePlacesBackground(double lat, double lg) { try { AllPlacesDto placesDto = restClient.getPlaces(lat, lg, getCurrentLogin().getUsername(), getCurrentLogin() .getApi_key()); setPlaces(placesDto.objects); } catch (RestClientException e) { showErrorToast("Places : " + e.getMessage()); } finally { onPlacesRetieved(); } } @Trace @UiThread protected void onPlacesRetieved() { lastPlaceRetrievedTime = TimeUtil.getCurrentTime(); setPlacesLoading(false); notifyObservers(PLACES_UPDATED); } /** * * CHECKIN SERVICE * */ private Messenger checkinServiceMessenger = null; private Messenger inMessenger = new Messenger(new IncomingHandler()); private boolean checkinUpdateRequested; protected boolean mIsBound = false; protected void doBindService() { mIsBound = true; context.bindService(new Intent(context, CheckinService.class), mConnection, Context.BIND_AUTO_CREATE); } protected void doUnbindService() { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (checkinServiceMessenger != null) { try { Message msg = Message.obtain(null, CheckinService.MSG_UNREGISTER_CLIENT); msg.replyTo = inMessenger; checkinServiceMessenger.send(msg); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } // Detach our existing connection. context.unbindService(mConnection); mIsBound = false; Toast.makeText(context, "Unbinding", Toast.LENGTH_SHORT).show(); } } @SuppressLint("HandlerLeak") class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case CheckinService.MSG_GET_CHECKINS: checkinsLoading = false; Toast.makeText(Model.this.context, "Checkin retieved", Toast.LENGTH_SHORT).show(); onCheckinsRetieved(); break; case CheckinService.MSG_GET_CHECKINS_FAILED: checkinsLoading = false; Toast.makeText(Model.this.context, "Checkin failed", Toast.LENGTH_SHORT).show(); onCheckinsRetieved(); break; default: super.handleMessage(msg); } } } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder binder) { checkinServiceMessenger = new Messenger(binder); connected = true; try { Message msg = Message.obtain(null, CheckinService.MSG_REGISTER_CLIENT); msg.replyTo = inMessenger; checkinServiceMessenger.send(msg); onCheckinsServiceConnected(); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected(ComponentName className) { checkinServiceMessenger = null; connected = false; } }; @UiThread public void retrieveCheckins() { if (connected) { if (!checkinsLoading) { checkinsLoading = true; notifyObservers(CHECKINS_UPDATING); Toast.makeText(context, "Retieving Checkins", Toast.LENGTH_SHORT).show(); Message msg = Message.obtain(null, CheckinService.MSG_GET_CHECKINS); try { checkinServiceMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } } else { checkinUpdateRequested = true; } } protected void onCheckinsRetieved() { notifyObservers(CHECKINS_UPDATED); } protected void onCheckinsServiceConnected() { if (checkinUpdateRequested) { checkinUpdateRequested = false; retrieveCheckins(); } } @Background public void insertCheckin(Checkin checkin) { try { restClient.insertCheckin(checkin, getCurrentLogin().getUsername(), getCurrentLogin().getApi_key()); } catch (HttpServerErrorException e) { e.printStackTrace(); Log.w("Model", e.getResponseBodyAsString()); showErrorToast(e.getMessage()); } catch (HttpClientErrorException e) { e.printStackTrace(); Log.w("Model", e.getResponseBodyAsString()); showErrorToast(e.getMessage()); } catch (RestClientException e) { e.printStackTrace(); showErrorToast(e.getMessage()); } finally { retrieveCheckins(); } } /* * IMAGE LOADER */ private static final int IMAGE_TASK_LIMIT = 3; public ImageLoader imageLoader; public static final String API_DOMAIN = "http://udrunk.valentinbourgoin.net"; private static ImageLoader createImageLoader(Context context) { // Install the file cache (if it is not already installed) JamendoCache.install(context); // Just use the default URLStreamHandlerFactory because // it supports all of the required URI schemes (http). URLStreamHandlerFactory streamFactory = null; // Load images using a BitmapContentHandler // and cache the image data in the file cache. ContentHandler bitmapHandler = JamendoCache.capture( new BitmapContentHandler(), null); // For pre-fetching, use a "sink" content handler so that the // the binary image data is captured by the cache without actually // parsing and loading the image data into memory. After pre-fetching, // the image data can be loaded quickly on-demand from the local cache. ContentHandler prefetchHandler = JamendoCache.capture( JamendoCache.sink(), null); // Perform callbacks on the main thread Handler handler = null; return new ImageLoader(IMAGE_TASK_LIMIT, streamFactory, bitmapHandler, prefetchHandler, ImageLoader.DEFAULT_CACHE_SIZE, handler); } @UiThread public void showErrorToast(String message) { Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } @Override public void addObserver(Observer observer) { super.addObserver(observer); } @Override public synchronized void deleteObserver(Observer observer) { super.deleteObserver(observer); } @Override public void notifyObservers(Object data) { setChanged(); super.notifyObservers(data); } }