This
Android tutorial is to walk you through create an Android chat application using Google Cloud Messaging (GCM) using its Google Cloud Connection Server (CSS) via XMPP. Using Google CCS we can send upstream messages from an Android device to another and we will be using that feature primarily to do this chat application.
If you are new to Google Cloud Messaging (GCM), then you need to check a previous introductory tutorial “Google Cloud Messaging GCM for Android and Push Notifications”. This tutorial will help to understand the basics, setup the prerequisite and start this wonderful GCM journey.
Making of GCM Chat Application
- A cool Splash screen (just for fun)
- A Simple Android List View (available chat users list screen)
- A List View wit Row Layout (chat conversation display)
- Chat Bubble
- GCM upstream communication with XMPP
When we put together all the above, we get a nice Android chat application. I have already written detailed tutorials on all the above topics. In this tutorial, we will see how to wire them together as a nice little chat application.
Android Splash Screen
Above shown screen shot is the splash screen of the chat app we are going to develop. If you want to know how to create this splash screen refer the tutorialAndroid splash screen.
Simple Android ListView
To list the logged in and available users to chat, we can use a simple list view using a layout already available as part of sdk. Recently I wrote a tutorial for Android ListView and it is a basic introductory tutorial.
Chat Conversation with ListView and Custom Layout
Chat activity is the interesting page. Most of the chat applications follow the same design. Conversations alternately displayed opposite to each other on a bubble background. Have a look at the Android ListView Custom Layout Tutorial and it will help create the layout and date model using the custom adapter.
Android Chat Bubble
We need to display the conversation on a bubble background. The bubble background image should scale according to the foreground text. We should design a ninepatch image and use it as a background for the view. Refer theAndroid Chat Bubble tutorial which gives detailed explanation on this.
GCM upstream communication with CCS via XMPP
As part of the Google Cloud Messaging service, we have support for XMPP. Using this we can have persistent asynchronous and bidirectional communication. Google CCS server relays the messages between our XMPP server and Android device back and forth. In this tutorial example application, I have not completely leveraged the XMPP features. This is just a starting point and should build on top of this. Refer the previous tutorial on Google Cloud Messaging GCM-CCS with XMPP to understand the fundamentals, communication flow and sequence of events.
XMPP Chat Server Application
We need to have a XMPP chat server application as our backend. We will use the Java SmackClient API as XMMP wrapper framework. Our server application is a command line based Java program, this is heavily borrowed from the Google example and customized to suit our need.
/* * Most part of this class is copyright Google. * It is from https://developer.android.com/google/gcm/ccs.html */ import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.PacketInterceptor; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.DefaultPacketExtension; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.PacketExtensionProvider; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.util.StringUtils; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; import org.xmlpull.v1.XmlPullParser; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import java.io.*; import javax.net.ssl.SSLSocketFactory; /** * Sample Smack implementation of a client for GCM Cloud Connection Server. * * For illustration purposes only. */ public class SmackCcsClient { static final String REG_ID_STORE = "gcmchat.txt"; static final String MESSAGE_KEY = "SM"; Logger logger = Logger.getLogger("SmackCcsClient"); public static final String GCM_SERVER = "gcm.googleapis.com"; public static final int GCM_PORT = 5235; public static final String GCM_ELEMENT_NAME = "gcm"; public static final String GCM_NAMESPACE = "google:mobile:data"; static Random random = new Random(); XMPPConnection connection; ConnectionConfiguration config; /** * XMPP Packet Extension for GCM Cloud Connection Server. */ class GcmPacketExtension extends DefaultPacketExtension { String json; public GcmPacketExtension(String json) { super(GCM_ELEMENT_NAME, GCM_NAMESPACE); this.json = json; } public String getJson() { return json; } @Override public String toXML() { return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME, GCM_NAMESPACE, json, GCM_ELEMENT_NAME); } @SuppressWarnings("unused") public Packet toPacket() { return new Message() { // Must override toXML() because it includes a <body> @Override public String toXML() { StringBuilder buf = new StringBuilder(); buf.append("<message"); if (getXmlns() != null) { buf.append(" xmlns=\"").append(getXmlns()).append("\""); } if (getLanguage() != null) { buf.append(" xml:lang=\"").append(getLanguage()) .append("\""); } if (getPacketID() != null) { buf.append(" id=\"").append(getPacketID()).append("\""); } if (getTo() != null) { buf.append(" to=\"") .append(StringUtils.escapeForXML(getTo())) .append("\""); } if (getFrom() != null) { buf.append(" from=\"") .append(StringUtils.escapeForXML(getFrom())) .append("\""); } buf.append(">"); buf.append(GcmPacketExtension.this.toXML()); buf.append("</message>"); return buf.toString(); } }; } } public SmackCcsClient() { // Add GcmPacketExtension ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, new PacketExtensionProvider() { @Override public PacketExtension parseExtension(XmlPullParser parser) throws Exception { String json = parser.nextText(); GcmPacketExtension packet = new GcmPacketExtension(json); return packet; } }); } /** * Returns a random message id to uniquely identify a message. * * <p> * Note: This is generated by a pseudo random number generator for * illustration purpose, and is not guaranteed to be unique. * */ public String getRandomMessageId() { return "m-" + Long.toString(random.nextLong()); } /** * Sends a downstream GCM message. */ public void send(String jsonRequest) { Packet request = new GcmPacketExtension(jsonRequest).toPacket(); connection.sendPacket(request); } /** * Handles an upstream data message from a device application. * * <p> * This sample echo server sends an echo message back to the device. * Subclasses should override this method to process an upstream message. */ public void handleIncomingDataMessage(Map<String, Object> jsonObject) { String from = jsonObject.get("from").toString(); // PackageName of the application that sent this message. String category = jsonObject.get("category").toString(); // Use the packageName as the collapseKey in the echo packet String collapseKey = "echo:CollapseKey"; @SuppressWarnings("unchecked") Map<String, String> payload = (Map<String, String>) jsonObject .get("data"); String action = payload.get("ACTION"); if ("ECHO".equals(action)) { String clientMessage = payload.get("CLIENT_MESSAGE"); payload.put(MESSAGE_KEY, "ECHO: " + clientMessage); // Send an ECHO response back String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); send(echo); } else if ("SIGNUP".equals(action)) { try { String userName = payload.get("USER_NAME"); writeToFile(userName, from); } catch (IOException e) { e.printStackTrace(); } } else if ("USERLIST".equals(action)) { Map<String, String> regIdMap = readFromFile(); String users = ""; for (Map.Entry<String, String> entry : regIdMap.entrySet()) { users = users + entry.getKey() + ":"; } payload.put(MESSAGE_KEY, "USERLIST"); payload.put("USERLIST",users); String message = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); send(message); } else if ("CHAT".equals(action)) { Map<String, String> regIdMap = readFromFile(); payload.put(MESSAGE_KEY, "CHAT"); String toUser = payload.get("TOUSER"); String toUserRegid = regIdMap.get(toUser); String message = createJsonMessage(toUserRegid, getRandomMessageId(), payload, collapseKey, null, false); send(message); } } /** * Handles an ACK. * * <p> * By default, it only logs a INFO message, but subclasses could override it * to properly handle ACKS. */ public void handleAckReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); } /** * Handles a NACK. * * <p> * By default, it only logs a INFO message, but subclasses could override it * to properly handle NACKS. */ public void handleNackReceipt(Map<String, Object> jsonObject) { String messageId = jsonObject.get("message_id").toString(); String from = jsonObject.get("from").toString(); logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); } /** * Creates a JSON encoded GCM message. * * @param to * RegistrationId of the target device (Required). * @param messageId * Unique messageId for which CCS will send an "ack/nack" * (Required). * @param payload * Message content intended for the application. (Optional). * @param collapseKey * GCM collapse_key parameter (Optional). * @param timeToLive * GCM time_to_live parameter (Optional). * @param delayWhileIdle * GCM delay_while_idle parameter (Optional). * @return JSON encoded GCM message. */ public static String createJsonMessage(String to, String messageId, Map<String, String> payload, String collapseKey, Long timeToLive, Boolean delayWhileIdle) { Map<String, Object> message = new HashMap<String, Object>(); message.put("to", to); if (collapseKey != null) { message.put("collapse_key", collapseKey); } if (timeToLive != null) { message.put("time_to_live", timeToLive); } if (delayWhileIdle != null && delayWhileIdle) { message.put("delay_while_idle", true); } message.put("message_id", messageId); message.put("data", payload); return JSONValue.toJSONString(message); } /** * Creates a JSON encoded ACK message for an upstream message received from * an application. * * @param to * RegistrationId of the device who sent the upstream message. * @param messageId * messageId of the upstream message to be acknowledged to CCS. * @return JSON encoded ack. */ public static String createJsonAck(String to, String messageId) { Map<String, Object> message = new HashMap<String, Object>(); message.put("message_type", "ack"); message.put("to", to); message.put("message_id", messageId); return JSONValue.toJSONString(message); } /** * Connects to GCM Cloud Connection Server using the supplied credentials. * * @param username * GCM_SENDER_ID@gcm.googleapis.com * @param password * API Key * @throws XMPPException */ public void connect(String username, String password) throws XMPPException { config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); config.setSecurityMode(SecurityMode.enabled); config.setReconnectionAllowed(true); config.setRosterLoadedAtLogin(false); config.setSendPresence(false); config.setSocketFactory(SSLSocketFactory.getDefault()); // NOTE: Set to true to launch a window with information about packets // sent and received config.setDebuggerEnabled(true); // -Dsmack.debugEnabled=true XMPPConnection.DEBUG_ENABLED = true; connection = new XMPPConnection(config); connection.connect(); connection.addConnectionListener(new ConnectionListener() { @Override public void reconnectionSuccessful() { logger.info("Reconnecting.."); } @Override public void reconnectionFailed(Exception e) { logger.log(Level.INFO, "Reconnection failed.. ", e); } @Override public void reconnectingIn(int seconds) { logger.log(Level.INFO, "Reconnecting in %d secs", seconds); } @Override public void connectionClosedOnError(Exception e) { logger.log(Level.INFO, "Connection closed on error."); } @Override public void connectionClosed() { logger.info("Connection closed."); } }); // Handle incoming packets connection.addPacketListener(new PacketListener() { @Override public void processPacket(Packet packet) { logger.log(Level.INFO, "Received: " + packet.toXML()); Message incomingMessage = (Message) packet; GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage .getExtension(GCM_NAMESPACE); String json = gcmPacket.getJson(); try { @SuppressWarnings("unchecked") Map<String, Object> jsonObject = (Map<String, Object>) JSONValue .parseWithException(json); // present for "ack"/"nack", null otherwise Object messageType = jsonObject.get("message_type"); if (messageType == null) { // Normal upstream data message handleIncomingDataMessage(jsonObject); // Send ACK to CCS String messageId = jsonObject.get("message_id") .toString(); String from = jsonObject.get("from").toString(); String ack = createJsonAck(from, messageId); send(ack); } else if ("ack".equals(messageType.toString())) { // Process Ack handleAckReceipt(jsonObject); } else if ("nack".equals(messageType.toString())) { // Process Nack handleNackReceipt(jsonObject); } else { logger.log(Level.WARNING, "Unrecognized message type (%s)", messageType.toString()); } } catch (ParseException e) { logger.log(Level.SEVERE, "Error parsing JSON " + json, e); } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't send echo.", e); } } }, new PacketTypeFilter(Message.class)); // Log all outgoing packets connection.addPacketInterceptor(new PacketInterceptor() { @Override public void interceptPacket(Packet packet) { logger.log(Level.INFO, "Sent: {0}", packet.toXML()); } }, new PacketTypeFilter(Message.class)); connection.login(username, password); } public void writeToFile(String name, String regId) throws IOException { Map<String, String> regIdMap = readFromFile(); regIdMap.put(name, regId); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( REG_ID_STORE, false))); for (Map.Entry<String, String> entry : regIdMap.entrySet()) { out.println(entry.getKey() + "," + entry.getValue()); } out.println(name + "," + regId); out.close(); } public Map<String, String> readFromFile() { Map<String, String> regIdMap = null; try { BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE)); String regIdLine = ""; regIdMap = new HashMap<String, String>(); while ((regIdLine = br.readLine()) != null) { String[] regArr = regIdLine.split(","); regIdMap.put(regArr[0], regArr[1]); } br.close(); } catch(IOException ioe) { } return regIdMap; } public static void main(String [] args) { final String userName = "512218038480" + "@gcm.googleapis.com"; final String password = "AIzaSyA9DQTcggUtfqOG9lnV_Xb5VEQ8iKBEaP4"; SmackCcsClient ccsClient = new SmackCcsClient(); try { ccsClient.connect(userName, password); } catch (XMPPException e) { e.printStackTrace(); } } }
COMPILE: javac -cp *;.; SmackCcsClient.java RUN: java -cp *;.; SmackCcsClient
Download the XMMP Server application XMPP Chat Server
Android Chat Application based on Google GCM
I am going to show only the key classes below in the source code listing. For the complete source, download the whole project source using the link give below.
ChatActivity.java
package com.javapapers.android.gcm.chat; import java.util.Random; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.DataSetObserver; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.widget.AbsListView; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import com.google.android.gms.gcm.GoogleCloudMessaging; public class ChatActivity extends Activity { private static final String TAG = "ChatActivity"; private ChatArrayAdapter chatArrayAdapter; private ListView listView; private EditText chatText; private Button buttonSend; GoogleCloudMessaging gcm; Intent intent; private static Random random; private String toUserName; MessageSender messageSender; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent i = getIntent(); toUserName = i.getStringExtra("TOUSER"); setContentView(R.layout.activity_chat); buttonSend = (Button) findViewById(R.id.buttonSend); intent = new Intent(this, GCMNotificationIntentService.class); registerReceiver(broadcastReceiver, new IntentFilter("com.javapapers.android.gcm.chat.chatmessage")); random = new Random(); messageSender = new MessageSender(); listView = (ListView) findViewById(R.id.listView1); gcm = GoogleCloudMessaging.getInstance(getApplicationContext()); chatArrayAdapter = new ChatArrayAdapter(getApplicationContext(), R.layout.activity_chat_singlemessage); listView.setAdapter(chatArrayAdapter); chatText = (EditText) findViewById(R.id.chatText); chatText.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { return sendChatMessage(); } return false; } }); buttonSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { sendChatMessage(); } }); listView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL); listView.setAdapter(chatArrayAdapter); chatArrayAdapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { super.onChanged(); listView.setSelection(chatArrayAdapter.getCount() - 1); } }); } private boolean sendChatMessage(){ //sending gcm message to the paired device Bundle dataBundle = new Bundle(); dataBundle.putString("ACTION", "CHAT"); dataBundle.putString("TOUSER", toUserName); dataBundle.putString("CHATMESSAGE", chatText.getText().toString()); messageSender.sendMessage(dataBundle,gcm); //updating the current device chatArrayAdapter.add(new ChatMessage(false, chatText.getText().toString())); chatText.setText(""); return true; } private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive: " + intent.getStringExtra("CHATMESSAGE")); chatArrayAdapter.add(new ChatMessage(true, intent.getStringExtra("CHATMESSAGE"))); } }; }
ChatArrayAdapter.java
package com.javapapers.android.gcm.chat; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.TextView; public class ChatArrayAdapter extends ArrayAdapter{ private TextView chatText; private List chatMessageList = new ArrayList (); private LinearLayout singleMessageContainer; @Override public void add(ChatMessage object) { chatMessageList.add(object); super.add(object); } public ChatArrayAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); } public int getCount() { return this.chatMessageList.size(); } public ChatMessage getItem(int index) { return this.chatMessageList.get(index); } public View getView(int position, View convertView, ViewGroup parent) { View row = convertView; if (row == null) { LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); row = inflater.inflate(R.layout.activity_chat_singlemessage, parent, false); } singleMessageContainer = (LinearLayout) row.findViewById(R.id.singleMessageContainer); ChatMessage chatMessageObj = getItem(position); chatText = (TextView) row.findViewById(R.id.singleMessage); chatText.setText(chatMessageObj.message); chatText.setBackgroundResource(chatMessageObj.left ? R.drawable.bubble_a : R.drawable.bubble_b); singleMessageContainer.setGravity(chatMessageObj.left ? Gravity.LEFT : Gravity.RIGHT); return row; } public Bitmap decodeToBitmap(byte[] decodedByte) { return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length); } }
ChatMessage.java
package com.javapapers.android.gcm.chat; public class ChatMessage { public boolean left; public String message; public ChatMessage(boolean left, String message) { super(); this.left = left; this.message = message; } }
GcmBroadcastReceiver.java
package com.javapapers.android.gcm.chat; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.support.v4.content.WakefulBroadcastReceiver; import android.util.Log; /** * Created by Joe on 5/28/2014. */ public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("GcmBroadcastReceiver", "onReceive: notification received."); ComponentName comp = new ComponentName(context.getPackageName(), GCMNotificationIntentService.class.getName()); startWakefulService(context, (intent.setComponent(comp))); setResultCode(Activity.RESULT_OK); } }
GCMNotificationIntentService.java
package com.javapapers.android.gcm.chat; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; /** * Created by Joe on 5/28/2014. */ public class GCMNotificationIntentService extends IntentService { public static final int NOTIFICATION_ID = 1; private NotificationManager mNotificationManager; NotificationCompat.Builder builder; public GCMNotificationIntentService() { super("GcmIntentService"); } public static final String TAG = "GCMNotificationIntentService"; @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "onHandleIntent "+intent.getDataString()); Bundle extras = intent.getExtras(); GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this); String messageType = gcm.getMessageType(intent); if (extras != null) { if (!extras.isEmpty()) { if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR .equals(messageType)) { sendNotification("Send error: " + extras.toString()); } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED .equals(messageType)) { sendNotification("Deleted messages on server: " + extras.toString()); } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE .equals(messageType)) { if("USERLIST".equals(extras.get("SM"))){ Log.d(TAG, "onHandleIntent - USERLIST "); //update the userlist view Intent userListIntent = new Intent("com.javapapers.android.gcm.chat.userlist"); String userList = extras.get("USERLIST").toString(); userListIntent.putExtra("USERLIST",userList); sendBroadcast(userListIntent); } else if("CHAT".equals(extras.get("SM"))){ Log.d(TAG, "onHandleIntent - CHAT "); Intent chatIntent = new Intent("com.javapapers.android.gcm.chat.chatmessage"); chatIntent.putExtra("CHATMESSAGE",extras.get("CHATMESSAGE").toString()); sendBroadcast(chatIntent); } Log.i(TAG, "SERVER_MESSAGE: " + extras.toString()); } } } GcmBroadcastReceiver.completeWakefulIntent(intent); } private void sendNotification(String msg) { Log.d(TAG, "Preparing to send notification...: " + msg); mNotificationManager = (NotificationManager) this .getSystemService(Context.NOTIFICATION_SERVICE); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, SignUpActivity.class), 0); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder( this).setSmallIcon(R.drawable.gcm_cloud) .setContentTitle("GCM XMPP Message") .setStyle(new NotificationCompat.BigTextStyle().bigText(msg)) .setContentText(msg); mBuilder.setContentIntent(contentIntent); mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); Log.d(TAG, "Notification sent successfully."); } }
MessageSender.java
package com.javapapers.android.gcm.chat; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; /** * Created by Joe on 6/1/2014. */ public class MessageSender { private static final String TAG = "MessageSender"; AsyncTasksendTask; AtomicInteger ccsMsgId = new AtomicInteger(); public void sendMessage(final Bundle data, final GoogleCloudMessaging gcm ) { sendTask = new AsyncTask () { @Override protected String doInBackground(Void... params) { String id = Integer.toString(ccsMsgId.incrementAndGet()); try { Log.d(TAG, "messageid: " + id); gcm.send(Config.GOOGLE_PROJECT_ID + "@gcm.googleapis.com", id, data); Log.d(TAG, "After gcm.send successful."); } catch (IOException e) { Log.d(TAG, "Exception: " + e); e.printStackTrace(); } return "Message ID: "+id+ " Sent."; } @Override protected void onPostExecute(String result) { sendTask = null; Log.d(TAG, "onPostExecute: result: " + result); } }; sendTask.execute(null, null, null); } }
UserListActivity.java
package com.javapapers.android.gcm.chat; import android.app.ListActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import com.google.android.gms.gcm.GoogleCloudMessaging; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class UserListActivity extends ListActivity { private static final String TAG = "UserListActivity"; TextView content; Button refreshButton; private Intent intent; MessageSender messageSender; GoogleCloudMessaging gcm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_user_list); content = (TextView)findViewById(R.id.output); content.setText("Select user to chat:"); refreshButton = (Button)findViewById(R.id.refreshButton); intent = new Intent(this, GCMNotificationIntentService.class); registerReceiver(broadcastReceiver, new IntentFilter("com.javapapers.android.gcm.chat.userlist")); messageSender = new MessageSender(); gcm = GoogleCloudMessaging.getInstance(getApplicationContext()); refreshButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // get user list Bundle dataBundle = new Bundle(); dataBundle.putString("ACTION", "USERLIST"); messageSender.sendMessage(dataBundle, gcm); } }); } private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive: " + intent.getStringExtra("USERLIST")); updateUI(intent.getStringExtra("USERLIST")); } }; private void updateUI(String userList) { //get userlist from the intents and update the list String[] userListArr = userList.split(":"); Log.d(TAG,"userListArr: "+userListArr.length+" tostr "+userListArr.toString()); //remove empty strings :-) Listlist = new ArrayList (); for(String s : userListArr) { if(s != null && s.length() > 0) { list.add(s); } } userListArr = list.toArray(new String[list.size()]); ArrayAdapter adapter = new ArrayAdapter (this, android.R.layout.simple_list_item_1, userListArr); setListAdapter(adapter); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // ListView Clicked item index int itemPosition = position; // ListView Clicked item value String itemValue = (String) l.getItemAtPosition(position); content.setText("User selected: " +itemValue); Intent i = new Intent(getApplicationContext(), ChatActivity.class); i.putExtra("TOUSER",itemValue); startActivity(i); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.user_list, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
SignUpActivity.java
package com.javapapers.android.gcm.chat; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.google.android.gms.gcm.GoogleCloudMessaging; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; public class SignUpActivity extends ActionBarActivity { private static final String TAG = "SignUpActivity"; public static final String REG_ID = "regId"; private static final String APP_VERSION = "appVersion"; Button buttonSignUp; Button buttonLogin; String regId; String signUpUser; AsyncTasksendTask; AtomicInteger ccsMsgId = new AtomicInteger(); GoogleCloudMessaging gcm; Context context; private boolean signupFlag = false; MessageSender messageSender; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_up); context = getApplicationContext(); buttonSignUp = (Button) findViewById(R.id.ButtonSignUp); messageSender = new MessageSender(); buttonSignUp.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { //step 1: register with Google GCM server if (TextUtils.isEmpty(regId)) { regId = registerGCM(); Log.d(TAG, "GCM RegId: " + regId); } //step 2: register with XMPP App Server if(!regId.isEmpty()) { EditText mUserName = (EditText) findViewById(R.id.userName); signUpUser = mUserName.getText().toString(); Bundle dataBundle = new Bundle(); dataBundle.putString("ACTION", "SIGNUP"); dataBundle.putString("USER_NAME", signUpUser); messageSender.sendMessage(dataBundle,gcm); signupFlag = true; Toast.makeText(context, "Sign Up Complete!", Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, "Google GCM RegId Not Available!", Toast.LENGTH_LONG).show(); } } }); buttonLogin = (Button) findViewById(R.id.ButtonLogin); buttonLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { //step 0: register with Google GCM server if (TextUtils.isEmpty(regId)) { regId = registerGCM(); Log.d(TAG, "GCM RegId: " + regId); } //step 1: user authentication //step 2: get user list Bundle dataBundle = new Bundle(); dataBundle.putString("ACTION", "USERLIST"); dataBundle.putString("USER_NAME", signUpUser); messageSender.sendMessage(dataBundle,gcm); Intent i = new Intent(context, UserListActivity.class); Log.d(TAG, "onClick of login: Before starting userlist activity."); startActivity(i); finish(); Log.d(TAG, "onClick of Login: After finish."); } }); } public String registerGCM() { gcm = GoogleCloudMessaging.getInstance(this); regId = getRegistrationId(); if (TextUtils.isEmpty(regId)) { registerInBackground(); Log.d(TAG, "registerGCM - successfully registered with GCM server - regId: " + regId); } else { Log.d(TAG, "Regid already available: " + regId ); } return regId; } private String getRegistrationId() { final SharedPreferences prefs = getSharedPreferences( SignUpActivity.class.getSimpleName(), Context.MODE_PRIVATE); String registrationId = prefs.getString(REG_ID, ""); if (registrationId.isEmpty()) { Log.i(TAG, "Registration not found."); return ""; } int registeredVersion = prefs.getInt(APP_VERSION, Integer.MIN_VALUE); int currentVersion = getAppVersion(); if (registeredVersion != currentVersion) { Log.i(TAG, "App version changed."); return ""; } return registrationId; } private int getAppVersion() { try { PackageInfo packageInfo; packageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), 0); return packageInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { Log.d("RegisterActivity", "I never expected this! Going down, going down!" + e); throw new RuntimeException(e); } } private void registerInBackground() { new AsyncTask () { @Override protected String doInBackground(Void... params) { String msg = ""; try { if (gcm == null) { gcm = GoogleCloudMessaging.getInstance(context); } regId = gcm.register(Config.GOOGLE_PROJECT_ID); Log.d("RegisterActivity", "registerInBackground - regId: " + regId); msg = "Device registered, registration ID=" + regId; storeRegistrationId(regId); } catch (IOException ex) { msg = "Error :" + ex.getMessage(); Log.d(TAG, "Error: " + msg); } Log.d(TAG, "AsyncTask completed: " + msg); return msg; } @Override protected void onPostExecute(String msg) { Log.d(TAG, "Registered with GCM Server." + msg); } }.execute(null, null, null); } private void storeRegistrationId(String regId) { final SharedPreferences prefs = getSharedPreferences( SignUpActivity.class.getSimpleName(), Context.MODE_PRIVATE); int appVersion = getAppVersion(); Log.i(TAG, "Saving regId on app version " + appVersion); SharedPreferences.Editor editor = prefs.edit(); editor.putString(REG_ID, regId); editor.putInt(APP_VERSION, appVersion); editor.commit(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.sign_up, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
activity_chat.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="80dp"> </ListView> <RelativeLayout android:id="@+id/form" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:orientation="vertical" > <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="textMultiLine" android:ems="10" android:id="@+id/chatText" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_toLeftOf="@+id/buttonSend" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" android:id="@+id/buttonSend" android:layout_alignBottom="@+id/chatText" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> </RelativeLayout> </RelativeLayout>
activity_chat_singlemessage.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <LinearLayout android:id="@+id/singleMessageContainer" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/singleMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="5dip" android:background="@drawable/bubble_b" android:paddingLeft="10dip" android:text="Hello bubbles!" android:textColor="@android:color/primary_text_light" /> </LinearLayout> </LinearLayout>
activity_user_list.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.javapapers.android.gcm.chat.UserListActivity"> <TextView android:id="@+id/output" android:background="@color/BrightBlue" android:layout_height="wrap_content" android:padding="@dimen/abc_action_bar_icon_vertical_padding" android:textColor="#ffffff" android:text="Click : " android:layout_width="fill_parent" /> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@android:id/list" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="103dp" /> <Button style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Refresh" android:id="@+id/refreshButton" android:layout_below="@+id/output" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" /> </RelativeLayout>
activity_chat_singlemessage.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.javapapers.android.gcm.chat.SignUpActivity"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/userName" android:layout_centerHorizontal="true" android:layout_margin="60dp" android:layout_marginBottom="74dp" android:contentDescription="Application Logo" android:src="@drawable/gcmchat" /> <EditText android:id="@+id/userName" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginBottom="20dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:singleLine="true" android:hint="@string/enter_username" /> <Button android:id="@+id/ButtonLogin" style="?android:attr/borderlessButtonStyle" android:layout_width="120dp" android:layout_height="45dp" android:background="@color/BrightBlue" android:text="@string/login" android:layout_marginTop="37dp" android:layout_below="@+id/editText" android:layout_alignLeft="@+id/editText" android:layout_alignStart="@+id/editText" /> <Button android:id="@+id/ButtonSignUp" style="?android:attr/borderlessButtonStyle" android:layout_width="120dp" android:layout_height="45dp" android:background="@color/BrightBlue" android:text="@string/signup" android:textColorHint="@color/White" android:layout_alignTop="@+id/ButtonLogin" android:layout_alignRight="@+id/userName" android:layout_alignEnd="@+id/userName" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="textPassword" android:ems="10" android:id="@+id/editText" android:layout_below="@+id/userName" android:layout_alignLeft="@+id/userName" android:layout_alignStart="@+id/userName" android:layout_alignRight="@+id/userName" android:layout_alignEnd="@+id/userName" android:hint="@string/enter_password" /> </RelativeLayout>
No comments:
Post a Comment