1、第16章 首頁畫面小工具與硬體介面,16-1 首頁畫面小工具-手機靜音切換 16-2 感測器與遊戲控制-跳跳球遊戲 16-3 相機-行車記錄器 16-4 相機與感測器的應用-聰明相機 16-5 藍芽-掃描藍芽裝置,16-1 首頁畫面小工具-手機靜音切換,16-1-1 顯示今天日期小工具 16-1-2 小工具與IntentService服務-手機靜音切換,16-1 首頁畫面小工具-手機靜音切換,首頁畫面小工具(Home Screen Widget)也稱為應用程式小工具(App Widget)是位在行動裝置的首頁畫面中,可以與之互動的一種程式,其主要功能是提供使用者重要的更新資訊,例如:顯示目前行
2、程、今天日期、現在時間、即時天氣、即時股票行情和背景播放音樂的詳細資料等。 當我們將小工具新增至首頁畫面後,它會佔用一塊固定區域來顯示應用程式提供的內容,使用者一樣可以透過小工具與應用程式進行互動,例如:手機靜音切換、切換WiFi、暫停或切換至下一首音樂等,如果擁有背景服務,我們還可以定時更新小工具顯示的內容。,16-1-1 顯示今天日期小工具 步驟一:開啟和執行Android專案,顯示今天日期小工具是一個在首頁畫面顯示今天日期的小工具,它是使用紅色粗體的文字來顯示今天日期。 請啟動Eclipse IDE開啟Android專案Ch16_1_1,專案的活動類別沒有任何功能,它只是在TextVie
3、w元件顯示一段文字內容,其執行結果如下圖所示:,16-1-1 顯示今天日期小工具 步驟二:建立小工具的定義檔,建立小工具的首要工作是建立定義檔,用來定義小工具的尺寸和更新頻率,這是位在resxml目錄下的XML檔appwidgetprovider.xml,在AndroidManifest.xml檔註冊小工具時也需參考此檔案,如下所示:,16-1-1 顯示今天日期小工具 步驟三:建立小工具介面的版面配置,小工具版面配置檔widget.xml是位在reslayout目錄,如下所示:,16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法1,首頁畫面小工具事
4、實上就是一個廣播接收器,收到廣播後更新小工具顯示的內容,在Android SDK提供AppWidgetProvider提供者類別,我們可以直接繼承AppWidgetProvider類別覆寫相關方法來建立小工具,如下所示: public class DateAppWidget extends AppWidgetProvider private SimpleDateFormat formatter = newSimpleDateFormat(“nnnyyyy年nMM月dd日“);. ,16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法2,onDelet
5、ed()方法 當刪除1或多個小工具實例後,也就是將首頁畫面的小工具移至垃圾桶,AppWidget管理員物件會送出ACTION_APPWIDGET_DELETED 廣播,onDeleted()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: Override public void onDeleted(Context context, int appWidgetIds) Toast.makeText(context, “onDeleted()“, Toast.LENGTH_LONG).show(); ,16-1-1 顯示今天日期小工具 步驟四:繼承AppWid
6、getProvider類別覆寫相關方法3,onDisabled()方法 當刪除屬於此小工具的最後1個物件實例後,就會送出ACTION_APPWIDGET_DISABLED廣播,onDisabled()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: Override public void onDisabled(Context context) Toast.makeText(context, “onDisabled()“,Toast.LENGTH_LONG).show(); ,16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類
7、別覆寫相關方法4,onEnabled()方法 當小工具初始化新增至首頁畫面後,就會送出ACTION_APPWIDGET_ENABLED廣播,onEnabled()方法可以回應此廣播,程式碼只是使用Toast類別顯示呼叫此方法的訊息文字,如下所示: Override public void onEnabled(Context context) Toast.makeText(context, “onEnabled()“,Toast.LENGTH_LONG).show(); ,16-1-1 顯示今天日期小工具 步驟四:繼承AppWidgetProvider類別覆寫相關方法5,onUpdate()方法
8、 當小工具被要求更新RemoteViews物件的介面元件時,就會送出ACTION_APPWIDGET_UPDATE廣播,它就是在定義檔使用android:updatePeriodMillis 屬性指定的更新頻率,onUpdate()方法可以回應此廣播,更新小工具顯示的內容,如下所示: Override public void onUpdate(Context context, AppWidgetManagerappWidgetManager, int appWidgetIds) super.onUpdate(context, appWidgetManager, appWidgetIds);St
9、ring today = formatter.format(new Date();RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget);remoteView.setTextViewText(R.id.widgettext, today);appWidgetManager.updateAppWidget(appWidgetIds, remoteView);Toast.makeText(context, “onUpdate()“, Toast.LENGTH_LONG).show();
10、,16-1-1 顯示今天日期小工具 步驟五:在AndroidManifest.xml註冊小工具,最後我們需要在AndroidManifest.xml檔註冊小工具,也就是註冊廣播接收器,如下所示:,在本節的手機靜音切換小工具擁有背景服務,可以送出Intent物件來更新小工具顯示的圖示,換句話說,按一下小工具可以切換鈴聲狀態成為靜音且顯示靜音圖示,再按一下就切換成正常且顯示正常圖示。 因為首頁畫面本身是一個在行動裝置上執行,名為啟動器的應用程式,小工具只是在首頁畫面中特定區域執行的程式,因為Android作業系統並不允許開發者修改執行中的程式碼,小工具為了能夠更新內容和與使用者互動,使用的是Rem
11、oteViews介面元件架構。 RemoteViews介面元件架構允許在首頁畫面建立遠端控制的介面元件,換句話說,在首頁畫面顯示的是獨立行程執行的遠端介面元件,實際處理此介面的程式就是繼承AppWidgetProvider提供者類別的物件。 當使用者在小工具的遠端介面進行互動時,例如:按一下,Android作業系統就像是一個路由器,可以將此廣播轉向送至小工具來處理,例如:更新遠端介面元件的內容。,16-1-2 小工具與IntentService服務- 手機靜音切換,16-1-2 小工具與IntentService服務-手機靜音切換 步驟一:開啟和執行Android專案,請啟動Eclipse I
12、DE開啟Android專案Ch16_1_2,專案的活動類別沒有任何功能,它只是在TextView元件顯示一段文字內容,其執行結果如下圖所示:,16-1-2 小工具與IntentService服務-手機靜音切換 步驟二:建立小工具的定義檔,此步驟的定義檔和第16-1-1節的步驟二完全相同,筆者就不重複說明。,16-1-2 小工具與IntentService服務-手機靜音切換 步驟三:建立小工具介面的版面配置,小工具版面配置檔widget.xml是位在reslayout目錄,它是使用LinearLayout垂直編排一個ImageView元件,如下所示: ,16-1-2 小工具與IntentServ
13、ice服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法1,首頁畫面小工具是一個繼承AppWidgetProvider類別覆寫相關方法的提供者類別,如下所示: public class SilentAppWidget extends AppWidgetProvider . ,16-1-2 小工具與IntentService服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法2,onRecieve()方法 通常覆寫onReceive()方法是用來呼叫AppWidgetProvider類別的其他方法,在此是處理使用者第1次在首頁畫面新增
14、小工具時,能夠更新成目前的鈴聲狀態,if條件是呼叫Intent物件的getAction()方法檢查是否有動作,沒有,就呼叫startService()方法啟動ToggleSilentService服務來更新鈴聲狀態,如下所示: Override public void onReceive(Context context, Intent intent) if (intent.getAction() = null) context.startService(new Intent(context, ToggleSilentService.class); else super.onReceive(co
15、ntext, intent); ,16-1-2 小工具與IntentService服務-手機靜音切換 步驟四:繼承AppWidgetProvider類別覆寫相關方法3,onUpdate()方法 在onUpdate()方法呼叫參數Context物件的startService()方法啟動ToggleSilentService服務,如下所示: Override public void onUpdate(Context context, AppWidgetManagerappWidgetManager, int appWidgetIds) context.startService(new Intent
16、(context, ToggleSilentService.class); ,16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-1,IntentService類別是Service類別的子類別,可以用來處理非同步Intent意圖的請求,每一個Intent物件是新增至佇列後再依序處理,即啟動執行緒來處理每一個Intent物件,當任務完成後就自動停止服務,並且可以使用廣播方式來將資料送回應用程式。 一般來說,當我們有任務需要在主執行緒外,啟動其他執行緒來執行此任務,以維持應用程式的執行效能時,或有多個處理請求,需要使用佇列來一一
17、快速處理時,就可以繼承IntentService類別來建立服務,如下所示: public class ToggleSilentService extends IntentService public ToggleSilentService() super(“ToggleSilentService“); ,16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-2,onHandleIntent()方法 onHandleIntent()方法是負責處理啟動服務的Intent物件,即處理步驟四呼叫startService()方法啟動服務
18、的方法參數,執行緒是在請求行程後就會呼叫此方法,在每一個時間只有一個Intent物件會處理,如下所示: Override protected void onHandleIntent(Intent arg0) ComponentName cn = new ComponentName(this, SilentAppWidget.class);AppWidgetManager awManager =AppWidgetManager.getInstance(this);awManager.updateAppWidget(cn, updatePhoneStatus(this); ,16-1-2 小工具與
19、IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-3,updatePhoneStatus()方法 updatePhoneStatus()方法的傳回值是RemoteViews物件,它是使用AUDIO_SERVICE系統服務來更新手機的鈴聲狀態,方法首先從版面配置資源載入來建立RemoteViews物件,如下所示: private RemoteViews updatePhoneStatus(Context context) RemoteViews remoteView = new RemoteViews(context.getPackageName
20、(), R.layout.widget);AudioManager manager = (AudioManager) context.getSystemService(Activity.AUDIO_SERVICE);,16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-4,if (manager.getRingerMode() = AudioManager.RINGER_MODE_SILENT) remoteView.setImageViewResource(R.id.phoneState, R.drawable.phone
21、_on);manager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); else remoteView.setImageViewResource(R.id.phoneState, R.drawable.phone_silent);,16-1-2 小工具與IntentService服務-手機靜音切換 步驟五:建立繼承IntentService類別的服務-5,manager.setRingerMode(AudioManager.RINGER_MODE_SILENT);Intent i = new Intent(this, SilentAppWidg
22、et.class);PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);remoteView.setOnClickPendingIntent(R.id.phoneState,pi);return remoteView; ,16-1-2 小工具與IntentService服務-手機靜音切換 步驟六:在AndroidManifest.xml註冊小工具與服務,最後我們需要在AndroidManifest.xml檔註冊小工具與服務,也就是註冊廣播接收器和ToggleSilentService服務,如下所示:,16-2 感測器與
23、遊戲控制-跳跳球遊戲,16-2-1 傾斜監測 16-2-2 感測器與遊戲控制-跳跳球遊戲,16-2 感測器與遊戲控制-跳跳球遊戲,Android支援多種感測器來監測行動裝置目前的狀態,例如:數位羅盤、加速感測器、重力感測器、趨近感測器、陀螺儀和環境光線感測器等,請注意!行動裝置可能只支援其中幾項感測器,而且Android模擬器不支援感測器,我們只能使用實機來測試感測器。 在實務上,我們最常使用加速感測器(Accelerometer),所以本節是以加速感測器為例,說明如何應用在遊戲控制。,16-2-1 傾斜監測,傾斜監測是使用加速感測器判斷行動裝置目前是否傾斜。當我們取得感測器系統服務的Serv
24、iceManager物件後,就可以註冊SensorEventListener傾聽者物件來取得感測器的偵測資料,為了避免耗用過多電力,建議在onResume()方法註冊;onPause()方法取消註冊。,16-2-1 傾斜監測 步驟一:開啟和執行Android專案,請啟動Eclipse IDE開啟Android專案Ch16_2_1,內含1個Java類別檔和版面配置檔main.xml,因為Android模擬器不支援感測器,筆者是使用2.3版的實機來測試,其執行結果如右圖所示:,16-2-1 傾斜監測 步驟二:建立使用介面的版面配置-1,16-2-1 傾斜監測 步驟二:建立使用介面的版面配置-2,1
25、6-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-1,在Ch16_2_1Activity活動類別實作SensorEventListener介面的2個方法,類別開頭宣告成員的SensorManager、Sensor和TextView物件變數,如下所示: public class Ch16_2_1Activity extends Activityimplements SensorEventListener private SensorManager manager; private Sensor accelerometer; private TextView txtOutp
26、ut, txtTop, txtBottom, txtLeft, txtRight; ,16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-2,onCreate()方法 在覆寫的onCreate()方法載入版面配置後,就可以取得感測器系統服務的SensorManager物件manager,如下所示: Override public void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);setContentView(R.layout.main);manager = (Senso
27、rManager) getSystemService(SENSOR_SERVICE);accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);txtOutput = (TextView) findViewById(R.id.output);txtTop = (TextView) findViewById(R.id.top);txtBottom = (TextView) findViewById(R.id.bottom);txtLeft = (TextView) findViewById(R.id.left);txt
28、Right = (TextView) findViewById(R.id.right); ,16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-3,onResume()方法 在覆寫onResume()方法註冊SensorEventListener傾聽者物件,即自己,如下所示: Override protected void onResume() super.onResume(); manager.registerListener(this, accelerometer,SensorManager.SENSOR_DELAY_NORMAL); ,16-2-1 傾斜監測 步驟
29、三:建立Activity活動類別使用加速感測器-4,onPause()方法 在覆寫onPause()方法取消註冊SensorEventListener傾聽者物件,如下所示: Override protected void onPause() super.onPause(); manager.unregisterListener(this); ,16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-5,實作SensorEventListener介面的方法 SensorEventListener傾聽者物件需要實作2個介面方法,不過,我們只有使用onSensorChanged(
30、)方法,這是當感測器資料改變時呼叫的方法,如下所示: Override public void onAccuracyChanged(Sensor arg0, int arg1) Override public void onSensorChanged(SensorEvent event) float values = event.values; float x, y; int xFactor, yFactor; x = values0 / 10; y = values1 / 10;,16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-6,xFactor = (int)
31、Math.min(Math.abs(x) * 255, 255);yFactor = (int) Math.min(Math.abs(y) * 255, 255);if (x 0) txtRight.setBackgroundColor(Color.TRANSPARENT); txtLeft.setBackgroundColor(Color.argb(xFactor, 255, 255, 0); else txtRight.setBackgroundColor(Color.argb(xFactor, 255, 255, 0); txtLeft.setBackgroundColor(Color.
32、TRANSPARENT); if (y 0) ,16-2-1 傾斜監測 步驟三:建立Activity活動類別使用加速感測器-7,txtTop.setBackgroundColor(Color.TRANSPARENT); txtBottom.setBackgroundColor(Color.argb(yFactor, 255, 255, 0); else txtTop.setBackgroundColor(Color.argb(yFactor, 255, 255, 0); txtBottom.setBackgroundColor(Color.TRANSPARENT); txtOutput.set
33、Text(String.format(“X軸: %1$1.2f, Y軸: %2$1.2f, Z軸: %3$1.2f“, values0, values1, values2); ,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟一:開啟和執行Android專案,加速感測器最常應用在遊戲程式,這一節我們準備使用加速感測器來移動螢幕上的黃色球,可以傾斜行動裝置來控制球的滾動方向。 請啟動Eclipse IDE開啟Android專案Ch16_2_2,內含2個Java類別檔和版面配置檔main.xml,因為Android模擬器不支援感測器,筆者是使用2.3版的實機來測試,其執行結果如右圖所示:,16-2
34、-2 感測器與遊戲控制-跳跳球遊戲 步驟二:建立使用介面的版面配置,使用介面的版面配置是定義在main.xml版面配置檔,只有一個FrameLayout版面配置,如下所示:,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-1,在Ch16_2_2Activity活動類別實作SensorEventListener介面的2個方法,類別開頭宣告成員的SensorManager、Sensor、重繪的Handler、計時的Timer、TimerTask和PointF物件變數,如下所示: public class Ch16_2_2Activity extend
35、s Activity implements SensorEventListener private SensorManager manager;private Sensor accelerometer;private MyBallView ball = null;private Handler redrawHandler = new Handler();private Timer moveTimer = null;private TimerTask moveTask = null;private int sWidth, sHeight;private PointF ballPos, ballS
36、peed; ,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-2,onCreate()方法 在覆寫onCreate()方法的開始是使用requestWindowFeature()方法來隱藏標題列,如下所示: Override public void onCreate(Bundle savedInstanceState) requestWindowFeature(Window.FEATURE_NO_TITLE);getWindow().setFlags(0xFFFFFFFF, LayoutParams.FLAG_FULLSCREEN |Layout
37、Params.FLAG_KEEP_SCREEN_ON);super.onCreate(savedInstanceState);setContentView(R.layout.main);final FrameLayout board = (FrameLayout)findViewById(R.id.gameboard);,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-3,Display display = getWindowManager().getDefaultDisplay();sWidth = display.getWidth();sHe
38、ight = display.getHeight();ballPos = new PointF();ballSpeed = new PointF();ballPos.x = sWidth / 2; ballPos.y = sHeight / 2; ballSpeed.x = 0;ballSpeed.y = 0; ball = new MyBallView(this, ballPos.x, ballPos.y, 10);board.addView(ball); ball.invalidate();,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-4
39、,manager = (SensorManager) getSystemService(SENSOR_SERVICE);accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);board.setOnTouchListener(new View.OnTouchListener() public boolean onTouch(View v, android.view.MotionEvent e) ballPos.x = e.getX();ballPos.y = e.getY();return true;); ,16-
40、2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-5,onResume()方法 在覆寫onResume()方法首先註冊SensorEventListener傾聽者物件,即自己後,建立Timer計時器物件來移動黃色球至新位置,如下所示: Override public void onResume() manager.registerListener(this, accelerometer,SensorManager.SENSOR_DELAY_NORMAL); moveTimer = new Timer();moveTask = new TimerTask(
41、) ,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-6,public void run() Log.d(“Ch16_2_2“,“更新時間 - “ + ballPos.x + “:“ + ballPos.y);ballPos.x += ballSpeed.x;ballPos.y += ballSpeed.y;float oX = 10 * Math.abs(ballSpeed.x);float oY = 10 * Math.abs(ballSpeed.y);if (ballPos.x sWidth) ballPos.x -= oX;if (bal
42、lPos.y sHeight) ballPos.y -= oY;if (ballPos.x 0) ballPos.x += oX;,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-7,if (ballPos.y 0) ballPos.y += oY;ball.updatePosition(ballPos.x, ballPos.y);redrawHandler.post(new Runnable() public void run() ball.invalidate();); moveTimer.schedule(moveTask, 10, 10)
43、;super.onResume(); ,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-8,onPause()方法 在覆寫onPause()方法呼叫cancel()方法取消Timer計時器物件和取消註冊SensorEventListener傾聽者物件,如下所示: Override public void onPause() moveTimer.cancel();moveTimer = null;moveTask = null;super.onPause();manager.unregisterListener(this); ,16-2-2 感測器
44、與遊戲控制-跳跳球遊戲 步驟三:建立Activity活動類別使用加速感測器-9,實作SensorEventListener介面的方法 SensorEventListener傾聽者物件需要實作介面的2個方法,我們只有使用onSensorChanged()方法,如下所示: Override public void onAccuracyChanged(Sensor sensor, int accuracy) Override public void onSensorChanged(SensorEvent event) ballSpeed.x = -event.values0;ballSpeed.y
45、= event.values1; ,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立畫出黃色球的View介面類別-1,在螢幕上顯示的黃色球是一個繼承View類別的自訂介面元件,如下所示: public class MyBallView extends View private float x;private float y;private final int r;private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);public MyBallView(Context context, float x, float y,
46、 int r) super(context);paint.setColor(Color.YELLOW);this.x = x;this.y = y;this.r = r;,16-2-2 感測器與遊戲控制-跳跳球遊戲 步驟四:建立畫出黃色球的View介面類別-2,Overrideprotected void onDraw(Canvas canvas) super.onDraw(canvas);canvas.drawCircle(x, y, r, paint);public void updatePosition(float x, float y) this.x = x;this.y = y; ,
47、16-3 相機-行車記錄器,16-3-1 照相-我的相機 16-3-2 錄影-行車記錄器,16-3-1 照相-我的相機,雖然Android作業系統已經內建相機程式(可以使用Intent物件啟動),但是對於應用程式需要使用相機功能時,我們就可以整合本節應用程式來提供程式基本的照相功能。 Android SDK提供android.hardware.Camera類別(不是android.graphics.Camera類別),它就是硬體相機的介面類別,相機服務的客戶端類別,可以照相、截取圖片、預覽圖片和更改相關設定。 在我的相機程式提供兩種照相功能:一是使用Intent物件啟動內建相機程式,然後將照相
48、結果顯示在ImageView元件;另一是使用SurfaceView元件預覽畫面,和Camera類別照相和儲存至SD卡。,16-3-1 照相-我的相機 步驟一:開啟和執行Android專案,請啟動Eclipse IDE開啟Android專案Ch16_3_1,內含2個Java類別檔和2個版面配置檔,因為Android模擬器的相機功能有限,筆者是使用2.3版的實機來測試相機的照相功能,其執行結果如下圖所示:,16-3-1 照相-我的相機 步驟二:建立主活動使用介面的版面配置,16-3-1 照相-我的相機 步驟三:建立Ch16_3_1Activity主活動類別-1,在Ch16_3_1Activity活
49、動類別提供按鈕來啟動內建相機程式來照相和直接進行照相,在類別開頭定義常數,和宣告成員的ImageView物件變數,如下所示: public class Ch16_3_1Activity extends Activity private static final int REQUEST_IMAGE = 100; private ImageView imageView; ,16-3-1 照相-我的相機 步驟三:建立Ch16_3_1Activity主活動類別-2,onCreate()方法 在覆寫的onCreate()方法載入版面配置後,取得ImageView元件,如下所示: Override public void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);imageView = (ImageView)findViewById(R.id.image); ,