diff --git a/Makefile b/Makefile index 09345fd3b..6272cf8a0 100644 --- a/Makefile +++ b/Makefile @@ -74,3 +74,4 @@ clean: ant clean .PHONY: clean + diff --git a/res/layout/chat_bubble_alt_incoming.xml b/res/layout/chat_bubble_alt_incoming.xml index 8080c0c0f..de8d7bb5b 100644 --- a/res/layout/chat_bubble_alt_incoming.xml +++ b/res/layout/chat_bubble_alt_incoming.xml @@ -11,6 +11,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" /> + + + + \ No newline at end of file diff --git a/res/layout/chat_bubble_incoming.xml b/res/layout/chat_bubble_incoming.xml index cb18f054e..91d3fed51 100644 --- a/res/layout/chat_bubble_incoming.xml +++ b/res/layout/chat_bubble_incoming.xml @@ -12,6 +12,12 @@ android:textColor="@android:color/black" android:layout_width="wrap_content" android:layout_height="wrap_content" /> + + + + sip.linphone.org https://www.linphone.org/wizard.php + https://www.linphone.org:444/upload.php true diff --git a/src/org/linphone/ChatFragment.java b/src/org/linphone/ChatFragment.java index 666d77aff..8a40ded69 100644 --- a/src/org/linphone/ChatFragment.java +++ b/src/org/linphone/ChatFragment.java @@ -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 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 org.apache.http.util.ByteArrayBuffer; import org.linphone.LinphoneSimpleListener.LinphoneOnMessageReceivedListener; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneChatMessage.State; import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneCore; +import org.linphone.core.Log; import org.linphone.ui.AvatarWithShadow; 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.os.Bundle; import android.os.Handler; +import android.provider.MediaStore; import android.support.v4.app.Fragment; +import android.support.v4.content.CursorLoader; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; @@ -49,6 +69,7 @@ import android.widget.TextView; * @author Sylvain Berfini */ public class ChatFragment extends Fragment implements OnClickListener, LinphoneOnMessageReceivedListener, LinphoneChatMessage.StateListener { + private static final int ADD_PHOTO = 0; private LinphoneChatRoom chatRoom; private View view; private String sipUri; @@ -60,6 +81,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO private int previousMessageID; private Handler mHandler = new Handler(); private BubbleChat lastSentMessageBubble; + private ProgressDialog mProgressDialog; @Override 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); 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); sendMessage.setOnClickListener(this); @@ -96,7 +124,11 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO previousMessageID = -1; ChatStorage chatStorage = LinphoneActivity.instance().getChatStorage(); 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()); } LinphoneActivity.instance().updateMissedChatCount(); @@ -132,7 +164,22 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO mHandler.post(new Runnable() { @Override 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) { lastSentMessageBubble = bubble; } @@ -173,6 +220,10 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO @Override public void onClick(View v) { + sendTextMessage(); + } + + private void sendTextMessage() { if (chatRoom != null && message != null && message.getText().length() > 0) { String messageToSend = message.getText().toString(); 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() { mHandler.postDelayed(new Runnable() { @Override @@ -199,10 +264,15 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO } @Override - public void onMessageReceived(LinphoneAddress from, String message) { + public void onMessageReceived(LinphoneAddress from, LinphoneChatMessage message) { if (from.asStringUriOnly().equals(sipUri)) { 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(); } } @@ -210,12 +280,17 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO @Override public void onLinphoneChatMessageStateChanged(LinphoneChatMessage msg, State state) { final String finalMessage = msg.getMessage(); + final Bitmap finalImage = downloadImage(msg.getMessage()); final State finalState = state; if (LinphoneActivity.isInstanciated() && state != State.InProgress) { mHandler.post(new Runnable() { @Override 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); } }); @@ -225,4 +300,145 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneO public String getSipUri() { 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); + } } diff --git a/src/org/linphone/ChatListFragment.java b/src/org/linphone/ChatListFragment.java index d4b3491df..b8da09b73 100644 --- a/src/org/linphone/ChatListFragment.java +++ b/src/org/linphone/ChatListFragment.java @@ -89,6 +89,9 @@ public class ChatListFragment extends Fragment implements OnClickListener, OnIte @Override public boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + if (info == null || info.targetView == null) { + return false; + } String sipUri = (String) info.targetView.getTag(); LinphoneActivity.instance().removeFromChatList(sipUri); diff --git a/src/org/linphone/ChatMessage.java b/src/org/linphone/ChatMessage.java index cbd8b30fa..b089ca3b4 100644 --- a/src/org/linphone/ChatMessage.java +++ b/src/org/linphone/ChatMessage.java @@ -1,6 +1,9 @@ package org.linphone; import org.linphone.core.LinphoneChatMessage; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; /* ChatMessage.java Copyright (C) 2012 Belledonne Communications, Grenoble, France @@ -29,14 +32,16 @@ public class ChatMessage { private boolean incoming; private int status; 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(); this.id = id; this.message = message; this.timestamp = timestamp; this.incoming = incoming; this.status = status; + this.image = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); } public int getId() { @@ -74,4 +79,8 @@ public class ChatMessage { public LinphoneChatMessage.State getStatus() { return LinphoneChatMessage.State.fromInt(status); } + + public Bitmap getImage() { + return image; + } } diff --git a/src/org/linphone/ChatStorage.java b/src/org/linphone/ChatStorage.java index e50cfd5d0..d6e856b88 100644 --- a/src/org/linphone/ChatStorage.java +++ b/src/org/linphone/ChatStorage.java @@ -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 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -27,6 +28,8 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; /** * @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); } + 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) { ContentValues values = new ContentValues(); if (from.equals("")) { @@ -76,6 +89,30 @@ public class ChatStorage { 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 getMessages(String correspondent) { List chatMessages = new ArrayList(); @@ -88,8 +125,9 @@ public class ChatStorage { message = c.getString(c.getColumnIndex("message")); timestamp = c.getString(c.getColumnIndex("time")); 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); } @@ -128,7 +166,7 @@ public class ChatStorage { 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"; ChatHelper(Context context) { @@ -137,7 +175,7 @@ public class ChatStorage { @Override 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 diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index 06c8a907d..d390fd2b9 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -36,6 +36,7 @@ import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCallLog; import org.linphone.core.LinphoneCallLog.CallStatus; +import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore.RegistrationState; import org.linphone.core.LinphoneCoreFactory; @@ -49,6 +50,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; +import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -593,8 +595,19 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene } @Override - public void onMessageReceived(LinphoneAddress from, String message) { - int id = getChatStorage().saveMessage(from.asStringUriOnly(), "", message); + public void onMessageReceived(LinphoneAddress from, LinphoneChatMessage 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); if (messageListenerFragment != null && messageListenerFragment.isVisible() && chatFragment.getSipUri().equals(from.asStringUriOnly())) { @@ -604,7 +617,7 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene displayMissedChats(getChatStorage().getUnreadMessageCount()); } LinphoneUtils.findUriPictureOfContactAndSetDisplayName(from, getContentResolver()); - LinphoneService.instance().displayMessageNotification(from.asStringUriOnly(), from.getDisplayName(), message); + LinphoneService.instance().displayMessageNotification(from.asStringUriOnly(), from.getDisplayName(), notificationText); } public void updateMissedChatCount() { @@ -615,9 +628,17 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene getChatStorage().saveMessage("", to, message); } + public void onMessageSent(String to, Bitmap image) { + getChatStorage().saveMessage("", to, image); + } + public void onMessageStateChanged(String to, String message, int newState) { getChatStorage().updateMessageStatus(to, message, newState); } + + public void onMessageStateChanged(String to, Bitmap image, int newState) { + getChatStorage().updateMessageStatus(to, image, newState); + } @Override public void onRegistrationStateChanged(RegistrationState state) { @@ -908,8 +929,7 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(resultCode, requestCode, data); + protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_FIRST_USER && requestCode == SETTINGS_ACTIVITY) { if (data.getExtras().getBoolean("Exit", false)) { exit(); @@ -927,6 +947,9 @@ public class LinphoneActivity extends FragmentActivity implements OnClickListene resetClassicMenuLayoutAndGoBackToCallIfStillRunning(); } } + else { + super.onActivityResult(requestCode, resultCode, data); + } } @Override diff --git a/src/org/linphone/LinphoneManager.java b/src/org/linphone/LinphoneManager.java index a36b0606f..c1451b6b6 100644 --- a/src/org/linphone/LinphoneManager.java +++ b/src/org/linphone/LinphoneManager.java @@ -54,6 +54,7 @@ import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAuthInfo; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; +import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore.EcCalibratorStatus; @@ -880,12 +881,16 @@ public final class LinphoneManager implements LinphoneCoreListener { public void textReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneAddress from, String message) { + //deprecated + } + + @Override + public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneAddress from, LinphoneChatMessage message) { for (LinphoneSimpleListener listener : getSimpleListeners(LinphoneActivity.class)) { ((LinphoneActivity) listener).onMessageReceived(from, message); } } - public String getLastLcStatusMessage() { return lastLcStatusMessage; } diff --git a/src/org/linphone/LinphoneSimpleListener.java b/src/org/linphone/LinphoneSimpleListener.java index 75106a99c..4af9f1027 100644 --- a/src/org/linphone/LinphoneSimpleListener.java +++ b/src/org/linphone/LinphoneSimpleListener.java @@ -21,6 +21,7 @@ package org.linphone; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; +import org.linphone.core.LinphoneChatMessage; import org.linphone.core.LinphoneCore.GlobalState; import org.linphone.core.LinphoneCore.RegistrationState; @@ -60,7 +61,7 @@ public interface 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 { diff --git a/src/org/linphone/core/LinphoneChatMessageImpl.java b/src/org/linphone/core/LinphoneChatMessageImpl.java index a6c54e10d..a93aae85f 100644 --- a/src/org/linphone/core/LinphoneChatMessageImpl.java +++ b/src/org/linphone/core/LinphoneChatMessageImpl.java @@ -5,6 +5,8 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage { private native void setUserData(long ptr); private native String getMessage(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) { nativePtr = aNativePtr; @@ -35,4 +37,14 @@ public class LinphoneChatMessageImpl implements LinphoneChatMessage { public LinphoneAddress getPeerAddress() { return getPeerAddress(nativePtr); } + + @Override + public String getExternalBodyUrl() { + return getExternalBodyUrl(nativePtr); + } + + @Override + public void setExternalBodyUrl(String url) { + setExternalBodyUrl(nativePtr, url); + } } diff --git a/src/org/linphone/ui/BubbleChat.java b/src/org/linphone/ui/BubbleChat.java index 70883c7e0..5714ca346 100644 --- a/src/org/linphone/ui/BubbleChat.java +++ b/src/org/linphone/ui/BubbleChat.java @@ -27,6 +27,7 @@ import org.linphone.R; import org.linphone.core.LinphoneChatMessage; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Color; import android.text.Html; import android.text.Spannable; @@ -81,7 +82,7 @@ public class BubbleChat { private RelativeLayout view; 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); 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); view.setLayoutParams(layoutParams); - Spanned text; - if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) { - text = getSmiledText(context, getTextWithHttpLinks(message)); - } else { - text = getTextWithHttpLinks(message); + Spanned text = null; + if (message != null) { + if (context.getResources().getBoolean(R.bool.emoticons_in_messages)) { + text = getSmiledText(context, getTextWithHttpLinks(message)); + } else { + text = getTextWithHttpLinks(message); + } } 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); - msgView.setText(text); - msgView.setMovementMethod(LinkMovementMethod.getInstance()); + if (message != null && msgView != null) { + 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); timeView.setText(timestampToHumanDate(context, time)); diff --git a/submodules/linphone b/submodules/linphone index c2e7592a2..417d5d93e 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit c2e7592a2a349104ed2aa4121e1e4d44633a0908 +Subproject commit 417d5d93e096c20ddf60da895682e412a779b572