Skip to content

Commit

Permalink
Merge pull request #499 from AEFeinstein/img-save
Browse files Browse the repository at this point in the history
#490 Rework image saving to use the MediaStore
  • Loading branch information
AEFeinstein committed Jan 22, 2020
2 parents 254a7fd + c1b4e73 commit e0cdf17
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,14 @@
import android.database.CursorIndexOutOfBoundsException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.provider.MediaStore;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.SpannableString;
Expand All @@ -60,14 +55,18 @@
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.gelakinetic.GathererScraper.JsonTypes.Card;
import com.gelakinetic.GathererScraper.Language;
import com.gelakinetic.mtgfam.BuildConfig;
import com.gelakinetic.mtgfam.FamiliarActivity;
import com.gelakinetic.mtgfam.R;
import com.gelakinetic.mtgfam.fragments.dialogs.CardViewDialogFragment;
Expand Down Expand Up @@ -95,8 +94,6 @@
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
Expand Down Expand Up @@ -644,82 +641,108 @@ public void saveImageWithGlide(int whereTo) {
return;
}

// Get a File where the image should be saved
File imageFile;
try {
imageFile = getSavedImageFile();
} catch (Exception e) {
SnackbarWrapper.makeAndShowText(getActivity(), e.getMessage(), SnackbarWrapper.LENGTH_SHORT);
return;
}
// Query the MediaStore to see if an image is already saved
MediaStoreInfo msi = getMediaStoreInfo();

// Check if the saved image already exists
if (imageFile.exists()) {
if (null != msi) {
if (SHARE == whereTo) {
// Image is already saved, just share it
shareImage();
shareImage(Uri.parse(MediaStore.Images.Media.getContentUri("external") + "/" + msi.getId()));
} else {
// Or display the path where it's saved
String strPath = imageFile.getAbsolutePath();
SnackbarWrapper.makeAndShowText(getActivity(), getString(R.string.card_view_image_saved) + strPath, SnackbarWrapper.LENGTH_LONG);
SnackbarWrapper.makeAndShowText(getActivity(), getString(R.string.card_view_image_saved) + msi.getFilePath(), SnackbarWrapper.LENGTH_LONG);
}
return;
}

runGlideTarget(new FamiliarGlideTarget(this, new FamiliarGlideTarget.DrawableLoadedCallback() {
/**
* When Glide loads the resource either from cache or the network, save it
* to a file then optionally launch the intent to share it
*
* @param resource The Drawable Glide loaded, hopefully a BitmapDrawable
*/
@Override
protected void onDrawableLoaded(Drawable resource) {
if (resource instanceof BitmapDrawable) {
// Save the image
BitmapDrawable bitmapDrawable = (BitmapDrawable) resource;

} else {
runGlideTarget(new FamiliarGlideTarget(this, new FamiliarGlideTarget.DrawableLoadedCallback() {
/**
* When Glide loads the resource either from cache or the network, save it
* to a file then optionally launch the intent to share it
*
* @param resource The Drawable Glide loaded, hopefully a BitmapDrawable
*/
@Override
protected void onDrawableLoaded(Drawable resource) {
try {
// Create the file
if (!imageFile.createNewFile()) {
// Couldn't create the file
SnackbarWrapper.makeAndShowText(getActivity(), R.string.card_view_unable_to_create_file, SnackbarWrapper.LENGTH_SHORT);
return;
}

// Now that the file is created, write to it
FileOutputStream fStream = new FileOutputStream(imageFile);
boolean bCompressed = bitmapDrawable.getBitmap().compress(Bitmap.CompressFormat.JPEG, 90, fStream);
fStream.flush();
fStream.close();
if (resource instanceof BitmapDrawable) {
// Save the image
String url = MediaStore.Images.Media.insertImage(
getContext().getContentResolver(),
((BitmapDrawable) resource).getBitmap(),
getSavedFileName(), mCard.getName() + " - " + mCard.getSetName());

// Couldn't save the image for some reason
if (null == url) {
SnackbarWrapper.makeAndShowText(getActivity(), R.string.card_view_save_failure, SnackbarWrapper.LENGTH_SHORT);
} else {
// Notify the system that a new image was saved
getFamiliarActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(url)));

// Couldn't save the image for some reason
if (!bCompressed) {
SnackbarWrapper.makeAndShowText(getActivity(), R.string.card_view_save_failure, SnackbarWrapper.LENGTH_SHORT);
return;
// Now that the image is saved, launch the intent
if (SHARE == whereTo) {
// Image is already saved, just share it
shareImage(Uri.parse(url));
} else {
// Or display the path where it's saved
MediaStoreInfo msi = getMediaStoreInfo();
if (null != msi) {
SnackbarWrapper.makeAndShowText(getActivity(), getString(R.string.card_view_image_saved) + msi.getFilePath(), SnackbarWrapper.LENGTH_LONG);
} else {
SnackbarWrapper.makeAndShowText(getActivity(), getString(R.string.card_view_image_saved) + url, SnackbarWrapper.LENGTH_LONG);
}
}
}
}
} catch (IOException e) {
} catch (Exception e) {
// Couldn't save it for some reason
SnackbarWrapper.makeAndShowText(getActivity(), R.string.card_view_save_failure, SnackbarWrapper.LENGTH_SHORT);
return;
}

// Notify the system that a new image was saved
getFamiliarActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.fromFile(imageFile)));

// Now that the image is saved, launch the intent
if (SHARE == whereTo) {
// Image is already saved, just share it
shareImage();
} else {
// Or display the path where it's saved
String strPath = imageFile.getAbsolutePath();
SnackbarWrapper.makeAndShowText(getActivity(), getString(R.string.card_view_image_saved) + strPath, SnackbarWrapper.LENGTH_LONG);
}
}
}), 0, 0);
}
}

private class MediaStoreInfo {
private final String filePath;
private final long mediaStoreId;

MediaStoreInfo(String fp, long id) {
filePath = fp;
mediaStoreId = id;
}

String getFilePath() {
return filePath;
}

long getId() {
return mediaStoreId;
}
}

/**
* Get the file path
*
* @return The file path and ID for this card's image in the MediaStore, or null
*/
@javax.annotation.Nullable
private MediaStoreInfo getMediaStoreInfo() {
try (Cursor mCursor = getContext().getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID},
MediaStore.Images.Media.DISPLAY_NAME + " = ?",
new String[]{getSavedFileName()},
MediaStore.Images.Media.DEFAULT_SORT_ORDER)) {
if (mCursor.getCount() > 0) {
mCursor.moveToFirst();
return new MediaStoreInfo(
mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA)),
mCursor.getLong(mCursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
}), 0, 0);
} catch (Exception e) {
// eat it
}
return null;
}

/**
Expand Down Expand Up @@ -849,11 +872,9 @@ public void showText() {
/**
* Launch the intent to share a saved image
*/
private void shareImage() {
private void shareImage(Uri uri) {
/* Start the intent to share the image */
try {
Uri uri = FileProvider.getUriForFile(mActivity,
BuildConfig.APPLICATION_ID + ".FileProvider", getSavedImageFile());
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Expand All @@ -866,37 +887,8 @@ private void shareImage() {
}
}

/**
* Returns the File used to save this card's image.
*
* @return A File, either with the image already or blank
* @throws Exception If something goes wrong
*/
private File getSavedImageFile() throws Exception {

String strPath;
try {
strPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
.getCanonicalPath() + "/MTGFamiliar";
} catch (IOException ex) {
throw new Exception(getString(R.string.card_view_no_pictures_folder));
}

File fPath = new File(strPath);

if (!fPath.exists()) {
if (!fPath.mkdir()) {
throw new Exception(getString(R.string.card_view_unable_to_create_dir));
}

if (!fPath.isDirectory()) {
throw new Exception(getString(R.string.card_view_unable_to_create_dir));
}
}

fPath = new File(strPath, mCard.getName() + "_" + mCard.getExpansion() + ".jpg");

return fPath;
private String getSavedFileName() {
return mCard.getName() + "_" + mCard.getExpansion() + ".jpg";
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class SnackbarWrapper {

public static final int LENGTH_LONG = Snackbar.LENGTH_LONG;
public static final int LENGTH_SHORT = Snackbar.LENGTH_SHORT;
public static final int LENGTH_XLONG = 2750 * 3; // Three times long, see SnackbarManager.LONG_DURATION_MS

private static Snackbar mSnackbar;

/**
Expand Down
46 changes: 12 additions & 34 deletions mobile/src/main/java/com/gelakinetic/mtgfam/helpers/ZipUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@

package com.gelakinetic.mtgfam.helpers;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.gelakinetic.mtgfam.FamiliarActivity;
import com.gelakinetic.mtgfam.R;

import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -66,34 +60,26 @@ public static void exportData(Activity activity) {
return;
}

/* Check if permission is granted */
if (ContextCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
/* Request the permission */
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
FamiliarActivity.REQUEST_WRITE_EXTERNAL_STORAGE_BACKUP);
return;
}

assert activity.getFilesDir() != null;

String sharedPrefsDir = activity.getFilesDir().getPath();
sharedPrefsDir = sharedPrefsDir.substring(0, sharedPrefsDir.lastIndexOf("/")) + "/shared_prefs/";

ArrayList<File> files = findAllFiles(activity.getFilesDir(),
new File(sharedPrefsDir));

File sdCard = Environment.getExternalStorageDirectory();
File sdCard = activity.getFilesDir();
File zipOut = new File(sdCard, BACKUP_FILE_NAME);
if (zipOut.exists()) {
if (!zipOut.delete()) {
return;
}
}

ArrayList<File> files = findAllFiles(activity.getFilesDir(),
new File(sharedPrefsDir));

try {
zipIt(zipOut, files, activity);
SnackbarWrapper.makeAndShowText(activity, activity.getString(R.string.main_export_success) + " " + zipOut.getAbsolutePath(),
SnackbarWrapper.LENGTH_SHORT);
SnackbarWrapper.LENGTH_XLONG);
} catch (ZipException e) {
if (Objects.requireNonNull(e.getMessage()).equals("No entries")) {
SnackbarWrapper.makeAndShowText(activity, R.string.main_export_no_data, SnackbarWrapper.LENGTH_SHORT);
Expand All @@ -118,24 +104,16 @@ public static void importData(Activity activity) {
return;
}

/* Only check permissions after Jelly Bean */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
/* Check if permission is granted */
if (ContextCompat.checkSelfPermission(activity,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
/* Request the permission */
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
FamiliarActivity.REQUEST_READ_EXTERNAL_STORAGE_BACKUP);
return;
}
}

/* Try unzipping the file */
try (ZipFile zipFile = new ZipFile(new File(Environment.getExternalStorageDirectory(), BACKUP_FILE_NAME))) {
try (ZipFile zipFile = new ZipFile(new File(activity.getFilesDir(), BACKUP_FILE_NAME))) {
unZipIt(zipFile, activity);
SnackbarWrapper.makeAndShowText(activity, R.string.main_import_success, SnackbarWrapper.LENGTH_SHORT);
} catch (IOException e) {
SnackbarWrapper.makeAndShowText(activity, R.string.main_import_fail, SnackbarWrapper.LENGTH_SHORT);
SnackbarWrapper.makeAndShowText(activity,
String.format(activity.getString(R.string.main_import_fail),
BACKUP_FILE_NAME,
activity.getFilesDir().getAbsolutePath()),
SnackbarWrapper.LENGTH_XLONG);
}
}

Expand Down
2 changes: 1 addition & 1 deletion mobile/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
<string name="main_export_fail">"Konnte die Daten nicht auf der SD-Karte speichern"</string>
<string name="main_import_data_title">"Daten von der SD-Karte laden"</string>
<string name="main_import_success">"Importieren erfolgreich"</string>
<string name="main_import_fail">"Importieren fehlgeschlagen. Bitte speicher die Datei MTGFamiliarBackup.zip im Stammverzeichnis deiner SD-Karte ab."</string>
<string name="main_import_fail">"Importieren fehlgeschlagen. Bitte speicher die Datei %1$s im %2$s."</string>
<string name="main_about">"Über diese App"</string>

<!-- Some grammar corrections and changes in language tone. -->
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
<string name="main_export_fail">"La exportación de datos a la tarjeta SD ha fallado"</string>
<string name="main_import_data_title">"Importar datos de la SD"</string>
<string name="main_import_success">"Datos importados correctamente"</string>
<string name="main_import_fail">"Importación fallida. Por favor pon MTGFamiliarBackup.zip en el directorio raíz de tu tarjeta SD."</string>
<string name="main_import_fail">"Importación fallida. Por favor pon %1$s en %2$s."</string>
<string name="main_about">"Acerca de"</string>
<string name="main_about_text">"Esta aplicación es mi regalo a la comunidad de MTG.&lt;br&gt;
&lt;br&gt;
Expand Down
2 changes: 1 addition & 1 deletion mobile/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
<string name="main_export_fail">"Echec de l'export des données vers carte SD"</string>
<string name="main_import_data_title">"Import depuis SD"</string>
<string name="main_import_success">"Import réalisé avec succès"</string>
<string name="main_import_fail">"Echec de l'import. Veuillez placer MTGFamiliarBackup.zip à la racine de votre carte SD"</string>
<string name="main_import_fail">"Echec de l'import. Veuillez placer %1$s dans %2$s"</string>
<string name="main_about">"A propos"</string>
<string name="main_about_text">"Cette appli est mon cadeau à la communauté MTG.&lt;br&gt;
&lt;br&gt;
Expand Down

0 comments on commit e0cdf17

Please sign in to comment.