|
|
|
@ -1,14 +1,39 @@ |
|
|
|
|
package com.keylesspalace.tusky; |
|
|
|
|
|
|
|
|
|
import android.Manifest; |
|
|
|
|
import android.app.ProgressDialog; |
|
|
|
|
import android.content.ContentResolver; |
|
|
|
|
import android.content.Context; |
|
|
|
|
import android.content.Intent; |
|
|
|
|
import android.content.SharedPreferences; |
|
|
|
|
import android.content.pm.PackageManager; |
|
|
|
|
import android.content.res.Resources; |
|
|
|
|
import android.database.Cursor; |
|
|
|
|
import android.graphics.Bitmap; |
|
|
|
|
import android.graphics.BitmapFactory; |
|
|
|
|
import android.media.MediaMetadataRetriever; |
|
|
|
|
import android.media.ThumbnailUtils; |
|
|
|
|
import android.net.Uri; |
|
|
|
|
import android.os.AsyncTask; |
|
|
|
|
import android.os.Build; |
|
|
|
|
import android.os.Bundle; |
|
|
|
|
import android.provider.OpenableColumns; |
|
|
|
|
import android.support.annotation.NonNull; |
|
|
|
|
import android.support.annotation.Nullable; |
|
|
|
|
import android.support.design.widget.Snackbar; |
|
|
|
|
import android.support.v4.app.ActivityCompat; |
|
|
|
|
import android.support.v4.content.ContextCompat; |
|
|
|
|
import android.support.v7.app.AppCompatActivity; |
|
|
|
|
import android.text.Editable; |
|
|
|
|
import android.text.TextWatcher; |
|
|
|
|
import android.view.View; |
|
|
|
|
import android.webkit.MimeTypeMap; |
|
|
|
|
import android.widget.Button; |
|
|
|
|
import android.widget.CheckBox; |
|
|
|
|
import android.widget.EditText; |
|
|
|
|
import android.widget.ImageButton; |
|
|
|
|
import android.widget.ImageView; |
|
|
|
|
import android.widget.LinearLayout; |
|
|
|
|
import android.widget.RadioGroup; |
|
|
|
|
import android.widget.TextView; |
|
|
|
|
import android.widget.Toast; |
|
|
|
@ -19,18 +44,107 @@ import com.android.volley.Response; |
|
|
|
|
import com.android.volley.VolleyError; |
|
|
|
|
import com.android.volley.toolbox.JsonObjectRequest; |
|
|
|
|
|
|
|
|
|
import org.json.JSONArray; |
|
|
|
|
import org.json.JSONException; |
|
|
|
|
import org.json.JSONObject; |
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
import java.io.FileNotFoundException; |
|
|
|
|
import java.io.IOException; |
|
|
|
|
import java.io.InputStream; |
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.Date; |
|
|
|
|
import java.util.HashMap; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.Random; |
|
|
|
|
|
|
|
|
|
public class ComposeActivity extends AppCompatActivity { |
|
|
|
|
private static int STATUS_CHARACTER_LIMIT = 500; |
|
|
|
|
private static final int STATUS_CHARACTER_LIMIT = 500; |
|
|
|
|
private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB
|
|
|
|
|
private static final int MEDIA_PICK_RESULT = 1; |
|
|
|
|
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1; |
|
|
|
|
|
|
|
|
|
private String domain; |
|
|
|
|
private String accessToken; |
|
|
|
|
private EditText textEditor; |
|
|
|
|
private ImageButton mediaPick; |
|
|
|
|
private CheckBox markSensitive; |
|
|
|
|
private LinearLayout mediaPreviewBar; |
|
|
|
|
private List<QueuedMedia> mediaQueued; |
|
|
|
|
private CountUpDownLatch waitForMediaLatch; |
|
|
|
|
|
|
|
|
|
private static class QueuedMedia { |
|
|
|
|
public enum Type { |
|
|
|
|
IMAGE, |
|
|
|
|
VIDEO |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public enum ReadyStage { |
|
|
|
|
DOWNSIZING, |
|
|
|
|
UPLOADING, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Type type; |
|
|
|
|
private ImageView preview; |
|
|
|
|
private Uri uri; |
|
|
|
|
private String id; |
|
|
|
|
private ReadyStage readyStage; |
|
|
|
|
private byte[] content; |
|
|
|
|
|
|
|
|
|
public QueuedMedia(Type type, Uri uri, ImageView preview) { |
|
|
|
|
this.type = type; |
|
|
|
|
this.uri = uri; |
|
|
|
|
this.preview = preview; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Type getType() { |
|
|
|
|
return type; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public ImageView getPreview() { |
|
|
|
|
return preview; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Uri getUri() { |
|
|
|
|
return uri; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public String getId() { |
|
|
|
|
return id; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public byte[] getContent() { |
|
|
|
|
return content; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public ReadyStage getReadyStage() { |
|
|
|
|
return readyStage; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public void setId(String id) { |
|
|
|
|
this.id = id; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public void setReadyStage(ReadyStage readyStage) { |
|
|
|
|
this.readyStage = readyStage; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public void setContent(byte[] content) { |
|
|
|
|
this.content = content; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void doErrorDialog(int descriptionId, int actionId, View.OnClickListener listener) { |
|
|
|
|
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(descriptionId), |
|
|
|
|
Snackbar.LENGTH_SHORT); |
|
|
|
|
bar.setAction(actionId, listener); |
|
|
|
|
bar.show(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void displayTransientError(int stringId) { |
|
|
|
|
Snackbar.make(findViewById(R.id.activity_compose), stringId, Snackbar.LENGTH_LONG).show(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onSendSuccess() { |
|
|
|
|
Toast.makeText(this, "Toot!", Toast.LENGTH_SHORT).show(); |
|
|
|
@ -41,13 +155,21 @@ public class ComposeActivity extends AppCompatActivity { |
|
|
|
|
textEditor.setError(getString(R.string.error_sending_status)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void sendStatus(String content, String visibility) { |
|
|
|
|
private void sendStatus(String content, String visibility, boolean sensitive) { |
|
|
|
|
String endpoint = getString(R.string.endpoint_status); |
|
|
|
|
String url = "https://" + domain + endpoint; |
|
|
|
|
JSONObject parameters = new JSONObject(); |
|
|
|
|
try { |
|
|
|
|
parameters.put("status", content); |
|
|
|
|
parameters.put("visibility", visibility); |
|
|
|
|
parameters.put("sensitive", sensitive); |
|
|
|
|
JSONArray media_ids = new JSONArray(); |
|
|
|
|
for (QueuedMedia item : mediaQueued) { |
|
|
|
|
media_ids.put(item.getId()); |
|
|
|
|
} |
|
|
|
|
if (media_ids.length() > 0) { |
|
|
|
|
parameters.put("media_ids", media_ids); |
|
|
|
|
} |
|
|
|
|
} catch (JSONException e) { |
|
|
|
|
onSendFailure(e); |
|
|
|
|
return; |
|
|
|
@ -74,6 +196,48 @@ public class ComposeActivity extends AppCompatActivity { |
|
|
|
|
VolleySingleton.getInstance(this).addToRequestQueue(request); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onReadyFailure(Exception exception, final String content, |
|
|
|
|
final String visibility, final boolean sensitive) { |
|
|
|
|
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry, |
|
|
|
|
new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
readyStatus(content, visibility, sensitive); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void readyStatus(final String content, final String visibility, |
|
|
|
|
final boolean sensitive) { |
|
|
|
|
final ProgressDialog dialog = ProgressDialog.show(this, "Finishing Media Upload", |
|
|
|
|
"Uploading...", true); |
|
|
|
|
new AsyncTask<Void, Void, Boolean>() { |
|
|
|
|
private Exception exception; |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected Boolean doInBackground(Void... params) { |
|
|
|
|
try { |
|
|
|
|
waitForMediaLatch.await(); |
|
|
|
|
} catch (InterruptedException e) { |
|
|
|
|
exception = e; |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onPostExecute(Boolean successful) { |
|
|
|
|
super.onPostExecute(successful); |
|
|
|
|
dialog.dismiss(); |
|
|
|
|
if (successful) { |
|
|
|
|
sendStatus(content, visibility, sensitive); |
|
|
|
|
} else { |
|
|
|
|
onReadyFailure(exception, content, visibility, sensitive); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}.execute(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onCreate(Bundle savedInstanceState) { |
|
|
|
|
super.onCreate(savedInstanceState); |
|
|
|
@ -103,6 +267,10 @@ public class ComposeActivity extends AppCompatActivity { |
|
|
|
|
}; |
|
|
|
|
textEditor.addTextChangedListener(textEditorWatcher); |
|
|
|
|
|
|
|
|
|
mediaPreviewBar = (LinearLayout) findViewById(R.id.compose_media_preview_bar); |
|
|
|
|
mediaQueued = new ArrayList<>(); |
|
|
|
|
waitForMediaLatch = new CountUpDownLatch(); |
|
|
|
|
|
|
|
|
|
final RadioGroup radio = (RadioGroup) findViewById(R.id.radio_visibility); |
|
|
|
|
final Button sendButton = (Button) findViewById(R.id.button_send); |
|
|
|
|
sendButton.setOnClickListener(new View.OnClickListener() { |
|
|
|
@ -127,11 +295,346 @@ public class ComposeActivity extends AppCompatActivity { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
sendStatus(editable.toString(), visibility); |
|
|
|
|
readyStatus(editable.toString(), visibility, markSensitive.isChecked()); |
|
|
|
|
} else { |
|
|
|
|
textEditor.setError(getString(R.string.error_compose_character_limit)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
mediaPick = (ImageButton) findViewById(R.id.compose_photo_pick); |
|
|
|
|
mediaPick.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onMediaPick(); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
markSensitive = (CheckBox) findViewById(R.id.compose_mark_sensitive); |
|
|
|
|
markSensitive.setVisibility(View.GONE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onMediaPick() { |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && |
|
|
|
|
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) |
|
|
|
|
!= PackageManager.PERMISSION_GRANTED) { |
|
|
|
|
ActivityCompat.requestPermissions(this, |
|
|
|
|
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, |
|
|
|
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); |
|
|
|
|
} else { |
|
|
|
|
initiateMediaPicking(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], |
|
|
|
|
@NonNull int[] grantResults) { |
|
|
|
|
switch (requestCode) { |
|
|
|
|
case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: { |
|
|
|
|
if (grantResults.length > 0 |
|
|
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
|
|
|
|
initiateMediaPicking(); |
|
|
|
|
} else { |
|
|
|
|
doErrorDialog(R.string.error_media_upload_permission, R.string.action_retry, |
|
|
|
|
new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
onMediaPick(); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void initiateMediaPicking() { |
|
|
|
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE); |
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
|
|
|
|
intent.setType("image/* video/*"); |
|
|
|
|
} else { |
|
|
|
|
String[] mimeTypes = new String[] { "image/*", "video/*" }; |
|
|
|
|
intent.setType("*/*"); |
|
|
|
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); |
|
|
|
|
} |
|
|
|
|
startActivityForResult(intent, MEDIA_PICK_RESULT); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** A replacement for View.setPaddingRelative to use under API level 16. */ |
|
|
|
|
private static void setPaddingRelative(View view, int left, int top, int right, int bottom) { |
|
|
|
|
view.setPadding( |
|
|
|
|
view.getPaddingLeft() + left, |
|
|
|
|
view.getPaddingTop() + top, |
|
|
|
|
view.getPaddingRight() + right, |
|
|
|
|
view.getPaddingBottom() + bottom); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void enableMediaPicking() { |
|
|
|
|
mediaPick.setEnabled(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void disableMediaPicking() { |
|
|
|
|
mediaPick.setEnabled(false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize) { |
|
|
|
|
assert(mediaQueued.size() < Status.MAX_MEDIA_ATTACHMENTS); |
|
|
|
|
final QueuedMedia item = new QueuedMedia(type, uri, new ImageView(this)); |
|
|
|
|
ImageView view = item.getPreview(); |
|
|
|
|
Resources resources = getResources(); |
|
|
|
|
int side = resources.getDimensionPixelSize(R.dimen.compose_media_preview_side); |
|
|
|
|
int margin = resources.getDimensionPixelSize(R.dimen.compose_media_preview_margin); |
|
|
|
|
int marginBottom = resources.getDimensionPixelSize( |
|
|
|
|
R.dimen.compose_media_preview_margin_bottom); |
|
|
|
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(side, side); |
|
|
|
|
layoutParams.setMargins(margin, margin, margin, marginBottom); |
|
|
|
|
view.setLayoutParams(layoutParams); |
|
|
|
|
view.setImageBitmap(preview); |
|
|
|
|
view.setScaleType(ImageView.ScaleType.CENTER_CROP); |
|
|
|
|
view.setOnClickListener(new View.OnClickListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onClick(View v) { |
|
|
|
|
removeMediaFromQueue(item); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
mediaPreviewBar.addView(view); |
|
|
|
|
mediaQueued.add(item); |
|
|
|
|
int queuedCount = mediaQueued.size(); |
|
|
|
|
if (queuedCount == 1) { |
|
|
|
|
/* The media preview bar is actually not inset in the EditText, it just overlays it and |
|
|
|
|
* is aligned to the bottom. But, so that text doesn't get hidden under it, extra |
|
|
|
|
* padding is added at the bottom of the EditText. */ |
|
|
|
|
int totalHeight = side + margin + marginBottom; |
|
|
|
|
setPaddingRelative(textEditor, 0, 0, 0, totalHeight); |
|
|
|
|
// If there's one video in the queue it is full, so disable the button to queue more.
|
|
|
|
|
if (item.getType() == QueuedMedia.Type.VIDEO) { |
|
|
|
|
disableMediaPicking(); |
|
|
|
|
} |
|
|
|
|
} else if (queuedCount >= Status.MAX_MEDIA_ATTACHMENTS) { |
|
|
|
|
// Limit the total media attachments, also.
|
|
|
|
|
disableMediaPicking(); |
|
|
|
|
} |
|
|
|
|
if (queuedCount >= 1) { |
|
|
|
|
markSensitive.setVisibility(View.VISIBLE); |
|
|
|
|
} |
|
|
|
|
waitForMediaLatch.countUp(); |
|
|
|
|
if (mediaSize > STATUS_MEDIA_SIZE_LIMIT && type == QueuedMedia.Type.IMAGE) { |
|
|
|
|
downsizeMedia(item); |
|
|
|
|
} else { |
|
|
|
|
uploadMedia(item); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void removeMediaFromQueue(QueuedMedia item) { |
|
|
|
|
int moveBottom = mediaPreviewBar.getMeasuredHeight(); |
|
|
|
|
mediaPreviewBar.removeView(item.getPreview()); |
|
|
|
|
mediaQueued.remove(item); |
|
|
|
|
if (mediaQueued.size() == 0) { |
|
|
|
|
markSensitive.setVisibility(View.GONE); |
|
|
|
|
/* If there are no image previews to show, the extra padding that was added to the |
|
|
|
|
* EditText can be removed so there isn't unnecessary empty space. */ |
|
|
|
|
setPaddingRelative(textEditor, 0, 0, 0, moveBottom); |
|
|
|
|
} |
|
|
|
|
enableMediaPicking(); |
|
|
|
|
cancelReadyingMedia(item); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void downsizeMedia(final QueuedMedia item) { |
|
|
|
|
item.setReadyStage(QueuedMedia.ReadyStage.DOWNSIZING); |
|
|
|
|
InputStream stream; |
|
|
|
|
try { |
|
|
|
|
stream = getContentResolver().openInputStream(item.getUri()); |
|
|
|
|
} catch (FileNotFoundException e) { |
|
|
|
|
onMediaDownsizeFailure(item); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
Bitmap bitmap = BitmapFactory.decodeStream(stream); |
|
|
|
|
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, new DownsizeImageTask.Listener() { |
|
|
|
|
@Override |
|
|
|
|
public void onSuccess(List<byte[]> contentList) { |
|
|
|
|
item.setContent(contentList.get(0)); |
|
|
|
|
uploadMedia(item); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onFailure() { |
|
|
|
|
onMediaDownsizeFailure(item); |
|
|
|
|
} |
|
|
|
|
}).execute(bitmap); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onMediaDownsizeFailure(QueuedMedia item) { |
|
|
|
|
displayTransientError(R.string.error_media_upload_size); |
|
|
|
|
removeMediaFromQueue(item); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static String randomAlphanumericString(int count) { |
|
|
|
|
char[] chars = new char[count]; |
|
|
|
|
Random random = new Random(); |
|
|
|
|
final String POSSIBLE_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
|
|
|
|
for (int i = 0; i < count; i++) { |
|
|
|
|
chars[i] = POSSIBLE_CHARS.charAt(random.nextInt(POSSIBLE_CHARS.length())); |
|
|
|
|
} |
|
|
|
|
return new String(chars); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
private static byte[] inputStreamGetBytes(InputStream stream) { |
|
|
|
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
|
|
|
|
int read; |
|
|
|
|
byte[] data = new byte[16384]; |
|
|
|
|
try { |
|
|
|
|
while ((read = stream.read(data, 0, data.length)) != -1) { |
|
|
|
|
buffer.write(data, 0, read); |
|
|
|
|
} |
|
|
|
|
buffer.flush(); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
return buffer.toByteArray(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void uploadMedia(final QueuedMedia item) { |
|
|
|
|
item.setReadyStage(QueuedMedia.ReadyStage.UPLOADING); |
|
|
|
|
|
|
|
|
|
String endpoint = getString(R.string.endpoint_media); |
|
|
|
|
String url = "https://" + domain + endpoint; |
|
|
|
|
|
|
|
|
|
final String mimeType = getContentResolver().getType(item.uri); |
|
|
|
|
MimeTypeMap map = MimeTypeMap.getSingleton(); |
|
|
|
|
String fileExtension = map.getExtensionFromMimeType(mimeType); |
|
|
|
|
final String filename = String.format("%s_%s_%s.%s", |
|
|
|
|
getString(R.string.app_name), |
|
|
|
|
String.valueOf(new Date().getTime()), |
|
|
|
|
randomAlphanumericString(10), |
|
|
|
|
fileExtension); |
|
|
|
|
|
|
|
|
|
MultipartRequest request = new MultipartRequest(Request.Method.POST, url, null, |
|
|
|
|
new Response.Listener<JSONObject>() { |
|
|
|
|
@Override |
|
|
|
|
public void onResponse(JSONObject response) { |
|
|
|
|
try { |
|
|
|
|
item.setId(response.getString("id")); |
|
|
|
|
} catch (JSONException e) { |
|
|
|
|
onUploadFailure(item, e); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
waitForMediaLatch.countDown(); |
|
|
|
|
} |
|
|
|
|
}, new Response.ErrorListener() { |
|
|
|
|
@Override |
|
|
|
|
public void onErrorResponse(VolleyError error) { |
|
|
|
|
onUploadFailure(item, error); |
|
|
|
|
} |
|
|
|
|
}) { |
|
|
|
|
@Override |
|
|
|
|
public Map<String, String> getHeaders() throws AuthFailureError { |
|
|
|
|
Map<String, String> headers = new HashMap<>(); |
|
|
|
|
headers.put("Authorization", "Bearer " + accessToken); |
|
|
|
|
return headers; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public DataItem getData() { |
|
|
|
|
byte[] content = item.getContent(); |
|
|
|
|
if (content == null) { |
|
|
|
|
InputStream stream; |
|
|
|
|
try { |
|
|
|
|
stream = getContentResolver().openInputStream(item.getUri()); |
|
|
|
|
} catch (FileNotFoundException e) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
content = inputStreamGetBytes(stream); |
|
|
|
|
if (content == null) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
DataItem data = new DataItem(); |
|
|
|
|
data.name = "file"; |
|
|
|
|
data.filename = filename; |
|
|
|
|
data.mimeType = mimeType; |
|
|
|
|
data.content = content; |
|
|
|
|
return data; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
request.addMarker("media_" + item.getUri().toString()); |
|
|
|
|
VolleySingleton.getInstance(this).addToRequestQueue(request); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void onUploadFailure(QueuedMedia item, @Nullable Exception exception) { |
|
|
|
|
displayTransientError(R.string.error_media_upload_sending); |
|
|
|
|
removeMediaFromQueue(item); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void cancelReadyingMedia(QueuedMedia item) { |
|
|
|
|
if (item.getReadyStage() == QueuedMedia.ReadyStage.UPLOADING) { |
|
|
|
|
VolleySingleton.getInstance(this).cancelRequest("media_" + item.getUri().toString()); |
|
|
|
|
} |
|
|
|
|
waitForMediaLatch.countDown(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
|
|
|
|
super.onActivityResult(requestCode, resultCode, data); |
|
|
|
|
if (requestCode == MEDIA_PICK_RESULT && resultCode == RESULT_OK && data != null) { |
|
|
|
|
Uri uri = data.getData(); |
|
|
|
|
ContentResolver contentResolver = getContentResolver(); |
|
|
|
|
Cursor cursor = getContentResolver().query(uri, null, null, null, null); |
|
|
|
|
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); |
|
|
|
|
cursor.moveToFirst(); |
|
|
|
|
long mediaSize = cursor.getLong(sizeIndex); |
|
|
|
|
cursor.close(); |
|
|
|
|
String mimeType = contentResolver.getType(uri); |
|
|
|
|
if (mimeType != null) { |
|
|
|
|
String topLevelType = mimeType.substring(0, mimeType.indexOf('/')); |
|
|
|
|
switch (topLevelType) { |
|
|
|
|
case "video": { |
|
|
|
|
if (mediaSize > STATUS_MEDIA_SIZE_LIMIT) { |
|
|
|
|
displayTransientError(R.string.error_media_upload_size); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (mediaQueued.size() > 0 |
|
|
|
|
&& mediaQueued.get(0).getType() == QueuedMedia.Type.IMAGE) { |
|
|
|
|
displayTransientError(R.string.error_media_upload_image_or_video); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
|
|
|
|
retriever.setDataSource(this, uri); |
|
|
|
|
Bitmap source = retriever.getFrameAtTime(); |
|
|
|
|
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 96, 96); |
|
|
|
|
source.recycle(); |
|
|
|
|
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case "image": { |
|
|
|
|
InputStream stream; |
|
|
|
|
try { |
|
|
|
|
stream = contentResolver.openInputStream(uri); |
|
|
|
|
} catch (FileNotFoundException e) { |
|
|
|
|
displayTransientError(R.string.error_media_upload_opening); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
Bitmap source = BitmapFactory.decodeStream(stream); |
|
|
|
|
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 96, 96); |
|
|
|
|
source.recycle(); |
|
|
|
|
try { |
|
|
|
|
stream.close(); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
bitmap.recycle(); |
|
|
|
|
displayTransientError(R.string.error_media_upload_opening); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
addMediaToQueue(QueuedMedia.Type.IMAGE, bitmap, uri, mediaSize); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
default: { |
|
|
|
|
displayTransientError(R.string.error_media_upload_type); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
displayTransientError(R.string.error_media_upload_type); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|