为什么要做成一个服务?大家知道,在android系统中一个activity是在前台活动着的并且会占掉大量的资源,而像我们的程序这样需要一直通过串口交换数据却不用怎么绘图的程序完全可以让它一直在后台运行。此外,我们把蓝牙车的控制程序看做是一个驱动,我们希望前台的与电脑通信的程序是多种多样的同时驱动程序的升级也不会影响到之前就已经做好的程序正常运行。启用一个服务是最佳的选择。
除此之外,使用服务还有一个很好的用途,就是可以再向系统发送INTENT之后自动启动,在长时间没有用户请求的情况下可以自行退出释放资源,如此就不用你在费劲的一个一个启动程序了,非常适合远程控制。
到这里就不再是只用helloworld和一些java的基础知识就能解决的了的了,需要对android系统的程序运行机制有一定的了解,android系统中的各种通信都是通过这个INTENT实现的线程的启动运行、线程间的通信、系统服务的调用甚至是一条短信都是用INTENT来实现的,仔细的学一下会发现这真的是一个非常好的事务处理机制,不知道iphone是不是也是用的这样的方法,虽然和以前接触的windows和linux的线程处理方式略有差异差异但是读懂了以后用着还是很方便的。不敢说太多了,毕竟自己也不是很熟悉这些高端的东西,说多了会弄出笑话的……这些东西网上资料还是不少的。
想要程序自动启动需要注册一个INTENT的RECEIVER,有两种方式,一种是在程序代码中注册,主要是用于程序运行以后实现线程间通信的。另一种是写在Manifest文件中,这样可以在接到相应的INTENT时自动启动对应的服务,主要是用来启动服务确保每一个INTENT都能送到活动的服务中。我们的服务只有这么一个写在Manifest文件中的接收器,这样上层的程序只要漫无目的的到处广播左转前进这样的命令就好了,系统会自动找到合适的控制程序启动合适的车子的。
控制的INTENT中,Type是消息的类型,比如控制,查询状态,电池电量等等,每一种Type对应着不同的传送数据,这个版本写得比较简单(后来一直没顾上返回来完善),只监听了控制命令着一种Type,控制命令中的参数分别为转向的量、速度的量和该命令持续的时间。
顺便写了一个广播方式控制的activity,当做例子来用了。车子不再身边,不上截图了,直接上代码,反正还是没少写注释的。
/AndroidBluetoothCarServer/src/android/lynx/BluetoothCarServer/AndroidBluetoothCarServerActivity.java
/**
* @author lynx
*/
package android.lynx.BluetoothCarServer;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* @author lynx
*与服务进程通信的广播接收器
*/
public class CarServiceBroadcastReceiver extends BroadcastReceiver {
private static final boolean D = false; //Debug模式标识
/* (non-Javadoc)
* @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
*/
@Override
public void onReceive(Context context, Intent intent) {
if(D)
Log.d("CarServiceBroadcastReceiver", "onReceive");
// 启动服务,通过启动Intent传递参数
Intent service = new Intent(context, BluetoothCarService.class);
service.putExtra("Type", intent.getStringExtra("Type"));
service.putExtra("MilliSeconds", intent.getIntExtra("MilliSeconds", 0));
service.putExtra("TurnPosition", intent.getDoubleExtra("TurnPosition", 0));
service.putExtra("MovePosition", intent.getDoubleExtra("MovePosition", 0));
if(service.getStringExtra("Type").equals("Exit"))
{
context.stopService(service);
}
else
{
context.startService(service);
}
}
}
/AndroidBluetoothCarServer/src/android/lynx/BluetoothCarServer/BluetoothCarService.java
/**
* @author lynx
*/
package android.lynx.BluetoothCarServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.Looper;
import android.text.format.Time;
import android.util.Log;
import android.widget.Toast;
/**
* @author lynx 这个是用于Intent与蓝牙串口通信的服务进程,动作数据的获取通过onStart、onBind方法传入的Intent获得
*/
public class BluetoothCarService extends Service implements Runnable {
private static final String TAG = "BluetoothCarServer";
private static final boolean D = true; // Debug模式开启标识
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothDevice device = null;
private BluetoothSocket btSocket = null;
private OutputStream outStream = null;
private InputStream inStream = null;
private static final UUID MY_UUID = UUID
.fromString("00001101-0000-1000-8000-00805F9B34FB"); // 这条是蓝牙串口通用的UUID,不要更改
private static String address = "00:19:5D:EE:2E:A5"; // <==要连接的蓝牙设备MAC地址
// 守护进程用field
private Thread Guarder = null;
private boolean ThreadFlag = false;
private static final int TimeOutMinute = 5; // 线程超时时间
// 任务队列用field,出于安全考虑只设计一个缓冲区处理任务,新来的任务会无条件覆盖老得任务
private Time ActionStartTime = new Time("GMT+8"); // 任务开始的时间
private Time ActionStopTime = new Time("GMT+8"); // 任务结束的时间
private double StateMove = 0; // 速度的状态记录,0为静止,-1为后退最大速度,1为前进最大速度。
private double StateTurn = 0; // 转向的状态记录,0为中点,-1为左转最大角度,1为右转最大角度。
// 任务栏通知用的field
NotificationManager notificationManager;
Notification CarServerNoti;
PendingIntent LaunchIntent;
Context NotiContext;
int NotificationRef = 1;
private static final String NOTIFY_EXIT_TIME = "守护进程将在最后一条命令完成"
+ TimeOutMinute + "分钟后自动退出,点击强制退出";
private static final String NOTIFY_CONNECTING = "连接中...";
private static final String NOTIFY_TITLE = "蓝牙越野车通信守护进程";
public static final String BroadcastName = "android.lynx.car.intent.INTENT_CAR_CONTROL";
/*
* (non-Javadoc)
*
* @see android.app.Service#onCreate()
*/
@Override
public void onCreate() {
if (D)
Log.d(TAG, "+++ ON CREATE +++");
// 打开任务栏通知
CarServerNoti = new Notification(R.drawable.icon, " ", System
.currentTimeMillis());
NotiContext = getApplicationContext();
Intent NotiIntent = new Intent(BroadcastName);
NotiIntent.putExtra("Type", "Exit");
LaunchIntent = PendingIntent
.getBroadcast(NotiContext, 0, NotiIntent, 0);
CarServerNoti.setLatestEventInfo(NotiContext, NOTIFY_TITLE, " ",
LaunchIntent);
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
CarServerNoti.when = System.currentTimeMillis();
notificationManager.notify(null, NotificationRef, CarServerNoti);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// 更新通知栏
CarServerNoti.tickerText = "蓝牙设备不可用";
CarServerNoti.setLatestEventInfo(NotiContext, NOTIFY_TITLE,
"蓝牙设备不可用", LaunchIntent);
CarServerNoti.when = System.currentTimeMillis();
notificationManager.notify(null, NotificationRef, CarServerNoti);
this.stopSelf();
return;
}
if (!mBluetoothAdapter.isEnabled()) {
// 更新通知栏
CarServerNoti.tickerText = "请打开蓝牙后再次调用本程序";
CarServerNoti.setLatestEventInfo(NotiContext, NOTIFY_TITLE,
"请打开蓝牙后再次调用本程序", LaunchIntent);
CarServerNoti.when = System.currentTimeMillis();
notificationManager.notify(null, NotificationRef, CarServerNoti);
this.stopSelf();
return;
}
this.init();
if (D)
Log.d(TAG, "+++ DONE IN ON CREATE, GOT LOCAL BT ADAPTER +++");
super.onCreate();
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onBind(android.content.Intent)
*/
@Override
public IBinder onBind(Intent intent) {
if (D)
Log.d(TAG, "+++ OnBind +++");
if (intent.getStringExtra("Type").equals("Action")) // 当获取的请求为动作请求时
{
if (D)
Log.d(TAG, "++ OnAction!!!!!! ++");
this.readNewAction(intent.getDoubleExtra("TurnPosition", 0), intent
.getDoubleExtra("MovePosition", 0), intent.getIntExtra(
"MilliSeconds", 0));
} else if (intent.getStringExtra("Type").equals("Exit")) // 请求退出时退出
{
this.ThreadDestory();
this.stopSelf();
}
return null;
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onDestroy()
*/
@Override
public void onDestroy() {
if (D)
Log.d(TAG, "+++ OnDestory +++");
this.ThreadDestory();
// 防止蓝牙未开启退出报错
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
// 关闭通知栏
notificationManager.cancel(NotificationRef);
super.onDestroy();
return;
}
try {
outStream.flush();
} catch (IOException e) {
Log.e(TAG, "ON DESTORY: Couldn't flush output stream.", e);
}
try {
btSocket.close();
} catch (IOException e2) {
Log.e(TAG, "ON DESTORY: Unable to close socket.", e2);
}
// 关闭通知栏
notificationManager.cancel(NotificationRef);
super.onDestroy();
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onRebind(android.content.Intent)
*/
@Override
public void onRebind(Intent intent) {
if (D)
Log.d(TAG, "+++ OnRebind +++");
this.init();
super.onRebind(intent);
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onStart(android.content.Intent, int)
*/
@Override
public void onStart(Intent intent, int startId) {
if (D)
Log.d(TAG, "+++ OnStart +++");
if (intent.getStringExtra("Type").equals("Action")) // 当获取的请求为动作请求时
{
if (D)
Log.d(TAG, "++ OnAction!!!!!! ++");
this.readNewAction(intent.getDoubleExtra("TurnPosition", 0), intent
.getDoubleExtra("MovePosition", 0), intent.getIntExtra(
"MilliSeconds", 0));
} else if (intent.getStringExtra("Type").equals("Exit")) // 请求退出时退出
{
this.ThreadDestory();
this.stopSelf();
}
super.onStart(intent, startId);
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onUnbind(android.content.Intent)
*/
@Override
public boolean onUnbind(Intent intent) {
if (D)
Log.d(TAG, "+++ OnUnbind +++");
return super.onUnbind(intent);
}
@Override
public void run() {
Time TimeNow = new Time("GMT+8"); // 当前的系统时间,用于与队列时间做比较
Time ThreadOffTime = new Time("GMT+8"); // 线程关闭的时间,该功能用于防止在控制程序退出以后该服务持续霸占系统资源
while (ThreadFlag) {
if (false) {
this.Connect();
}
TimeNow.setToNow(); // 读取当前的时间
if (TimeNow.after(ActionStopTime)) // 当超过队列时间时令车子处于静止状态
{
StateMove = 0;
StateTurn = 0;
ThreadOffTime.set(ActionStopTime.toMillis(true) + 1000 * 60
* TimeOutMinute); // 设置线程超时时间为队列超时以后的TimeOutMinute分钟(100*60*TimeOutMinute毫秒)
if (TimeNow.after(ThreadOffTime)) // 线程超时,关闭服务
{
if (D)
Log.d(TAG, "+++++ Thread time out, EXIT +++++");
this.ThreadDestory();
this.stopSelf();
}
}
this.setPositionTurn(StateTurn);
try {
Thread.sleep(50); // 如果过快的发送两个状态改变命令会导致车子无法响应后一条命令
} catch (Exception ex) {
}
this.setPositionMove(StateMove);
try {
Thread.sleep(50);
} catch (Exception ex) {
}
}
}
/**
* 创建用于定时发送位置状态的守护进程
*/
private void ThreadCreate() {
if (Guarder != null) // 确保只有一个守护进程可以被创建
return;
Guarder = new Thread(this);
ThreadFlag = true;
Guarder.start();
}
/**
* 销毁守护进程
*/
private void ThreadDestory() {
ThreadFlag = false;
Guarder = null;
}
private void init() {
Time TimeNow = new Time("GMT+8"); // 当前的系统时间,用于与队列时间做比较
Time ThreadOffTime = new Time("GMT+8"); // 线程关闭的时间,用于计算初始化时是否会时间以出
this.Connect();
TimeNow.setToNow(); // 读取当前的时间
ThreadOffTime.set(ActionStopTime.toMillis(true) + 1000 * 60
* TimeOutMinute); // 设置线程超时时间为队列超时以后的TimeOutMinute分钟(100*60*TimeOutMinute毫秒)
if (TimeNow.after(ThreadOffTime)) {
ActionStartTime.setToNow(); // 把队列的时间设定到当前时间,防止1970年的超时退出(刚初始化就退出线程是不好的)
ActionStopTime.setToNow();
}
this.ThreadCreate();
}
/**
* 获取新的移动命令(注意过长时间无命令会自动关闭守护进程,进程关闭后会在新命令带来时重启)
*
* @param TurnPosition
* 转向的参数,0为正中,-1为左转最大角度,1为右转最大角度。
* @param MovePosition
* 移动的参数,0为静止,-1为后退最大速度,1为前进最大速度。
* @param ContinueMilliSeconds
* 该动作持续的时间(0~1000毫秒)
*/
public void readNewAction(double TurnPosition, double MovePosition,
int ContinueMilliSeconds) {
/* 合理化参数范围 */
if (TurnPosition > 1)
TurnPosition = 1;
else if (TurnPosition < -1)
TurnPosition = -1;
if (MovePosition > 1)
MovePosition = 1;
else if (MovePosition < -1)
MovePosition = -1;
ContinueMilliSeconds = ContinueMilliSeconds * 10; // 不知道为什么android读到的是微秒,所以毫秒要乘以10
if (ContinueMilliSeconds > 10000)
ContinueMilliSeconds = 10000;
else if (ContinueMilliSeconds < 0)
ContinueMilliSeconds = 0;
/* 尝试5次端口是否打开,只有端口打开时才写入数据 */
/*
* int TryTimes = 5; while(!isBonded()) { TryTimes--; if(TryTimes < 1) {
* Log .e(TAG,"READ NEW ACTION: Unable to open socket"); return; } }
*/
/* 写入队列(直接覆盖) */
ActionStartTime.setToNow();
ActionStopTime.set(ActionStartTime.toMillis(true)
+ ContinueMilliSeconds);
StateMove = MovePosition;
StateTurn = TurnPosition;
if (D)
Log.d(TAG, "READ NEW ACTION: Start at "
+ ActionStartTime.format3339(false) + ", Stop at "
+ ActionStopTime.format3339(false) + ", Turn "
+ TurnPosition + ", Move " + MovePosition + "");
}
private void setPositionMove(double movePosition) {
String message;
byte[] msgBuffer;
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON SET POSITION: Output stream creation failed.", e);
}
if (movePosition > 1 || movePosition < -1)
message = "CM4";
else if (movePosition > 0.7)
message = "CM7";
else if (movePosition > 0.3)
message = "CM6";
else if (movePosition > 0.1)
message = "CM5";
else if (movePosition < -0.7)
message = "CM1";
else if (movePosition < -0.3)
message = "CM2";
else if (movePosition < -0.1)
message = "CM3";
else
message = "CM4";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON SET POSITION: Exception during write.", e);
}
}
private void setPositionTurn(double turnPosition) {
String message;
byte[] msgBuffer;
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON SET POSITION: Output stream creation failed.", e);
}
if (turnPosition > 1 || turnPosition < -1)
message = "CT4";
else if (turnPosition > 0.75)
message = "CT7";
else if (turnPosition > 0.5)
message = "CT6";
else if (turnPosition > 0.25)
message = "CT5";
else if (turnPosition < -0.75)
message = "CT1";
else if (turnPosition < -0.5)
message = "CT2";
else if (turnPosition < -0.25)
message = "CT3";
else
message = "CT4";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON SET POSITION: Exception during write.", e);
}
}
private void Connect() {
if (D) {
Log.d(TAG, "+++ ABOUT TO ATTEMPT CLIENT CONNECT +++");
}
// 更新通知栏
CarServerNoti.tickerText = NOTIFY_CONNECTING;
CarServerNoti.setLatestEventInfo(NotiContext, NOTIFY_TITLE,
NOTIFY_CONNECTING, LaunchIntent);
CarServerNoti.when = System.currentTimeMillis();
notificationManager.notify(null, NotificationRef, CarServerNoti);
if (device == null) {
device = mBluetoothAdapter.getRemoteDevice(address);
}
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "ON CONNECT: Socket creation failed.", e);
}
mBluetoothAdapter.cancelDiscovery();
try {
btSocket.connect();
Log
.e(TAG,
"ON CONNECT: BT connection established, data transfer link open.");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log
.e(
TAG,
"ON CONNECT: Unable to close socket during connection failure",
e2);
}
}
// 更新通知栏
CarServerNoti.tickerText = NOTIFY_EXIT_TIME;
CarServerNoti.setLatestEventInfo(NotiContext, NOTIFY_TITLE,
NOTIFY_EXIT_TIME, LaunchIntent);
CarServerNoti.when = System.currentTimeMillis();
notificationManager.notify(null, NotificationRef, CarServerNoti);
}
private boolean isBonded() {
try {
outStream = btSocket.getOutputStream();
inStream = btSocket.getInputStream();
} catch (IOException e) {
Log.e(TAG, "IS BONDED: Output stream creation failed.", e);
return false;
}
String message = "A";
byte[] msgBuffer = message.getBytes();
byte[] msgBuffer2 = new byte[20];
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "IS BONDED: Exception during write.", e);
return false;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
inStream.read(msgBuffer2);
} catch (IOException e) {
Log.e(TAG, "IS BONDED: Exception during read.", e);
return false;
}
for (byte ThisByte : msgBuffer2) {
if (ThisByte == (byte) 'A')
return true;
}
return false;
/*
* if(device.getBondState() == BluetoothDevice.BOND_NONE || device ==
* null) { return false; } else { return true; }
*/
}
}
/AndroidBluetoothCarServer/src/android/lynx/BluetoothCarServer/CarServiceBroadcastReceiver.java
/**
* @author lynx
*/
package android.lynx.BluetoothCarServer;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* @author lynx
*与服务进程通信的广播接收器
*/
public class CarServiceBroadcastReceiver extends BroadcastReceiver {
private static final boolean D = false; //Debug模式标识
/* (non-Javadoc)
* @see android.content.BroadcastReceiver#onReceive(android.content.Context, android.content.Intent)
*/
@Override
public void onReceive(Context context, Intent intent) {
if(D)
Log.d("CarServiceBroadcastReceiver", "onReceive");
// 启动服务,通过启动Intent传递参数
Intent service = new Intent(context, BluetoothCarService.class);
service.putExtra("Type", intent.getStringExtra("Type"));
service.putExtra("MilliSeconds", intent.getIntExtra("MilliSeconds", 0));
service.putExtra("TurnPosition", intent.getDoubleExtra("TurnPosition", 0));
service.putExtra("MovePosition", intent.getDoubleExtra("MovePosition", 0));
if(service.getStringExtra("Type").equals("Exit"))
{
context.stopService(service);
}
else
{
context.startService(service);
}
}
}
/AndroidBluetoothCarServer/res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1"
>
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout1" android:layout_height="30dp" android:layout_weight="0.06">
<RelativeLayout android:id="@+id/relativeLayout4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnF" android:layout_width="100dp" android:text="Forward" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"></Button>
<Button android:id="@+id/btnFR" android:layout_width="100dp" android:text="FR" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentRight="true"></Button>
<Button android:id="@+id/btnFL" android:layout_width="100dp" android:text="FL" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentLeft="true"></Button>
</RelativeLayout>
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout2" android:layout_height="30dp" android:layout_weight="0.06">
<RelativeLayout android:id="@+id/relativeLayout5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnS" android:layout_width="100dp" android:text="Middle" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"></Button>
<Button android:id="@+id/btnR" android:layout_width="100dp" android:text="Right" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentRight="true"></Button>
<Button android:id="@+id/btnL" android:layout_width="100dp" android:text="Left" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentLeft="true"></Button>
</RelativeLayout>
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout3" android:layout_height="46dp" android:layout_weight="0.01">
<RelativeLayout android:id="@+id/relativeLayout6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnB" android:layout_width="100dp" android:text="Back" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"></Button>
<Button android:id="@+id/btnBR" android:layout_width="100dp" android:text="BR" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentRight="true"></Button>
<Button android:id="@+id/btnBL" android:layout_width="100dp" android:text="BL" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentLeft="true"></Button>
</RelativeLayout>
</LinearLayout>
/AndroidBluetoothCarServer/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.lynx.BluetoothCarServer"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AndroidBluetoothCarServerActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="BluetoothCarService">
</service>
<receiver android:name="CarServiceBroadcastReceiver">
<intent-filter>
<action android:name="android.lynx.car.intent.INTENT_CAR_CONTROL"></action>
</intent-filter>
</receiver>
</application>
</manifest>