- 浏览: 263456 次
- 性别:
- 来自: 济南
文章分类
- 全部博客 (303)
- c (31)
- c++ (16)
- java (18)
- c# (1)
- python (3)
- java web (6)
- oracle (7)
- sqlserver (2)
- mysql (2)
- android (24)
- android系统 (15)
- android多媒体部分 (15)
- android游戏 (12)
- linux (26)
- javaScript (1)
- ajax (1)
- node JS (2)
- html (5)
- apache (3)
- jboss (1)
- weblogic (0)
- 通信协议 (10)
- 云计算 (1)
- 分布式 (5)
- ejb (1)
- webservice (5)
- 设计模式 (16)
- JNI (6)
- swing (13)
- 版本控制 (1)
- UML (1)
- xml (4)
- spring (5)
- hibernate (5)
- struts1 (3)
- struts2 (4)
- ibatis (0)
- tomcat (2)
- 心得体会 (1)
- css (1)
- 嵌入式 (41)
- arm体系结构 (10)
Android的传感器系统
传感器的种类
传感器名称 |
Java中的名称 |
本地接口名称 |
数值 |
加速度 |
TYPE_ACCELEROMETER |
SENSOR_TYPE_ACCELEROMETER |
1 |
磁场 |
TYPE_MAGNETIC_FIELD |
SENSOR_TYPE_MAGNETIC_FIELD |
2 |
方向 |
TYPE_ORIENTATION |
SENSOR_TYPE_ORIENTATION |
3 |
陀螺仪 |
TYPE_GYROSCOPE |
SENSOR_TTYPE_GYROSCOPE |
4 |
光线(亮度) |
TYPE_LIGHT |
SENSOR_TTYPE_LIGHT |
5 |
压力 |
TYPE_PRESSURE |
SENSOR_TTYPE_PRESSURE |
6 |
温度 |
TYPE_TEMPERATURE |
SENSOR_TTYPE_TEMPERATURE |
7 |
接近 |
TYPE_PROXIMITY |
SENSOR_TTYPE_PROXIMITY |
8 |
重力传感
直线加速度
旋转矢量传感器
NFC(nearfieldcommunication)近场通信
传感器的系统结构
传感器系统的java部分
代码路径为:\frameworks\base\core\java\android\hardware
传感器系统的JNI部分
代码路径为:\frameworks\base\core\jni\android_hardware_SensorManager.cpp
传感器系统的底层部分
代码路径为:\frameworks\base\include\gui
传感器的系统层次结构
传感器系统的各个层次,自下而上为:
驱动程序
硬件抽象层
JNI层
JAVA层
框架层对传感器器的应用
应用对传感器的应用
注本地部分已包含在JNI层中没有单独的实现库
传感器的JNI层
直接调用硬件抽象层,包含头文件sensor.h头文件该文件的路径为:ardware\libhardware\include\hardware
staticJNINativeMethodgMethods[]={
{"nativeClassInit","()V",(void*)nativeClassInit},
{"sensors_module_init","()I",(void*)sensors_module_init},
{"sensors_module_get_next_sensor","(Landroid/hardware/Sensor;I)I",
(void*)sensors_module_get_next_sensor},
{"sensors_create_queue","()I",(void*)sensors_create_queue},
{"sensors_destroy_queue","(I)V",(void*)sensors_destroy_queue},
{"sensors_enable_sensor","(ILjava/lang/String;II)Z",
(void*)sensors_enable_sensor},
{"sensors_data_poll","(I[F[I[J)I",(void*)sensors_data_poll},
};
这些方法主要是供java层的sensorManager.java
staticjint
sensors_module_init(JNIEnv*env,jclassclazz)
{
SensorManager::getInstance();
return0;
}
调用hw_get_module()函数是硬件模块的通用接口,根据传感器硬件模块的标识SENSORS_HARDWARE_MODULE_ID打开这个硬件模块
传感器系统的java代码
代码的路径为:\frameworks\base\core\java\android\hardware
sensorManager.java传感器的核心管理类
Sensor.java传感器的实体类
sensorEvent.java传感器的事件类
(接口)
sensorEventListener.java传感器事件的监听者
sensorListener.java传感器的监听者(不推荐使用)
sensorManager.java的几个主要方法:
publicclassSensorManager{
publicSensorgetDefaultSensor(inttype){}//取得默认传感器
publicList<Sensor>getSensorList(inttype){}//取默认传感器列表
以下两个方法使用sensorEventListener接口作为传感器的监听者
publicbooleanregisterListener(SensorListenerlistener,intsensors){}//注册传感器的监听者
publicvoidunregisterListener(SensorListenerlistener,intsensors){}//注销注册监听
在这个类中还定义了一些常量
publicstaticfinalfloatGRAVITY_***//重力常量
publicstaticfinalfloatMAGNETIC_***磁场常量
publicstaticfinalfloatLIGHT_***//亮度传感器
publicclassSensor{
//以下是android支持的十一种传感器类型
publicstaticfinalintTYPE_ACCELEROMETER=1;
publicstaticfinalintTYPE_MAGNETIC_FIELD=2;
publicstaticfinalintTYPE_ORIENTATION=3;
publicstaticfinalintTYPE_GYROSCOPE=4;
publicstaticfinalintTYPE_LIGHT=5;
publicstaticfinalintTYPE_PRESSURE=6;
publicstaticfinalintTYPE_TEMPERATURE=7;
publicstaticfinalintTYPE_PROXIMITY=8;
publicstaticfinalintTYPE_GRAVITY=9;
publicstaticfinalintTYPE_LINEAR_ACCELERATION=10;
publicstaticfinalintTYPE_ROTATION_VECTOR=11;
//所有传感器类型
publicstaticfinalintTYPE_ALL=-1;
privateStringmName;//名称
privateStringmVendor;//
privateintmVersion;//版本
privateintmType;//类型
privatefloatmMaxRange;//取得传感器的最大范围
privatefloatmResolution;//传感器的解析度
privateintmHandle;//
//以下是2.3版本中新添加
privatefloatmPower;
privateintmMinDelay;
privateintmLegacyType;
}
//传感器监听
publicinterfaceSensorEventListener{
//数值发生变化时调用
publicvoidonSensorChanged(SensorEventevent);
//传感器精度发生变化时调用
publicvoidonAccuracyChanged(Sensorsensor,intaccuracy);
}
传感器系统的硬件抽象层
代码路径如下:\hardware\libhardware\include\hardware
Sensors.h头文件的部分路径如下:
//定义传感器类型
#defineSENSOR_TYPE_ACCELEROMETER1
#defineSENSOR_TYPE_MAGNETIC_FIELD2
#defineSENSOR_TYPE_ORIENTATION3
#defineSENSOR_TYPE_GYROSCOPE4
#defineSENSOR_TYPE_LIGHT5
#defineSENSOR_TYPE_PRESSURE6
#defineSENSOR_TYPE_TEMPERATURE7
#defineSENSOR_TYPE_PROXIMITY8
#defineSENSOR_TYPE_GRAVITY9
#defineSENSOR_TYPE_LINEAR_ACCELERATION10
#defineSENSOR_TYPE_ROTATION_VECTOR11
//在hw_module_tcommon标准的硬件模块
structsensors_module_t{
structhw_module_tcommon;
//获取传感器列表
int(*get_sensors_list)(structsensors_module_t*module,
structsensor_tconst**list);
};
//传感器的描述性定义
structsensor_t{
constchar*name;
constchar*vendor;
intversion;
inthandle;//传感器的句柄
inttype;
floatmaxRange;
floatresolution;
floatpower;//能耗ma
int32_tminDelay;
void*reserved[8];
};
//表示传感器的数据
union{
floatdata[16];
sensors_vec_tacceleration;//方向
sensors_vec_tmagnetic;//磁矢量
sensors_vec_torientation;//加速度
sensors_vec_tgyro;//陀螺仪
floattemperature;//温度
floatdistance;//距离
floatlight;//亮度
floatpressure;//压强
};
uint32_treserved1[4];
}sensors_event_t;
//用户控制设备
structsensors_poll_device_t{
structhw_device_tcommon;
int(*activate)(structsensors_poll_device_t*dev,
inthandle,intenabled);
int(*setDelay)(structsensors_poll_device_t*dev,
inthandle,int64_tns);
int(*poll)(structsensors_poll_device_t*dev,
sensors_event_t*data,intcount);
};
//用于打开和关闭传感器设备,打开的过程从native_handle_t开始,pull函数是核心,
调用时被子阻塞,直到传感器获得数据返回
staticinlineintsensors_open(conststructhw_module_t*module,
structsensors_poll_device_t**device){
returnmodule->methods->open(module,
SENSORS_HARDWARE_POLL,(structhw_device_t**)device);
}
staticinlineintsensors_close(structsensors_poll_device_t*device){
returndevice->common.close(&device->common);
}
硬件抽象层的示例实现
模似器提供了一个示例实现,代码路径为:sdk\emulator\sensors
//
staticstructhw_module_methods_tsensors_module_methods={
.open=open_sensors
};
open_sensors函数用于构建控制设备和数据设备
staticint
open_sensors(conststructhw_module_t*module,
constchar*name,
structhw_device_t**device)
{
intstatus=-EINVAL;
D("%s:name=%s",__FUNCTION__,name);
if(!strcmp(name,SENSORS_HARDWARE_CONTROL))
{
SensorControl*dev=malloc(sizeof(*dev));
memset(dev,0,sizeof(*dev));
dev->device.common.tag=HARDWARE_DEVICE_TAG;
dev->device.common.version=0;
dev->device.common.module=(structhw_module_t*)module;
dev->device.common.close=control__close;
dev->device.open_data_source=control__open_data_source;
dev->device.activate=control__activate;
dev->device.set_delay=control__set_delay;
dev->device.wake=control__wake;
dev->fd=-1;
*device=&dev->device.common;
status=0;
}
elseif(!strcmp(name,SENSORS_HARDWARE_DATA)){
SensorData*dev=malloc(sizeof(*dev));
memset(dev,0,sizeof(*dev));
dev->device.common.tag=HARDWARE_DEVICE_TAG;
dev->device.common.version=0;
dev->device.common.module=(structhw_module_t*)module;
dev->device.common.close=data__close;
dev->device.data_open=data__data_open;
dev->device.data_close=data__data_close;
dev->device.poll=data__poll;
dev->events_fd=-1;
*device=&dev->device.common;
status=0;
}
returnstatus;
}
//定义取得传感器列表的函数指针
conststructsensors_module_tHAL_MODULE_INFO_SYM={
.common={
.tag=HARDWARE_MODULE_TAG,
.version_major=1,
.version_minor=0,
.id=SENSORS_HARDWARE_MODULE_ID,
.name="GoldfishSENSORSModule",
.author="TheAndroidOpenSourceProject",
.methods=&sensors_module_methods,
},
.get_sensors_list=sensors__get_sensors_list
};
staticuint32_tsensors__get_sensors_list(structsensors_module_t*module,
structsensor_tconst**list)
{
intfd=qemud_channel_open(SENSORS_SERVICE_NAME);
charbuffer[12];
intmask,nn,count;
intret;
if(fd<0){
E("%s:noqemudconnection",__FUNCTION__);
return0;
}
ret=qemud_channel_send(fd,"list-sensors",-1);
if(ret<0){
E("%s:couldnotquerysensorlist:%s",__FUNCTION__,
strerror(errno));
close(fd);
return0;
}
ret=qemud_channel_recv(fd,buffer,sizeofbuffer-1);
if(ret<0){
E("%s:couldnotreceivesensorlist:%s",__FUNCTION__,
strerror(errno));
close(fd);
return0;
}
buffer[ret]=0;
close(fd);
/*theresultisaintegerusedasamaskforavailablesensors*/
mask=atoi(buffer);
count=0;
for(nn=0;nn<MAX_NUM_SENSORS;nn++){
if(((1<<nn)&mask)==0)
continue;
sSensorList[count++]=sSensorListInit[nn];
}
D("%s:returned%dsensors(mask=%d)",__FUNCTION__,count,mask);
*list=sSensorList;
returncount;
}
最后返回的是一个sensor_t类型的数组,部分代码如下(定义了四个传感器):
staticconststructsensor_tsSensorListInit[]={
//加速度
{.name="Goldfish3-axisAccelerometer",
.vendor="TheAndroidOpenSourceProject",
.version=1,
.handle=ID_ACCELERATION,
.type=SENSOR_TYPE_ACCELEROMETER,
.maxRange=2.8f,
.resolution=1.0f/4032.0f,
.power=3.0f,
.reserved={}
},
//磁场
{.name="Goldfish3-axisMagneticfieldsensor",
.vendor="TheAndroidOpenSourceProject",
.version=1,
.handle=ID_MAGNETIC_FIELD,
.type=SENSOR_TYPE_MAGNETIC_FIELD,
.maxRange=2000.0f,
.resolution=1.0f,
.power=6.7f,
.reserved={}
},
//方向
{.name="GoldfishOrientationsensor",
.vendor="TheAndroidOpenSourceProject",
.version=1,
.handle=ID_ORIENTATION,
.type=SENSOR_TYPE_ORIENTATION,
.maxRange=360.0f,
.resolution=1.0f,
.power=9.7f,
.reserved={}
},
//温度
{.name="GoldfishTemperaturesensor",
.vendor="TheAndroidOpenSourceProject",
.version=1,
.handle=ID_TEMPERATURE,
.type=SENSOR_TYPE_TEMPERATURE,
.maxRange=80.0f,
.resolution=1.0f,
.power=0.0f,
.reserved={}
},
};
staticint
data__poll(structsensors_data_device_t*dev,sensors_data_t*values)
{
SensorData*data=(void*)dev;
intfd=data->events_fd;
D("%s:data=%p",__FUNCTION__,dev);
//therearependingsensors,returnsthemnow...
if(data->pendingSensors){
returnpick_sensor(data,values);
}
//waituntilwegetacompleteeventforanenabledsensor
uint32_tnew_sensors=0;
//读取传感器信息,设置sensor_data_t结构体数据
while(1){
/*readthenextevent*/
charbuff[256];
intlen=qemud_channel_recv(data->events_fd,buff,sizeofbuff-1);
floatparams[3];
int64_tevent_time;
if(len<0){
E("%s:len=%d,errno=%d:%s",__FUNCTION__,len,errno,strerror(errno));
return-errno;
}
buff[len]=0;
/*"wake"issentfromtheemulatortoexitthisloop.Thisshall
*reallybebecauseanotherthreadcalled"control__wake"inthis
*process.
*/
if(!strcmp((constchar*)data,"wake")){
return0x7FFFFFFF;
}
/*"acceleration:<x>:<y>:<z>"correspondstoanaccelerationevent*/
if(sscanf(buff,"acceleration:%g:%g:%g",params+0,params+1,params+2)==3){
new_sensors|=SENSORS_ACCELERATION;
data->sensors[ID_ACCELERATION].acceleration.x=params[0];
data->sensors[ID_ACCELERATION].acceleration.y=params[1];
data->sensors[ID_ACCELERATION].acceleration.z=params[2];
continue;
}
/*"orientation:<azimuth>:<pitch>:<roll>"issentwhenorientationchanges*/
if(sscanf(buff,"orientation:%g:%g:%g",params+0,params+1,params+2)==3){
new_sensors|=SENSORS_ORIENTATION;
data->sensors[ID_ORIENTATION].orientation.azimuth=params[0];
data->sensors[ID_ORIENTATION].orientation.pitch=params[1];
data->sensors[ID_ORIENTATION].orientation.roll=params[2];
continue;
}
/*"magnetic:<x>:<y>:<z>"issentfortheparamsofthemagneticfield*/
if(sscanf(buff,"magnetic:%g:%g:%g",params+0,params+1,params+2)==3){
new_sensors|=SENSORS_MAGNETIC_FIELD;
data->sensors[ID_MAGNETIC_FIELD].magnetic.x=params[0];
data->sensors[ID_MAGNETIC_FIELD].magnetic.y=params[1];
data->sensors[ID_MAGNETIC_FIELD].magnetic.z=params[2];
continue;
}
/*"temperature:<celsius>"*/
if(sscanf(buff,"temperature:%g",params+0)==2){
new_sensors|=SENSORS_TEMPERATURE;
data->sensors[ID_TEMPERATURE].temperature=params[0];
continue;
}
/*"sync:<time>"issentafteraseriesofsensorevents.
*where'time'isexpressedinmicro-secondsandcorresponds
*totheVMtimewhentherealpolloccured.
*/
if(sscanf(buff,"sync:%lld",&event_time)==1){
if(new_sensors){
data->pendingSensors=new_sensors;
int64_tt=event_time*1000LL;/*converttonano-seconds*/
/*usethetimeatthefirstsync:asthebaseforlater
*timevalues*/
if(data->timeStart==0){
data->timeStart=data__now_ns();
data->timeOffset=data->timeStart-t;
}
t+=data->timeOffset;
while(new_sensors){
uint32_ti=31-__builtin_clz(new_sensors);
new_sensors&=~(1<<i);
data->sensors[i].time=t;
}
returnpick_sensor(data,values);
}else{
D("huh?syncwithoutanysensordata?");
}
continue;
}
D("huh?unsupportedcommand");
}
}
传感器硬件层实现的要点
1可以支持多个传感器,也可支多个同类型的传感器,需要构建一个sensor_t类型的数组
2传感器的控制设备和数据设备可能被扩展,用来保存传感器抽象层的上下文
前提是sensor_control_device_t和sensors_data_device_t两个数据结构需要作为扩展结构体的第一个成员
3传感器在linux内核的驱动程序很可能使用misc驱动程序这时需要在开发控制设备时,同样使用open打开传感器的设备结点
4传感器数据设备poll指针是实现的重点,传感器在没有数据变化时实现阻塞,在数据变化时返回,
根据驱动程序的情况可以使用poll,read或者ioctl等接口来实现,这些驱动程序可以实现相应的阻塞
当传感器控制设备的wake()被调用时,需要让数据设备的pool立即返回0x7fffffff
传感器的使用
取得SensorManager(系统服务)
sensorManager=(SensorManager)this.getSystemService(Context.SENSOR_SERVICE);
OrientationEventListener扩展了sensorEventListener
publicOrientationEventListener(Contextcontext,intrate){
//取得传感器服务
mSensorManager=(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate=rate;
//取得加速度传感器
mSensor=mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if(mSensor!=null){
//Createlisteneronlyifsensorsdoexist
mSensorEventListener=newSensorEventListenerImpl();
}
}
//注册传感器的监听事件
publicvoidenable(){
if(mSensor==null){
Log.w(TAG,"Cannotdetectsensors.Notenabled");
return;
}
if(mEnabled==false){
if(localLOGV)Log.d(TAG,"OrientationEventListenerenabled");
mSensorManager.registerListener(mSensorEventListener,mSensor,mRate);
mEnabled=true;
}
}
classSensorEventListenerImplimplementsSensorEventListener{
//通过加速度信信息取得方向信息
publicvoidonSensorChanged(SensorEventevent){}
}
在清单文件中设置activity属性
android:screenOrientation="sensor"//根据传感器设置屏幕方向
如果属性设置为nosensor则不会改变方向
发表评论
-
android系统学习笔记一
2012-09-03 15:01 9161.android的系统架构(linux2.6内核) Lin ... -
android系统学习笔记二
2012-09-03 15:03 920android的linux内核和驱动程序 Androidr专 ... -
android系统学习笔记三
2012-09-03 15:05 843第四章android的底层库和程序 知识点: And ... -
android系统学习笔记四
2012-09-03 15:07 1141Android的GUI系统 AndroidGUI系统综 ... -
android系统学习笔记五
2012-09-03 15:12 2205android中的audio系统 Audo系统主要分如下几 ... -
android系统学习笔记六
2012-09-03 15:16 2229android的多媒体系统 ... -
android系统学习笔记七
2012-09-03 15:18 747Android电话部分综述 主 ... -
android系统学习八
2012-09-03 15:19 761Android的联接部分 Wifi部分 Wif ... -
android系统学习笔记九
2012-09-03 15:21 740蓝牙部分 蓝牙协议栈1.11.22.02.13.0 异步数 ... -
android系统学习笔记十一
2012-09-03 15:22 1513Gps(globalPositionSystem全球定位系统) ... -
Android编译系统详解(一)
2012-09-10 13:45 840+++++++++++++++++++++++++++++ ... -
android----ServiceManager
2012-09-18 00:33 4324serviceManager 在c++层如何使用servi ... -
android 自定义核心服务
2012-09-18 23:27 803native service zygote 服务 kern ... -
android系统移植学习笔记一
2012-09-21 16:08 635系统框架 applicationjava应用程序 ...
相关推荐
Android学习笔记是Android开发者的必读书籍,书中涵盖了Android系统架构、Activity、Intent、资源管理等多方面的知识。本笔记对应的学习资源《第一行代码》是Android开发者的入门必读书籍,书中系统地介绍了Android...
Android基础学习笔记主要涵盖了一系列关于Android开发的基本概念和关键组件,以下是这些知识点的详细解析: 1. **Activity**: 是Android应用程序的基本单元,它代表用户在屏幕上看到的一个界面。每个Activity都必须...
这篇学习笔记将引导新手入门,通过创建第一个简单的案例来了解Android Studio的基本操作。 首先,让我们了解一下Android Studio的核心功能。它基于IntelliJ IDEA,具备代码自动补全、重构、调试等强大的开发特性。...
Android Activity学习笔记 Android Activity是Android系统中最基本的组件之一,它负责处理用户交互和显示用户界面。本文将深入讲解Activity的生命周期、Activity之间的数据交互、Activity启动模式、Task和BackStack...
### Android学习笔记 #### 1. Android概述 **1.1 Android的特性** - **应用框架**:Android提供了一个强大的应用框架,使得开发者能够轻松地重用基础组件和服务,简化了应用程序的开发流程。 - **Dalvik虚拟机**...
- Android系统基于Linux系统开发,拥有开放源码。 - Android系统框架由多个组件构成,其中包括Activity、Intent、Service、ContentProvider、View和BroadcastReceiver。 - Activity作为组件容器,主要负责用户...
根据给定的信息,我们可以从Java和Android学习笔记中提取出一系列重要的知识点,下面将逐一进行详细解释。 ### Java基础知识 #### 1. 命令行基础操作 - **`javacmd`**: 这个命令是Java命令行工具的一部分,用于...
### Android学习笔记知识点详解 #### 一、简介与背景 Android作为全球最受欢迎的移动操作系统之一,自2007年谷歌发布以来,迅速占领市场并持续引领移动技术的发展。随着移动互联网的兴起和发展,Android应用开发...
### Android学习笔记——从HelloWorld开始 #### 一、Google的Android SDK介绍 自从Google发布了Android SDK以来,这款开源移动操作系统迅速吸引了众多开发者的关注。Android SDK为开发者提供了丰富的工具和API,...
【Android学习笔记】 Android平台是谷歌推出的一个开放源代码的移动设备操作系统,它为开发者提供了一个全面的软件包,包括操作系统、中间件和关键应用程序。这个平台的主要目标是促进移动应用的创新和多样性,允许...
Android全程学习笔记旨在提供一个详尽且全面的指南,涵盖了Android开发中的关键技术点和实践案例。以下是关于Android开发的一些核心知识点: 1. **第一个Android应用**:开发Android应用的起点通常是从创建并运行你...
这个"Android_学习笔记.zip"文件很可能包含了一个详细的Android开发学习路径和关键知识点的总结。下面将基于这个主题,详细讲解Android开发的一些核心概念和技术。 首先,Android是Google开发的一款开源操作系统,...
这篇学习笔记主要涵盖了关于布局的一些基本概念,特别是`fill_parent`和`wrap_content`这两种尺寸指定方式,以及如何通过XML布局文件来精确控制组件的位置。 首先,`fill_parent`和`wrap_content`是Android布局中的...
在Android系统相关学习笔记中,我们可以深入探讨这个广泛而复杂的移动操作系统的核心概念和技术。Android以其开源性和灵活性,吸引了大量的开发者和爱好者。以下是一些关键的知识点: 1. **Android架构**:Android...
【Android学习笔记详解】 在移动应用开发领域,Android操作系统占据着重要的地位,为开发者提供了...通过系统学习并实践其中的内容,你将能够逐步掌握Android开发的核心技能,为你的Android开发者之路打下坚实的基础。
这篇学习笔记将深入探讨PreferenceActivity的使用方法、功能以及与源码相关的知识。 首先,PreferenceActivity是Android SDK提供的一种特殊类型的Activity,它允许开发者快速构建具有可配置选项的界面,类似于系统...
### Android开发学习笔记知识点梳理 #### 一、Android概述与架构 - **定义与发布**:Android是由Google在2007年11月5日宣布的基于Linux平台的开源手机操作系统。它不仅用于智能手机,还广泛应用于平板电脑、可穿戴...