Sending pictures through chat

This commit is contained in:
Sylvain Berfini 2012-09-14 16:54:57 +02:00
commit 58521a4501
16 changed files with 369 additions and 25 deletions

View file

@ -74,3 +74,4 @@ clean:
ant clean ant clean
.PHONY: clean .PHONY: clean

View file

@ -12,6 +12,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="1" />
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView <TextView
android:id="@+id/time" android:id="@+id/time"
android:textColor="@android:color/darker_gray" android:textColor="@android:color/darker_gray"

View file

@ -31,4 +31,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

View file

@ -13,6 +13,12 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/time" android:id="@+id/time"
android:textColor="@android:color/darker_gray" android:textColor="@android:color/darker_gray"

View file

@ -15,6 +15,12 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -7,6 +7,7 @@
<string name="default_domain">sip.linphone.org</string> <string name="default_domain">sip.linphone.org</string>
<string name="wizard_url">https://www.linphone.org/wizard.php</string> <string name="wizard_url">https://www.linphone.org/wizard.php</string>
<string name="upload_url">https://www.linphone.org:444/upload.php</string>
<!-- Interface settings --> <!-- Interface settings -->
<bool name="use_simple_history">true</bool> <bool name="use_simple_history">true</bool>

View file

@ -17,21 +17,41 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ */
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.List; import java.util.List;
import org.apache.http.util.ByteArrayBuffer;
import org.linphone.LinphoneSimpleListener.LinphoneOnMessageReceivedListener; import org.linphone.LinphoneSimpleListener.LinphoneOnMessageReceivedListener;
import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatMessage.State; import org.linphone.core.LinphoneChatMessage.State;
import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore;
import org.linphone.core.Log;
import org.linphone.ui.AvatarWithShadow; import org.linphone.ui.AvatarWithShadow;
import org.linphone.ui.BubbleChat; import org.linphone.ui.BubbleChat;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.provider.MediaStore;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.CursorLoader;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -49,6 +69,7 @@ import android.widget.TextView;
* @author Sylvain Berfini * @author Sylvain Berfini
*/ */
public class ChatFragment extends Fragment implements OnClickListener, LinphoneOnMessageReceivedListener, LinphoneChatMessage.StateListener { public class ChatFragment extends Fragment implements OnClickListener, LinphoneOnMessageReceivedListener, LinphoneChatMessage.StateListener {
private static final int ADD_PHOTO = 0;
private LinphoneChatRoom chatRoom; private LinphoneChatRoom chatRoom;
private View view; private View view;
private String sipUri; private String sipUri;
@ -60,6 +81,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
private int previousMessageID; private int previousMessageID;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private BubbleChat lastSentMessageBubble; private BubbleChat lastSentMessageBubble;
private ProgressDialog mProgressDialog;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -72,6 +94,12 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
contactName = (TextView) view.findViewById(R.id.contactName); contactName = (TextView) view.findViewById(R.id.contactName);
contactPicture = (AvatarWithShadow) view.findViewById(R.id.contactPicture); contactPicture = (AvatarWithShadow) view.findViewById(R.id.contactPicture);
contactPicture.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
pickImage(); //FIXME: use a specific button instead
}
});
ImageView sendMessage = (ImageView) view.findViewById(R.id.sendMessage); ImageView sendMessage = (ImageView) view.findViewById(R.id.sendMessage);
sendMessage.setOnClickListener(this); sendMessage.setOnClickListener(this);
@ -96,7 +124,11 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
previousMessageID = -1; previousMessageID = -1;
ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage(); ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage();
for (ChatMessage msg : messagesList) { for (ChatMessage msg : messagesList) {
displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout); if (msg.getMessage() != null) {
displayMessage(msg.getId(), msg.getMessage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
} else {
displayImageMessage(msg.getId(), msg.getImage(), msg.getTimestamp(), msg.isIncoming(), msg.getStatus(), messagesLayout);
}
chatStorage.markMessageAsRead(msg.getId()); chatStorage.markMessageAsRead(msg.getId());
} }
LinphoneActivity.instance().updateMissedChatCount(); LinphoneActivity.instance().updateMissedChatCount();
@ -132,7 +164,22 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, time, isIncoming, status, previousMessageID); BubbleChat bubble = new BubbleChat(layout.getContext(), id, message, null, time, isIncoming, status, previousMessageID);
if (!isIncoming) {
lastSentMessageBubble = bubble;
}
previousMessageID = id;
layout.addView(bubble.getView());
registerForContextMenu(bubble.getView());
}
});
}
private void displayImageMessage(final int id, final Bitmap image, final String time, final boolean isIncoming, final LinphoneChatMessage.State status, final RelativeLayout layout) {
mHandler.post(new Runnable() {
@Override
public void run() {
BubbleChat bubble = new BubbleChat(layout.getContext(), id, null, image, time, isIncoming, status, previousMessageID);
if (!isIncoming) { if (!isIncoming) {
lastSentMessageBubble = bubble; lastSentMessageBubble = bubble;
} }
@ -173,6 +220,10 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
@Override @Override
public void onClick(View v) { public void onClick(View v) {
sendTextMessage();
}
private void sendTextMessage() {
if (chatRoom != null && message != null && message.getText().length() > 0) { if (chatRoom != null && message != null && message.getText().length() > 0) {
String messageToSend = message.getText().toString(); String messageToSend = message.getText().toString();
message.setText(""); message.setText("");
@ -189,6 +240,20 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
} }
} }
private void sendImageMessage(String url, Bitmap bitmap) {
if (chatRoom != null && url != null && url.length() > 0) {
LinphoneChatMessage chatMessage = chatRoom.createLinphoneChatMessage("");
chatMessage.setExternalBodyUrl(url);
chatRoom.sendMessage(chatMessage, this);
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().onMessageSent(sipUri, bitmap);
}
}
displayImageMessage(previousMessageID + 2, bitmap, String.valueOf(System.currentTimeMillis()), false, State.InProgress, messagesLayout);
scrollToEnd();
}
private void scrollToEnd() { private void scrollToEnd() {
mHandler.postDelayed(new Runnable() { mHandler.postDelayed(new Runnable() {
@Override @Override
@ -199,10 +264,15 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
} }
@Override @Override
public void onMessageReceived(LinphoneAddress from, String message) { public void onMessageReceived(LinphoneAddress from, LinphoneChatMessage message) {
if (from.asStringUriOnly().equals(sipUri)) { if (from.asStringUriOnly().equals(sipUri)) {
int id = previousMessageID + 2; int id = previousMessageID + 2;
displayMessage(id, message, String.valueOf(System.currentTimeMillis()), true, null, messagesLayout); if (message.getMessage() != null) {
displayMessage(id, message.getMessage(), String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
} else if (message.getExternalBodyUrl() != null) {
Bitmap bm = downloadImage(message.getExternalBodyUrl());
displayImageMessage(id, bm, String.valueOf(System.currentTimeMillis()), true, null, messagesLayout);
}
scrollToEnd(); scrollToEnd();
} }
} }
@ -210,12 +280,17 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
@Override @Override
public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) { public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) {
final String finalMessage = msg.getMessage(); final String finalMessage = msg.getMessage();
final Bitmap finalImage = downloadImage(msg.getMessage());
final State finalState = state; final State finalState = state;
if (LinphoneActivity.isInstanciated() && state != State.InProgress) { if (LinphoneActivity.isInstanciated() && state != State.InProgress) {
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
LinphoneActivity.instance().onMessageStateChanged(sipUri, finalMessage, finalState.toInt()); if (finalMessage != null) {
LinphoneActivity.instance().onMessageStateChanged(sipUri, finalMessage, finalState.toInt());
} else if (finalImage != null) {
LinphoneActivity.instance().onMessageStateChanged(sipUri, finalImage, finalState.toInt());
}
lastSentMessageBubble.updateStatusView(finalState); lastSentMessageBubble.updateStatusView(finalState);
} }
}); });
@ -225,4 +300,145 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO
public String getSipUri() { public String getSipUri() {
return sipUri; return sipUri;
} }
private void pickImage() {
//TODO allow user to take a photo or to choose an existing one
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, ADD_PHOTO);
}
public static Bitmap downloadImage(String stringUrl) {
URL url;
Bitmap bm = null;
try {
url = new URL(stringUrl);
URLConnection ucon = url.openConnection();
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
while ((current = bis.read()) != -1) {
baf.append((byte) current);
}
byte[] rawImage = baf.toByteArray();
bm = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return bm;
}
private String uploadImage(String filePath, Bitmap file) {
String upLoadServerUri = getActivity().getResources().getString(R.string.upload_url);
File sourceFile = new File(filePath);
if (!sourceFile.isFile()) {
Log.e("Can't read source file " + filePath + ", upload failed");
return null;
}
String response = null;
HttpURLConnection conn = null;
try {
String lineEnd = "\r\n";
String twoHyphens = "--";
String boundary = "---------------------------14737809831466499882746641449";
FileInputStream fileInputStream = new FileInputStream(sourceFile);
URL url = new URL(upLoadServerUri);
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("ENCTYPE", "multipart/form-data");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("uploaded_file", sourceFile.getName());
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
dos.writeBytes(lineEnd + twoHyphens + boundary + lineEnd);
dos.writeBytes("Content-Disposition: form-data; name=\"userfile\"; filename=\""+ sourceFile.getName() + "\"" + lineEnd);
dos.writeBytes("Content-Type: application/octet-stream" + lineEnd);
dos.writeBytes(lineEnd);
file.compress(CompressFormat.JPEG, 75, dos);
dos.writeBytes(lineEnd);
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
fileInputStream.close();
dos.flush();
dos.close();
InputStream is = conn.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bytesRead;
byte[] bytes = new byte[1024];
while((bytesRead = is.read(bytes)) != -1) {
baos.write(bytes, 0, bytesRead);
}
byte[] bytesReceived = baos.toByteArray();
baos.close();
is.close();
response = new String(bytesReceived);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return response;
}
public String getRealPathFromURI(Uri contentUri) {
String[] proj = { MediaStore.Images.Media.DATA };
CursorLoader loader = new CursorLoader(getActivity(), contentUri, proj, null, null, null);
Cursor cursor = loader.loadInBackground();
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_PHOTO && resultCode == Activity.RESULT_OK) {
final String filePath = getRealPathFromURI(data.getData());
mProgressDialog = ProgressDialog.show(getActivity(), "Please wait", "Long operation starts...", true);
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bm = BitmapFactory.decodeFile(filePath);
final String url = uploadImage(filePath, bm);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
bm.compress(CompressFormat.JPEG, 75, outStream);
bm.recycle();
byte[] bitmap = outStream.toByteArray();
final Bitmap fbm = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length);
mHandler.post(new Runnable() {
@Override
public void run() {
mProgressDialog.dismiss();
sendImageMessage(url, fbm);
}
});
}
}).start();
}
super.onActivityResult(requestCode, resultCode, data);
}
} }

View file

@ -89,6 +89,9 @@ public class ChatListFragment extends Fragment implements OnClickListener, OnIte
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
if (info == null || info.targetView == null) {
return false;
}
String sipUri = (String) info.targetView.getTag(); String sipUri = (String) info.targetView.getTag();
LinphoneActivity.instance().removeFromChatList(sipUri); LinphoneActivity.instance().removeFromChatList(sipUri);

View file

@ -1,6 +1,9 @@
package org.linphone; package org.linphone;
import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneChatMessage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/* /*
ChatMessage.java ChatMessage.java
Copyright (C) 2012 Belledonne Communications, Grenoble, France Copyright (C) 2012 Belledonne Communications, Grenoble, France
@ -29,14 +32,16 @@ public class ChatMessage {
private boolean incoming; private boolean incoming;
private int status; private int status;
private int id; private int id;
private Bitmap image;
public ChatMessage(int id, String message, String timestamp, boolean incoming, int status) { public ChatMessage(int id, String message, byte[] rawImage, String timestamp, boolean incoming, int status) {
super(); super();
this.id = id; this.id = id;
this.message = message; this.message = message;
this.timestamp = timestamp; this.timestamp = timestamp;
this.incoming = incoming; this.incoming = incoming;
this.status = status; this.status = status;
this.image = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
} }
public int getId() { public int getId() {
@ -74,4 +79,8 @@ public class ChatMessage {
public LinphoneChatMessage.State getStatus() { public LinphoneChatMessage.State getStatus() {
return LinphoneChatMessage.State.fromInt(status); return LinphoneChatMessage.State.fromInt(status);
} }
public Bitmap getImage() {
return image;
}
} }

View file

@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ */
import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -27,6 +28,8 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
/** /**
* @author Sylvain Berfini * @author Sylvain Berfini
@ -56,6 +59,16 @@ public class ChatStorage {
db.update(TABLE_NAME, values, "direction LIKE " + OUTGOING + " AND remoteContact LIKE \"" + to + "\" AND message LIKE \"" + message + "\"", null); db.update(TABLE_NAME, values, "direction LIKE " + OUTGOING + " AND remoteContact LIKE \"" + to + "\" AND message LIKE \"" + message + "\"", null);
} }
public void updateMessageStatus(String to, Bitmap image, int status) {
ContentValues values = new ContentValues();
values.put("status", status);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(CompressFormat.JPEG, 100, baos);
db.update(TABLE_NAME, values, "direction LIKE " + OUTGOING + " AND remoteContact LIKE \"" + to + "\" AND image LIKE \"" + baos.toByteArray() + "\"", null);
}
public int saveMessage(String from, String to, String message) { public int saveMessage(String from, String to, String message) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
if (from.equals("")) { if (from.equals("")) {
@ -76,6 +89,30 @@ public class ChatStorage {
return (int) db.insert(TABLE_NAME, null, values); return (int) db.insert(TABLE_NAME, null, values);
} }
public int saveMessage(String from, String to, Bitmap image) {
ContentValues values = new ContentValues();
if (from.equals("")) {
values.put("localContact", from);
values.put("remoteContact", to);
values.put("direction", OUTGOING);
values.put("read", READ);
values.put("status", LinphoneChatMessage.State.InProgress.toInt());
} else if (to.equals("")) {
values.put("localContact", to);
values.put("remoteContact", from);
values.put("direction", INCOMING);
values.put("read", NOT_READ);
values.put("status", LinphoneChatMessage.State.Idle.toInt());
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(CompressFormat.JPEG, 100, baos);
values.put("image", baos.toByteArray());
values.put("time", System.currentTimeMillis());
return (int) db.insert(TABLE_NAME, null, values);
}
public List<ChatMessage> getMessages(String correspondent) { public List<ChatMessage> getMessages(String correspondent) {
List<ChatMessage> chatMessages = new ArrayList<ChatMessage>(); List<ChatMessage> chatMessages = new ArrayList<ChatMessage>();
@ -88,8 +125,9 @@ public class ChatStorage {
message = c.getString(c.getColumnIndex("message")); message = c.getString(c.getColumnIndex("message"));
timestamp = c.getString(c.getColumnIndex("time")); timestamp = c.getString(c.getColumnIndex("time"));
int status = c.getInt(c.getColumnIndex("status")); int status = c.getInt(c.getColumnIndex("status"));
byte[] rawImage = c.getBlob(c.getColumnIndex("image"));
ChatMessage chatMessage = new ChatMessage(id, message, timestamp, direction == INCOMING, status); ChatMessage chatMessage = new ChatMessage(id, message, rawImage, timestamp, direction == INCOMING, status);
chatMessages.add(chatMessage); chatMessages.add(chatMessage);
} }
@ -128,7 +166,7 @@ public class ChatStorage {
class ChatHelper extends SQLiteOpenHelper { class ChatHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 3; private static final int DATABASE_VERSION = 4;
private static final String DATABASE_NAME = "linphone-android"; private static final String DATABASE_NAME = "linphone-android";
ChatHelper(Context context) { ChatHelper(Context context) {
@ -137,7 +175,7 @@ public class ChatStorage {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT NOT NULL, time NUMERIC, read INTEGER, status INTEGER);"); db.execSQL("CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, localContact TEXT NOT NULL, remoteContact TEXT NOT NULL, direction INTEGER, message TEXT, image BLOB, time NUMERIC, read INTEGER, status INTEGER);");
} }
@Override @Override

View file

@ -36,6 +36,7 @@ import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneCallLog; import org.linphone.core.LinphoneCallLog;
import org.linphone.core.LinphoneCallLog.CallStatus; import org.linphone.core.LinphoneCallLog.CallStatus;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCore.RegistrationState; import org.linphone.core.LinphoneCore.RegistrationState;
import org.linphone.core.LinphoneCoreFactory; import org.linphone.core.LinphoneCoreFactory;
@ -49,6 +50,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -593,8 +595,19 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
} }
@Override @Override
public void onMessageReceived(LinphoneAddress from, String message) { public void onMessageReceived(LinphoneAddress from, LinphoneChatMessage message) {
int id = getChatStorage().saveMessage(from.asStringUriOnly(), "", message); String textMessage = message.getMessage();
String url = message.getExternalBodyUrl();
String notificationText = null;
int id = -1;
if (textMessage != null && textMessage.length() > 0) {
id = getChatStorage().saveMessage(from.asStringUriOnly(), "", textMessage);
notificationText = textMessage;
} else if (url != null && url.length() > 0) {
Bitmap bm = ChatFragment.downloadImage(url);
id = getChatStorage().saveMessage(from.asStringUriOnly(), "", bm);
notificationText = url;
}
ChatFragment chatFragment = ((ChatFragment) messageListenerFragment); ChatFragment chatFragment = ((ChatFragment) messageListenerFragment);
if (messageListenerFragment != null && messageListenerFragment.isVisible() && chatFragment.getSipUri().equals(from.asStringUriOnly())) { if (messageListenerFragment != null && messageListenerFragment.isVisible() && chatFragment.getSipUri().equals(from.asStringUriOnly())) {
@ -604,7 +617,7 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
displayMissedChats(getChatStorage().getUnreadMessageCount()); displayMissedChats(getChatStorage().getUnreadMessageCount());
} }
LinphoneUtils.findUriPictureOfContactAndSetDisplayName(from, getContentResolver()); LinphoneUtils.findUriPictureOfContactAndSetDisplayName(from, getContentResolver());
LinphoneService.instance().displayMessageNotification(from.asStringUriOnly(), from.getDisplayName(), message); LinphoneService.instance().displayMessageNotification(from.asStringUriOnly(), from.getDisplayName(), notificationText);
} }
public void updateMissedChatCount() { public void updateMissedChatCount() {
@ -615,10 +628,18 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
getChatStorage().saveMessage("", to, message); getChatStorage().saveMessage("", to, message);
} }
public void onMessageSent(String to, Bitmap image) {
getChatStorage().saveMessage("", to, image);
}
public void onMessageStateChanged(String to, String message, int newState) { public void onMessageStateChanged(String to, String message, int newState) {
getChatStorage().updateMessageStatus(to, message, newState); getChatStorage().updateMessageStatus(to, message, newState);
} }
public void onMessageStateChanged(String to, Bitmap image, int newState) {
getChatStorage().updateMessageStatus(to, image, newState);
}
@Override @Override
public void onRegistrationStateChanged(RegistrationState state) { public void onRegistrationStateChanged(RegistrationState state) {
if (statusFragment != null) { if (statusFragment != null) {
@ -909,7 +930,6 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(resultCode, requestCode, data);
if (resultCode == Activity.RESULT_FIRST_USER && requestCode == SETTINGS_ACTIVITY) { if (resultCode == Activity.RESULT_FIRST_USER && requestCode == SETTINGS_ACTIVITY) {
if (data.getExtras().getBoolean("Exit", false)) { if (data.getExtras().getBoolean("Exit", false)) {
exit(); exit();
@ -927,6 +947,9 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene
resetClassicMenuLayoutAndGoBackToCallIfStillRunning(); resetClassicMenuLayoutAndGoBackToCallIfStillRunning();
} }
} }
else {
super.onActivityResult(requestCode, resultCode, data);
}
} }
@Override @Override

View file

@ -54,6 +54,7 @@ import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneAuthInfo; import org.linphone.core.LinphoneAuthInfo;
import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneChatRoom;
import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCore.EcCalibratorStatus; import org.linphone.core.LinphoneCore.EcCalibratorStatus;
@ -880,12 +881,16 @@ public final class LinphoneManager implements LinphoneCoreListener {
public void textReceived(LinphoneCore lc, LinphoneChatRoom cr, public void textReceived(LinphoneCore lc, LinphoneChatRoom cr,
LinphoneAddress from, String message) { LinphoneAddress from, String message) {
//deprecated
}
@Override
public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneAddress from, LinphoneChatMessage message) {
for (LinphoneSimpleListener listener : getSimpleListeners(LinphoneActivity.class)) { for (LinphoneSimpleListener listener : getSimpleListeners(LinphoneActivity.class)) {
((LinphoneActivity) listener).onMessageReceived(from, message); ((LinphoneActivity) listener).onMessageReceived(from, message);
} }
} }
public String getLastLcStatusMessage() { public String getLastLcStatusMessage() {
return lastLcStatusMessage; return lastLcStatusMessage;
} }

View file

@ -21,6 +21,7 @@ package org.linphone;
import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneChatMessage;
import org.linphone.core.LinphoneCore.GlobalState; import org.linphone.core.LinphoneCore.GlobalState;
import org.linphone.core.LinphoneCore.RegistrationState; import org.linphone.core.LinphoneCore.RegistrationState;
@ -60,7 +61,7 @@ public interface LinphoneSimpleListener {
} }
public static interface LinphoneOnMessageReceivedListener extends LinphoneSimpleListener { public static interface LinphoneOnMessageReceivedListener extends LinphoneSimpleListener {
void onMessageReceived(LinphoneAddress from, String message); void onMessageReceived(LinphoneAddress from, LinphoneChatMessage message);
} }
public static interface LinphoneOnRegistrationStateChangedListener extends LinphoneSimpleListener { public static interface LinphoneOnRegistrationStateChangedListener extends LinphoneSimpleListener {

View file

@ -5,6 +5,8 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage {
private native void setUserData(long ptr); private native void setUserData(long ptr);
private native String getMessage(long ptr); private native String getMessage(long ptr);
private native LinphoneAddress getPeerAddress(long ptr); private native LinphoneAddress getPeerAddress(long ptr);
private native String getExternalBodyUrl(long ptr);
private native void setExternalBodyUrl(long ptr, String url);
protected LinphoneChatMessageImpl(long aNativePtr) { protected LinphoneChatMessageImpl(long aNativePtr) {
nativePtr = aNativePtr; nativePtr = aNativePtr;
@ -35,4 +37,14 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage {
public LinphoneAddress getPeerAddress() { public LinphoneAddress getPeerAddress() {
return getPeerAddress(nativePtr); return getPeerAddress(nativePtr);
} }
@Override
public String getExternalBodyUrl() {
return getExternalBodyUrl(nativePtr);
}
@Override
public void setExternalBodyUrl(String url) {
setExternalBodyUrl(nativePtr, url);
}
} }

View file

@ -27,6 +27,7 @@ import org.linphone.R;
import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneChatMessage;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.text.Html; import android.text.Html;
import android.text.Spannable; import android.text.Spannable;
@ -81,7 +82,7 @@ public class BubbleChat {
private RelativeLayout view; private RelativeLayout view;
private ImageView statusView; private ImageView statusView;
public BubbleChat(Context context, int id, String message, String time, boolean isIncoming, LinphoneChatMessage.State status, int previousID) { public BubbleChat(Context context, int id, String message, Bitmap image, String time, boolean isIncoming, LinphoneChatMessage.State status, int previousID) {
view = new RelativeLayout(context); view = new RelativeLayout(context);
LayoutParams layoutParams = new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); LayoutParams layoutParams = new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
@ -103,11 +104,13 @@ public class BubbleChat {
layoutParams.setMargins(0, LinphoneUtils.pixelsToDpi(context.getResources(), 10), 0, 0); layoutParams.setMargins(0, LinphoneUtils.pixelsToDpi(context.getResources(), 10), 0, 0);
view.setLayoutParams(layoutParams); view.setLayoutParams(layoutParams);
Spanned text; Spanned text = null;
if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) { if (message != null) {
text = getSmiledText(context, getTextWithHttpLinks(message)); if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) {
} else { text = getSmiledText(context, getTextWithHttpLinks(message));
text = getTextWithHttpLinks(message); } else {
text = getTextWithHttpLinks(message);
}
} }
if (context.getResources().getBoolean(R.bool.display_messages_time_and_status)) { if (context.getResources().getBoolean(R.bool.display_messages_time_and_status)) {
@ -127,8 +130,15 @@ public class BubbleChat {
} }
TextView msgView = (TextView) layout.findViewById(R.id.message); TextView msgView = (TextView) layout.findViewById(R.id.message);
msgView.setText(text); if (message != null && msgView != null) {
msgView.setMovementMethod(LinkMovementMethod.getInstance()); msgView.setText(text);
msgView.setMovementMethod(LinkMovementMethod.getInstance());
}
ImageView imageView = (ImageView) layout.findViewById(R.id.image);
if (image != null && imageView != null) {
imageView.setImageBitmap(image);
}
TextView timeView = (TextView) layout.findViewById(R.id.time); TextView timeView = (TextView) layout.findViewById(R.id.time);
timeView.setText(timestampToHumanDate(context, time)); timeView.setText(timestampToHumanDate(context, time));

@ -1 +1 @@
Subproject commit c2e7592a2a349104ed2aa4121e1e4d44633a0908 Subproject commit 417d5d93e096c20ddf60da895682e412a779b572