Skip to main content

Simple Chat App in Android using GCM

http://androidknowledgeblog.blogspot.com/2016/04/simple-chat-app-in-android.html



Today we are build chat app using gcm (Google Cloud messaging). Prerequisite : 1)   API KEY 2)   SENDER ID  =  PROJECT NUMBER 3)...
 GCM Notification android
 Create PDF in android


Today we are build chat app using gcm (Google Cloud messaging).

Prerequisite :

1)  API KEY
2)  SENDER ID  =  PROJECT NUMBER
3)  GCM ID
4)  API ( WEBSERVICE )

 First two value  (API KEY ,  SENDER ID)  get using this VEDIO
How to get GCM ID next question in your mind right??
GCM ID get using coding i will show you in coding part.
 And last need is  Download  API from  HERE  

I assuming that you are smart enough to Run this api in your local or server. So run this api and get this URL for later use.   You can also use my api i uploaded in server just copy this url

chat_api.php

  1. <?php
  2. $method = $_SERVER['REQUEST_METHOD'];
  3. switch ($method){
  4.     case 'GET':
  5.         get();
  6.         break;
  7.     case 'POST':
  8.         post();
  9.         break;
  10.     case 'PUT':
  11.         put();
  12.         break;
  13.     case 'DELETE':
  14.         delete();
  15.         break;
  16.     default :
  17.         break;
  18. }
  19. function get(){
  20.     $category;
  21.     for($i=0 ; $i<5 ; $i++){
  22.         $category[$i] = array(
  23.         'cat_id' => $i,
  24.         'cat_name' => 'first',
  25.         'cat_desc' => 'second'
  26.     );
  27.     }
  28.    
  29.    
  30.     $response = array(
  31.         'status' => '200',
  32.         'categories' => $category
  33.             );
  34.            
  35.     header('Content-type: application/json');
  36.     echo json_encode($response);
  37. }
  38. function p(){
  39.     $text = $_POST['text'];
  40.     $response = array(
  41.     'result' => $text,
  42.         );
  43.     echo json_encode($response);
  44. }
  45. function post(){
  46.    
  47.     $text = $_POST['text'];
  48.     //echo $text;
  49.    
  50.     // API access key from Google API's Console
  51. define( 'API_ACCESS_KEY', 'AIzaSyCGYYyl3FNVFORs3Cz1QAsvkvaqIrLjfuU' );
  52.     // pass here your GCM ID and other Mobile GCM ID
  53.     //if you use server then fetch GCM registration id coloumn array from database an pass it so everyone get notify
  54.     $registrationIds = array('dgiQy412lH8:APA91bEC6d8yngpmUDU0VUIkDCaO_f1jvYa-Mjk3mkRjlVLmJXZ1HDSLosH1cljFmt9AGZEhOd1taz7Zcf012spRUGGoxQi73CZFFCmspcskqTYsn4d_lBS1-DCbT5R5L79mOYojuluf');
  55. // prep the bundle
  56. $msg = array
  57. (
  58.     'Message'   => $text,
  59.     'title'     => 'This is a title. title',
  60.     'subtitle'  => 'This is a subtitle. subtitle',
  61.     'tickerText'    => 'Ticker text here...Ticker text here...Ticker text here',
  62.     'vibrate'   => 1,
  63.     'sound'     => 1,
  64.     'largeIcon' => 'large_icon',
  65.     'smallIcon' => 'small_icon'
  66. );
  67. $fields = array
  68. (
  69.     'registration_ids'  => $registrationIds,
  70.     'data'          => $msg
  71. );
  72. $headers = array
  73. (
  74.     'Authorization: key=' . API_ACCESS_KEY,
  75.     'Content-Type: application/json'
  76. );
  77. $ch = curl_init();
  78. curl_setopt( $ch,CURLOPT_URL, 'https://android.googleapis.com/gcm/send' );
  79. curl_setopt( $ch,CURLOPT_POST, true );
  80. curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
  81. curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
  82. curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
  83. curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
  84. $result = curl_exec($ch );
  85. curl_close( $ch );
  86. echo $result;
  87. }
  88. function put(){
  89.     echo 'PUT request';
  90. }
  91. function delete(){
  92.     echo 'DELETE request';
  93. }
  94. ?>

here is my api(webservice) coding you have to change your API KEY and GCM ID in this file. 

GCM ID is get using coding as i said before.So will tell you later.  

STEP 1 :

Create project with empty activity in android studio. Then add this three library in your
gradle file. and compare my gradle to your gradle.

1) Play service for GCM
 compile "com.google.android.gms:play-services-gcm:8.4.0"
2 & 3) Json parsing and post data using this two library (Volly , okhttp)

  1. compile 'com.android.volley:volley:1.0.0'
  2.     compile 'com.squareup.okhttp3:okhttp:3.2.0'

build.gradle (Module:app)

  1. apply plugin: 'com.android.application'
  2. android {
  3.     compileSdkVersion 23
  4.     buildToolsVersion "23.0.3"
  5.     defaultConfig {
  6.         applicationId "com.example.androiddeveloper.simplechat"
  7.         minSdkVersion 14
  8.         targetSdkVersion 23
  9.         versionCode 1
  10.         versionName "1.0"
  11.     }
  12.     buildTypes {
  13.         release {
  14.             minifyEnabled false
  15.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  16.         }
  17.     }
  18. }
  19. dependencies {
  20.     compile fileTree(dir: 'libs', include: ['*.jar'])
  21.     testCompile 'junit:junit:4.12'
  22.     compile 'com.android.support:appcompat-v7:23.3.0'
  23.     compile "com.google.android.gms:play-services-gcm:8.4.0"
  24.     compile 'com.android.volley:volley:1.0.0'
  25.     compile 'com.squareup.okhttp3:okhttp:3.2.0'
  26. }
Now we are moving to coding part. first create the design. rename it actvity_main.xml to activity_gcm.xml

activity_gcm.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:tools="http://schemas.android.com/tools"
  4.    android:layout_width="match_parent"
  5.    android:layout_height="match_parent"
  6.    tools:context=".MainActivity">
  7.     <ListView
  8.        android:layout_width="wrap_content"
  9.        android:layout_height="wrap_content"
  10.        android:id="@+id/listView"
  11.        android:layout_alignParentTop="true"
  12.        android:layout_alignParentLeft="true"
  13.        android:layout_alignParentStart="true"
  14.        android:layout_marginBottom="50dp" />
  15.     <LinearLayout
  16.        android:orientation="horizontal"
  17.        android:layout_width="match_parent"
  18.        android:layout_height="50dp"
  19.        android:layout_alignParentBottom="true"
  20.        android:layout_alignParentLeft="true"
  21.        android:layout_alignParentStart="true">
  22.         <EditText
  23.            android:layout_width="wrap_content"
  24.            android:layout_height="wrap_content"
  25.            android:id="@+id/etText"
  26.            android:layout_weight="9" />
  27.         <Button
  28.            android:layout_width="wrap_content"
  29.            android:layout_height="wrap_content"
  30.            android:text="Send"
  31.            android:id="@+id/btnSend"
  32.            android:layout_weight="1" />
  33.     </LinearLayout>
  34. </RelativeLayout>

Now Copy this code into your MainActivity.java you can get the GCM ID from here.
Using this AsyncTask you get the GCM ID in Logcat (Android Monitor)
And paste this id into your api file.

  1. /*Post data using volley*/
  2.         new AsyncTask<VoidVoid, Void>(){
  3.             @Override
  4.             protected Void doInBackground(Void... params) {
  5.                 try {
  6.                     InstanceID instanceID = InstanceID.getInstance(getApplicationContext());
  7.                     final String token = instanceID.getToken("820466753885",
  8.                             GoogleCloudMessaging.INSTANCE_ID_SCOPEnull);
  9.                     Log.d(TAG, "GCM_ID : "+token);
  10.                     StringRequest stringRequest = new StringRequest(Request.Method.POST
  11.                             ,"http://104.237.134.200/android_test/push_notf.php?"
  12.                             ,new Response.Listener<String>() {
  13.                         @Override
  14.                         public void onResponse(String response) {
  15.                             Log.d(TAG, "onResponse: Success");
  16.                         }
  17.                     },
  18.                             new Response.ErrorListener() {  @Override
  19.                             public void onErrorResponse(VolleyError error) {
  20.                                 Log.d(TAG, "onErrorResponse: Error");
  21.                             }
  22.                             }
  23.                     ){
  24.                         @Override
  25.                         protected Map<String,String> getParams(){
  26.                             Map<String,String> params = new HashMap<String, String>();
  27.                             params.put("id",token);
  28.                             return params;
  29.                         }
  30.                     };
  31.                     requestQueue.add(stringRequest);
  32.                 } catch (IOException e) {
  33.                     e.printStackTrace();
  34.                 }
  35.                 return null;
  36.             }
  37.         }.execute();

Here is the full code of MainActivity.java

MainActivity.java

  1. import android.content.Intent;
  2. import android.os.AsyncTask;
  3. import android.support.v7.app.AppCompatActivity;
  4. import android.os.Bundle;
  5. import android.util.Log;
  6. import android.view.View;
  7. import android.widget.Button;
  8. import android.widget.EditText;
  9. import android.widget.ListView;
  10. import com.android.volley.Request;
  11. import com.android.volley.RequestQueue;
  12. import com.android.volley.Response;
  13. import com.android.volley.VolleyError;
  14. import com.android.volley.toolbox.StringRequest;
  15. import com.google.android.gms.gcm.GoogleCloudMessaging;
  16. import com.google.android.gms.iid.InstanceID;
  17. import java.io.IOException;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.concurrent.TimeUnit;
  21. import okhttp3.FormBody;
  22. import okhttp3.OkHttpClient;
  23. import okhttp3.RequestBody;
  24. public class MainActivity extends AppCompatActivity {
  25.     public static final String TAG = "From GCM";
  26.     RequestQueue requestQueue;
  27.     static ListView listView;
  28.     EditText etText;
  29.     Button btnSend;
  30.     public static ListViewAdapter listViewAdapter;
  31.     String text;
  32.     @Override
  33.     protected void onCreate(Bundle savedInstanceState) {
  34.         super.onCreate(savedInstanceState);
  35.         setContentView(R.layout.activity_gcm);
  36.         requestQueue = VolleySingleton.getInstance(getApplicationContext()).getRequestQueue();
  37.         listView = (ListView) findViewById(R.id.listView);
  38.         etText = (EditText) findViewById(R.id.etText);
  39.         btnSend = (Button) findViewById(R.id.btnSend);
  40.         listViewAdapter = new ListViewAdapter(this);
  41.         listView.setAdapter(listViewAdapter);
  42.         Intent intent = new Intent();
  43.         intent.setAction("com.example.chat");
  44.         sendBroadcast(intent);
  45.         btnSend.setOnClickListener(new View.OnClickListener() {
  46.             @Override
  47.             public void onClick(View v) {
  48.                 text = etText.getText().toString();
  49.                 new AsyncTask<VoidVoid, Void>(){
  50.                     @Override
  51.                     protected Void doInBackground(Void... params) {
  52.                         OkHttpClient client = new OkHttpClient.Builder()
  53.                                 .connectTimeout(0, TimeUnit.SECONDS)
  54.                                 .writeTimeout(0, TimeUnit.SECONDS)
  55.                                 .readTimeout(0, TimeUnit.SECONDS)
  56.                                 .build();
  57.                         RequestBody formBody = new FormBody.Builder()
  58.                                 .add("text", text)
  59.                                 .build();
  60.                         okhttp3.Request request = new okhttp3.Request.Builder()
  61.                                 .url("http://openspace.tranetech.com/mis/chat.php")
  62.                                 .post(formBody)
  63.                                 .build();
  64.                         try {
  65.                             okhttp3.Response response = client.newCall(request).execute();
  66.                             String responseString = response.body().string();
  67.                             response.body().close();
  68.                             Log.d(TAG, "doInBackground: RESPONSE : "+responseString);
  69.                         } catch (Exception e){
  70.                         }
  71.                         return null;
  72.                     }
  73.                 }.execute();
  74.                 etText.setText("");
  75.             }
  76.         });
  77.         /*Post data using volley*/
  78.         new AsyncTask<VoidVoid, Void>(){
  79.             @Override
  80.             protected Void doInBackground(Void... params) {
  81.                 try {
  82.              InstanceID instanceID = InstanceID.getInstance(getApplicationContext());
  83.                     final String token = instanceID.getToken("820466753885",
  84.                             GoogleCloudMessaging.INSTANCE_ID_SCOPEnull);
  85.                     Log.d(TAG, "GCM_ID : "+token);
  86.                     StringRequest stringRequest = new StringRequest(Request.Method.POST
  87.                             ,"http://104.237.134.200/android_test/push_notf.php?"
  88.                             ,new Response.Listener<String>() {
  89.                         @Override
  90.                         public void onResponse(String response) {
  91.                             Log.d(TAG, "onResponse: Success");
  92.                         }
  93.                     },
  94.                             new Response.ErrorListener() {  @Override
  95.                             public void onErrorResponse(VolleyError error) {
  96.                                 Log.d(TAG, "onErrorResponse: Error");
  97.                             }
  98.                             }
  99.                     ){
  100.                         @Override
  101.                         protected Map<String,String> getParams(){
  102.                             Map<String,String> params = new HashMap<String, String>();
  103.                             params.put("id",token);
  104.                             return params;
  105.                         }
  106.                     };
  107.                     requestQueue.add(stringRequest);
  108.                 } catch (IOException e) {
  109.                     e.printStackTrace();
  110.                 }
  111.                 return null;
  112.             }
  113.         }.execute();
  114.     }
  115. }

Now add MyGcmListenerService.java in your project. This class is used for getting  gcm response send by GCM server. 

MyGcmListenerService.java

  1. import android.app.Notification;
  2. import android.app.NotificationManager;
  3. import android.app.PendingIntent;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.os.Bundle;
  7. import android.support.v4.app.NotificationCompat;
  8. import android.util.Log;
  9. import com.google.android.gms.gcm.GcmListenerService;
  10. import java.util.ArrayList;
  11. public class MyGcmListenerService extends GcmListenerService {
  12.     public static final String TAG = "From Service";
  13.    static  String message;
  14.     ArrayList<String> data1 = new ArrayList<>();
  15.     @Override
  16.     public void onMessageReceived(String from, Bundle data) {
  17.         message = data.getString("Message");
  18.         Log.d(TAG, "onMessageReceived: From "+from);
  19.         Log.d(TAG, "onMessageReceived: Message "+ message);
  20.         Intent intent = new Intent();
  21.         intent.setAction("com.example.chat");
  22.         intent.putExtra("message",message);
  23.         sendBroadcast(intent);
  24.         notifyUser(message);
  25.     }
  26.     public void notifyUser(String message){
  27.    Intent intent = new Intent(getApplicationContext(), MainActivity.class);
  28.    PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext()0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  29.    NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext());
  30.         notification.setAutoCancel(true)
  31.                 .setDefaults(Notification.DEFAULT_ALL)
  32.                 .setWhen(System.currentTimeMillis())
  33.                 .setSmallIcon(R.mipmap.ic_launcher)
  34.                 .setTicker("Demo")
  35.                 .setContentTitle("Demo Notification")
  36.                 .setContentText(message)
  37.                 .setDefaults(Notification.DEFAULT_LIGHTS| Notification.DEFAULT_SOUND)
  38.                 .setContentIntent(contentIntent)
  39.                 .setContentInfo("Info");
  40.         NotificationManager notificationManager = (NotificationManager)getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
  41.         notificationManager.notify(10, notification.build());
  42.     }
  43. }

Then create single listitem for the listview so user can see.

listview_item.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    android:layout_width="match_parent"
  4.    android:layout_height="match_parent">
  5.     <TextView
  6.        android:id="@+id/rank"
  7.        android:layout_width="match_parent"
  8.        android:layout_height="wrap_content"
  9.        android:layout_margin="5dp"
  10.        android:padding="5dp" />
  11. </RelativeLayout>

Now add this java class adapter in you project.

ListViewAdapter.java 

  1. import android.content.Context;
  2. import android.view.LayoutInflater;
  3. import android.view.View;
  4. import android.view.ViewGroup;
  5. import android.widget.BaseAdapter;
  6. import android.widget.TextView;
  7. import java.util.ArrayList;
  8. public class ListViewAdapter extends BaseAdapter {
  9.     // Declare Variables
  10.     Context context;
  11.     LayoutInflater inflater;
  12.     ArrayList<String> data;
  13.     public ListViewAdapter(Context context
  14.             ) {
  15.         this.context = context;
  16.         data = new ArrayList<>();
  17.     }
  18.     @Override
  19.     public int getCount() {
  20.         return data.size();
  21.     }
  22.     @Override
  23.     public Object getItem(int position) {
  24.         return data.get(position);
  25.     }
  26.     @Override
  27.     public long getItemId(int position) {
  28.         return position;
  29.     }
  30.     public View getView(final int position, View convertView, ViewGroup parent) {
  31.         // Declare Variables
  32.         TextView rank;
  33.         inflater = (LayoutInflater) context
  34.                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  35.         View itemView = inflater.inflate(R.layout.listview_item, parent, false);
  36.         // Locate the TextViews in listview_item.xml
  37.         rank = (TextView) itemView.findViewById(R.id.rank);
  38.         // Capture position and set results to the TextViews
  39.         rank.setText(data.get(position));
  40.         return itemView;
  41.     }
  42.     public void updateList(String message){
  43.         data.add(message);
  44.         notifyDataSetChanged();
  45.     }
  46. }

Just few steps remain for chat application. Now you create the ChatReceiver.java  BroadcastReceiver class because we can not update the ui (Design) using MyGcmListenerService because it's extending  GcmListenerService.

ChatRecevier.java

  1. import android.content.BroadcastReceiver;
  2. import android.content.Context;
  3. import android.content.Intent;
  4. import android.util.Log;
  5. import java.util.ArrayList;
  6. /**
  7.  * Created by Arpit on 28-Apr-16.
  8.  */
  9. public class ChatReceiver extends BroadcastReceiver{
  10.     public static final String TAG = "ChatReceiver";
  11.     ArrayList<String> data1 = new ArrayList<>();
  12.     @Override
  13.     public void onReceive(Context context, Intent intent) {
  14.         Log.d(TAG, "onReceive: Chat  Received");
  15.         String message = intent.getStringExtra("message");
  16.         Log.d(TAG, "onReceive: Chat : "+message);
  17.         data1.add(message);
  18.         MainActivity.listViewAdapter.updateList(message);
  19.         MainActivity.listView.setSelection(MainActivity.listView.getAdapter().getCount()-1);
  20.     }
  21. }

We are just getting chat message using this class and set it into the listview adapter.

Now add this VollySingleton.java class in your project for Json parsing and post data to server

VollySingleton.java 

  1. import android.content.Context;
  2. import com.android.volley.RequestQueue;
  3. import com.android.volley.toolbox.Volley;
  4. /**
  5.  * Created by Arpit on 16-Mar-16.
  6.  */
  7. public class VolleySingleton {
  8.     private static VolleySingleton volleySingleton;
  9.     private RequestQueue requestQueue;
  10.     private VolleySingleton(Context context){
  11.         requestQueue = Volley.newRequestQueue(context);
  12.     }
  13.     public static VolleySingleton getInstance(Context context){
  14.         if(volleySingleton == null){
  15.             return new VolleySingleton(context);
  16.         }
  17.         return volleySingleton;
  18.     }
  19.     public RequestQueue getRequestQueue(){
  20.         return requestQueue;
  21.     }
  22. }

One last step for coding add Reciver in you manifest file one receiver for GCM and other for our ChatReciver.java 
Compare your manifest file with my manifest.

Note : Don't forget to change your package name and add  permission also.

Android Manifest.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.    package="com.example.androiddeveloper.simplechat">
  4.     <uses-permission android:name="android.permission.INTERNET" />
  5.     <permission
  6.        android:name="com.example.androiddeveloper.simplechat.permission.C2D_MESSAGE"
  7.        android:protectionLevel="signature" />
  8.     <uses-permission android:name="com.example.androiddeveloper.simplechat.permission.C2D_MESSAGE" />
  9.     <application
  10.        android:allowBackup="true"
  11.        android:icon="@mipmap/ic_launcher"
  12.        android:label="@string/app_name"
  13.        android:supportsRtl="true"
  14.        android:theme="@style/AppTheme">
  15.         <activity android:name=".MainActivity">
  16.             <intent-filter>
  17.                 <action android:name="android.intent.action.MAIN" />
  18.                 <category android:name="android.intent.category.LAUNCHER" />
  19.             </intent-filter>
  20.         </activity>
  21.         <!-- This is default reciever, If you need your reciever then impliment it using below code -->
  22.         <receiver
  23.            android:name="com.google.android.gms.gcm.GcmReceiver"
  24.            android:exported="true"
  25.            android:permission="com.google.android.c2dm.permission.SEND">
  26.             <intent-filter>
  27.                 <action android:name="com.google.android.c2dm.intent.RECEIVE" />
  28.                 <category android:name="com.example.gcm" />
  29.             </intent-filter>
  30.         </receiver>
  31.         <!-- Make service by extending GcmListenerService -->
  32.         <service
  33.            android:name=".MyGcmListenerService"
  34.            android:exported="false">
  35.             <intent-filter>
  36.                 <action android:name="com.google.android.c2dm.intent.RECEIVE" />
  37.             </intent-filter>
  38.         </service>
  39.         <receiver android:name=".ChatReceiver">
  40.             <intent-filter>
  41.                 <action android:name="com.example.chat"/>
  42.             </intent-filter>
  43.         </receiver>
  44.     </application>
  45. </manifest>


There you go complete the coding part one last step for chat app 

Final Step

Goto this LINK For register your app add your app name and package name they provide google-json file after complete all step the.
this file put into your app folder and define google_app_id in string file.

Download Code


Comments

Popular posts from this blog

web2apk

http://web2apk.com/create.aspx Create App   Intro   About   Changes   MalWare ?   Contact   Privacy Useful Links Bluetooth Mini Keyboards Android Mini PC Reset Android URL App Title Icon or

Android Bar Chart Using MpAndroidChart Library Tutorial

https://www.numetriclabz.com/android-bar-chart-using-mpandroidchart-library-tutorial/ Android Bar Chart Using MpAndroidChart Library Tutorial Objective In this tutorial we learn how to implement Bar Chart using MpAndroidChart Library in your Android App. Download Source Code       Step 1 Contents ·        1  Introduction ·        2  Creating Bar chart o    2.1  Create a new Project o    2.2  Adding library in Project o    2.3  Create Layout o    2.4  To Plot Bar Chart §   2.4.1  Initialize the graph id §   2.4.2  Creating a Dataset §   2.4.3  Defining X-axis labels §   2.4.4  Set the data §   2.4.5  Add the description to the chart §   2.4.6  Run your App § ...

how to retrieve image from sqlite database in android and display in listview

 Android platform provides several ways to store data in our application. 1. SQLite database 2. SharedPreferences etc For our post, we will only work with SQLite database. First and foremost, we need to understand what an SQLite database is? SQLite database  is an open source SQL database that stores data to a text file on a device. It executes SQL Commands to perform a set of functions, that is, create, read, update and delete operations. On my previous post, I showed how to  store data in SQLite database from edit text, retrieve and populate it in a listview . For this post, I will show the SQLite CRUD operations with images from gallery and text from EditText. We need to understand this; images are stored in SQLite database as BLOB data type. A BLOB is a large binary object that can hold a variable amount of data.  Note, we can only store images in the database as BLOB data type. We need to convert our image path to a bitmap th...