package com.openxc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.google.common.base.MoreObjects;
import com.openxc.interfaces.VehicleInterface;
import com.openxc.interfaces.VehicleInterfaceDescriptor;
import com.openxc.measurements.BaseMeasurement;
import com.openxc.measurements.Measurement;
import com.openxc.measurements.UnrecognizedMeasurementTypeException;
import com.openxc.messages.Command;
import com.openxc.messages.Command.CommandType;
import com.openxc.messages.CommandResponse;
import com.openxc.messages.DiagnosticRequest;
import com.openxc.messages.ExactKeyMatcher;
import com.openxc.messages.KeyMatcher;
import com.openxc.messages.KeyedMessage;
import com.openxc.messages.MessageKey;
import com.openxc.messages.VehicleMessage;
import com.openxc.remote.VehicleService;
import com.openxc.remote.VehicleServiceException;
import com.openxc.remote.VehicleServiceInterface;
import com.openxc.remote.ViConnectionListener;
import com.openxc.sinks.MessageListenerSink;
import com.openxc.sinks.UserSink;
import com.openxc.sinks.VehicleDataSink;
import com.openxc.sources.RemoteListenerSource;
import com.openxc.sources.VehicleDataSource;
/**
* The VehicleManager is an in-process Android service and the primary entry
* point into the OpenXC library.
*
* An OpenXC application should bind to this service and request vehicle
* measurements through it either synchronously or asynchronously. The service
* will shut down when no more clients are bound to it.
*
* Synchronous measurements are obtained by passing the type of the desired
* measurement to the {@link #get(Class)} method. Asynchronous measurements are
* obtained by defining a Measurement.Listener or VehicleMessage.Listener object
* and passing it to the service via the addListener method.
*
* There are three major components in the VehicleManager:
* {@link com.openxc.sources.VehicleDataSource},
* {@link com.openxc.sinks.VehicleDataSink} and
* {@link com.openxc.interfaces.VehicleInterface}.
*
* The instance of a {@link com.openxc.interfaces.VehicleInterface} is perhaps the
* most important. This represents the actual physical connection to the
* vehicle, and is bi-directional - it can both provide data to an
* application and send data back to the vehicle. In most cases,
* this should not be instantiated by applications; the
* {@link #setVehicleInterface(Class, String)} method takes enough metadata for
* the remote {@link com.openxc.remote.VehicleService} to instantiate the
* interface in a remote process. That way a single USB or
* Bluetooth connection can be shared among many applications.
*
* The VehicleManager also supports custom user-defined data sources, which can
* be controlled with {@link #addSource(VehicleDataSource)} and
* {@link #removeSource(VehicleDataSource)} methods. Even though data sources are
* instantiated by the application, their data is still shared among all OpenXC
* applications using the same remove process {@link
* com.openxc.remote.VehicleService}
*
* In addition to applications registered to receive updates as a Measurement
* or VehicleMessage listener, the VehicleManager supports custom data sinks (e.g.
* {@link com.openxc.sinks.FileRecorderSink}) that be controlled with the
* {@link #addSink(VehicleDataSink)} and {@link #removeSink(VehicleDataSink)}
* methods.
*
* When a message is received from a
* {@link com.openxc.sources.VehicleDataSource}, it is passed to every active
* {@link com.openxc.sinks.VehicleDataSink}. There will always be at
* least one sink that stores the latest messages and handles passing on data to
* users of this service.
*/
public class VehicleManager extends Service implements DataPipeline.Operator {
public final static String VEHICLE_LOCATION_PROVIDER =
VehicleLocationProvider.VEHICLE_LOCATION_PROVIDER;
private final static String TAG = "VehicleManager";
private Lock mRemoteBoundLock = new ReentrantLock();
private Condition mRemoteBoundCondition = mRemoteBoundLock.newCondition();
private IBinder mBinder = new VehicleBinder();
// The mRemoteOriginPipeline in this class must only have 1 source - the
// special RemoteListenerSource that receives measurements from the
// VehicleService and propagates them to all of the user-registered sinks.
private DataPipeline mRemoteOriginPipeline = new DataPipeline();
// The mUserOriginPipeline, oppositely, must have only 1 sink - the special
// UserSink that funnels data from user defined sources back to the
// VehicleService. Any user-registered sources go in the mUserOriginPipeline
// so they don't try to circumvent the VehicleService and send their values
// directly to the in-process sinks (otherwise no other applications could
// receive updates from that source).
private DataPipeline mUserOriginPipeline = new DataPipeline(this);
private boolean mIsBound;
private VehicleServiceInterface mRemoteService;
private RemoteListenerSource mRemoteSource;
private MessageListenerSink mNotifier = new MessageListenerSink();
private UserSink mUserSink;
/**
* Binder to connect IBinder in a ServiceConnection with the VehicleManager.
*
* This class is used in the onServiceConnected method of a
* ServiceConnection in a client of this service - the IBinder given to the
* application can be cast to the VehicleBinder to retrieve the actual
* service instance. This is required to actually call any of its methods.
*/
public class VehicleBinder extends Binder {
/**
* Return this Binder's parent VehicleManager instance.
*
* @return an instance of VehicleManager.
*/
public VehicleManager getService() {
return VehicleManager.this;
}
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service starting");
mRemoteOriginPipeline.addSink(mNotifier);
bindRemote();
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service being destroyed");
mRemoteOriginPipeline.stop();
unbindRemote();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Service binding in response to " + intent);
bindRemote();
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "Service unbinding in response to " + intent);
return true;
}
/**
* Block until the VehicleManager is alive and can return measurements.
*
* Blocks for at most 3 seconds.
*
* Most applications don't need this and don't wait this method, but it can
* be useful for testing when you need to make sure you will get a
* measurement back from the system.
*/
public void waitUntilBound() throws VehicleServiceException {
mRemoteBoundLock.lock();
Log.i(TAG, "Waiting for the VehicleService to bind to " + this);
while(mRemoteService == null) {
try {
if(!mRemoteBoundCondition.await(3, TimeUnit.SECONDS)) {
throw new VehicleServiceException(
"Not bound to remote service after 3 seconds");
}
} catch(InterruptedException e) {}
}
Log.i(TAG, mRemoteService + " is now bound");
mRemoteBoundLock.unlock();
}
/**
* Retrieve the most current value of a measurement.
*
* @param measurementType The class of the requested Measurement
* (e.g. VehicleSpeed.class)
* @return An instance of the requested Measurement which may or may
* not have a value.
* @throws UnrecognizedMeasurementTypeException if passed a measurementType
* that does not extend Measurement
* @throws NoValueException if no value has yet been received for this
* measurementType
* @see BaseMeasurement
*/
public Measurement get(
Class<? extends Measurement> measurementType)
throws UnrecognizedMeasurementTypeException, NoValueException {
return BaseMeasurement.getMeasurementFromMessage(measurementType,
get(BaseMeasurement.getKeyForMeasurement(measurementType)).asSimpleMessage());
}
/**
* Retrieve the most current value of a keyed message.
*
* @param key The key of the requested Measurement
* (e.g. VehicleSpeed.class)
* @return An instance of the requested Measurement which may or may
* not have a value.
* @throws NoValueException if no value has yet been received for this
* measurementType
* @see BaseMeasurement
*/
public VehicleMessage get(MessageKey key) throws NoValueException {
if(mRemoteService == null) {
Log.w(TAG, "Not connected to the VehicleService -- " +
"throwing a NoValueException");
throw new NoValueException();
}
try {
VehicleMessage message = mRemoteService.get(key);
if(message == null) {
throw new NoValueException();
}
return message;
} catch(RemoteException | ClassCastException e) {
Log.w(TAG, "Unable to get value from remote vehicle service", e);
throw new NoValueException();
}
}
/**
* Send a message to the vehicle through the active
* {@link com.openxc.interfaces.VehicleInterface} without waiting for
* a response.
*
* @param message The desired message to send to the vehicle.
* @return true if the message was sent successfully on an interface.
*/
public boolean send(VehicleMessage message) {
VehicleMessage wrappedMessage = message;
if(message instanceof DiagnosticRequest) {
// Wrap the request in a Command
wrappedMessage = new Command(message.asDiagnosticRequest(),
DiagnosticRequest.ADD_ACTION_KEY);
}
boolean sent = false;
// Don't want to keep this in the same list as local interfaces because
// if that quits after the first interface reports success.
if(mRemoteService != null) {
try {
sent = mRemoteService.send(wrappedMessage);
} catch(RemoteException e) {
Log.v(TAG, "Unable to propagate command to remote interface", e);
}
}
if(sent) {
// Add a timestamp of when the message was actually sent
message.timestamp();
}
return sent;
}
/**
* Convert a Measurement to a SimpleVehicleMessage and send it through the
* active VehicleInterface.
*
* @param message The desired message to send to the vehicle.
* @return true if the message was sent successfully on an interface.
*/
public boolean send(Measurement message) {
return send(message.toVehicleMessage());
}
/**
* Send a message to the VehicleInterface and register the given listener to
* receive the first response matching the message's key.
*
* This function is non-blocking.
*
* The listener is unregistered after the first response is received. If you
* need to accept multiple responses for the same request, you must manually
* register your own listener to control its lifecycle.
*
* @param message The desired message to send to the vehicle.
* @param listener The message listener that should receive a callback when
* a response matching the outgoing message's key is received from the
* VI.
*/
public void request(KeyedMessage message,
VehicleMessage.Listener listener) {
// Register the listener as non-persistent, so it is deleted after
// receiving the first response
mNotifier.register(ExactKeyMatcher.buildExactMatcher(message.getKey()),
listener, false);
send(message);
}
/**
* Send a message to the VehicleInterface and wait up to 2 seconds to
* receive a response.
*
* This is a blocking version of the other request(...) method.
*
* @return The response if one is received before the timeout, otherwise
* null.
*/
public VehicleMessage request(KeyedMessage message) {
BlockingMessageListener callback = new BlockingMessageListener();
request(message, callback);
return callback.waitForResponse();
}
/**
* Register to receive asynchronous updates for a specific Measurement type.
*
* A Measurement is a specific, known VehicleMessage subtype.
*
* Use this method to register an object implementing the
* Measurement.Listener interface to receive real-time updates
* whenever a new value is received for the specified measurementType.
*
* Make sure you unregister your listeners with
* VehicleManager.removeListener(...) when your activity or service closes
* and no longer requires updates.
*
* @param measurementType The class of the Measurement
* (e.g. VehicleSpeed.class) the listener was listening for
* @param listener An listener instance to receive the callback.
*/
public void addListener(Class<? extends Measurement> measurementType,
Measurement.Listener listener) {
Log.i(TAG, "Adding listener " + listener + " for " + measurementType);
mNotifier.register(measurementType, listener);
}
/**
* Register to receive asynchronous updates for a specific VehicleMessage
* type.
*
* Use this method to register an object implementing the
* VehicleMessage.Listener interface to receive real-time updates
* whenever a new value is received for the specified message type.
*
* Make sure you unregister your listeners with
* VehicleManager.removeListener(...) when your activity or service closes
* and no longer requires updates.
*
* @param messageType The class of the VehicleMessage
* (e.g. SimpleVehicleMessage.class) the listener is listening for
* @param listener An listener instance to receive the callback.
*/
public void addListener(Class<? extends VehicleMessage> messageType,
VehicleMessage.Listener listener) {
Log.i(TAG, "Adding listener " + listener + " for " + messageType);
mNotifier.register(messageType, listener);
}
/**
* Register to receive a callback when a message with same key as the given
* KeyedMessage is received.
*
* @param keyedMessage A message with the key you want to receive updates
* for - the response to a command typically has the same key as the
* request, so you can use the outgoing message's KeyedMessage to
* register to receive a response.
* @param listener An listener instance to receive the callback.
*/
public void addListener(KeyedMessage keyedMessage,
VehicleMessage.Listener listener) {
addListener(keyedMessage.getKey(), listener);
}
/**
* Register to receive a callback when a message with the given key is
* received.
*
* @param key The key you want to receive updates.
* @param listener An listener instance to receive the callback.
*/
public void addListener(MessageKey key, VehicleMessage.Listener listener) {
addListener(ExactKeyMatcher.buildExactMatcher(key), listener);
}
/**
* Register to receive a callback when a message with key matching the given
* KeyMatcher is received.
*
* This function can be used to set up a wildcard listener, or one that
* receives a wider range of responses than just a 1 to 1 match of keys.
*
* @param matcher A KeyMatcher implement the desired filtering logic.
* @param listener An listener instance to receive the callback.
*/
public void addListener(KeyMatcher matcher, VehicleMessage.Listener listener) {
Log.i(TAG, "Adding listener " + listener + " to " + matcher);
mNotifier.register(matcher, listener);
}
/**
* Unregister a previously registered Measurement.Listener instance.
*
* When an application is no longer interested in receiving measurement
* updates (e.g. when it's pausing or exiting) it should unregister all
* previously registered listeners to save on CPU.
*
* @param measurementType The class of the requested Measurement
* (e.g. VehicleSpeed.class)
* @param listener The listener to remove.
*/
public void removeListener(Class<? extends Measurement> measurementType,
Measurement.Listener listener) {
Log.i(TAG, "Removing listener " + listener + " for " + measurementType);
mNotifier.unregister(measurementType, listener);
}
/**
* Unregister a previously registered message type listener.
*
* @param messageType The class of the VehicleMessage this listener was
* registered to receive. A listener can be registered to receive
* multiple message types, which is why this must be specified when
* removing a listener.
* @param listener The listener to remove.
*/
public void removeListener(Class<? extends VehicleMessage> messageType,
VehicleMessage.Listener listener) {
mNotifier.unregister(messageType, listener);
}
/**
* Unregister a previously registered keyed message listener.
*
* @param message The message with the key this listener was previously
* registered to receive.
* @param listener The listener to remove.
*/
public void removeListener(KeyedMessage message,
VehicleMessage.Listener listener) {
removeListener(message.getKey(), listener);
}
/**
* Unregister a previously registered key matcher listener.
*
* @param matcher The KeyMatcher this listener was previously registered
* to receive matches from.
* @param listener The listener to remove.
*/
public void removeListener(KeyMatcher matcher,
VehicleMessage.Listener listener) {
mNotifier.unregister(matcher, listener);
}
/**
* Unregister a previously registered key listener.
*
* @param key The key this listener was previously registered to
* receive updates on.
* @param listener The listener to remove.
*/
public void removeListener(MessageKey key, VehicleMessage.Listener listener) {
removeListener(ExactKeyMatcher.buildExactMatcher(key), listener);
}
/**
* Add a new data source to the vehicle service.
*
* For example, to use the trace data source to playback a trace file, call
* the addSource method after binding with VehicleManager:
*
* service.addSource(new TraceVehicleDataSource(
* new URI("/sdcard/openxc/trace.json"))));
*
* @param source an instance of a VehicleDataSource
*/
public void addSource(VehicleDataSource source) {
Log.i(TAG, "Adding data source " + source);
mUserOriginPipeline.addSource(source);
}
/**
* Remove a previously registered source from the data pipeline.
*/
public void removeSource(VehicleDataSource source) {
if(source != null) {
Log.i(TAG, "Removing data source " + source);
mUserOriginPipeline.removeSource(source);
}
}
/**
* Add a new data sink to the vehicle service.
*
* A data sink added with this method will receive all new measurements as
* they arrive from registered data sources. For example, to use the trace
* file recorder sink, call the addSink method after binding with
* VehicleManager:
*
* service.addSink(new FileRecorderSink(
* new AndroidFileOpener("openxc", this)));
*
* @param sink an instance of a VehicleDataSink
*/
public void addSink(VehicleDataSink sink) {
Log.i(TAG, "Adding data sink " + sink);
mRemoteOriginPipeline.addSink(sink);
}
/**
* Remove a previously registered sink from the data pipeline.
*/
public void removeSink(VehicleDataSink sink) {
if(sink != null) {
mRemoteOriginPipeline.removeSink(sink);
sink.stop();
}
}
/**
* Send a command request to the vehicle that does not require any metadata.
*
* @param type The command request type to send to the VI.
* @return The message returned by the VI in response to this command or
* null if none was received.
*/
public String requestCommandMessage(CommandType type) {
VehicleMessage message = request(new Command(type));
String value = null;
if(message != null) {
// Because we use the same key and value for commands and command
// responses, if for some reason a Command is echoed back to the
// device instead of a CommandResponse, you could get a casting
// exception when trying to cast this message here. If we got a
// Command, just ignore it and assume no response - I wasn't able to
// reproduce it but we did have a few Bugsnag reports about it.
try {
CommandResponse response = message.asCommandResponse();
if(response.getStatus()) {
value = response.getMessage();
}
} catch(ClassCastException e) {
Log.w(TAG, "Expected a command response but got " + message +
" -- ignoring, assuming no response");
}
}
return value;
}
/**
* Query for the unique device ID of the active VI.
*
* @return the device ID string or null if not known.
*/
public String getVehicleInterfaceDeviceId() {
return requestCommandMessage(CommandType.DEVICE_ID);
}
/**
* Query for the firmware version of the active VI.
*
* @return the firmware version string or null if not known.
*/
public String getVehicleInterfaceVersion() {
return requestCommandMessage(CommandType.VERSION);
}
/**
* Query for the platform of the active VI.
*
* @return the platform string or null if not known.
*/
public String getVehicleInterfacePlatform() {
return requestCommandMessage(CommandType.PLATFORM);
}
/**
* Register a listener to receive a callback when the selected VI is
* connected.
*
* @param listener The listener that should receive the callback.
*/
public void addOnVehicleInterfaceConnectedListener(
ViConnectionListener listener) throws VehicleServiceException {
if(mRemoteService != null) {
try {
mRemoteService.addViConnectionListener(listener);
} catch(RemoteException e) {
throw new VehicleServiceException(
"Unable to add connection status listener", e);
}
}
}
/**
* Change the active vehicle interface to a new type using its default
* resource identifier.
*
* To disable all vehicle interfaces, pass null to this function.
*
* @param vehicleInterfaceType the VI type to activate or null to disable
* all VIs.
*/
public void setVehicleInterface(
Class<? extends VehicleInterface> vehicleInterfaceType)
throws VehicleServiceException {
setVehicleInterface(vehicleInterfaceType, null);
}
/**
* Change the active vehicle interface to a new type using the given
* resource.
*
* To disable all vehicle interfaces, pass null to this function.
*
* The only valid VehicleInterface types are those included with the library
* - the vehicle service running in a remote process is the one to actually
* instantiate the interfaces. Interfaces added with this method will be
* available for all other OpenXC applications running in the system.
*
* @param vehicleInterfaceType A class implementing VehicleInterface that is
* included in the OpenXC library
* @param resource A descriptor or a resource necessary to initialize the
* interface. See the specific implementation of {@link VehicleService}
* to find the required format of this parameter.
*/
public void setVehicleInterface(
Class<? extends VehicleInterface> vehicleInterfaceType,
String resource) throws VehicleServiceException {
Log.i(TAG, "Setting VI to: " + vehicleInterfaceType);
String interfaceName = null;
if(vehicleInterfaceType != null) {
interfaceName = vehicleInterfaceType.getName();
}
if(mRemoteService != null) {
try {
mRemoteService.setVehicleInterface(interfaceName, resource);
} catch(RemoteException e) {
throw new VehicleServiceException(
"Unable to set vehicle interface", e);
}
} else {
Log.w(TAG, "Can't set vehicle interface, not connected to the " +
"VehicleService");
}
}
/**
* Control whether the device's built-in GPS is used to provide location.
*
* @param enabled True if GPS should be read from the Android device and
* injected as vehicle data whenever a vehicle interface is connected.
*/
public void setNativeGpsStatus(boolean enabled) {
Log.i(TAG, (enabled ? "Enabling" : "Disabling") + " native GPS");
if(mRemoteService != null) {
try {
mRemoteService.setNativeGpsStatus(enabled);
} catch(RemoteException e) {
Log.w(TAG, "Unable to change native GPS status", e);
}
} else {
Log.w(TAG, "Not connected to the VehicleService");
}
}
/**
* Control whether polling is used to connect to a Bluetooth device or not.
*
* @param enabled True if polling should be used to connect to a Bluetooth
* device.
*/
public void setBluetoothPollingStatus(boolean enabled) {
Log.i(TAG, (enabled ? "Enabling" : "Disabling") + " Bluetooth polling");
if(mRemoteService != null) {
try {
mRemoteService.setBluetoothPollingStatus(enabled);
} catch(RemoteException e) {
Log.w(TAG, "Unable to change Bluetooth polling status", e);
}
} else {
Log.w(TAG, "Not connected to the VehicleService");
}
}
/**
* Returns a descriptor of the active vehicle interface.
*
* @return A VehicleInterfaceDescriptor for the active VI or null if none is
* enabled.
*/
public VehicleInterfaceDescriptor getActiveVehicleInterface() {
VehicleInterfaceDescriptor descriptor = null;
if(mRemoteService != null) {
try {
descriptor = mRemoteService.getVehicleInterfaceDescriptor();
} catch(RemoteException e) {
Log.w(TAG, "Unable to retrieve VI descriptor", e);
}
}
return descriptor;
}
/**
* Read the number of messages received by the vehicle service.
*
* @throws VehicleServiceException if the listener is unable to be
* unregistered with the library internals - an exceptional situation
* that shouldn't occur.
*/
public int getMessageCount() throws VehicleServiceException {
if(mRemoteService != null) {
try {
return mRemoteService.getMessageCount();
} catch(RemoteException e) {
throw new VehicleServiceException(
"Unable to retrieve message count", e);
}
} else {
throw new VehicleServiceException(
"Unable to retrieve message count");
}
}
/**
* Return the connection status of the selected VI.
*
* @return true if the selected VI reports that it is connected to the
* vehicle.
*/
public boolean isViConnected() {
if(mRemoteService != null) {
try {
return mUserOriginPipeline.isActive() || mRemoteService.isViConnected();
} catch(RemoteException e) {
Log.d(TAG, "Unable to send message to remote service", e);
}
}
return false;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("remoteService", mRemoteService)
.toString();
}
@Override
public void onPipelineActivated() {
if(mRemoteService != null) {
try {
mRemoteService.userPipelineActivated();
} catch(RemoteException e) {
Log.d(TAG, "Unable to send message to remote service", e);
}
}
}
@Override
public void onPipelineDeactivated() {
if(mRemoteService != null) {
try {
mRemoteService.userPipelineDeactivated();
} catch(RemoteException e) {
Log.d(TAG, "Unable to send message to remote service", e);
}
}
}
private class BlockingMessageListener implements VehicleMessage.Listener {
private final static int RESPONSE_TIMEOUT_S = 2;
private Lock mLock = new ReentrantLock();
private Condition mResponseReceived = mLock.newCondition();
private VehicleMessage mResponse;
public void receive(VehicleMessage message) {
try {
mLock.lock();
mResponse = message;
mResponseReceived.signal();
} finally {
mLock.unlock();
}
}
public VehicleMessage waitForResponse() {
try {
mLock.lock();
if(mResponse == null) {
mResponseReceived.await(RESPONSE_TIMEOUT_S, TimeUnit.SECONDS);
}
} catch(InterruptedException e) {
} finally {
mLock.unlock();
}
return mResponse;
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
Log.i(TAG, "Bound to VehicleService");
mRemoteService = VehicleServiceInterface.Stub.asInterface(service);
mRemoteSource = new RemoteListenerSource(mRemoteService);
mRemoteOriginPipeline.addSource(mRemoteSource);
mUserSink = new UserSink(mRemoteService);
mUserOriginPipeline.addSink(mUserSink);
mRemoteBoundLock.lock();
mRemoteBoundCondition.signalAll();
mRemoteBoundLock.unlock();
}
@Override
public void onServiceDisconnected(ComponentName className) {
Log.w(TAG, "VehicleService disconnected unexpectedly");
mRemoteService = null;
mRemoteOriginPipeline.removeSource(mRemoteSource);
mUserOriginPipeline.removeSink(mUserSink);
bindRemote();
}
};
private void bindRemote() {
Log.i(TAG, "Binding to VehicleService");
Intent intent = new Intent(VehicleService.class.getName());
intent.setComponent(new ComponentName("com.openxcplatform.enabler", "com.openxc.remote.VehicleService"));
try {
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
} catch(SecurityException e) {
Log.e(TAG, "Unable to bind with remote service, it's not exported "
+ "-- is the instrumentation tests package installed?", e);
}
}
private void unbindRemote() {
if(mRemoteBoundLock != null) {
mRemoteBoundLock.lock();
}
if(mIsBound) {
Log.i(TAG, "Unbinding from VehicleService");
unbindService(mConnection);
mRemoteService = null;
mIsBound = false;
}
if(mRemoteBoundLock != null) {
mRemoteBoundLock.unlock();
}
}
}