/**
* Copyright (C) 2013 - 2015 the enviroCar community
* <p>
* This file is part of the enviroCar app.
* <p>
* The enviroCar app is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* The enviroCar app is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along
* with the enviroCar app. If not, see http://www.gnu.org/licenses/.
*/
package org.envirocar.app.handler;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.widget.Toast;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import org.envirocar.core.ContextInternetAccessProvider;
import org.envirocar.core.entity.Car;
import org.envirocar.core.entity.Track;
import org.envirocar.core.events.NewCarTypeSelectedEvent;
import org.envirocar.core.events.NewUserSettingsEvent;
import org.envirocar.core.exception.DataCreationFailureException;
import org.envirocar.core.exception.NotConnectedException;
import org.envirocar.core.exception.UnauthorizedException;
import org.envirocar.core.injection.InjectApplicationScope;
import org.envirocar.core.logging.Logger;
import org.envirocar.core.utils.CarUtils;
import org.envirocar.remote.DAOProvider;
import org.envirocar.storage.EnviroCarDB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import rx.Observable;
import rx.Subscriber;
/**
* The manager for cars.
*/
@Singleton
public class CarPreferenceHandler {
private static final Logger LOG = Logger.getLogger(CarPreferenceHandler.class);
private static final String PREFERENCE_TAG_DOWNLOADED = "cars_downloaded";
private final Context mContext;
private final Bus mBus;
private final UserHandler mUserManager;
private final DAOProvider mDAOProvider;
private final EnviroCarDB mEnviroCarDB;
private final SharedPreferences mSharedPreferences;
private Car mSelectedCar;
private Set<Car> mDeserialzedCars;
private Set<String> mSerializedCarStrings;
private Map<String, String> temporaryAlreadyRegisteredCars = new HashMap<>();
/**
* Constructor.
*
* @param context the context of the activity or application.
*/
@Inject
public CarPreferenceHandler(@InjectApplicationScope Context context, Bus bus, UserHandler
userManager, DAOProvider daoProvider, EnviroCarDB enviroCarDB,
SharedPreferences sharedPreferences) {
this.mContext = context;
this.mBus = bus;
this.mUserManager = userManager;
this.mDAOProvider = daoProvider;
this.mEnviroCarDB = enviroCarDB;
this.mSharedPreferences = sharedPreferences;
// no unregister required because it is applications scoped.
this.mBus.register(this);
mSelectedCar = CarUtils.instantiateCar(sharedPreferences.getString(PreferenceConstants
.PREFERENCE_TAG_CAR, null));
// Get the serialized car strings of all added cars.
mSerializedCarStrings = sharedPreferences
.getStringSet(PreferenceConstants.PREFERENCE_TAG_CARS, new HashSet<>());
// Instantiate the cars from the set of serialized strings.
mDeserialzedCars = new HashSet<>();
for (String serializedCar : mSerializedCarStrings) {
Car car = CarUtils.instantiateCar(serializedCar);
if (car == null) {
mSerializedCarStrings.remove(serializedCar);
flushCarListState();
} else {
mDeserialzedCars.add(CarUtils.instantiateCar(serializedCar));
}
}
}
public Observable<List<Car>> getAllDeserializedCars() {
return Observable.create(new Observable.OnSubscribe<List<Car>>() {
@Override
public void call(Subscriber<? super List<Car>> subscriber) {
subscriber.onStart();
subscriber.onNext(getDeserialzedCars());
subscriber.onCompleted();
}
});
}
public Observable<List<Car>> downloadRemoteCarsOfUser() {
return Observable.just(mUserManager.getUser())
.flatMap(user -> mDAOProvider.getSensorDAO().getCarsByUserObservable(user))
.map(cars -> {
LOG.info(String.format(
"Successfully downloaded %s remote cars. Add these to the preferences.",
cars.size()));
for (Car car : cars) {
addCar(car);
}
setIsDownloaded(true);
return cars;
});
}
public Observable<Car> assertTemporaryCar(Car car) {
return Observable.just(car)
.flatMap(car1 -> {
LOG.info("assertTemporaryCar() assert whether car is uploaded or the car " +
"needs to be registered.");
// If the car is already uploaded, then just return car instance.
if (CarUtils.isCarUploaded(car1)) {
LOG.info("assertTemporaryCar(): car has already been uploaded");
return Observable.just(car1);
}
// the car is already uploaded before but the car has not the right remote id
if (temporaryAlreadyRegisteredCars.containsKey(car1.getId())) {
LOG.info("assertTemporaryCar(): car has already been uploaded");
car1.setId(temporaryAlreadyRegisteredCars.get(car1.getId()));
return Observable.just(car1);
}
LOG.info("assertTemporaryCar(): car is not uploaded. Trying to register.");
// create a new car instance.
return registerCar(car1);
});
}
private Observable<Car> registerCar(Car car) {
LOG.info(String.format("registerCarBeforeUpload(%s)", car.toString()));
String oldID = car.getId();
return mDAOProvider.getSensorDAO()
// Create a new remote car and update the car remote id.
.createCarObservable(car)
// update all IDs of tracks that have this car as a reference
.flatMap(updCar -> updateCarIDsOfTracksObservable(oldID, updCar))
// sum all tracks to a list of tracks.
.toList()
// Just set the current car reference to the updated one and return it.
.map(tracks -> {
LOG.info("kommta hier an?");
if (!temporaryAlreadyRegisteredCars.containsKey(oldID))
temporaryAlreadyRegisteredCars.put(oldID, car.getId());
if (getCar().getId().equals(oldID))
setCar(car);
return car;
});
}
private Observable<Track> updateCarIDsOfTracksObservable(String oldID, Car car) {
return mEnviroCarDB.getAllTracksByCar(oldID, true)
.first()
.flatMap(tracks -> Observable.from(tracks))
.map(track -> {
LOG.info("Track has been updated! -> [" + track.toString() + "]");
track.setCar(car);
return track;
})
.concatMap(track -> mEnviroCarDB.updateTrackObservable(track));
}
/**
* Adds the car to the set of cars in the shared preferences.
*
* @param car the car to add to the shared preferences.
* @return true if the car has been successfully added.
*/
public boolean addCar(Car car) {
LOG.info(String.format("addCar(%s %s)", car.getManufacturer(), car.getModel()));
// Serialize the car.
String serializedCar = CarUtils.serializeCar(car);
// if the car is not already part of the list, add it
if (mDeserialzedCars.contains(car) || mSerializedCarStrings.contains(serializedCar))
return false;
mSerializedCarStrings.add(serializedCar);
mDeserialzedCars.add(car);
// Finally flush the state to shared preferences
flushCarListState();
return true;
}
/**
* Removes the car from the set of cars in the shared preferences.
*
* @param car the car to remove from the shared preferences.
* @return true if the car has been successfully deleted.
*/
public boolean removeCar(Car car) {
LOG.info(String.format("removeCar(%s %s)", car.getManufacturer(), car.getModel()));
// If the cartype equals the selected car, then set it to null and fire an event on the
// event bus.
if (mSelectedCar != null && mSelectedCar.equals(car)) {
LOG.info(String.format("%s %s equals the selected car type.",
car.getManufacturer(), car.getModel()));
mSelectedCar = null;
flushSelectedCarState();
mBus.post(new NewCarTypeSelectedEvent(null));
}
// Return false when the car is not contained in the set.
if (!mDeserialzedCars.contains(car))
return false;
// Remove from both sets.
mDeserialzedCars.remove(car);
// Finally flush the state to shared preferences
flushCarListState();
return true;
}
/**
* Returns true if there already are some cars created.
*
* @return true if there are some cars.
*/
public boolean hasCars() {
return !(mSerializedCarStrings == null || mSerializedCarStrings.isEmpty());
}
/**
* Returns the instance of the current Car
*
* @return instance of the selected car.
*/
public Car getCar() {
return mSelectedCar;
}
/**
* @param c
*/
public void setCar(Car c) {
LOG.info(String.format("setCar(%s %s)", c.getManufacturer(), c.getModel()));
if (c == null || (mSelectedCar != null && this.mSelectedCar.equals(c))) {
LOG.info("setCar(): car is null or the same as already set");
return;
}
// Set the selected car and flush the car state.
this.mSelectedCar = c;
flushSelectedCarState();
// Post a new event holding the new setted car type.
mBus.post(new NewCarTypeSelectedEvent(mSelectedCar));
}
/**
* Getter method for the list of deserialized cars in sorted order.
*
* @return list of sorted cars. (Sorted by manufacturer and model)
*/
public List<Car> getDeserialzedCars() {
List<Car> carList = new ArrayList<>(mDeserialzedCars);
Collections.sort(carList, new Comparator<Car>() {
@Override
public int compare(Car lhs, Car rhs) {
int res = lhs.getManufacturer().compareTo(rhs.getManufacturer());
if (res == 0)
res = lhs.getModel().compareTo(rhs.getModel());
return res;
}
});
return carList;
}
/**
* Registers a new car at the server.
*
* @param car the car to register
*/
public void registerCarAtServer(final Car car) {
try {
if (car.getFuelType() == null ||
car.getManufacturer() == null ||
car.getModel() == null ||
car.getConstructionYear() == 0 ||
car.getEngineDisplacement() == 0)
throw new Exception("Empty value!");
if (car.getManufacturer().isEmpty() || car.getModel().isEmpty()) {
throw new Exception("Empty value!");
}
} catch (Exception e) {
//TODO i18n
Toast.makeText(mContext, "Not all values were defined.", Toast.LENGTH_SHORT).show();
return;
}
if (new ContextInternetAccessProvider(mContext).isConnected() &&
mUserManager.isLoggedIn()) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
String sensorId = mDAOProvider.getSensorDAO().createCar(car);
//put the sensor id into shared preferences
car.setId(sensorId);
} catch (final NotConnectedException e1) {
LOG.warn(e1.getMessage());
} catch (final UnauthorizedException e1) {
LOG.warn(e1.getMessage());
} catch (DataCreationFailureException e1) {
LOG.warn(e1.getMessage());
}
return null;
}
}.execute();
} else {
String uuid = UUID.randomUUID().toString();
String sensorId = Car.TEMPORARY_SENSOR_ID.concat(uuid.substring(0, uuid.length() -
Car.TEMPORARY_SENSOR_ID.length()));
car.setId(sensorId);
}
}
@Subscribe
public void onReceiveNewUserSettingsEvent(NewUserSettingsEvent event) {
LOG.info("Received NewUserSettingsEvent: " + event.toString());
if (!event.mIsLoggedIn) {
setIsDownloaded(false);
LOG.info("Downloaded setted to false");
}
}
/**
* Getter method for the serialized car strings.
*
* @return the serialized car strings
*/
public Set<String> getSerializedCars() {
return mSerializedCarStrings;
}
/**
* Stores and updates the current set of serialized car strings in the shared preferences of
* the application.
*/
private void flushCarListState() {
LOG.info("flushCarListState()");
// Recreate serialized car strings.
mSerializedCarStrings.clear();
for (Car car : mDeserialzedCars) {
mSerializedCarStrings.add(CarUtils.serializeCar(car));
}
// First, delete the entry set of serialized car strings. Very important here to note is
// that there has to be a commit happen before setting the next string set.
boolean deleteSuccess = PreferenceManager.getDefaultSharedPreferences(mContext).edit()
.remove(PreferenceConstants.PREFERENCE_TAG_CARS)
.commit();
// then set the new string set.
boolean insertSuccess = PreferenceManager.getDefaultSharedPreferences(mContext).edit()
.putStringSet(PreferenceConstants.PREFERENCE_TAG_CARS, mSerializedCarStrings)
.commit();
if (deleteSuccess && insertSuccess)
LOG.info("flushCarListState(): Successfully inserted into shared preferences");
else
LOG.severe("flushCarListState(): Error on insert.");
}
/**
* Stores and updates the currently selected car in the shared preferences of the application.
*/
private void flushSelectedCarState() {
LOG.info("flushSelectedCarState()");
// Delete the entry of the selected car and its hash code.
boolean deleteSuccess = removeSelectedCarState();
if (deleteSuccess)
LOG.info("flushSelectedCarState(): Successfully deleted from the shared " +
"preferences");
else
LOG.severe("flushSelectedCarState(): Error on delete.");
if (mSelectedCar != null) {
// Set the new selected car type and hashcode.
boolean insertSuccess = PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
.putString(PreferenceConstants.PREFERENCE_TAG_CAR,
CarUtils.serializeCar(mSelectedCar))
.putInt(PreferenceConstants.CAR_HASH_CODE, mSelectedCar.hashCode())
.commit();
if (insertSuccess)
LOG.info("flushSelectedCarState(): Successfully inserted into shared preferences");
else
LOG.severe("flushSelectedCarState(): Error on insert.");
}
}
public void setIsDownloaded(boolean isDownloaded) {
LOG.info(String.format("setIsDownloaded() to [%s]", isDownloaded));
mSharedPreferences.edit().remove(PREFERENCE_TAG_DOWNLOADED).commit();
if (isDownloaded) {
mSharedPreferences.edit().putBoolean(PREFERENCE_TAG_DOWNLOADED, isDownloaded).commit();
}
}
public boolean isDownloaded() {
return mSharedPreferences.getBoolean(PREFERENCE_TAG_DOWNLOADED, false);
}
private boolean removeSelectedCarState() {
// Delete the entry of the selected car and its hash code.
return mSharedPreferences.edit()
.remove(PreferenceConstants.PREFERENCE_TAG_CAR)
.remove(PreferenceConstants.CAR_HASH_CODE)
.commit();
}
}