mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Fix app stuck and memory issues while uploading images (#2287)
* Do not use an image array to store all bitmap pixels at once * Extract image preprocessing to a different service and use computation thread * Add java docs * Cleanup code to remove temp file logic * Add logs in upload flow * Fix tests * Fix more tests
This commit is contained in:
parent
21f82dd346
commit
559127dfa3
21 changed files with 320 additions and 891 deletions
|
|
@ -42,7 +42,6 @@ import fr.free.nrw.commons.logging.LogUtils;
|
|||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.ContributionUtils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -113,9 +112,6 @@ public class CommonsApplication extends Application {
|
|||
// TODO: Remove when we're able to initialize Fresco in test builds.
|
||||
}
|
||||
|
||||
// Empty temp directory in case some temp files are created and never removed.
|
||||
ContributionUtils.emptyTemporaryDirectory();
|
||||
|
||||
if (BuildConfig.DEBUG && !isRoboUnitTest()) {
|
||||
Stetho.initializeWithDefaults(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
|
||||
public void startMainActivity() {
|
||||
NavigationBaseActivity.startActivityWithFlags(this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavigationBaseActivity.startActivityWithFlags(this, MainActivity.class, Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
|
||||
|
|
@ -59,40 +59,40 @@ public class ContributionController {
|
|||
this.directKvStore = directKvStore;
|
||||
}
|
||||
|
||||
public void initiateCameraPick(Fragment fragment,
|
||||
public void initiateCameraPick(Activity activity,
|
||||
int requestCode) {
|
||||
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
|
||||
if (!useExtStorage) {
|
||||
initiateCameraUpload(fragment, requestCode);
|
||||
initiateCameraUpload(activity, requestCode);
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionUtils.checkPermissionsAndPerformAction(fragment.getActivity(),
|
||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
() -> initiateCameraUpload(fragment, requestCode),
|
||||
() -> initiateCameraUpload(activity, requestCode),
|
||||
R.string.storage_permission_title,
|
||||
R.string.write_storage_permission_rationale);
|
||||
}
|
||||
|
||||
public void initiateGalleryPick(Fragment fragment,
|
||||
public void initiateGalleryPick(Activity activity,
|
||||
int imageLimit,
|
||||
int requestCode) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
initiateGalleryUpload(fragment, imageLimit, requestCode);
|
||||
initiateGalleryUpload(activity, imageLimit, requestCode);
|
||||
} else {
|
||||
PermissionUtils.checkPermissionsAndPerformAction(fragment.getActivity(),
|
||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
() -> initiateGalleryUpload(fragment, imageLimit, requestCode),
|
||||
() -> initiateGalleryUpload(activity, imageLimit, requestCode),
|
||||
R.string.storage_permission_title,
|
||||
R.string.read_storage_permission_rationale);
|
||||
}
|
||||
}
|
||||
|
||||
private void initiateGalleryUpload(Fragment fragment,
|
||||
private void initiateGalleryUpload(Activity activity,
|
||||
int imageLimit,
|
||||
int requestCode) {
|
||||
ImagePicker imagePicker = ImagePicker.ImagePickerWithFragment
|
||||
.create(fragment)
|
||||
.create(activity)
|
||||
.showCamera(false)
|
||||
.folderMode(true)
|
||||
.includeVideo(false)
|
||||
|
|
@ -106,9 +106,9 @@ public class ContributionController {
|
|||
}
|
||||
}
|
||||
|
||||
private void initiateCameraUpload(Fragment fragment, int requestCode) {
|
||||
private void initiateCameraUpload(Activity activity, int requestCode) {
|
||||
ImagePicker.cameraOnly()
|
||||
.start(fragment, requestCode);
|
||||
.start(activity, requestCode);
|
||||
}
|
||||
|
||||
public Intent handleImagesPicked(ArrayList<Uri> uriList, int requestCode) {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,6 @@ import android.widget.ListAdapter;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
|
|
@ -31,8 +26,6 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
|||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.IntentUtils;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
|
@ -104,8 +97,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
private void setListeners() {
|
||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(this, CAMERA_UPLOAD_REQUEST_CODE));
|
||||
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(this, MULTIPLE_UPLOAD_IMAGE_LIMIT, GALLERY_UPLOAD_REQUEST_CODE));
|
||||
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(getActivity(), CAMERA_UPLOAD_REQUEST_CODE));
|
||||
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity(), MULTIPLE_UPLOAD_IMAGE_LIMIT, GALLERY_UPLOAD_REQUEST_CODE));
|
||||
}
|
||||
|
||||
private void animateFAB(boolean isFabOpen) {
|
||||
|
|
@ -135,18 +128,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
parentFragment.waitForContributionsListFragment.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (IntentUtils.shouldContributionsListHandle(requestCode, resultCode, data)) {
|
||||
List<Image> images = ImagePicker.getImages(data);
|
||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
||||
startActivity(shareIntent);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Responsible to set progress bar invisible and visible
|
||||
* @param isVisible True when contributions list should be hidden.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
|
|
@ -32,6 +37,8 @@ import fr.free.nrw.commons.nearby.NearbyFragment;
|
|||
import fr.free.nrw.commons.nearby.NearbyNotificationCardView;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.IntentUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.content.ContentResolver.requestSync;
|
||||
|
|
@ -41,6 +48,7 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
|
|||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject ContributionController controller;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.pager)
|
||||
|
|
@ -438,12 +446,13 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
|
|||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (IntentUtils.shouldContributionsListHandle(requestCode, resultCode, data)) {
|
||||
List<Image> images = ImagePicker.getImages(data);
|
||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
||||
startActivity(shareIntent);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
ContributionsListFragment contributionsListFragment =
|
||||
(ContributionsListFragment) contributionsActivityPagerAdapter
|
||||
.getItem(0).getChildFragmentManager()
|
||||
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
contributionsListFragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import fr.free.nrw.commons.category.QueryContinue;
|
|||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
import fr.free.nrw.commons.notification.NotificationUtils;
|
||||
import fr.free.nrw.commons.utils.ContributionUtils;
|
||||
import fr.free.nrw.commons.utils.DateUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import in.yuvi.http.fluent.Http;
|
||||
|
|
@ -910,8 +909,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
return new UploadResult(resultStatus, errorCode);
|
||||
} else {
|
||||
// If success we have to remove file from temp directory
|
||||
ContributionUtils.removeTemporaryFile(fileUri);
|
||||
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
|
||||
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
|
||||
String imageUrl = result.getString("/api/upload/imageinfo/@url");
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
|
@ -31,12 +29,9 @@ import fr.free.nrw.commons.contributions.ContributionController;
|
|||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.UriDeserializer;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.utils.IntentUtils.shouldNearbyHandle;
|
||||
|
||||
public class NearbyListFragment extends DaggerFragment {
|
||||
private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location
|
||||
|
||||
|
|
@ -134,17 +129,6 @@ public class NearbyListFragment extends DaggerFragment {
|
|||
return placeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (shouldNearbyHandle(requestCode, resultCode, data)) {
|
||||
List<Image> images = ImagePicker.getImages(data);
|
||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
||||
startActivity(shareIntent);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bundles for updates in map. Ie. user is moved too much so we need to update nearby markers.
|
||||
* @param bundleForUpdates includes new calculated nearby places.
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ import android.widget.LinearLayout;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
|
@ -868,7 +866,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
if (fabCamera.isShown()) {
|
||||
Timber.d("Camera button tapped. Place: %s", place.toString());
|
||||
storeSharedPrefs();
|
||||
controller.initiateCameraPick(this, NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
||||
controller.initiateCameraPick(getActivity(), NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -876,7 +874,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
if (fabGallery.isShown()) {
|
||||
Timber.d("Gallery button tapped. Place: %s", place.toString());
|
||||
storeSharedPrefs();
|
||||
controller.initiateGalleryPick(this, NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
||||
controller.initiateGalleryPick(getActivity(), NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -886,17 +884,6 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
directKvStore.putJson(PLACE_OBJECT, place);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (IntentUtils.shouldNearbyHandle(requestCode, resultCode, data)) {
|
||||
List<Image> images = ImagePicker.getImages(data);
|
||||
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
|
||||
startActivity(shareIntent);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void openWebView(Uri link) {
|
||||
Utils.handleWebUrl(getContext(), link);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class PlaceRenderer extends Renderer<Place> {
|
|||
} else {
|
||||
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
||||
storeSharedPrefs();
|
||||
controller.initiateCameraPick(fragment, NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
||||
controller.initiateCameraPick(fragment.getActivity(), NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ public class PlaceRenderer extends Renderer<Place> {
|
|||
}else {
|
||||
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
|
||||
storeSharedPrefs();
|
||||
controller.initiateGalleryPick(fragment, NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
||||
controller.initiateGalleryPick(fragment.getActivity(), NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,14 @@ import android.support.annotation.Nullable;
|
|||
|
||||
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;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
@ -94,256 +89,6 @@ public class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static String createCopyPathAndCopy(boolean useExternalStorage,
|
||||
Uri uri,
|
||||
ContentResolver contentResolver,
|
||||
Context context) throws IOException {
|
||||
return useExternalStorage ? createExternalCopyPathAndCopy(uri, contentResolver) :
|
||||
createCopyPathAndCopy(uri, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* In older devices getPath() may fail depending on the source URI. Creating and using a copy of the file seems to work instead.
|
||||
*
|
||||
* @return path of copy
|
||||
*/
|
||||
@Nullable
|
||||
private static String createExternalCopyPathAndCopy(Uri uri, ContentResolver contentResolver) throws IOException {
|
||||
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r");
|
||||
if (parcelFileDescriptor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
|
||||
String copyPath = Environment.getExternalStorageDirectory().toString() + "/CommonsApp/" + new Date().getTime() + "." + getFileExt(uri, contentResolver);
|
||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
newFile.mkdir();
|
||||
FileUtils.copy(fileDescriptor, copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* In older devices getPath() may fail depending on the source URI. Creating and using a copy of the file seems to work instead.
|
||||
*
|
||||
* @return path of copy
|
||||
*/
|
||||
@Nullable
|
||||
private static String createCopyPathAndCopy(Uri uri, Context context) throws IOException {
|
||||
FileDescriptor fileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r").getFileDescriptor();
|
||||
String copyPath = context.getCacheDir().getAbsolutePath() + "/" + new Date().getTime() + "." + getFileExt(uri, context.getContentResolver());
|
||||
FileUtils.copy(fileDescriptor, copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
|
||||
@SuppressLint("NewApi")
|
||||
@Nullable
|
||||
public static String getPath(Context context,
|
||||
Uri uri,
|
||||
boolean useExternalStorage) {
|
||||
|
||||
String returnPath = null;
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
returnPath = Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/document"), Long.valueOf(id));
|
||||
|
||||
returnPath = getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
switch (type) {
|
||||
case "image":
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
case "video":
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
case "audio":
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[]{
|
||||
split[1]
|
||||
};
|
||||
|
||||
returnPath = getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
returnPath = getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
returnPath = uri.getPath();
|
||||
}
|
||||
|
||||
if (returnPath == null) {
|
||||
//fetching path may fail depending on the source URI and all hope is lost
|
||||
//so we will create and use a copy of the file, which seems to work
|
||||
String copyPath = null;
|
||||
try {
|
||||
ParcelFileDescriptor descriptor
|
||||
= context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
if (descriptor != null) {
|
||||
|
||||
if (useExternalStorage) {
|
||||
copyPath = Environment.getExternalStorageDirectory().toString()
|
||||
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
|
||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
newFile.mkdir();
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
copyPath = context.getCacheDir().getAbsolutePath()
|
||||
+ "/" + new Date().getTime() + ".jpg";
|
||||
FileUtils.copy(
|
||||
descriptor.getFileDescriptor(),
|
||||
copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.w(e, "Error in file " + copyPath);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return returnPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Nullable
|
||||
private static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = MediaStore.Images.ImageColumns.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);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Timber.d(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
private static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
*/
|
||||
private static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
*/
|
||||
private static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the URI is owned by the current app.
|
||||
*/
|
||||
public static boolean isSelfOwned(Context context, Uri uri) {
|
||||
return uri.getAuthority().equals(context.getPackageName() + ".provider");
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy content from source file to destination file.
|
||||
*
|
||||
* @param source stream copied from
|
||||
* @param destination stream copied to
|
||||
* @throws IOException thrown when failing to read source or opening destination file
|
||||
*/
|
||||
public static void copy(@NonNull FileInputStream source, @NonNull FileOutputStream destination)
|
||||
throws IOException {
|
||||
FileChannel sourceChannel = source.getChannel();
|
||||
FileChannel destinationChannel = destination.getChannel();
|
||||
sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy content from source file to destination file.
|
||||
*
|
||||
* @param source file descriptor copied from
|
||||
* @param destination file path copied to
|
||||
* @throws IOException thrown when failing to read source or opening destination file
|
||||
*/
|
||||
private static void copy(@NonNull FileDescriptor source, @NonNull String destination)
|
||||
throws IOException {
|
||||
copy(new FileInputStream(source), new FileOutputStream(destination));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read and return the content of a resource file as string.
|
||||
|
|
@ -393,56 +138,6 @@ public class FileUtils {
|
|||
return deletedAll;
|
||||
}
|
||||
|
||||
public static File createAndGetAppLogsFile(String logs) {
|
||||
try {
|
||||
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
if (!commonsAppDirectory.exists()) {
|
||||
commonsAppDirectory.mkdir();
|
||||
}
|
||||
|
||||
File logsFile = new File(commonsAppDirectory, "logs.txt");
|
||||
if (logsFile.exists()) {
|
||||
//old logs file is useless
|
||||
logsFile.delete();
|
||||
}
|
||||
|
||||
logsFile.createNewFile();
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream(logsFile);
|
||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
||||
outputStreamWriter.append(logs);
|
||||
outputStreamWriter.close();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
return logsFile;
|
||||
} catch (IOException ioe) {
|
||||
Timber.e(ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFilename(Uri uri, ContentResolver contentResolver) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
|
||||
return "";
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = result.lastIndexOf('/');
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getFileExt(String fileName) {
|
||||
//Default file extension
|
||||
String extension = ".jpg";
|
||||
|
|
@ -454,10 +149,6 @@ public class FileUtils {
|
|||
return extension;
|
||||
}
|
||||
|
||||
private static String getFileExt(Uri uri, ContentResolver contentResolver) {
|
||||
return getFileExt(getFilename(uri, contentResolver));
|
||||
}
|
||||
|
||||
public static FileInputStream getFileInputStream(String filePath) throws FileNotFoundException {
|
||||
return new FileInputStream(filePath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,6 @@ public class FileUtilsWrapper {
|
|||
|
||||
}
|
||||
|
||||
public String createCopyPathAndCopy(boolean useExtStorage,
|
||||
Uri uri,
|
||||
ContentResolver contentResolver,
|
||||
Context context) throws IOException {
|
||||
return FileUtils.createCopyPathAndCopy(useExtStorage,
|
||||
uri,
|
||||
contentResolver,
|
||||
context);
|
||||
}
|
||||
|
||||
public String getFileExt(String fileName) {
|
||||
return FileUtils.getFileExt(fileName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||
import fr.free.nrw.commons.utils.StringUtils;
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
* Methods for pre-processing images to be uploaded
|
||||
*/
|
||||
@Singleton
|
||||
public class ImageProcessingService {
|
||||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
|
||||
private final ImageUtilsWrapper imageUtilsWrapper;
|
||||
private final MediaWikiApi mwApi;
|
||||
|
||||
@Inject
|
||||
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
||||
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
|
||||
ImageUtilsWrapper imageUtilsWrapper,
|
||||
MediaWikiApi mwApi) {
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
|
||||
|
||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||
this.mwApi = mwApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check image quality before upload
|
||||
* - checks duplicate image
|
||||
* - checks dark image
|
||||
*/
|
||||
public Single<Integer> checkImageQuality(String filePath) {
|
||||
return checkImageQuality(null, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check image quality before upload
|
||||
* - checks duplicate image
|
||||
* - checks dark image
|
||||
* - checks geolocation for image
|
||||
*/
|
||||
public Single<Integer> checkImageQuality(Place place, String filePath) {
|
||||
return Single.zip(
|
||||
checkDuplicateImage(filePath),
|
||||
checkImageGeoLocation(place, filePath),
|
||||
checkDarkImage(filePath), //Returns IMAGE_DARK or IMAGE_OK
|
||||
(dupe, wrongGeo, dark) -> dupe | wrongGeo | dark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate image
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DUPLICATE or IMAGE_OK
|
||||
*/
|
||||
private Single<Integer> checkDuplicateImage(String filePath) {
|
||||
return Single.fromCallable(() ->
|
||||
fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.map(mwApi::existingFile)
|
||||
.map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for dark image
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DARK or IMAGE_OK
|
||||
*/
|
||||
private Single<Integer> checkDarkImage(String filePath) {
|
||||
return Single.fromCallable(() ->
|
||||
fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
|
||||
.flatMap(imageUtilsWrapper::checkIfImageIsTooDark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for image geolocation
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
||||
*/
|
||||
private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
|
||||
if (place == null || StringUtils.isNullOrWhiteSpace(place.getWikiDataEntityId())) {
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
return Single.fromCallable(() -> filePath)
|
||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
||||
.flatMap(geoLocation -> imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation()));
|
||||
}
|
||||
}
|
||||
|
|
@ -69,10 +69,7 @@ import timber.log.Timber;
|
|||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.Result;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
|
||||
|
||||
public class UploadActivity extends AuthenticatedActivity implements UploadView, SimilarImageInterface {
|
||||
@Inject MediaWikiApi mwApi;
|
||||
|
|
@ -643,12 +640,8 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
|
|||
return;
|
||||
}
|
||||
|
||||
if (intent.hasExtra(PLACE_OBJECT)) {
|
||||
Place place = intent.getParcelableExtra(PLACE_OBJECT);
|
||||
presenter.receiveDirect(urisList.get(0), mimeType, source, place);
|
||||
} else {
|
||||
presenter.receive(urisList, mimeType, source);
|
||||
}
|
||||
presenter.receive(urisList, mimeType, source, place);
|
||||
|
||||
resetDirectPrefs();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.database.Cursor;
|
|||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
|
@ -20,24 +19,17 @@ import fr.free.nrw.commons.CommonsApplication;
|
|||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||
import fr.free.nrw.commons.utils.StringUtils;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||
|
||||
public class UploadModel {
|
||||
|
||||
private MediaWikiApi mwApi;
|
||||
|
|
@ -67,9 +59,8 @@ public class UploadModel {
|
|||
private SessionManager sessionManager;
|
||||
private Uri currentMediaUri;
|
||||
private FileUtilsWrapper fileUtilsWrapper;
|
||||
private ImageUtilsWrapper imageUtilsWrapper;
|
||||
private BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
|
||||
private FileProcessor fileProcessor;
|
||||
private final ImageProcessingService imageProcessingService;
|
||||
|
||||
@Inject
|
||||
UploadModel(@Named("licenses") List<String> licenses,
|
||||
|
|
@ -79,13 +70,10 @@ public class UploadModel {
|
|||
MediaWikiApi mwApi,
|
||||
SessionManager sessionManager,
|
||||
FileUtilsWrapper fileUtilsWrapper,
|
||||
ImageUtilsWrapper imageUtilsWrapper,
|
||||
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
|
||||
FileProcessor fileProcessor) {
|
||||
FileProcessor fileProcessor, ImageProcessingService imageProcessingService) {
|
||||
this.licenses = licenses;
|
||||
this.basicKvStore = basicKvStore;
|
||||
this.license = basicKvStore.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
|
||||
this.licensesByName = licensesByName;
|
||||
this.context = context;
|
||||
this.mwApi = mwApi;
|
||||
|
|
@ -93,90 +81,53 @@ public class UploadModel {
|
|||
this.sessionManager = sessionManager;
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.fileProcessor = fileProcessor;
|
||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||
useExtStorage = this.basicKvStore.getBoolean("useExternalStorage", false);
|
||||
this.imageProcessingService = imageProcessingService;
|
||||
useExtStorage = this.basicKvStore.getBoolean("useExternalStorage", false);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
void receive(List<Uri> mediaUri,
|
||||
Observable<UploadItem> preProcessImages(List<Uri> mediaUris,
|
||||
String mimeType,
|
||||
Place place,
|
||||
String source,
|
||||
SimilarImageInterface similarImageInterface) {
|
||||
initDefaultValues();
|
||||
|
||||
Observable<UploadItem> itemObservable = Observable.fromIterable(mediaUri)
|
||||
.map(media -> {
|
||||
currentMediaUri = media;
|
||||
return cacheFileUpload(media);
|
||||
})
|
||||
.map(filePath -> {
|
||||
return Observable.fromIterable(mediaUris)
|
||||
.map(mediaUri -> {
|
||||
if (mediaUri == null || mediaUri.getPath() == null) {
|
||||
return null;
|
||||
}
|
||||
String filePath = mediaUri.getPath();
|
||||
long fileCreatedDate = getFileCreatedDate(currentMediaUri);
|
||||
Uri uri = Uri.fromFile(new File(filePath));
|
||||
String fileExt = fileUtilsWrapper.getFileExt(filePath);
|
||||
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
|
||||
|
||||
fileProcessor.initFileDetails(filePath, context.getContentResolver());
|
||||
UploadItem item = new UploadItem(uri, mimeType, source, fileProcessor.processFileCoordinates(similarImageInterface),
|
||||
fileUtilsWrapper.getFileExt(filePath), null, fileCreatedDate);
|
||||
checkImageQuality(null, null, filePath)
|
||||
.observeOn(Schedulers.io())
|
||||
UploadItem item = new UploadItem(mediaUri, mimeType, source, gpsExtractor,
|
||||
fileExt, place.getWikiDataEntityId(), fileCreatedDate);
|
||||
imageProcessingService.checkImageQuality(place, filePath)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.subscribe(item.imageQuality::onNext, Timber::e);
|
||||
return item;
|
||||
});
|
||||
items = itemObservable.toList().blockingGet();
|
||||
items.get(0).selected = true;
|
||||
items.get(0).first = true;
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
void receiveDirect(Uri media, String mimeType, String source, Place place, SimilarImageInterface similarImageInterface) {
|
||||
initDefaultValues();
|
||||
long fileCreatedDate = getFileCreatedDate(media);
|
||||
String filePath = this.cacheFileUpload(media);
|
||||
Uri uri = Uri.fromFile(new File(filePath));
|
||||
fileProcessor.initFileDetails(filePath, context.getContentResolver());
|
||||
UploadItem item = new UploadItem(uri, mimeType, source, fileProcessor.processFileCoordinates(similarImageInterface),
|
||||
fileUtilsWrapper.getFileExt(filePath), place.getWikiDataEntityId(), fileCreatedDate);
|
||||
item.title.setTitleText(place.getName());
|
||||
item.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
||||
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
|
||||
items = uploadItems;
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UploadItem uploadItem = items.get(0);
|
||||
uploadItem.selected = true;
|
||||
uploadItem.first = true;
|
||||
if (place != null) {
|
||||
uploadItem.title.setTitleText(place.getName());
|
||||
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
||||
//TODO figure out if default descriptions in other languages exist
|
||||
item.descriptions.get(0).setLanguageCode("en");
|
||||
checkImageQuality(place.getWikiDataEntityId(), place.getLocation(), filePath)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(item.imageQuality::onNext, Timber::e);
|
||||
items.add(item);
|
||||
items.get(0).selected = true;
|
||||
items.get(0).first = true;
|
||||
uploadItem.descriptions.get(0).setLanguageCode("en");
|
||||
}
|
||||
|
||||
private Single<Integer> checkImageQuality(String wikiDataEntityId, LatLng latLng, String filePath) {
|
||||
return Single.zip(
|
||||
checkDuplicateFile(filePath),
|
||||
checkImageCoordinates(wikiDataEntityId, latLng, filePath),
|
||||
checkDarkImage(filePath), //Returns IMAGE_DARK or IMAGE_OK
|
||||
(dupe, wrongGeo, dark) -> dupe | wrongGeo | dark);
|
||||
}
|
||||
|
||||
private Single<Integer> checkDarkImage(String filePath) {
|
||||
return Single.fromCallable(() ->
|
||||
fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(file -> bitmapRegionDecoderWrapper.newInstance(file, false))
|
||||
.map(imageUtilsWrapper::checkIfImageIsTooDark);
|
||||
}
|
||||
|
||||
private Single<Integer> checkImageCoordinates(String wikiDataEntityId, LatLng latLng, String filePath) {
|
||||
if (StringUtils.isNullOrWhiteSpace(wikiDataEntityId)) {
|
||||
return Single.just(IMAGE_OK);
|
||||
}
|
||||
return Single.fromCallable(() -> filePath)
|
||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
||||
.map(geoLocation -> imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, latLng))
|
||||
.map(r -> r ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT : IMAGE_OK);
|
||||
}
|
||||
|
||||
private Single<Integer> checkDuplicateFile(String filePath) {
|
||||
return Single.fromCallable(() ->
|
||||
fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.map(mwApi::existingFile)
|
||||
.map(b -> b ? ImageUtils.IMAGE_DUPLICATE : IMAGE_OK);
|
||||
}
|
||||
|
||||
private void initDefaultValues() {
|
||||
|
|
@ -274,8 +225,10 @@ public class UploadModel {
|
|||
}
|
||||
|
||||
public void next() {
|
||||
Timber.d("UploadModel:next; Handling next");
|
||||
if (badImageSubscription != null)
|
||||
badImageSubscription.dispose();
|
||||
Timber.d("UploadModel:next; disposing badImageSubscription");
|
||||
markCurrentUploadVisited();
|
||||
if (currentStepIndex < items.size() + 1) {
|
||||
currentStepIndex++;
|
||||
|
|
@ -325,6 +278,7 @@ public class UploadModel {
|
|||
}
|
||||
|
||||
private void updateItemState() {
|
||||
Timber.d("Updating item state");
|
||||
int count = items.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UploadItem item = items.get(i);
|
||||
|
|
@ -334,6 +288,7 @@ public class UploadModel {
|
|||
}
|
||||
|
||||
private void markCurrentUploadVisited() {
|
||||
Timber.d("Marking current upload visited");
|
||||
if (currentStepIndex < items.size() && currentStepIndex >= 0) {
|
||||
items.get(currentStepIndex).visited = true;
|
||||
}
|
||||
|
|
@ -372,29 +327,6 @@ public class UploadModel {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy files into local storage and return file path
|
||||
* If somehow copy fails, it returns the original path
|
||||
* @param media Uri of the file
|
||||
* @return path of the enw file
|
||||
*/
|
||||
private String cacheFileUpload(Uri media) {
|
||||
String finalFilePath;
|
||||
try {
|
||||
String copyFilePath = fileUtilsWrapper.createCopyPathAndCopy(useExtStorage, media, contentResolver, context);
|
||||
Timber.i("Copied file path is %s", copyFilePath);
|
||||
finalFilePath = copyFilePath;
|
||||
} catch (Exception e) {
|
||||
Timber.w(e, "Error in copying URI %s. Using original file path instead", media.getPath());
|
||||
finalFilePath = media.getPath();
|
||||
}
|
||||
|
||||
if (StringUtils.isNullOrWhiteSpace(finalFilePath)) {
|
||||
finalFilePath = media.getPath();
|
||||
}
|
||||
return finalFilePath;
|
||||
}
|
||||
|
||||
void keepPicture() {
|
||||
items.get(currentStepIndex).imageQuality.onNext(ImageUtils.IMAGE_KEEP);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.net.Uri;
|
|||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -20,7 +19,6 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
|||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
|
@ -64,10 +62,6 @@ public class UploadPresenter {
|
|||
this.mediaWikiApi = mediaWikiApi;
|
||||
}
|
||||
|
||||
void receive(Uri mediaUri, String mimeType, String source) {
|
||||
receive(Collections.singletonList(mediaUri), mimeType, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the items received to {@link #uploadModel} and displays the items.
|
||||
*
|
||||
|
|
@ -76,41 +70,30 @@ public class UploadPresenter {
|
|||
* @param source File source from {@link Contribution.FileSource}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void receive(List<Uri> media, String mimeType, @Contribution.FileSource String source) {
|
||||
Completable.fromRunnable(() -> uploadModel.receive(media, mimeType, source, similarImageInterface))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
updateCards();
|
||||
updateLicenses();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem())
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}, Timber::e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the direct upload item received to {@link #uploadModel} and displays the items.
|
||||
*
|
||||
* @param media The Uri's of the media being uploaded.
|
||||
* @param mimeType the mimeType of the files.
|
||||
* @param source File source from {@link Contribution.FileSource}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void receiveDirect(Uri media, String mimeType,
|
||||
void receive(List<Uri> media,
|
||||
String mimeType,
|
||||
@Contribution.FileSource String source,
|
||||
Place place) {
|
||||
Completable.fromRunnable(() -> uploadModel.receiveDirect(media, mimeType, source, place, similarImageInterface))
|
||||
Observable<UploadItem> uploadItemObservable = uploadModel
|
||||
.preProcessImages(media, mimeType, place, source, similarImageInterface);
|
||||
|
||||
uploadItemObservable
|
||||
.toList()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
.subscribe(uploadItems -> onImagesProcessed(uploadItems, place),
|
||||
throwable -> Timber.e(throwable, "Error occurred in processing images"));
|
||||
}
|
||||
|
||||
private void onImagesProcessed(List<UploadItem> uploadItems, Place place) {
|
||||
uploadModel.onItemsProcessed(place, uploadItems);
|
||||
updateCards();
|
||||
updateLicenses();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem())
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}, Timber::e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the license to parameter and updates {@link UploadActivity}
|
||||
*
|
||||
|
|
@ -129,10 +112,12 @@ public class UploadPresenter {
|
|||
@SuppressLint("CheckResult")
|
||||
void handleNext(Title title,
|
||||
List<Description> descriptions) {
|
||||
Timber.e("Inside handleNext");
|
||||
validateCurrentItemTitle()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(errorCode -> handleImage(errorCode, title, descriptions));
|
||||
.subscribe(errorCode -> handleImage(errorCode, title, descriptions),
|
||||
throwable -> Timber.e(throwable, "Error occurred while handling image"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,33 +136,41 @@ public class UploadPresenter {
|
|||
private void handleImage(Integer errorCode, Title title, List<Description> descriptions) {
|
||||
switch (errorCode) {
|
||||
case EMPTY_TITLE:
|
||||
Timber.d("Title is empty. Showing toast");
|
||||
view.showErrorMessage(R.string.add_title_toast);
|
||||
break;
|
||||
case FILE_NAME_EXISTS:
|
||||
if(getCurrentItem().imageQuality.getValue().equals(IMAGE_KEEP)) {
|
||||
Timber.d("Set title and desc; Show next uploaded item");
|
||||
setTitleAndDescription(title, descriptions);
|
||||
nextUploadedItem();
|
||||
} else {
|
||||
Timber.d("Trying to show duplicate picture popup");
|
||||
view.showDuplicatePicturePopup();
|
||||
}
|
||||
break;
|
||||
case IMAGE_OK:
|
||||
Timber.d("Image is OK. Proceeding");
|
||||
default:
|
||||
Timber.d("Default: Setting title and desc; Show next uploaded item");
|
||||
setTitleAndDescription(title, descriptions);
|
||||
nextUploadedItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void nextUploadedItem() {
|
||||
Timber.d("Trying to show next uploaded item");
|
||||
uploadModel.next();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem()) {
|
||||
Timber.d("Is showing item is true");
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}
|
||||
view.dismissKeyboard();
|
||||
}
|
||||
|
||||
private void setTitleAndDescription(Title title, List<Description> descriptions) {
|
||||
Timber.d("setTitleAndDescription: Setting title and desc");
|
||||
uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* This class includes utility methods for uploading process of images.
|
||||
*/
|
||||
|
||||
public class ContributionUtils {
|
||||
|
||||
private static String TEMP_EXTERNAL_DIRECTORY =
|
||||
android.os.Environment.getExternalStorageDirectory().getPath()+
|
||||
File.separatorChar+"UploadingByCommonsApp";
|
||||
|
||||
/**
|
||||
* Saves images temporarily to a fixed folder and use Uri of that file during upload process.
|
||||
* Otherwise, temporary Uri provided by content provider sometimes points to a null space and
|
||||
* consequently upload fails. See: issue #1400A and E.
|
||||
* Not: Saved image will be deleted, our directory will be empty after upload process.
|
||||
* @return URI of saved image
|
||||
*/
|
||||
public static Uri saveFileBeingUploadedTemporarily(Context context, Uri URIfromContentProvider) {
|
||||
// TODO add exceptions for Google Drive URİ is needed
|
||||
Uri result = null;
|
||||
|
||||
if (checkIfDirectoryExists(TEMP_EXTERNAL_DIRECTORY)) {
|
||||
String destinationFilename = decideTempDestinationFileName();
|
||||
result = saveFileFromURI(context, URIfromContentProvider, destinationFilename);
|
||||
} else { // If directory doesn't exist, create it and recursive call current method to check again
|
||||
|
||||
File file = new File(TEMP_EXTERNAL_DIRECTORY);
|
||||
if (file.mkdirs()) {
|
||||
Timber.d("saveFileBeingUploadedTemporarily() parameters: URI from Content Provider %s", URIfromContentProvider);
|
||||
result = saveFileBeingUploadedTemporarily(context, URIfromContentProvider); // If directory is created
|
||||
} else { //An error occurred to create directory
|
||||
Timber.e("saveFileBeingUploadedTemporarily() parameters: URI from Content Provider %s", URIfromContentProvider);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes temp file created during upload
|
||||
* @param tempFileUri
|
||||
*/
|
||||
public static void removeTemporaryFile(Uri tempFileUri) {
|
||||
//TODO: do I have to notify file system about deletion?
|
||||
File tempFile = new File(tempFileUri.getPath());
|
||||
if (tempFile.exists()) {
|
||||
boolean isDeleted = tempFile.delete();
|
||||
Timber.e("removeTemporaryFile() parameters: URI tempFileUri %s, deleted status %b", tempFileUri, isDeleted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary directory and returns pathname
|
||||
* @return
|
||||
*/
|
||||
private static String decideTempDestinationFileName() {
|
||||
int i = 0;
|
||||
while (new File(TEMP_EXTERNAL_DIRECTORY + File.separatorChar + i + "_tmp").exists()) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Use time stamp for file name, so that two temporary file never has same file name
|
||||
// to prevent previous file reference bug
|
||||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
|
||||
// For multiple uploads, time randomisation should be combined with another random
|
||||
// parameter, since they created at same time
|
||||
int multipleUploadRandomParameter = new Random().nextInt(100);
|
||||
return TEMP_EXTERNAL_DIRECTORY + File.separatorChar + timeStamp + multipleUploadRandomParameter + "_tmp";
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties files in Temporary Directory
|
||||
*/
|
||||
public static void emptyTemporaryDirectory() {
|
||||
File dir = new File(TEMP_EXTERNAL_DIRECTORY);
|
||||
if (dir.isDirectory()) {
|
||||
String[] children = dir.list();
|
||||
|
||||
if (children == null) return;
|
||||
|
||||
for (String child : children) {
|
||||
new File(dir, child).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves file from source URI to destination.
|
||||
* @param sourceUri Uri which points to file to be saved
|
||||
* @param destinationFilename where file will be located at
|
||||
* @return Uri points to file saved
|
||||
*/
|
||||
private static Uri saveFileFromURI(Context context, Uri sourceUri, String destinationFilename) {
|
||||
File file = new File(destinationFilename);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = context.getContentResolver().openInputStream(sourceUri);
|
||||
out = new FileOutputStream(new File(destinationFilename));
|
||||
|
||||
byte[] buf = new byte[1024];
|
||||
int length;
|
||||
while ((length = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, length);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return Uri.parse("file://" + destinationFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if directory exists
|
||||
* @param pathToCheck path of directory to check
|
||||
* @return true if directory exists, false otherwise
|
||||
*/
|
||||
private static boolean checkIfDirectoryExists(String pathToCheck) {
|
||||
File dir = new File(pathToCheck);
|
||||
return dir.exists() && dir.isDirectory();
|
||||
}
|
||||
}
|
||||
|
|
@ -128,44 +128,42 @@ public class ImageUtils {
|
|||
int bitmapHeight = bitmap.getHeight();
|
||||
|
||||
int allPixelsCount = bitmapWidth * bitmapHeight;
|
||||
int[] bitmapPixels = new int[allPixelsCount];
|
||||
Timber.d("total %s", Integer.toString(allPixelsCount));
|
||||
|
||||
bitmap.getPixels(bitmapPixels,0,bitmapWidth,0,0,bitmapWidth,bitmapHeight);
|
||||
int numberOfBrightPixels = 0;
|
||||
int numberOfMediumBrightnessPixels = 0;
|
||||
double brightPixelThreshold = 0.025*allPixelsCount;
|
||||
double mediumBrightPixelThreshold = 0.3*allPixelsCount;
|
||||
double brightPixelThreshold = 0.025 * allPixelsCount;
|
||||
double mediumBrightPixelThreshold = 0.3 * allPixelsCount;
|
||||
|
||||
for (int pixel : bitmapPixels) {
|
||||
for (int x = 0; x < bitmapWidth; x++) {
|
||||
for (int y = 0; y < bitmapHeight; y++) {
|
||||
int pixel = bitmap.getPixel(x, y);
|
||||
int r = Color.red(pixel);
|
||||
int g = Color.green(pixel);
|
||||
int b = Color.blue(pixel);
|
||||
|
||||
int secondMax = r>g ? r:g;
|
||||
double max = (secondMax>b ? secondMax:b)/255.0;
|
||||
int secondMax = r > g ? r : g;
|
||||
double max = (secondMax > b ? secondMax : b) / 255.0;
|
||||
|
||||
int secondMin = r<g ? r:g;
|
||||
double min = (secondMin<b ? secondMin:b)/255.0;
|
||||
int secondMin = r < g ? r : g;
|
||||
double min = (secondMin < b ? secondMin : b) / 255.0;
|
||||
|
||||
double luminance = ((max+min)/2.0)*100;
|
||||
double luminance = ((max + min) / 2.0) * 100;
|
||||
|
||||
int highBrightnessLuminance = 40;
|
||||
int mediumBrightnessLuminance = 26;
|
||||
|
||||
if (luminance<highBrightnessLuminance){
|
||||
if (luminance>mediumBrightnessLuminance){
|
||||
if (luminance < highBrightnessLuminance) {
|
||||
if (luminance > mediumBrightnessLuminance) {
|
||||
numberOfMediumBrightnessPixels++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
numberOfBrightPixels++;
|
||||
}
|
||||
|
||||
if (numberOfBrightPixels>=brightPixelThreshold || numberOfMediumBrightnessPixels>=mediumBrightPixelThreshold){
|
||||
if (numberOfBrightPixels >= brightPixelThreshold || numberOfMediumBrightnessPixels >= mediumBrightPixelThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import android.graphics.BitmapRegionDecoder;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.*;
|
||||
|
|
@ -17,11 +19,18 @@ public class ImageUtilsWrapper {
|
|||
|
||||
}
|
||||
|
||||
public @Result int checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
||||
return ImageUtils.checkIfImageIsTooDark(bitmapRegionDecoder);
|
||||
public Single<Integer> checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
||||
int isImageDark = ImageUtils.checkIfImageIsTooDark(bitmapRegionDecoder);
|
||||
return Single.just(isImageDark)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(Schedulers.computation());
|
||||
}
|
||||
|
||||
public boolean checkImageGeolocationIsDifferent(String geolocationOfFileString, LatLng latLng) {
|
||||
return ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng);
|
||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString, LatLng latLng) {
|
||||
boolean isImageGeoLocationDifferent = ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng);
|
||||
return Single.just(isImageGeoLocationDifferent)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(Schedulers.computation())
|
||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT : ImageUtils.IMAGE_OK);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.BitmapRegionDecoder
|
||||
import android.net.Uri
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.utils.BitmapRegionDecoderWrapper
|
||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper
|
||||
import io.reactivex.Single
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.*
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
|
|
@ -27,6 +23,7 @@ import org.mockito.Mockito.mock
|
|||
import org.mockito.MockitoAnnotations
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
|
|
@ -51,11 +48,9 @@ class UploadModelTest {
|
|||
@Mock
|
||||
internal var fileUtilsWrapper: FileUtilsWrapper? = null
|
||||
@Mock
|
||||
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
|
||||
@Mock
|
||||
internal var bitmapRegionDecoderWrapper: BitmapRegionDecoderWrapper? = null
|
||||
@Mock
|
||||
internal var fileProcessor: FileProcessor? = null
|
||||
@Mock
|
||||
internal var imageProcessingService: ImageProcessingService? = null
|
||||
|
||||
@InjectMocks
|
||||
var uploadModel: UploadModel? = null
|
||||
|
|
@ -67,8 +62,6 @@ class UploadModelTest {
|
|||
|
||||
`when`(context!!.applicationContext)
|
||||
.thenReturn(mock(Application::class.java))
|
||||
`when`(fileUtilsWrapper!!.createCopyPathAndCopy(anyBoolean(), any(Uri::class.java), nullable(ContentResolver::class.java), any(Context::class.java)))
|
||||
.thenReturn("file.jpg")
|
||||
`when`(fileUtilsWrapper!!.getFileExt(anyString()))
|
||||
.thenReturn("jpg")
|
||||
`when`(fileUtilsWrapper!!.getSHA1(any(InputStream::class.java)))
|
||||
|
|
@ -77,12 +70,10 @@ class UploadModelTest {
|
|||
.thenReturn(mock(FileInputStream::class.java))
|
||||
`when`(fileUtilsWrapper!!.getGeolocationOfFile(anyString()))
|
||||
.thenReturn("")
|
||||
`when`(imageUtilsWrapper!!.checkIfImageIsTooDark(any(BitmapRegionDecoder::class.java)))
|
||||
.thenReturn(IMAGE_OK)
|
||||
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(anyString(), any(LatLng::class.java)))
|
||||
.thenReturn(false)
|
||||
`when`(bitmapRegionDecoderWrapper!!.newInstance(any(FileInputStream::class.java), anyBoolean()))
|
||||
.thenReturn(mock(BitmapRegionDecoder::class.java))
|
||||
`when`(imageProcessingService!!.checkImageQuality(anyString()))
|
||||
.thenReturn(Single.just(IMAGE_OK))
|
||||
`when`(imageProcessingService!!.checkImageQuality(any(Place::class.java), anyString()))
|
||||
.thenReturn(Single.just(IMAGE_OK))
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -93,154 +84,99 @@ class UploadModelTest {
|
|||
|
||||
@Test
|
||||
fun receive() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
preProcessImages.doOnComplete {
|
||||
assertTrue(uploadModel!!.items.size == 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun receiveDirect() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.items.size == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyPreviousNotAvailableForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertFalse(uploadModel!!.isPreviousAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNextAvailableForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.isNextAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyPreviousNotAvailable() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertFalse(uploadModel!!.isPreviousAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNextAvailable() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.isNextAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSubmitAvailable() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.isNextAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSubmitAvailableForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.isNextAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentStepForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.currentStep == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentStep() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.currentStep == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getStepCount() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
preProcessImages.doOnComplete {
|
||||
assertTrue(uploadModel!!.stepCount == 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getStepCountForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.stepCount == 3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDirectCount() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.count == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCount() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
preProcessImages.doOnComplete {
|
||||
assertTrue(uploadModel!!.count == 2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getUploads() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
preProcessImages.doOnComplete {
|
||||
assertTrue(uploadModel!!.uploads.size == 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getDirectUploads() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
assertTrue(uploadModel!!.uploads.size == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isTopCardState() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.isTopCardState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isTopCardStateForDirectUpload() {
|
||||
val element = mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", mock(Place::class.java)) { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.isTopCardState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun next() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.currentStep == 1)
|
||||
uploadModel!!.next()
|
||||
assertTrue(uploadModel!!.currentStep == 2)
|
||||
|
|
@ -248,10 +184,10 @@ class UploadModelTest {
|
|||
|
||||
@Test
|
||||
fun previous() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
assertTrue(uploadModel!!.currentStep == 1)
|
||||
uploadModel!!.next()
|
||||
assertTrue(uploadModel!!.currentStep == 2)
|
||||
|
|
@ -261,12 +197,20 @@ class UploadModelTest {
|
|||
|
||||
@Test
|
||||
fun isShowingItem() {
|
||||
val element = mock(Uri::class.java)
|
||||
val element2 = mock(Uri::class.java)
|
||||
val element = getElement()
|
||||
val element2 = getElement()
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadModel!!.receive(uriList, "image/jpeg", "external") { _, _ -> }
|
||||
val preProcessImages = uploadModel!!.preProcessImages(uriList, "image/jpeg", mock(Place::class.java), "external") { _, _ -> }
|
||||
preProcessImages.doOnComplete {
|
||||
assertTrue(uploadModel!!.isShowingItem)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getElement(): Uri {
|
||||
val mock = mock(Uri::class.java)
|
||||
`when`(mock.path).thenReturn(UUID.randomUUID().toString() + "/file.jpg")
|
||||
return mock
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildContributions() {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ package fr.free.nrw.commons.upload
|
|||
import android.net.Uri
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import io.reactivex.Observable
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.*
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
|
||||
class UploadPresenterTest {
|
||||
|
||||
|
|
@ -26,6 +26,12 @@ class UploadPresenterTest {
|
|||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(Uri::class.java),
|
||||
ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.any(Place::class.java),
|
||||
ArgumentMatchers.anyString(),
|
||||
ArgumentMatchers.any(SimilarImageInterface::class.java)))
|
||||
.thenReturn(Observable.just(mock(UploadModel.UploadItem::class.java)))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -33,18 +39,6 @@ class UploadPresenterTest {
|
|||
val element = Mockito.mock(Uri::class.java)
|
||||
val element2 = Mockito.mock(Uri::class.java)
|
||||
var uriList: List<Uri> = mutableListOf<Uri>(element, element2)
|
||||
uploadPresenter!!.receive(uriList, "image/jpeg", "external")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun receiveSingleItem() {
|
||||
val element = Mockito.mock(Uri::class.java)
|
||||
uploadPresenter!!.receive(element, "image/jpeg", "external")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun receiveDirect() {
|
||||
val element = Mockito.mock(Uri::class.java)
|
||||
uploadModel!!.receiveDirect(element, "image/jpeg", "external", Mockito.mock(Place::class.java)) { _, _ -> }
|
||||
uploadPresenter!!.receive(uriList, "image/jpeg", "external", mock(Place::class.java))
|
||||
}
|
||||
}
|
||||
|
|
@ -7,17 +7,6 @@ import org.junit.Test
|
|||
import java.io.*
|
||||
|
||||
class FileUtilsTest {
|
||||
@Test
|
||||
fun copiedFileIsIdenticalToSource() {
|
||||
val source = File.createTempFile("temp", "")
|
||||
val dest = File.createTempFile("temp", "")
|
||||
writeToFile(source, "Hello, World")
|
||||
|
||||
FileUtils.copy(FileInputStream(source), FileOutputStream(dest))
|
||||
|
||||
assertEquals(getString(source), getString(dest))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteFile() {
|
||||
val file = File.createTempFile("testfile", "")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue