layout fix, added asynctask for saving images, memory leak fix
This commit is contained in:
parent
8daf1fa1a5
commit
3b736b2d3c
@ -23,8 +23,10 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.R;
|
import org.nuclearfog.twidda.R;
|
||||||
|
import org.nuclearfog.twidda.backend.ImageSaver;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@ -34,6 +36,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
|||||||
import static android.content.Intent.ACTION_PICK;
|
import static android.content.Intent.ACTION_PICK;
|
||||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
import static android.os.AsyncTask.Status.RUNNING;
|
||||||
import static android.os.Environment.DIRECTORY_PICTURES;
|
import static android.os.Environment.DIRECTORY_PICTURES;
|
||||||
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
@ -43,7 +46,7 @@ import static android.widget.Toast.LENGTH_SHORT;
|
|||||||
*
|
*
|
||||||
* @author nuclearfog
|
* @author nuclearfog
|
||||||
*/
|
*/
|
||||||
abstract class MediaActivity extends AppCompatActivity implements LocationListener, Runnable {
|
public abstract class MediaActivity extends AppCompatActivity implements LocationListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* permission set
|
* permission set
|
||||||
@ -96,14 +99,8 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
*/
|
*/
|
||||||
protected static final int REQUEST_STORE_IMG = 9;
|
protected static final int REQUEST_STORE_IMG = 9;
|
||||||
|
|
||||||
/**
|
|
||||||
* Quality of the saved jpeg images
|
|
||||||
*/
|
|
||||||
private static final int JPEG_QUALITY = 90;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private LocationManager locationManager;
|
private ImageSaver imageTask;
|
||||||
private Thread imageSaveThread;
|
|
||||||
private Bitmap image;
|
private Bitmap image;
|
||||||
private String filename;
|
private String filename;
|
||||||
private boolean locationPending = false;
|
private boolean locationPending = false;
|
||||||
@ -112,14 +109,20 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle b) {
|
protected void onCreate(Bundle b) {
|
||||||
super.onCreate(b);
|
super.onCreate(b);
|
||||||
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
if (locationManager != null)
|
if (locationPending) {
|
||||||
|
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
if (locationManager != null) {
|
||||||
locationManager.removeUpdates(this);
|
locationManager.removeUpdates(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (imageTask != null && imageTask.getStatus() == RUNNING) {
|
||||||
|
imageTask.cancel(true);
|
||||||
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,8 +139,9 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
else
|
else
|
||||||
onAttachLocation(null);
|
onAttachLocation(null);
|
||||||
} else if ((PERMISSIONS[2][0].equals(permissions[0]))) {
|
} else if ((PERMISSIONS[2][0].equals(permissions[0]))) {
|
||||||
if (grantResults[0] == PERMISSION_GRANTED)
|
if (grantResults[0] == PERMISSION_GRANTED) {
|
||||||
writeImageToStorage();
|
saveImage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,12 +165,19 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
@Override
|
* save image to external storage
|
||||||
public final void run() {
|
*/
|
||||||
boolean imageSaved = false;
|
private void saveImage() {
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (imageTask == null || imageTask.getStatus() != RUNNING) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// store images directly
|
||||||
|
File imageFile = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), filename);
|
||||||
|
OutputStream fileStream = new FileOutputStream(imageFile);
|
||||||
|
imageTask = new ImageSaver(this);
|
||||||
|
imageTask.execute(image, fileStream);
|
||||||
|
} else {
|
||||||
// use scoped storage
|
// use scoped storage
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
|
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
|
||||||
@ -176,39 +187,14 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
|
Uri imageUri = getContentResolver().insert(EXTERNAL_CONTENT_URI, values);
|
||||||
if (imageUri != null) {
|
if (imageUri != null) {
|
||||||
OutputStream fileStream = getContentResolver().openOutputStream(imageUri);
|
OutputStream fileStream = getContentResolver().openOutputStream(imageUri);
|
||||||
if (fileStream != null) {
|
imageTask = new ImageSaver(this);
|
||||||
imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
|
imageTask.execute(image, fileStream);
|
||||||
fileStream.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// store images directly
|
|
||||||
File imageFile = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), filename + ".jpg");
|
|
||||||
OutputStream fileStream = new FileOutputStream(imageFile);
|
|
||||||
imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
|
|
||||||
fileStream.close();
|
|
||||||
// start media scanner to scan for new image
|
|
||||||
String[] fileName = {imageFile.toString()};
|
|
||||||
MediaScannerConnection.scanFile(getApplicationContext(), fileName, null, null);
|
|
||||||
}
|
}
|
||||||
} catch (Exception err) {
|
} catch (FileNotFoundException err) {
|
||||||
err.printStackTrace();
|
err.printStackTrace();
|
||||||
}
|
}
|
||||||
if (imageSaved) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.info_image_saved, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.error_image_save, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -235,6 +221,25 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
public final void onStatusChanged(String provider, int status, Bundle extras) {
|
public final void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when an image was successfully saved to external storage
|
||||||
|
*/
|
||||||
|
public void onImageSaved() {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.info_image_saved, Toast.LENGTH_LONG).show();
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// start media scanner to scan for new image
|
||||||
|
String[] fileName = {filename};
|
||||||
|
MediaScannerConnection.scanFile(getApplicationContext(), fileName, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when an error occurs while storing image
|
||||||
|
*/
|
||||||
|
public void onError() {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.error_image_save, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask for GPS location
|
* Ask for GPS location
|
||||||
*/
|
*/
|
||||||
@ -268,9 +273,12 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
protected void storeImage(Bitmap image, String filename) {
|
protected void storeImage(Bitmap image, String filename) {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
|
if (!filename.endsWith(".jpg")) {
|
||||||
|
this.filename += ".jpg";
|
||||||
|
}
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
&& checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) {
|
|| checkSelfPermission(PERMISSIONS[2][0]) == PERMISSION_GRANTED) {
|
||||||
writeImageToStorage();
|
saveImage();
|
||||||
} else {
|
} else {
|
||||||
requestPermissions(PERMISSIONS[2], REQUEST_STORE_IMG);
|
requestPermissions(PERMISSIONS[2], REQUEST_STORE_IMG);
|
||||||
}
|
}
|
||||||
@ -290,6 +298,7 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
*/
|
*/
|
||||||
@SuppressLint("MissingPermission") // suppressing because of an android studio bug
|
@SuppressLint("MissingPermission") // suppressing because of an android studio bug
|
||||||
private void fetchLocation() {
|
private void fetchLocation() {
|
||||||
|
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null);
|
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null);
|
||||||
locationPending = true;
|
locationPending = true;
|
||||||
@ -331,16 +340,6 @@ abstract class MediaActivity extends AppCompatActivity implements LocationListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* start background thread to save image
|
|
||||||
*/
|
|
||||||
private void writeImageToStorage() {
|
|
||||||
if (imageSaveThread == null || !imageSaveThread.isAlive()) {
|
|
||||||
imageSaveThread = new Thread(this);
|
|
||||||
imageSaveThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called when location information was successfully fetched
|
* called when location information was successfully fetched
|
||||||
*
|
*
|
||||||
|
@ -40,6 +40,7 @@ import org.nuclearfog.twidda.backend.utils.StringTools;
|
|||||||
import org.nuclearfog.twidda.database.GlobalSettings;
|
import org.nuclearfog.twidda.database.GlobalSettings;
|
||||||
import org.nuclearfog.zoomview.ZoomView;
|
import org.nuclearfog.zoomview.ZoomView;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -116,6 +117,7 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
|
|||||||
IDLE
|
IDLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WeakReference<MediaViewer> updateEvent = new WeakReference<>(this);
|
||||||
@Nullable
|
@Nullable
|
||||||
private ScheduledExecutorService progressUpdate;
|
private ScheduledExecutorService progressUpdate;
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -186,14 +188,19 @@ public class MediaViewer extends MediaActivity implements OnImageClickListener,
|
|||||||
controlPanel.setVisibility(VISIBLE);
|
controlPanel.setVisibility(VISIBLE);
|
||||||
if (!mediaLinks[0].startsWith("http"))
|
if (!mediaLinks[0].startsWith("http"))
|
||||||
share.setVisibility(GONE); // local image
|
share.setVisibility(GONE); // local image
|
||||||
|
final Runnable seekUpdate = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (updateEvent.get() != null) {
|
||||||
|
updateEvent.get().updateSeekBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
progressUpdate = Executors.newScheduledThreadPool(1);
|
progressUpdate = Executors.newScheduledThreadPool(1);
|
||||||
progressUpdate.scheduleWithFixedDelay(new Runnable() {
|
progressUpdate.scheduleWithFixedDelay(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
runOnUiThread(new Runnable() {
|
if (updateEvent.get() != null) {
|
||||||
public void run() {
|
updateEvent.get().runOnUiThread(seekUpdate);
|
||||||
updateSeekBar();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, PROGRESS_UPDATE, PROGRESS_UPDATE, TimeUnit.MILLISECONDS);
|
}, PROGRESS_UPDATE, PROGRESS_UPDATE, TimeUnit.MILLISECONDS);
|
||||||
case MEDIAVIEWER_ANGIF:
|
case MEDIAVIEWER_ANGIF:
|
||||||
|
@ -4,6 +4,7 @@ import android.view.View;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.nuclearfog.twidda.R;
|
import org.nuclearfog.twidda.R;
|
||||||
@ -23,9 +24,11 @@ public final class ImageItem extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
public ImageItem(View view, GlobalSettings settings) {
|
public ImageItem(View view, GlobalSettings settings) {
|
||||||
super(view);
|
super(view);
|
||||||
|
CardView cardBackground = (CardView) view;
|
||||||
preview = view.findViewById(R.id.item_image_preview);
|
preview = view.findViewById(R.id.item_image_preview);
|
||||||
saveButton = view.findViewById(R.id.item_image_save);
|
saveButton = view.findViewById(R.id.item_image_save);
|
||||||
saveButton.setImageResource(R.drawable.save);
|
saveButton.setImageResource(R.drawable.save);
|
||||||
|
cardBackground.setCardBackgroundColor(settings.getCardColor());
|
||||||
AppStyles.setButtonColor(saveButton, settings.getFontColor());
|
AppStyles.setButtonColor(saveButton, settings.getFontColor());
|
||||||
AppStyles.setDrawableColor(saveButton, settings.getIconColor());
|
AppStyles.setDrawableColor(saveButton, settings.getIconColor());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package org.nuclearfog.twidda.backend;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import org.nuclearfog.twidda.activity.MediaActivity;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* background task to save an image to the external storage
|
||||||
|
*
|
||||||
|
* @author nuclearfog
|
||||||
|
* @see MediaActivity
|
||||||
|
*/
|
||||||
|
public class ImageSaver extends AsyncTask<Object, Void, Boolean> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quality of the saved jpeg images
|
||||||
|
*/
|
||||||
|
private static final int JPEG_QUALITY = 90;
|
||||||
|
|
||||||
|
private WeakReference<MediaActivity> callback;
|
||||||
|
|
||||||
|
|
||||||
|
public ImageSaver(MediaActivity activity) {
|
||||||
|
super();
|
||||||
|
callback = new WeakReference<>(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Object... data) {
|
||||||
|
try {
|
||||||
|
if (data != null && data.length == 2) {
|
||||||
|
if (data[0] instanceof Bitmap && data[1] instanceof OutputStream) {
|
||||||
|
Bitmap image = (Bitmap) data[0];
|
||||||
|
OutputStream fileStream = (OutputStream) data[1];
|
||||||
|
boolean imageSaved = image.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, fileStream);
|
||||||
|
fileStream.close();
|
||||||
|
return imageSaved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception err) {
|
||||||
|
err.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean success) {
|
||||||
|
if (callback.get() != null) {
|
||||||
|
if (success) {
|
||||||
|
callback.get().onImageSaved();
|
||||||
|
} else {
|
||||||
|
callback.get().onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user