mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +01:00 
			
		
		
		
	Upload tests (#2086)
* Add unit tests for upload flows * Tests for upload model * Test fixes * Remove empty test cases * Changes based on comments
This commit is contained in:
		
							parent
							
								
									1070137741
								
							
						
					
					
						commit
						f3a90c004c
					
				
					 19 changed files with 533 additions and 252 deletions
				
			
		|  | @ -119,7 +119,7 @@ public class CommonsApplication extends Application { | |||
|         ContributionUtils.emptyTemporaryDirectory(); | ||||
| 
 | ||||
|         initAcra(); | ||||
|         if (BuildConfig.DEBUG) { | ||||
|         if (BuildConfig.DEBUG && !isRoboUnitTest()) { | ||||
|             Stetho.initializeWithDefaults(this); | ||||
|         } | ||||
| 
 | ||||
|  | @ -162,6 +162,10 @@ public class CommonsApplication extends Application { | |||
|         Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isRoboUnitTest() { | ||||
|         return "robolectric".equals(Build.FINGERPRINT); | ||||
|     } | ||||
| 
 | ||||
|     private ThreadPoolService getFileLoggingThreadPool() { | ||||
|         return new ThreadPoolService.Builder("file-logging-thread") | ||||
|                 .setPriority(Process.THREAD_PRIORITY_LOWEST) | ||||
|  |  | |||
|  | @ -2,37 +2,31 @@ package fr.free.nrw.commons.upload; | |||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.media.ExifInterface; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.os.ParcelFileDescriptor; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| import fr.free.nrw.commons.caching.CacheController; | ||||
| import fr.free.nrw.commons.di.ApplicationlessInjection; | ||||
| import fr.free.nrw.commons.mwapi.CategoryApi; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext; | ||||
| 
 | ||||
| /** | ||||
|  * Processing of the image file that is about to be uploaded via ShareActivity is done here | ||||
|  */ | ||||
| @Singleton | ||||
| public class FileProcessor implements SimilarImageDialogFragment.onResponse { | ||||
| 
 | ||||
|     @Inject | ||||
|  | @ -47,24 +41,23 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse { | |||
|     private String filePath; | ||||
|     private ContentResolver contentResolver; | ||||
|     private GPSExtractor imageObj; | ||||
|     private Context context; | ||||
|     private String decimalCoords; | ||||
|     private ExifInterface exifInterface; | ||||
|     private boolean useExtStorage; | ||||
|     private boolean haveCheckedForOtherImages = false; | ||||
|     private GPSExtractor tempImageObj; | ||||
| 
 | ||||
|     FileProcessor(@NonNull String filePath, ContentResolver contentResolver, Context context) { | ||||
|     @Inject | ||||
|     FileProcessor() { | ||||
|     } | ||||
| 
 | ||||
|     void initFileDetails(@NonNull String filePath, ContentResolver contentResolver) { | ||||
|         this.filePath = filePath; | ||||
|         this.contentResolver = contentResolver; | ||||
|         this.context = context; | ||||
|         ApplicationlessInjection.getInstance(context.getApplicationContext()).getCommonsApplicationComponent().inject(this); | ||||
|         try { | ||||
|             exifInterface=new ExifInterface(filePath); | ||||
|             exifInterface = new ExifInterface(filePath); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e); | ||||
|         } | ||||
|         useExtStorage = prefs.getBoolean("useExternalStorage", true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -85,10 +78,6 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse { | |||
|         return imageObj; | ||||
|     } | ||||
| 
 | ||||
|     String getDecimalCoords() { | ||||
|         return decimalCoords; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Find other images around the same location that were taken within the last 20 sec | ||||
|      * @param similarImageInterface | ||||
|  | @ -142,7 +131,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse { | |||
|      * Then initiates the calls to MediaWiki API through an instance of CategoryApi. | ||||
|      */ | ||||
|     @SuppressLint("CheckResult") | ||||
|     public void useImageCoords() { | ||||
|     private void useImageCoords() { | ||||
|         if (decimalCoords != null) { | ||||
|             Timber.d("Decimal coords of image: %s", decimalCoords); | ||||
|             Timber.d("is EXIF data present:" + imageObj.imageCoordsExists + " from findOther image"); | ||||
|  |  | |||
|  | @ -234,8 +234,8 @@ public class FileUtils { | |||
|      * @return The value of the _data column, which is typically a file path. | ||||
|      */ | ||||
|     @Nullable | ||||
|     public static String getDataColumn(Context context, Uri uri, String selection, | ||||
|                                        String[] selectionArgs) { | ||||
|     private static String getDataColumn(Context context, Uri uri, String selection, | ||||
|                                         String[] selectionArgs) { | ||||
| 
 | ||||
|         Cursor cursor = null; | ||||
|         final String column = MediaStore.Images.ImageColumns.DATA; | ||||
|  | @ -311,7 +311,7 @@ public class FileUtils { | |||
|      * @param destination file path copied to | ||||
|      * @throws IOException thrown when failing to read source or opening destination file | ||||
|      */ | ||||
|     public static void copy(@NonNull FileDescriptor source, @NonNull String destination) | ||||
|     private static void copy(@NonNull FileDescriptor source, @NonNull String destination) | ||||
|             throws IOException { | ||||
|         copy(new FileInputStream(source), new FileOutputStream(destination)); | ||||
|     } | ||||
|  | @ -415,7 +415,7 @@ public class FileUtils { | |||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     public static String getFileExt(String fileName){ | ||||
|     static String getFileExt(String fileName){ | ||||
|         //Default file extension | ||||
|         String extension=".jpg"; | ||||
| 
 | ||||
|  | @ -426,7 +426,11 @@ public class FileUtils { | |||
|         return extension; | ||||
|     } | ||||
| 
 | ||||
|     public static String getFileExt(Uri uri, ContentResolver contentResolver) { | ||||
|     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); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,42 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import java.io.FileInputStream; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Singleton; | ||||
| 
 | ||||
| @Singleton | ||||
| public class FileUtilsWrapper { | ||||
| 
 | ||||
|     @Inject | ||||
|     public FileUtilsWrapper() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public String createExternalCopyPathAndCopy(Uri uri, ContentResolver contentResolver) throws IOException { | ||||
|         return FileUtils.createExternalCopyPathAndCopy(uri, contentResolver); | ||||
|     } | ||||
| 
 | ||||
|     public String createCopyPathAndCopy(Uri uri, Context context) throws IOException { | ||||
|         return FileUtils.createCopyPathAndCopy(uri, context); | ||||
|     } | ||||
| 
 | ||||
|     public String getFileExt(String fileName) { | ||||
|         return FileUtils.getFileExt(fileName); | ||||
|     } | ||||
| 
 | ||||
|     public String getSHA1(InputStream is) { | ||||
|         return FileUtils.getSHA1(is); | ||||
|     } | ||||
| 
 | ||||
|     public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException { | ||||
|         return FileUtils.getFileInputStream(filePath); | ||||
|     } | ||||
| } | ||||
|  | @ -14,12 +14,12 @@ import timber.log.Timber; | |||
|  * Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation | ||||
|  * is uploaded, extract latitude and longitude from EXIF data of image. | ||||
|  */ | ||||
| public class GPSExtractor { | ||||
| class GPSExtractor { | ||||
| 
 | ||||
|     public static final GPSExtractor DUMMY= new GPSExtractor(); | ||||
|     static final GPSExtractor DUMMY= new GPSExtractor(); | ||||
|     private double decLatitude; | ||||
|     private double decLongitude; | ||||
|     public boolean imageCoordsExists; | ||||
|     boolean imageCoordsExists; | ||||
|     private String latitude; | ||||
|     private String longitude; | ||||
|     private String latitudeRef; | ||||
|  | @ -37,7 +37,7 @@ public class GPSExtractor { | |||
|      * @param fileDescriptor the file descriptor of the image | ||||
|      */ | ||||
|     @RequiresApi(24) | ||||
|     public GPSExtractor(@NonNull FileDescriptor fileDescriptor) { | ||||
|     GPSExtractor(@NonNull FileDescriptor fileDescriptor) { | ||||
|         try { | ||||
|             ExifInterface exif = new ExifInterface(fileDescriptor); | ||||
|             processCoords(exif); | ||||
|  | @ -51,7 +51,7 @@ public class GPSExtractor { | |||
|      * @param path file path of the image | ||||
|      * | ||||
|      */ | ||||
|     public GPSExtractor(@NonNull String path) { | ||||
|     GPSExtractor(@NonNull String path) { | ||||
|         try { | ||||
|             ExifInterface exif = new ExifInterface(path); | ||||
|             processCoords(exif); | ||||
|  | @ -65,7 +65,7 @@ public class GPSExtractor { | |||
|      * @param exif exif interface of the image | ||||
|      * | ||||
|      */ | ||||
|     public GPSExtractor(@NonNull ExifInterface exif){ | ||||
|     GPSExtractor(@NonNull ExifInterface exif){ | ||||
|         processCoords(exif); | ||||
|     } | ||||
| 
 | ||||
|  | @ -89,7 +89,7 @@ public class GPSExtractor { | |||
|      * @return coordinates as string (needs to be passed as a String in API query) | ||||
|      */ | ||||
|     @Nullable | ||||
|     public String getCoords() { | ||||
|     String getCoords() { | ||||
|         if(decimalCoords!=null){ | ||||
|             return decimalCoords; | ||||
|         }else if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) { | ||||
|  | @ -103,11 +103,11 @@ public class GPSExtractor { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public double getDecLatitude() { | ||||
|     double getDecLatitude() { | ||||
|         return decLatitude; | ||||
|     } | ||||
| 
 | ||||
|     public double getDecLongitude() { | ||||
|     double getDecLongitude() { | ||||
|         return decLongitude; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ public class UploadController { | |||
|     } | ||||
| 
 | ||||
|     private boolean isUploadServiceConnected; | ||||
|     private ServiceConnection uploadServiceConnection = new ServiceConnection() { | ||||
|     public ServiceConnection uploadServiceConnection = new ServiceConnection() { | ||||
|         @Override | ||||
|         public void onServiceConnected(ComponentName componentName, IBinder binder) { | ||||
|             uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService(); | ||||
|  | @ -61,6 +61,7 @@ public class UploadController { | |||
|         @Override | ||||
|         public void onServiceDisconnected(ComponentName componentName) { | ||||
|             // this should never happen | ||||
|             isUploadServiceConnected = false; | ||||
|             Timber.e(new RuntimeException("UploadService died but the rest of the process did not!")); | ||||
|         } | ||||
|     }; | ||||
|  | @ -68,7 +69,7 @@ public class UploadController { | |||
|     /** | ||||
|      * Prepares the upload service. | ||||
|      */ | ||||
|     public void prepareService() { | ||||
|     void prepareService() { | ||||
|         Intent uploadServiceIntent = new Intent(context, UploadService.class); | ||||
|         uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); | ||||
|         context.startService(uploadServiceIntent); | ||||
|  | @ -78,7 +79,7 @@ public class UploadController { | |||
|     /** | ||||
|      * Disconnects the upload service. | ||||
|      */ | ||||
|     public void cleanup() { | ||||
|     void cleanup() { | ||||
|         if (isUploadServiceConnected) { | ||||
|             context.unbindService(uploadServiceConnection); | ||||
|         } | ||||
|  | @ -89,7 +90,7 @@ public class UploadController { | |||
|      * | ||||
|      * @param contribution the contribution object | ||||
|      */ | ||||
|     public void startUpload(Contribution contribution) { | ||||
|     void startUpload(Contribution contribution) { | ||||
|         startUpload(contribution, c -> {}); | ||||
|     } | ||||
| 
 | ||||
|  | @ -100,7 +101,7 @@ public class UploadController { | |||
|      * @param onComplete   the progress tracker | ||||
|      */ | ||||
|     @SuppressLint("StaticFieldLeak") | ||||
|     public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { | ||||
|     private void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { | ||||
|         //Set creator, desc, and license | ||||
|         if (TextUtils.isEmpty(contribution.getCreator())) { | ||||
|             Account currentAccount = sessionManager.getCurrentAccount(); | ||||
|  |  | |||
|  | @ -53,16 +53,20 @@ public class UploadModel { | |||
|     private boolean useExtStorage; | ||||
|     private Disposable badImageSubscription; | ||||
| 
 | ||||
|     @Inject | ||||
|     SessionManager sessionManager; | ||||
|     private SessionManager sessionManager; | ||||
|     private Uri currentMediaUri; | ||||
|     private FileUtilsWrapper fileUtilsWrapper; | ||||
|     private FileProcessor fileProcessor; | ||||
| 
 | ||||
|     @Inject | ||||
|     UploadModel(@Named("licenses") List<String> licenses, | ||||
|                 @Named("default_preferences") SharedPreferences prefs, | ||||
|                 @Named("licenses_by_name") Map<String, String> licensesByName, | ||||
|                 Context context, | ||||
|                 MediaWikiApi mwApi) { | ||||
|                 MediaWikiApi mwApi, | ||||
|                 SessionManager sessionManager, | ||||
|                 FileUtilsWrapper fileUtilsWrapper, | ||||
|                 FileProcessor fileProcessor) { | ||||
|         this.licenses = licenses; | ||||
|         this.prefs = prefs; | ||||
|         this.license = Prefs.Licenses.CC_BY_SA_3; | ||||
|  | @ -70,6 +74,9 @@ public class UploadModel { | |||
|         this.context = context; | ||||
|         this.mwApi = mwApi; | ||||
|         this.contentResolver = context.getContentResolver(); | ||||
|         this.sessionManager = sessionManager; | ||||
|         this.fileUtilsWrapper = fileUtilsWrapper; | ||||
|         this.fileProcessor = fileProcessor; | ||||
|         useExtStorage = this.prefs.getBoolean("useExternalStorage", false); | ||||
|     } | ||||
| 
 | ||||
|  | @ -84,17 +91,17 @@ public class UploadModel { | |||
|                 .map(filePath -> { | ||||
|                     long fileCreatedDate = getFileCreatedDate(currentMediaUri); | ||||
|                     Uri uri = Uri.fromFile(new File(filePath)); | ||||
|                     FileProcessor fp = new FileProcessor(filePath, context.getContentResolver(), context); | ||||
|                     UploadItem item = new UploadItem(uri, mimeType, source, fp.processFileCoordinates(similarImageInterface), | ||||
|                             FileUtils.getFileExt(filePath), null,fileCreatedDate); | ||||
|                     fileProcessor.initFileDetails(filePath, context.getContentResolver()); | ||||
|                     UploadItem item = new UploadItem(uri, mimeType, source, fileProcessor.processFileCoordinates(similarImageInterface), | ||||
|                             fileUtilsWrapper.getFileExt(filePath), null,fileCreatedDate); | ||||
|                     Single.zip( | ||||
|                             Single.fromCallable(() -> | ||||
|                                     new FileInputStream(filePath)) | ||||
|                                     .map(FileUtils::getSHA1) | ||||
|                                     fileUtilsWrapper.getFileInputStream(filePath)) | ||||
|                                     .map(fileUtilsWrapper::getSHA1) | ||||
|                                     .map(mwApi::existingFile) | ||||
|                                     .map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK), | ||||
|                             Single.fromCallable(() -> | ||||
|                                     new FileInputStream(filePath)) | ||||
|                                     fileUtilsWrapper.getFileInputStream(filePath)) | ||||
|                                     .map(file -> BitmapRegionDecoder.newInstance(file, false)) | ||||
|                                     .map(ImageUtils::checkIfImageIsTooDark), //Returns IMAGE_DARK or IMAGE_OK | ||||
|                             (dupe, dark) -> dupe | dark) | ||||
|  | @ -113,24 +120,24 @@ public class UploadModel { | |||
|         long fileCreatedDate = getFileCreatedDate(media); | ||||
|         String filePath = this.cacheFileUpload(media); | ||||
|         Uri uri = Uri.fromFile(new File(filePath)); | ||||
|         FileProcessor fp = new FileProcessor(filePath, context.getContentResolver(), context); | ||||
|         UploadItem item = new UploadItem(uri, mimeType, source, fp.processFileCoordinates(similarImageInterface), | ||||
|                 FileUtils.getFileExt(filePath), wikidataEntityIdPref,fileCreatedDate); | ||||
|         fileProcessor.initFileDetails(filePath, context.getContentResolver()); | ||||
|         UploadItem item = new UploadItem(uri, mimeType, source, fileProcessor.processFileCoordinates(similarImageInterface), | ||||
|                 fileUtilsWrapper.getFileExt(filePath), wikidataEntityIdPref,fileCreatedDate); | ||||
|         item.title.setTitleText(title); | ||||
|         item.descriptions.get(0).setDescriptionText(desc); | ||||
|         //TODO figure out if default descriptions in other languages exist | ||||
|         item.descriptions.get(0).setLanguageCode("en"); | ||||
|         Single.zip( | ||||
|                 Single.fromCallable(() -> | ||||
|                         new FileInputStream(filePath)) | ||||
|                         .map(FileUtils::getSHA1) | ||||
|                         fileUtilsWrapper.getFileInputStream(filePath)) | ||||
|                         .map(fileUtilsWrapper::getSHA1) | ||||
|                         .map(mwApi::existingFile) | ||||
|                         .map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK), | ||||
|                 Single.fromCallable(() -> | ||||
|                         new FileInputStream(filePath)) | ||||
|                         fileUtilsWrapper.getFileInputStream(filePath)) | ||||
|                         .map(file -> BitmapRegionDecoder.newInstance(file, false)) | ||||
|                         .map(ImageUtils::checkIfImageIsTooDark), //Returns IMAGE_DARK or IMAGE_OK | ||||
|                 (dupe, dark) -> dupe | dark).subscribe(item.imageQuality::onNext); | ||||
|                 (dupe, dark) -> dupe | dark).subscribe(item.imageQuality::onNext, Timber::e); | ||||
|         items.add(item); | ||||
|         items.get(0).selected = true; | ||||
|         items.get(0).first = true; | ||||
|  | @ -239,7 +246,7 @@ public class UploadModel { | |||
|         updateItemState(); | ||||
|     } | ||||
| 
 | ||||
|     public void setCurrentTitleAndDescriptions(Title title, List<Description> descriptions) { | ||||
|     void setCurrentTitleAndDescriptions(Title title, List<Description> descriptions) { | ||||
|         setCurrentUploadTitle(title); | ||||
|         setCurrentUploadDescriptions(descriptions); | ||||
|     } | ||||
|  | @ -337,9 +344,9 @@ public class UploadModel { | |||
|         try { | ||||
|             String copyPath; | ||||
|             if (useExtStorage) | ||||
|                 copyPath = FileUtils.createExternalCopyPathAndCopy(media, contentResolver); | ||||
|                 copyPath = fileUtilsWrapper.createExternalCopyPathAndCopy(media, contentResolver); | ||||
|             else | ||||
|                 copyPath = FileUtils.createCopyPathAndCopy(media, context); | ||||
|                 copyPath = fileUtilsWrapper.createCopyPathAndCopy(media, context); | ||||
|             Timber.i("File path is " + copyPath); | ||||
|             return copyPath; | ||||
|         } catch (IOException e) { | ||||
|  | @ -362,6 +369,9 @@ public class UploadModel { | |||
|         badImageSubscription = getCurrentItem().imageQuality.subscribe(consumer, Timber::e); | ||||
|     } | ||||
| 
 | ||||
|     public List<UploadItem> getItems() { | ||||
|         return items; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     static class UploadItem { | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         String notificationProgressTitle; | ||||
|         String notificationFinishingTitle; | ||||
| 
 | ||||
|         public NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, String notificationFinishingTitle, Contribution contribution) { | ||||
|         NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, String notificationFinishingTitle, Contribution contribution) { | ||||
|             this.notificationTag = notificationTag; | ||||
|             this.notificationProgressTitle = notificationProgressTitle; | ||||
|             this.notificationFinishingTitle = notificationFinishingTitle; | ||||
|  |  | |||
|  | @ -1,71 +0,0 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| /** | ||||
|  * This is a Util class which provides the necessary token to open the Commons License | ||||
|  * info in the user language | ||||
|  */ | ||||
| public class UrlLicense { | ||||
|     public static HashMap<String,String> urlLicense = new HashMap<>(); | ||||
|     static { | ||||
|         urlLicense.put("en", "https://commons.wikimedia.org/wiki/Commons:Licensing"); | ||||
|         urlLicense.put("ar", "https://commons.wikimedia.org/wiki/Commons:Licensing/ar"); | ||||
|         urlLicense.put("ast", "https://commons.wikimedia.org/wiki/Commons:Licensing/ast"); | ||||
|         urlLicense.put("az", "https://commons.wikimedia.org/wiki/Commons:Licensing/az"); | ||||
|         urlLicense.put("be", "https://commons.wikimedia.org/wiki/Commons:Licensing/be"); | ||||
|         urlLicense.put("bg", "https://commons.wikimedia.org/wiki/Commons:Licensing/bg"); | ||||
|         urlLicense.put("bn", "https://commons.wikimedia.org/wiki/Commons:Licensing/bn"); | ||||
|         urlLicense.put("ca", "https://commons.wikimedia.org/wiki/Commons:Licensing/ca"); | ||||
|         urlLicense.put("cs", "https://commons.wikimedia.org/wiki/Commons:Licensing/cs"); | ||||
|         urlLicense.put("da", "https://commons.wikimedia.org/wiki/Commons:Licensing/da"); | ||||
|         urlLicense.put("de", "https://commons.wikimedia.org/wiki/Commons:Licensing/de"); | ||||
|         urlLicense.put("el", "https://commons.wikimedia.org/wiki/Commons:Licensing/el"); | ||||
|         urlLicense.put("eo", "https://commons.wikimedia.org/wiki/Commons:Licensing/eo"); | ||||
|         urlLicense.put("es", "https://commons.wikimedia.org/wiki/Commons:Licensing/es"); | ||||
|         urlLicense.put("eu", "https://commons.wikimedia.org/wiki/Commons:Licensing/eu"); | ||||
|         urlLicense.put("fa", "https://commons.wikimedia.org/wiki/Commons:Licensing/fa"); | ||||
|         urlLicense.put("fi", "https://commons.wikimedia.org/wiki/Commons:Licensing/fi"); | ||||
|         urlLicense.put("fr", "https://commons.wikimedia.org/wiki/Commons:Licensing/fr"); | ||||
|         urlLicense.put("gl", "https://commons.wikimedia.org/wiki/Commons:Licensing/gl"); | ||||
|         urlLicense.put("gsw", "https://commons.wikimedia.org/wiki/Commons:Licensing/gsw"); | ||||
|         urlLicense.put("he", "https://commons.wikimedia.org/wiki/Commons:Licensing/he"); | ||||
|         urlLicense.put("hi", "https://commons.wikimedia.org/wiki/Commons:Licensing/hi"); | ||||
|         urlLicense.put("hu", "https://commons.wikimedia.org/wiki/Commons:Licensing/hu"); | ||||
|         urlLicense.put("id", "https://commons.wikimedia.org/wiki/Commons:Licensing/id"); | ||||
|         urlLicense.put("is", "https://commons.wikimedia.org/wiki/Commons:Licensing/is"); | ||||
|         urlLicense.put("it", "https://commons.wikimedia.org/wiki/Commons:Licensing/it"); | ||||
|         urlLicense.put("ja", "https://commons.wikimedia.org/wiki/Commons:Licensing/ja"); | ||||
|         urlLicense.put("ka", "https://commons.wikimedia.org/wiki/Commons:Licensing/ka"); | ||||
|         urlLicense.put("km", "https://commons.wikimedia.org/wiki/Commons:Licensing/km"); | ||||
|         urlLicense.put("ko", "https://commons.wikimedia.org/wiki/Commons:Licensing/ko"); | ||||
|         urlLicense.put("ku", "https://commons.wikimedia.org/wiki/Commons:Licensing/ku"); | ||||
|         urlLicense.put("mk", "https://commons.wikimedia.org/wiki/Commons:Licensing/mk"); | ||||
|         urlLicense.put("mr", "https://commons.wikimedia.org/wiki/Commons:Licensing/mr"); | ||||
|         urlLicense.put("ms", "https://commons.wikimedia.org/wiki/Commons:Licensing/ms"); | ||||
|         urlLicense.put("my", "https://commons.wikimedia.org/wiki/Commons:Licensing/my"); | ||||
|         urlLicense.put("nl", "https://commons.wikimedia.org/wiki/Commons:Licensing/nl"); | ||||
|         urlLicense.put("oc", "https://commons.wikimedia.org/wiki/Commons:Licensing/oc"); | ||||
|         urlLicense.put("pl", "https://commons.wikimedia.org/wiki/Commons:Licensing/pl"); | ||||
|         urlLicense.put("pt", "https://commons.wikimedia.org/wiki/Commons:Licensing/pt"); | ||||
|         urlLicense.put("pt-br", "https://commons.wikimedia.org/wiki/Commons:Licensing/pt-br"); | ||||
|         urlLicense.put("ro", "https://commons.wikimedia.org/wiki/Commons:Licensing/ro"); | ||||
|         urlLicense.put("ru", "https://commons.wikimedia.org/wiki/Commons:Licensing/ru"); | ||||
|         urlLicense.put("scn", "https://commons.wikimedia.org/wiki/Commons:Licensing/scn"); | ||||
|         urlLicense.put("sk", "https://commons.wikimedia.org/wiki/Commons:Licensing/sk"); | ||||
|         urlLicense.put("sl", "https://commons.wikimedia.org/wiki/Commons:Licensing/sl"); | ||||
|         urlLicense.put("sv", "https://commons.wikimedia.org/wiki/Commons:Licensing/sv"); | ||||
|         urlLicense.put("tr", "https://commons.wikimedia.org/wiki/Commons:Licensing/tr"); | ||||
|         urlLicense.put("uk", "https://commons.wikimedia.org/wiki/Commons:Licensing/uk"); | ||||
|         urlLicense.put("ur", "https://commons.wikimedia.org/wiki/Commons:Licensing/ur"); | ||||
|         urlLicense.put("vi", "https://commons.wikimedia.org/wiki/Commons:Licensing/vi"); | ||||
|         urlLicense.put("zh", "https://commons.wikimedia.org/wiki/Commons:Licensing/zh"); | ||||
|     } | ||||
|     public static String getLicenseUrl ( String language){ | ||||
|         if (urlLicense.containsKey(language)) { | ||||
|             return urlLicense.get(language); | ||||
|         } else { | ||||
|             return urlLicense.get("en"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,115 +0,0 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.content.ContentResolver; | ||||
| import android.graphics.Bitmap; | ||||
| import android.graphics.BitmapRegionDecoder; | ||||
| import android.graphics.Point; | ||||
| import android.graphics.Rect; | ||||
| import android.net.Uri; | ||||
| import android.provider.MediaStore; | ||||
| import android.support.v4.graphics.BitmapCompat; | ||||
| import android.view.View; | ||||
| import android.widget.FrameLayout; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| 
 | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * Contains utility methods for the Zoom function in ShareActivity. | ||||
|  */ | ||||
| public class Zoom { | ||||
| 
 | ||||
|     private View thumbView; | ||||
|     private ContentResolver contentResolver; | ||||
|     private FrameLayout flContainer; | ||||
| 
 | ||||
|     Zoom(View thumbView, FrameLayout flContainer, ContentResolver contentResolver) { | ||||
|         this.thumbView = thumbView; | ||||
|         this.contentResolver = contentResolver; | ||||
|         this.flContainer = flContainer; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a scaled bitmap to display the zoomed-in image | ||||
|      * @param input the input stream corresponding to the uploaded image | ||||
|      * @param imageUri the uploaded image's URI | ||||
|      * @return a zoomable bitmap | ||||
|      */ | ||||
|     Bitmap createScaledImage(InputStream input, Uri imageUri) { | ||||
| 
 | ||||
|         Bitmap scaled = null; | ||||
|         BitmapRegionDecoder decoder = null; | ||||
|         Bitmap bitmap = null; | ||||
| 
 | ||||
|         try { | ||||
|             decoder = BitmapRegionDecoder.newInstance(input, false); | ||||
|             bitmap = decoder.decodeRegion(new Rect(10, 10, 50, 50), null); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e); | ||||
|         } catch (NullPointerException e) { | ||||
|             Timber.e(e); | ||||
|         } | ||||
|         try { | ||||
|             //Compress the Image | ||||
|             System.gc(); | ||||
|             Runtime rt = Runtime.getRuntime(); | ||||
|             long maxMemory = rt.freeMemory(); | ||||
|             bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri); | ||||
|             int bitmapByteCount = BitmapCompat.getAllocationByteCount(bitmap); | ||||
|             long height = bitmap.getHeight(); | ||||
|             long width = bitmap.getWidth(); | ||||
|             long calHeight = (long) ((height * maxMemory) / (bitmapByteCount * 1.1)); | ||||
|             long calWidth = (long) ((width * maxMemory) / (bitmapByteCount * 1.1)); | ||||
|             scaled = Bitmap.createScaledBitmap(bitmap, (int) Math.min(width, calWidth), (int) Math.min(height, calHeight), true); | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e); | ||||
|         } catch (NullPointerException e) { | ||||
|             Timber.e(e); | ||||
|             scaled = bitmap; | ||||
|         } | ||||
|         return scaled; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  Calculate the starting and ending bounds for the zoomed-in image. | ||||
|      *  Also set the container view's offset as the origin for the | ||||
|      * bounds, since that's the origin for the positioning animation | ||||
|      * properties (X, Y). | ||||
|      * @param startBounds the global visible rectangle of the thumbnail | ||||
|      * @param finalBounds the global visible rectangle of the container view | ||||
|      * @param globalOffset the container view's offset | ||||
|      * @return scaled start bounds | ||||
|      */ | ||||
|     float adjustStartEndBounds(Rect startBounds, Rect finalBounds, Point globalOffset) { | ||||
| 
 | ||||
|         thumbView.getGlobalVisibleRect(startBounds); | ||||
|         flContainer.getGlobalVisibleRect(finalBounds, globalOffset); | ||||
|         startBounds.offset(-globalOffset.x, -globalOffset.y); | ||||
|         finalBounds.offset(-globalOffset.x, -globalOffset.y); | ||||
| 
 | ||||
|         // Adjust the start bounds to be the same aspect ratio as the final | ||||
|         // bounds using the "center crop" technique. This prevents undesirable | ||||
|         // stretching during the animation. Also calculate the start scaling | ||||
|         // factor (the end scaling factor is always 1.0). | ||||
|         float startScale; | ||||
|         if ((float) finalBounds.width() / finalBounds.height() | ||||
|                 > (float) startBounds.width() / startBounds.height()) { | ||||
|             // Extend start bounds horizontally | ||||
|             startScale = (float) startBounds.height() / finalBounds.height(); | ||||
|             float startWidth = startScale * finalBounds.width(); | ||||
|             float deltaWidth = (startWidth - startBounds.width()) / 2; | ||||
|             startBounds.left -= deltaWidth; | ||||
|             startBounds.right += deltaWidth; | ||||
|         } else { | ||||
|             // Extend start bounds vertically | ||||
|             startScale = (float) startBounds.width() / finalBounds.width(); | ||||
|             float startHeight = startScale * finalBounds.height(); | ||||
|             float deltaHeight = (startHeight - startBounds.height()) / 2; | ||||
|             startBounds.top -= deltaHeight; | ||||
|             startBounds.bottom += deltaHeight; | ||||
|         } | ||||
|         return startScale; | ||||
|     } | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| package fr.free.nrw.commons.widget; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
|  | @ -13,10 +13,7 @@ import android.view.Display; | |||
|  * Created by Ilgaz Er on 8/7/2018. | ||||
|  */ | ||||
| public class HeightLimitedRecyclerView extends RecyclerView { | ||||
| 
 | ||||
|     int height; | ||||
| 
 | ||||
| 
 | ||||
|     public HeightLimitedRecyclerView(Context context) { | ||||
|         super(context); | ||||
|         DisplayMetrics displayMetrics = new DisplayMetrics(); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vivek Maskara
						Vivek Maskara