/**
* 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.services;
import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.widget.Toast;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import org.envirocar.app.handler.BluetoothHandler;
import org.envirocar.app.handler.CarPreferenceHandler;
import org.envirocar.app.handler.PreferencesHandler;
import org.envirocar.app.handler.TrackRecordingHandler;
import org.envirocar.app.services.obd.OBDServiceHandler;
import org.envirocar.app.services.obd.OBDServiceState;
import org.envirocar.core.events.NewCarTypeSelectedEvent;
import org.envirocar.core.events.bluetooth.BluetoothDeviceSelectedEvent;
import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent;
import org.envirocar.core.injection.Injector;
import org.envirocar.core.logging.Logger;
import org.envirocar.core.utils.ServiceUtils;
import org.envirocar.obd.events.BluetoothServiceStateChangedEvent;
import org.envirocar.obd.service.BluetoothServiceState;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.observers.SafeSubscriber;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
import static org.envirocar.app.services.obd.OBDServiceHandler.context;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class SystemStartupService extends Service {
private static final Logger LOGGER = Logger.getLogger(SystemStartupService.class);
public static final void startService(Context context) {
ServiceUtils.startService(context, SystemStartupService.class);
}
public static final void stopService(Context context) {
ServiceUtils.stopService(context, SystemStartupService.class);
}
private static final int REDISCOVERY_INTERVAL = 30;
// Static identifiers for actions for the broadcast receiver.
public static final String ACTION_START_BT_DISCOVERY = "action_start_bt_discovery";
public static final String ACTION_STOP_BT_DISCOVERY = "action_stop_bt_discvoery";
public static final String ACTION_START_TRACK_RECORDING = "action_start_track_recording";
public static final String ACTION_STOP_TRACK_RECORDING = "action_stop_track_recording";
// Injected variables
@Inject
protected Bus mBus;
@Inject
protected BluetoothHandler mBluetoothHandler;
@Inject
protected TrackRecordingHandler mTrackRecordingHandler;
@Inject
protected CarPreferenceHandler mCarManager;
private Scheduler.Worker mWorkerThread = Schedulers.newThread().createWorker();
private Scheduler.Worker mMainThreadWorker = AndroidSchedulers.mainThread().createWorker();
private boolean mIsAutoconnect = PreferencesHandler.DEFAULT_BLUETOOTH_AUTOCONNECT;
private boolean hasCarSelected = false;
private int mDiscoveryInterval = PreferencesHandler.DEFAULT_BLUETOOTH_DISCOVERY_INTERVAL;
// private member fields.
private Subscription mWorkerSubscription;
private Subscription mDiscoverySubscription;
private CompositeSubscription subscriptions = new CompositeSubscription();
// Broadcast receiver that handles the different actions that could be issued by the
// corresponding notification of the notification bar.
private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Received action matches the command for starting the discovery process for the
// selected OBDII-Adapter.
if (ACTION_START_BT_DISCOVERY.equals(action)) {
LOGGER.info("Received Broadcast: Start Discovery.");
// If the bluetooth is currently disabled, then do not issue the discovery.
if (!mBluetoothHandler.isBluetoothEnabled()) {
LOGGER.severe("Bluetooth is disabled. No Bluetooth discovery is issued");
}
startDiscoveryForSelectedDevice();
}
// Received action matches the command for stopping the Bluetooth discovery process.
else if (ACTION_STOP_BT_DISCOVERY.equals(action)) {
LOGGER.info("Received Broadcast: Stop Discovery.");
mBluetoothHandler.stopBluetoothDeviceDiscovery();
// UNUSED: This leads sometimes to some errors if you always ski
if (mDiscoverySubscription != null) {
mDiscoverySubscription.unsubscribe();
mDiscoverySubscription = null;
// Set the notification state to unconnected.
OBDServiceHandler.setRecordingState(OBDServiceState.UNCONNECTED);
}
}
// Received action matches the command for starting the recording of a track.
else if (ACTION_START_TRACK_RECORDING.equals(action)) {
LOGGER.info("Received Broadcast: Start Track Recording.");
startOBDConnectionService();
// mNotificationHandler.setNotificationState(SystemStartupService
// .this,
// NotificationHandler.NotificationState.OBD_FOUND);
}
// Received action matches the command for stopping the recording process of a track.
else if (ACTION_STOP_TRACK_RECORDING.equals(action)) {
LOGGER.info("Received Broadcast: Stop Track Recording.");
// Finish the current track.
mTrackRecordingHandler.finishCurrentTrack();
}
}
};
@Override
public void onCreate() {
LOGGER.info("onCreate()");
super.onCreate();
// Inject ourselves.
((Injector) getApplicationContext()).injectObjects(this);
// Register on the event bus.
this.mBus.register(this);
// Get the required preference settings.
this.mDiscoveryInterval = PreferencesHandler.getDiscoveryInterval(context);
// Register a new BroadcastReceiver that waits for different incoming actions issued from
// the notification.
IntentFilter notificationClickedFilter = new IntentFilter();
notificationClickedFilter.addAction(ACTION_START_BT_DISCOVERY);
notificationClickedFilter.addAction(ACTION_STOP_BT_DISCOVERY);
notificationClickedFilter.addAction(ACTION_START_TRACK_RECORDING);
notificationClickedFilter.addAction(ACTION_STOP_TRACK_RECORDING);
registerReceiver(mBroadcastReciever, notificationClickedFilter);
// Set the Notification to
if (this.mBluetoothHandler.isBluetoothEnabled()) {
// State: No OBD device selected.
if (mBluetoothHandler.getSelectedBluetoothDevice() == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_OBD_SELECTED);
} else if (mCarManager.getCar() == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_CAR_SELECTED);
} else {
OBDServiceHandler.setRecordingState(OBDServiceState.UNCONNECTED);
}
}
subscriptions.add(
PreferencesHandler.getSelectedCarObsevable()
.map(car -> (car != null))
.subscribe(hasCar -> {
LOGGER.info(String.format("Received changed selected car -> [%s]",
hasCar));
if(!hasCarSelected){
if(hasCar){
hasCarSelected = hasCar;
scheduleDiscovery(mDiscoveryInterval);
}
} else if (hasCarSelected && !hasCar){
hasCarSelected = hasCar;
unscheduleDiscovery();
}
hasCarSelected = hasCar;
}));
subscriptions.add(
PreferencesHandler.getDiscoveryIntervalObservable(getApplicationContext())
.subscribe(integer -> {
LOGGER.info(String.format("Received changed discovery interval -> [%s]",
integer));
mDiscoveryInterval = integer;
scheduleDiscovery(mDiscoveryInterval);
})
);
subscriptions.add(
PreferencesHandler.getAutoconnectObservable(getApplicationContext())
.subscribe(aBoolean -> {
LOGGER.info(String.format("Received changed autoconnect -> [%s]",
aBoolean));
mIsAutoconnect = aBoolean;
scheduleDiscovery(mDiscoveryInterval);
})
);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LOGGER.info("onStartCommand()");
// only start the discovery process if the required settings has been selected.
if (mBluetoothHandler.isBluetoothEnabled() &&
mBluetoothHandler.getSelectedBluetoothDevice() != null &&
mCarManager.getCar() != null &&
mIsAutoconnect) {
scheduleDiscovery(-1);
}
return START_STICKY;
}
@Override
public void onDestroy() {
LOGGER.info("onDestroy()");
super.onDestroy();
// Unbind the connection remoteService.
// unbindOBDConnectionService();
// unregister all boradcast receivers.
unregisterReceiver(mBroadcastReciever);
// Unsubscribe subscriptions.
if (mWorkerSubscription != null)
mWorkerSubscription.unsubscribe();
if (mDiscoverySubscription != null)
mDiscoverySubscription.unsubscribe();
if (!subscriptions.isUnsubscribed()) {
subscriptions.unsubscribe();
}
// Close the corresponding notification.
OBDServiceHandler.closeNotification();
mBluetoothHandler.stopBluetoothDeviceDiscovery();
}
@Subscribe
public void onReceiveBluetoothStateChangedEvent(BluetoothStateChangedEvent event) {
LOGGER.info(String.format("Received event. %s", event.toString()));
if (!event.isBluetoothEnabled) {
// When Bluetooth has been turned off, then this remoteService is required to be closed.
if (mBluetoothHandler.isDiscovering())
mBluetoothHandler.stopBluetoothDeviceDiscovery();
stopSelf();
}
}
@Subscribe
public void onReceiveBluetoothDeviceSelectedEvent(BluetoothDeviceSelectedEvent event) {
LOGGER.info(String.format("Received event. %s", event.toString()));
if (event.mDevice == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_OBD_SELECTED);
} else if (OBDServiceHandler.getRecordingState() == OBDServiceState.NO_OBD_SELECTED) {
if (mCarManager.getCar() == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_CAR_SELECTED);
} else {
OBDServiceHandler.setRecordingState(OBDServiceState.UNCONNECTED);
}
}
}
/**
* Receiver method for {@link BluetoothServiceStateChangedEvent}s posted on the event bus.
*
* @param event the corresponding event type.
*/
@Subscribe
public void onReceiveBluetoothServiceStateChangedEvent(
BluetoothServiceStateChangedEvent event) {
LOGGER.info(String.format("onReceiveBluetoothServiceStateChangedEvent(): %s",
event.toString()));
// Update the notification state depending on the event's state.
switch (event.mState) {
case SERVICE_STARTING:
OBDServiceHandler.setRecordingState(OBDServiceState.CONNECTING);
break;
case SERVICE_STARTED:
OBDServiceHandler.setRecordingState(OBDServiceState.CONNECTED);
if (mWorkerSubscription != null)
mWorkerSubscription.unsubscribe();
break;
case SERVICE_STOPPING:
OBDServiceHandler.setRecordingState(OBDServiceState.STOPPING);
break;
case SERVICE_STOPPED:
OBDServiceHandler.setRecordingState(OBDServiceState.UNCONNECTED);
scheduleDiscovery(REDISCOVERY_INTERVAL);
break;
case SERVICE_DEVICE_DISCOVERY_RUNNING:
OBDServiceHandler.setRecordingState(OBDServiceState.DISCOVERING);
break;
case SERVICE_DEVICE_DISCOVERY_PENDING:
break;
}
}
@Subscribe
public void onReceiveNewCarTypeSelectedEvent(NewCarTypeSelectedEvent event) {
LOGGER.info(String.format("onReceiveNewCarTypeSelectedEvent(): %s", event.toString()));
if (event.mCar == null) {
updateNotificationState(OBDServiceState.NO_CAR_SELECTED);
} else if (OBDConnectionService.CURRENT_SERVICE_STATE == BluetoothServiceState
.SERVICE_STOPPED) {
updateNotificationState(OBDServiceState.UNCONNECTED);
}
}
private void updateNotificationState(OBDServiceState state) {
if (OBDConnectionService.CURRENT_SERVICE_STATE == BluetoothServiceState.SERVICE_STOPPED) {
if (mBluetoothHandler.getSelectedBluetoothDevice() == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_OBD_SELECTED);
} else if (mCarManager.getCar() == null) {
OBDServiceHandler.setRecordingState(OBDServiceState.NO_CAR_SELECTED);
} else {
OBDServiceState currentState = OBDServiceHandler.getRecordingState();
if (currentState != OBDServiceState.DISCOVERING &&
state != OBDServiceState.DISCOVERING) {
if (mIsAutoconnect) {
scheduleDiscovery(REDISCOVERY_INTERVAL);
}
}
OBDServiceHandler.setRecordingState(state);
}
}
}
/**
* Schedules the immediate discovery for the selected OBDII adapter.
*/
private void scheduleDiscovery() {
this.scheduleDiscovery(-1);
}
/**
* Schedules the discovery for the selected OBDII adapter with a specific delay.
*
* @param delay time to wait before the scheduled action gets executes. A non-positive delay
* indicate an undelayed execution.
*/
private void scheduleDiscovery(int delay) {
// Unschedule all outstanding work.
unscheduleDiscovery();
// if autoconnect has been enabled and a car has been selected, then schedule a new
// discovery.
if (mIsAutoconnect && hasCarSelected && this.mBluetoothHandler.isBluetoothEnabled()) {
// Reschedule a fresh discovery.
mWorkerSubscription = mWorkerThread.schedule(() -> {
startDiscoveryForSelectedDevice();
}, delay, TimeUnit.SECONDS);
LOGGER.info("Discovery subscription has been scheduled -> [%s]", "" +
delay);
}
}
/**
* Stops the current discovery and/or the scheduled upcoming discovery.
*/
private void unscheduleDiscovery() {
if (mWorkerSubscription != null) {
if (mBluetoothHandler.isDiscovering())
mBluetoothHandler.stopBluetoothDeviceDiscovery();
mWorkerSubscription.unsubscribe();
}
}
// /**
// * Establishes a binding to the OBDConnectionService if the remoteService is running.
// */
// private void bindOBDConnectionService() {
// if (ServiceUtils.isServiceRunning(getApplicationContext(),
// // Defines callbacks for the remoteService binding, passed to bindService()
// OBDConnectionService.class)) {
//
// // Bind to OBDConnectionService
// Intent intent = new Intent(this, OBDConnectionService.class);
// bindService(intent, mOBDConnectionServiceCon,
// Context.BIND_ABOVE_CLIENT);
// }
// }
/**
* Starts the OBDConnectionService if it is not already running. This also initiates the
* start of a new track.
*/
private void startOBDConnectionService() {
if (!ServiceUtils
.isServiceRunning(getApplicationContext(), OBDConnectionService.class)) {
// Start the OBD Connection Service
getApplicationContext().startService(
new Intent(getApplicationContext(), OBDConnectionService.class));
// binds the OBD Connection Service.
// bindOBDConnectionService();
}
}
/**
* Removes a binding to the OBDConnection remoteService if the remoteService is running and
* this remoteService
* is bound.
*/
// private void unbindOBDConnectionService() {
// // Only when the remoteService is running and this remoteService is bounded to that
// // remoteService.
// if (mOBDConnectionService != null && ServiceUtils
// .isServiceRunning(getApplicationContext(), OBDConnectionService.class)) {
//
// // Unbinds the OBD connection remoteService.
// unbindService(mOBDConnectionServiceCon);
// }
// }
/**
* Starts the discovery for the selected OBDII device. If the device has been found then the
* device either auto-connects or updates the notification accordinlgy depending on the
* individual settings.
*/
private void startDiscoveryForSelectedDevice() {
BluetoothDevice device = mBluetoothHandler.getSelectedBluetoothDevice();
if (device == null) {
mMainThreadWorker.schedule(() -> Toast.makeText(getApplicationContext(), "No paired " +
"bluetooth device selected", Toast.LENGTH_SHORT).show());
} else {
// If the remoteService is already discovering, then skip the current discovery and
// unsubscribe on the corresponding subscription.
if (mDiscoverySubscription != null) {
mBluetoothHandler.stopBluetoothDeviceDiscovery();
mDiscoverySubscription.unsubscribe();
mDiscoverySubscription = null;
}
// Initialize a new discovery of the bluetooth.
mDiscoverySubscription = mBluetoothHandler
.startBluetoothDiscoveryForSingleDevice(device)
.subscribe(new SafeSubscriber<BluetoothDevice>(new Subscriber<BluetoothDevice>() {
private boolean isFound = false;
@Override
public void onStart() {
LOGGER.info("Device Discovery started...");
OBDServiceHandler.setRecordingState(OBDServiceState.DISCOVERING);
}
@Override
public void onNext(BluetoothDevice device) {
LOGGER.info("Device Discovered...");
// The device has been successfully discovered. Set the flag to true
// and stop the discovery process.
isFound = true;
mBluetoothHandler.stopBluetoothDeviceDiscovery();
// Depending on the individual settings either start the background
// remoteService or update the notification state.
if (mIsAutoconnect) {
LOGGER.info("[Autoconnect is on]. Try to start the connection to " +
"the selected OBD adapter.");
getApplicationContext().startService(
new Intent(getApplicationContext(), OBDConnectionService
.class));
} else {
LOGGER.info("[Autoconnect is off]. Update the notification.");
// If the device has been successful discovered, set the
// notification state to OBD_FOUND and stop the bluetooth discovery.
// TODO
// OBDServiceHandler.setRecordingState();
// mNotificationHandler.setNotificationState(SystemStartupService
// .this,
// NotificationHandler.NotificationState.OBD_FOUND);
scheduleDiscovery(REDISCOVERY_INTERVAL);
}
}
@Override
public void onCompleted() {
LOGGER.info("Device Discovery finished...");
// If the device to search for has not been found during the
// discovery period, then set back the notification state to
// unconnected.
if (!isFound) {
LOGGER.info("The selected OBDII device has not been found. " +
"Schedule a new discovery in " + mDiscoveryInterval + " " +
"seconds.");
OBDServiceHandler.setRecordingState(OBDServiceState.UNCONNECTED);
// Reschedule the discovery if it is enabled.
if (mIsAutoconnect) {
scheduleDiscovery(mDiscoveryInterval);
}
}
if (mDiscoverySubscription != null) {
mDiscoverySubscription.unsubscribe();
mDiscoverySubscription = null;
}
}
@Override
public void onError(Throwable e) {
LOGGER.error("Error while discovering for the selected Bluetooth " +
"devices", e);
}
}) {
@Override
public void onStart() {
getActual().onStart();
}
});
}
}
}