diff --git a/res/drawable-xhdpi/chat_attachment.png b/res/drawable-xhdpi/chat_attachment.png new file mode 100644 index 000000000..f46b54e12 Binary files /dev/null and b/res/drawable-xhdpi/chat_attachment.png differ diff --git a/res/layout/chat_bubble.xml b/res/layout/chat_bubble.xml index 181c85711..d26e87504 100644 --- a/res/layout/chat_bubble.xml +++ b/res/layout/chat_bubble.xml @@ -77,13 +77,53 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> - + + + + + + + + + + + italic + + + + \ No newline at end of file diff --git a/src/android/org/linphone/ChatFragment.java b/src/android/org/linphone/ChatFragment.java index 39c179bbc..f049616a3 100644 --- a/src/android/org/linphone/ChatFragment.java +++ b/src/android/org/linphone/ChatFragment.java @@ -893,7 +893,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC //File transfer private void pickImage() { - //TODO : update to use with any file types List cameraIntents = new ArrayList(); Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File file = new File(Environment.getExternalStorageDirectory(), getString(R.string.temp_photo_name_with_date).replace("%s", String.valueOf(System.currentTimeMillis()))); @@ -902,7 +901,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC cameraIntents.add(captureIntent); Intent galleryIntent = new Intent(); - //galleryIntent.setType("image/*"); galleryIntent.setType("*/*"); galleryIntent.setAction(Intent.ACTION_PICK); @@ -1048,9 +1046,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC @Override protected byte[] doInBackground(File... params) { File file = params[0]; - //FileInputStream fileInputStream = null; - //ByteArrayOutputStream stream = new ByteArrayOutputStream(); - //String extension = LinphoneUtils.getExtensionFromFileName(path); byte[] byteArray = new byte[(int) file.length()]; FileInputStream fis = null; @@ -1096,9 +1091,13 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC String fileToUploadPath = null; if (data != null && data.getData() != null) { fileToUploadPath = getRealPathFromURI(data.getData()); + if(fileToUploadPath == null) + fileToUploadPath = data.getData().toString(); } else if (imageToUploadUri != null) { fileToUploadPath = imageToUploadUri.getPath(); } + sendImageMessage(fileToUploadPath, 0); + } else { super.onActivityResult(requestCode, resultCode, data); } @@ -1123,7 +1122,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC newChatConversation = false; initChatRoom(sipUri); - //TODO : if(fileSharedUri != null){ //save SipUri into bundle onSaveInstanceState(getArguments()); @@ -1215,6 +1213,8 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC public LinearLayout imdmLayout; public ImageView imdmIcon; public TextView imdmLabel; + public TextView fileExtensionLabel; + public TextView fileNameLabel; public ViewHolder(View view) { id = view.getId(); @@ -1234,6 +1234,8 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC imdmLayout = (LinearLayout) view.findViewById(R.id.imdmLayout); imdmIcon = (ImageView) view.findViewById(R.id.imdmIcon); imdmLabel = (TextView) view.findViewById(R.id.imdmText); + fileExtensionLabel = (TextView) view.findViewById(R.id.file_extension); + fileNameLabel = (TextView) view.findViewById(R.id.file_name); } @Override @@ -1356,6 +1358,8 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC holder.delete.setVisibility(View.GONE); holder.messageText.setVisibility(View.GONE); holder.messageImage.setVisibility(View.GONE); + holder.fileExtensionLabel.setVisibility(View.GONE); + holder.fileNameLabel.setVisibility(View.GONE); holder.fileTransferLayout.setVisibility(View.GONE); holder.fileTransferProgressBar.setProgress(0); holder.fileTransferAction.setEnabled(true); @@ -1415,9 +1419,14 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC holder.imdmLayout.setVisibility(View.INVISIBLE); } + + + + if (externalBodyUrl != null || fileTransferContent != null) { String appData = message.getAppData(); + if (message.isOutgoing() && appData != null) { holder.messageImage.setVisibility(View.VISIBLE); if (!sameMessage) { @@ -1457,6 +1466,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC loadBitmap(appData, holder.messageImage); holder.messageImage.setTag(message.getAppData()); } + } } } @@ -1494,10 +1504,10 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC public void onClick(View v) { if (context.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context.getPackageName()) == PackageManager.PERMISSION_GRANTED) { v.setEnabled(false); - String extension = message.getFileTransferInformation().getSubtype(); - String filename = context.getString(R.string.temp_photo_name_with_date).replace("%s", String.valueOf(System.currentTimeMillis())) + "." + extension; + String filename = message.getFileTransferInformation().getName(); + String filename2 = context.getString(R.string.temp_photo_name_with_date).replace("%s", String.valueOf(System.currentTimeMillis())) ; //+ "." + extension; File file = new File(Environment.getExternalStorageDirectory(), filename); - message.setAppData(filename); + message.setAppData(file.getPath()); LinphoneManager.getInstance().addDownloadMessagePending(message); message.setListener(LinphoneManager.getInstance()); message.setFileTransferFilepath(file.getPath()); @@ -1536,6 +1546,51 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC } holder.bubbleLayout.setLayoutParams(layoutParams); + if(message.getAppData() != null && holder.fileTransferLayout.getVisibility() != View.VISIBLE){ + if(LinphoneUtils.isExtensionImage(message.getAppData())){ + holder.fileExtensionLabel.setVisibility(View.GONE); + holder.fileNameLabel.setVisibility(View.GONE); + }else { + String extension = (LinphoneUtils.getExtensionFromFileName(message.getAppData())); + if(extension != null) + extension = extension.toUpperCase(); + else + extension = "FILE"; + + if (extension.length() > 4) + extension = extension.substring(0, 3); + + //holder.messageImage.setImageResource(R.drawable.chat_attachment); + holder.fileExtensionLabel.setText(extension); + holder.fileExtensionLabel.setVisibility(View.VISIBLE); + //holder.fileExtensionLabel.setTag(message.getAppData()); + holder.fileNameLabel.setText(LinphoneUtils.getNameFromFilePath(message.getAppData())); + holder.fileNameLabel.setVisibility(View.VISIBLE); + holder.fileExtensionLabel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW); + File file = null; + Uri contentUri = null; + String imageUri = (String)holder.messageImage.getTag(); + if (imageUri.startsWith("file://")) { + imageUri = imageUri.substring("file://".length()); + file = new File(imageUri); + contentUri = FileProvider.getUriForFile(getActivity(), "org.linphone.provider", file); + } else if (imageUri.startsWith("content://")) { + contentUri = Uri.parse(imageUri); + } else { + file = new File(imageUri); + contentUri = FileProvider.getUriForFile(getActivity(), "org.linphone.provider", file); + } + intent.setDataAndType(contentUri, "*/*"); + intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + context.startActivity(intent); + } + }); + } + } + if (isEditMode) { holder.delete.setVisibility(View.VISIBLE); holder.delete.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @@ -1635,7 +1690,7 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC if(LinphoneUtils.isExtensionImage(path)) defaultBitmap = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.chat_picture_over); else - defaultBitmap = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.chat_attachment_over); + defaultBitmap = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.chat_attachment); BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncBitmap asyncBitmap = new AsyncBitmap(context.getResources(), defaultBitmap, task); @@ -1660,7 +1715,6 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC path = params[0]; Bitmap bm = null; Bitmap thumbnail = null; - if(LinphoneUtils.isExtensionImage(path)) { if (path.startsWith("content")) { try { @@ -1723,17 +1777,17 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW); - + File file = null; Uri contentUri = null; String imageUri = (String)v.getTag(); if (imageUri.startsWith("file://")) { imageUri = imageUri.substring("file://".length()); - File file = new File(imageUri); + file = new File(imageUri); contentUri = FileProvider.getUriForFile(getActivity(), "org.linphone.provider", file); } else if (imageUri.startsWith("content://")) { contentUri = Uri.parse(imageUri); } else { - File file = new File(imageUri); + file = new File(imageUri); contentUri = FileProvider.getUriForFile(getActivity(), "org.linphone.provider", file); } intent.setDataAndType(contentUri, "*/*"); diff --git a/src/android/org/linphone/ContactsManager.java b/src/android/org/linphone/ContactsManager.java index 7bd05b680..ee5830c3e 100644 --- a/src/android/org/linphone/ContactsManager.java +++ b/src/android/org/linphone/ContactsManager.java @@ -299,7 +299,6 @@ public class ContactsManager extends ContentObserver { } } } - Log.e(" =====>>>> ContacsManager - fetchContactsSync Ended"); } long timeElapsed = (new Date()).getTime() - contactsTime.getTime(); diff --git a/src/android/org/linphone/LinphoneActivity.java b/src/android/org/linphone/LinphoneActivity.java index b71406261..a1d3e0436 100644 --- a/src/android/org/linphone/LinphoneActivity.java +++ b/src/android/org/linphone/LinphoneActivity.java @@ -647,14 +647,12 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick return; } - Log.e(" ===>>> displayChat : message = "+message+" - fileUri = "+fileUri); String pictureUri = null; String thumbnailUri = null; String displayName = null; LinphoneAddress lAddress = null; if(sipUri != null) { - Log.e(" ===>>> displayChat : sipUri = "+ sipUri); try { lAddress = LinphoneManager.getLc().interpretUrl(sipUri); } catch (LinphoneCoreException e) { @@ -692,7 +690,6 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick changeCurrentFragment(FragmentsAvailable.CHAT, extras); } } else { - Log.e(" ===>>> displayChat : currentFragment != Chat"); if(isTablet()){ changeCurrentFragment(FragmentsAvailable.CHAT_LIST, null); //displayChat(sipUri, message, fileUri); @@ -1326,7 +1323,6 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick } else { if (!ContactsManager.getInstance().contactsFetchedOnce()) { ContactsManager.getInstance().enableContactsAccess(); - Log.e(" ====>>>> LinphoneActivity - ContactsManager.getInstance().fetchContactsAsync() 2 !!!"); ContactsManager.getInstance().fetchContactsAsync(); } } diff --git a/src/android/org/linphone/LinphoneLauncherActivity.java b/src/android/org/linphone/LinphoneLauncherActivity.java index e201eff4e..174e473f8 100644 --- a/src/android/org/linphone/LinphoneLauncherActivity.java +++ b/src/android/org/linphone/LinphoneLauncherActivity.java @@ -19,14 +19,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. package org.linphone; import android.app.Activity; -import android.content.CursorLoader; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.provider.MediaStore; import org.linphone.assistant.RemoteProvisioningActivity; import org.linphone.mediastream.Log; @@ -127,9 +124,13 @@ public class LinphoneLauncherActivity extends Activity { stringFileShared = intent.getStringExtra(Intent.EXTRA_STREAM); }else { fileUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); - stringFileShared = getRealPathFromURI(fileUri); + stringFileShared = LinphoneUtils.getRealPathFromURI(getBaseContext(), fileUri); if(stringFileShared == null) - stringFileShared = fileUri.getPath(); + if(fileUri.getPath().contains("/0/1/mediakey:/local")) + stringFileShared = LinphoneUtils.getFilePath(getBaseContext(), fileUri); + else + stringFileShared = fileUri.getPath(); + } newIntent.putExtra("fileShared", stringFileShared); } @@ -160,18 +161,7 @@ public class LinphoneLauncherActivity extends Activity { }, 1000); } - public String getRealPathFromURI(Uri contentUri) { - String[] proj = {MediaStore.Images.Media.DATA}; - CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null); - Cursor cursor = loader.loadInBackground(); - if (cursor != null && cursor.moveToFirst()) { - int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - String result = cursor.getString(column_index); - cursor.close(); - return result; - } - return null; - } + private class ServiceWaitThread extends Thread { public void run() { diff --git a/src/android/org/linphone/LinphoneUtils.java b/src/android/org/linphone/LinphoneUtils.java index 4b1e1efbd..6044426c3 100644 --- a/src/android/org/linphone/LinphoneUtils.java +++ b/src/android/org/linphone/LinphoneUtils.java @@ -23,8 +23,10 @@ import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.CursorLoader; import android.content.Intent; import android.content.res.Resources; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.ConnectivityManager; @@ -32,9 +34,11 @@ import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; @@ -60,6 +64,8 @@ import org.linphone.mediastream.video.capture.hwconf.Hacks; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -73,6 +79,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.ZipEntry; @@ -472,6 +479,16 @@ public final class LinphoneUtils { } } + public static String getNameFromFilePath(String filePath) { + String name = filePath; + int i = filePath.lastIndexOf('/'); + if (i > 0) { + name = filePath.substring(i+1); + } + return name; + } + + public static String getExtensionFromFileName(String fileName) { String extension = null; int i = fileName.lastIndexOf('.'); @@ -713,5 +730,148 @@ public final class LinphoneUtils { .show(); } } + + + /************************************************************************************************ + * Picasa/Photos management workaround * + ************************************************************************************************/ + + public static String getFilePath(final Context context, final Uri uri) { + + // Google photo uri example + // content://com.google.android.apps.photos.contentprovider/0/1/mediakey%3A%2FAF1QipMObgoK_wDY66gu0QkMAi/ORIGINAL/NONE/114919 + + if ("content".equalsIgnoreCase(uri.getScheme())) { + String result = getDataColumn(context, uri, null, null); // + if (TextUtils.isEmpty(result)) + if (uri.getAuthority().contains("com.google.android")) { + try { + File localFile = createImageFile(context, null); + FileInputStream remoteFile = getSourceStream(context, uri); + if(copyToFile(remoteFile, localFile)) + result = localFile.getAbsolutePath(); + remoteFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Copy data from a source stream to destFile. + * Return true if succeed, return false if failed. + */ + private static boolean copyToFile(InputStream inputStream, File destFile) { + if (inputStream == null || destFile == null) return false; + try { + OutputStream out = new FileOutputStream(destFile); + try { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) >= 0) { + out.write(buffer, 0, bytesRead); + } + } finally { + out.close(); + } + return true; + } catch (IOException e) { + return false; + } + } + + public static String getTimestamp() { + try { + return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ROOT).format(new Date()); + } catch (RuntimeException e) { + return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + } + } + + public static File createImageFile(Context context, String imageFileName) throws IOException { + if (TextUtils.isEmpty(imageFileName)) + imageFileName = getTimestamp()+".JPEG"; // make random filename if you want. + + final File root; + imageFileName = imageFileName; + root = context.getExternalCacheDir(); + + if (root != null && !root.exists()) + root.mkdirs(); + return new File(root, imageFileName); + } + + + public static FileInputStream getSourceStream(Context context, Uri u) throws FileNotFoundException { + FileInputStream out = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + ParcelFileDescriptor parcelFileDescriptor = + context.getContentResolver().openFileDescriptor(u, "r"); + FileDescriptor fileDescriptor = null; + if (parcelFileDescriptor != null) { + fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + out = new FileInputStream(fileDescriptor); + } + } else { + out = (FileInputStream) context.getContentResolver().openInputStream(u); + } + return out; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + + return null; + } + + public static String getRealPathFromURI(Context context, Uri contentUri) { + String[] proj = {MediaStore.Images.Media.DATA}; + CursorLoader loader = new CursorLoader(context, contentUri, proj, null, null, null); + Cursor cursor = loader.loadInBackground(); + if (cursor != null && cursor.moveToFirst()) { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + String result = cursor.getString(column_index); + cursor.close(); + return result; + } + return null; + } + }