Skip to content

Commit

Permalink
Merge branch 'devel' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
aforge committed Feb 25, 2023
2 parents 54a50cb + 612b433 commit 310aa5a
Show file tree
Hide file tree
Showing 17 changed files with 377 additions and 144 deletions.
48 changes: 22 additions & 26 deletions app/src/main/java/co/tinode/tindroid/AttachmentHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public class AttachmentHandler extends Worker {

private static final String TAG = "AttachmentHandler";

private final List<LargeFileHelper> mUploaders = Collections.synchronizedList(new ArrayList<>());
private LargeFileHelper mUploader = null;

public AttachmentHandler(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
Expand Down Expand Up @@ -452,10 +452,8 @@ public ListenableWorker.Result doWork() {

@Override
public void onStopped() {
synchronized (mUploaders) {
for (LargeFileHelper lfh : mUploaders) {
lfh.cancel();
}
if (mUploader != null) {
mUploader.cancel();
}

super.onStopped();
Expand Down Expand Up @@ -608,46 +606,42 @@ private ListenableWorker.Result uploadMessageAttachment(final Context context, f
.putLong(ARG_FILE_SIZE, uploadDetails.fileSize).build());

// Upload results.
//noinspection unchecked
PromisedReply<ServerMessage>[] uploadResults = (PromisedReply<ServerMessage>[]) new PromisedReply[2];
// noinspection unchecked
PromisedReply<ServerMessage>[] uploadPromises = (PromisedReply<ServerMessage>[]) new PromisedReply[2];

// Upload large media.
LargeFileHelper uploader = Cache.getTinode().getLargeFileHelper();
mUploaders.add(uploader);
uploadResults[0] = uploader.uploadAsync(is, uploadDetails.fileName,
mUploader = Cache.getTinode().getLargeFileHelper();
uploadPromises[0] = mUploader.uploadAsync(is, uploadDetails.fileName,
uploadDetails.mimeType, uploadDetails.fileSize,
topicName, (progress, size) -> setProgressAsync(new Data.Builder()
.putAll(result.build())
.putLong(ARG_PROGRESS, progress)
.putLong(ARG_FILE_SIZE, size)
.build()));
mUploaders.remove(uploader);
if (uploader.isCanceled()) {
throw new CancellationException();
}

// Optionally upload video poster.
if (uploadDetails.previewRef != null) {
LargeFileHelper posterUploader = Cache.getTinode().getLargeFileHelper();
mUploaders.add(posterUploader);
uploadResults[1] = uploader.uploadAsync(is, uploadDetails.fileName, uploadDetails.mimeType,
uploadDetails.fileSize, topicName, null);
mUploaders.remove(posterUploader);
// Don't care if it's cancelled.
uploadPromises[1] = mUploader.uploadAsync(new ByteArrayInputStream(uploadDetails.previewBits),
"poster", uploadDetails.previewMime, uploadDetails.previewSize,
topicName, null);
// ByteArrayInputStream:close() is a noop. No need to call close().
} else {
uploadResults[1] = null;
uploadPromises[1] = null;
}

ServerMessage[] msgs = new ServerMessage[2];
try {
// Wait for uploads to finish. This is a long-running blocking call.
Object[] objs = PromisedReply.allOf(uploadResults).getResult();
Object[] objs = PromisedReply.allOf(uploadPromises).getResult();
msgs[0] = (ServerMessage) objs[0];
msgs[1] = (ServerMessage) objs[1];
} catch (Exception ex) {
throw new CancellationException();
store.msgFailed(topic, msgId);
throw ex;
}

mUploader = null;

success = msgs[0] != null && msgs[0].ctrl != null && msgs[0].ctrl.code == 200;

if (success) {
Expand All @@ -673,7 +667,7 @@ private ListenableWorker.Result uploadMessageAttachment(final Context context, f

case VIDEO:
String posterUrl = null;
if (msgs[1] != null && msgs[1].ctrl != null && msgs[1].ctrl.code == 200){
if (msgs[1] != null && msgs[1].ctrl != null && msgs[1].ctrl.code == 200) {
posterUrl = msgs[1].ctrl.getStringParam("url", null);
}
content = draftyVideo(args.getString(ARG_IMAGE_CAPTION), uploadDetails.mimeType,
Expand All @@ -700,7 +694,7 @@ private ListenableWorker.Result uploadMessageAttachment(final Context context, f
} catch (CancellationException ignored) {
result.putString(ARG_ERROR, context.getString(R.string.canceled));
Log.d(TAG, "Upload cancelled");
} catch (IOException | SecurityException | IllegalArgumentException ex) {
} catch (Exception ex) {
result.putString(ARG_ERROR, ex.getMessage());
Log.w(TAG, "Failed to upload file", ex);
} finally {
Expand Down Expand Up @@ -958,8 +952,10 @@ static class UploadDetails {
String valueRef;
byte[] valueBits;

// Video poster.
String previewFileName;
int previewSize;
String previewRef;
byte[] previewBits;
int previewSize;
}
}
19 changes: 10 additions & 9 deletions app/src/main/java/co/tinode/tindroid/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,16 @@ public void onDataMessage(MsgServerData data) {
}

int effectiveSeq = UiUtils.parseSeqReference(data.getStringHeader("replace"));
if (effectiveSeq > 0) {
// Check if we have a later version of the message (which means the call
// has been not yet either accepted or finished).
Storage.Message msg = topic.getMessage(effectiveSeq);
if (msg != null) {
webrtc = msg.getStringHeader("webrtc");
if (webrtc != null && MsgServerData.parseWebRTC(webrtc) != callState) {
return;
}
if (effectiveSeq <= 0) {
effectiveSeq = data.seq;
}
// Check if we have a later version of the message (which means the call
// has been not yet either accepted or finished).
Storage.Message msg = topic.getMessage(effectiveSeq);
if (msg != null) {
webrtc = msg.getStringHeader("webrtc");
if (webrtc != null && MsgServerData.parseWebRTC(webrtc) != callState) {
return;
}
}

Expand Down
17 changes: 11 additions & 6 deletions app/src/main/java/co/tinode/tindroid/CallFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,

mLayout = v.findViewById(R.id.callMainLayout);

AudioManager audioManager = (AudioManager) inflater.getContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_CALL);
audioManager.setSpeakerphoneOn(true);

// Button click handlers: speakerphone on/off, mute/unmute, video/audio-only, hang up.
mToggleSpeakerphoneBtn.setOnClickListener(v0 ->
toggleSpeakerphone((FloatingActionButton) v0));
Expand Down Expand Up @@ -206,6 +202,10 @@ public void onViewCreated(@NonNull View view, Bundle savedInstance) {
}

mAudioOnly = args.getBoolean(Const.INTENT_EXTRA_CALL_AUDIO_ONLY);
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setSpeakerphoneOn(!mAudioOnly);
mToggleSpeakerphoneBtn.setImageResource(mAudioOnly ? R.drawable.ic_volume_off : R.drawable.ic_volume_up);

if (!mTopic.isAttached()) {
mTopic.setListener(new Topic.Listener<VxCard, PrivateType, VxCard, PrivateType>() {
Expand Down Expand Up @@ -254,6 +254,7 @@ public void onDestroyView() {
if (ctx != null) {
AudioManager audioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
if (audioManager != null) {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.setMicrophoneMute(false);
audioManager.setSpeakerphoneOn(false);
}
Expand Down Expand Up @@ -641,8 +642,12 @@ private void handleSendAnswer(SessionDescription sd) {
}

private void sendToPeer(String msg) {
mDataChannel.send(new DataChannel.Buffer(
ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)), false));
if (mDataChannel != null) {
mDataChannel.send(new DataChannel.Buffer(
ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)), false));
} else {
Log.w(TAG, "Data channel is null. Peer will not receive the message: '" + msg + "'");
}
}

// Data channel observer for receiving video mute/unmute events.
Expand Down
165 changes: 86 additions & 79 deletions app/src/main/java/co/tinode/tindroid/ImageViewFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -246,85 +247,97 @@ public void onGlobalLayout() {
private void loadImage(final Activity activity, final Bundle args) {
// Check if the bitmap is directly attached.
int length = 0;
Bitmap bmp = args.getParcelable(AttachmentHandler.ARG_SRC_BITMAP);
if (bmp == null) {
Bitmap preview = args.getParcelable(AttachmentHandler.ARG_SRC_BITMAP);
if (preview == null) {
// Check if bitmap is attached as an array of bytes (received).
byte[] bits = args.getByteArray(AttachmentHandler.ARG_SRC_BYTES);
if (bits != null) {
bmp = BitmapFactory.decodeByteArray(bits, 0, bits.length);
preview = BitmapFactory.decodeByteArray(bits, 0, bits.length);
length = bits.length;
}
}

if (bmp == null) {
// Preview large image before sending.
Uri uri = args.getParcelable(AttachmentHandler.ARG_LOCAL_URI);
if (uri != null) {
// Local image.
final ContentResolver resolver = activity.getContentResolver();
// Resize image to ensure it's under the maximum in-band size.
try {
InputStream is = resolver.openInputStream(uri);
if (is != null) {
bmp = BitmapFactory.decodeStream(is, null, null);
is.close();
}
// Make sure the bitmap is properly oriented in preview.
is = resolver.openInputStream(uri);
if (is != null) {
ExifInterface exif = new ExifInterface(is);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
if (bmp != null) {
bmp = UiUtils.rotateBitmap(bmp, orientation);
}
is.close();
// Preview large image before sending.
Bitmap bmp = null;
Uri uri = args.getParcelable(AttachmentHandler.ARG_LOCAL_URI);
if (uri != null) {
// Local image.
final ContentResolver resolver = activity.getContentResolver();
// Resize image to ensure it's under the maximum in-band size.
try {
InputStream is = resolver.openInputStream(uri);
if (is != null) {
bmp = BitmapFactory.decodeStream(is, null, null);
is.close();
}
// Make sure the bitmap is properly oriented in preview.
is = resolver.openInputStream(uri);
if (is != null) {
ExifInterface exif = new ExifInterface(is);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
if (bmp != null) {
bmp = UiUtils.rotateBitmap(bmp, orientation);
}
} catch (IOException ex) {
Log.i(TAG, "Failed to read image from " + uri, ex);
is.close();
}
} else {
// Remote image.
final Uri ref = args.getParcelable(AttachmentHandler.ARG_REMOTE_URI);
if (ref != null) {
mRemoteState = RemoteState.LOADING;
Picasso.get().load(ref)
.error(R.drawable.ic_broken_image)
.into(mImageView, new Callback() {
@Override
public void onSuccess() {
mRemoteState = RemoteState.SUCCESS;

Activity activity = getActivity();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
return;
}

final Bitmap bmp = ((BitmapDrawable) mImageView.getDrawable()).getBitmap();
mInitialRect = new RectF(0, 0, bmp.getWidth(), bmp.getHeight());
mWorkingRect = new RectF(mInitialRect);
mMatrix.setRectToRect(mInitialRect, mScreenRect, Matrix.ScaleToFit.CENTER);
mWorkingMatrix = new Matrix(mMatrix);
mImageView.setImageMatrix(mMatrix);
mImageView.setScaleType(ImageView.ScaleType.MATRIX);
mImageView.enableOverlay(false);

activity.findViewById(R.id.metaPanel).setVisibility(View.VISIBLE);
setupImagePostview(activity, args, bmp.getByteCount());
}

@Override
public void onError(Exception e) {
mRemoteState = RemoteState.FAILED;
Log.i(TAG, "Failed to fetch image: " + e.getMessage() + " (" + ref + ")");
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
((MenuHost) activity).removeMenuProvider(ImageViewFragment.this);
}
});
} catch (IOException ex) {
Log.i(TAG, "Failed to read image from " + uri, ex);
}
} else {
// Remote image.
final Uri ref = args.getParcelable(AttachmentHandler.ARG_REMOTE_URI);
if (ref != null) {
mRemoteState = RemoteState.LOADING;
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
RequestCreator rc = Picasso.get().load(ref)
.error(R.drawable.ic_broken_image);
if (preview != null) {
rc = rc.placeholder(new BitmapDrawable(getResources(), preview));
// No need to show preview separately from Picasso.
preview = null;
} else {
rc = rc.placeholder(R.drawable.ic_image);
}

rc.into(mImageView, new Callback() {
@Override
public void onSuccess() {
mRemoteState = RemoteState.SUCCESS;
Log.i(TAG, "Remote load: success" + ref);
Activity activity = getActivity();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
return;
}

final Bitmap bmp = ((BitmapDrawable) mImageView.getDrawable()).getBitmap();
mInitialRect = new RectF(0, 0, bmp.getWidth(), bmp.getHeight());
mWorkingRect = new RectF(mInitialRect);
mMatrix.setRectToRect(mInitialRect, mScreenRect, Matrix.ScaleToFit.CENTER);
mWorkingMatrix = new Matrix(mMatrix);
mImageView.setImageMatrix(mMatrix);
mImageView.setScaleType(ImageView.ScaleType.MATRIX);
mImageView.enableOverlay(false);

activity.findViewById(R.id.metaPanel).setVisibility(View.VISIBLE);
setupImagePostview(activity, args, bmp.getByteCount());
}

@Override
public void onError(Exception e) {
mRemoteState = RemoteState.FAILED;
Log.w(TAG, "Failed to fetch image: " + e.getMessage() + " (" + ref + ")");
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
((MenuHost) activity).removeMenuProvider(ImageViewFragment.this);
}
});
}
}

if (bmp == null) {
bmp = preview;
}

if (bmp != null) {
// Must ensure the bitmap is not too big (some cameras can produce
// bigger bitmaps that the phone can render)
Expand Down Expand Up @@ -367,14 +380,11 @@ public void onError(Exception e) {
} else {
mMatrix.setRectToRect(mInitialRect, mScreenRect, Matrix.ScaleToFit.CENTER);
}
} else if (mRemoteState != RemoteState.SUCCESS) {
// Show placeholder or a broken image.
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setImageDrawable(ResourcesCompat.getDrawable(getResources(),
mRemoteState == RemoteState.LOADING ?
R.drawable.ic_image :
R.drawable.ic_broken_image,
null));
} else if (mRemoteState == RemoteState.NONE) {
// Local broken image.
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setImageDrawable(ResourcesCompat.getDrawable(getResources(),
R.drawable.ic_broken_image, null));
activity.findViewById(R.id.metaPanel).setVisibility(View.INVISIBLE);
((MenuHost) activity).removeMenuProvider(this);
}
Expand Down Expand Up @@ -424,16 +434,13 @@ private void setupImagePostview(final Activity activity, Bundle args, long lengt

@Override
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
// Inflate the menu; this adds items to the action bar if it is present.
menu.clear();
inflater.inflate(R.menu.menu_download, menu);
}

@Override
public boolean onMenuItemSelected(@NonNull MenuItem item) {
final Activity activity = getActivity();
if (activity == null) {
return false;
}
final Activity activity = requireActivity();

if (item.getItemId() == R.id.action_download) {
// Save image to Gallery.
Expand Down
Loading

0 comments on commit 310aa5a

Please sign in to comment.