mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-31 23:03:54 +01:00
Merge "commons" into the project root directory
This commit is contained in:
parent
d42db0612e
commit
b4231bbfdc
324 changed files with 22 additions and 23 deletions
138
app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
Normal file
138
app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.DocumentsContract;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
|
||||
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)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[]{
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_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);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
}
|
||||
202
app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
Normal file
202
app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Criteria;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.media.ExifInterface;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 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. If a picture without
|
||||
* geolocation is uploaded, retrieve user's location (if enabled in Settings).
|
||||
*/
|
||||
public class GPSExtractor {
|
||||
|
||||
private static final String TAG = GPSExtractor.class.getName();
|
||||
|
||||
private String filePath;
|
||||
private double decLatitude, decLongitude;
|
||||
private double currentLatitude, currentLongitude;
|
||||
private Context context;
|
||||
public boolean imageCoordsExists;
|
||||
private MyLocationListener myLocationListener;
|
||||
private LocationManager locationManager;
|
||||
private String provider;
|
||||
private Criteria criteria;
|
||||
|
||||
public GPSExtractor(String filePath, Context context){
|
||||
this.filePath = filePath;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user enabled retrieval of their current location in Settings
|
||||
* @return true if enabled, false if disabled
|
||||
*/
|
||||
private boolean gpsPreferenceEnabled() {
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean gpsPref = sharedPref.getBoolean("allowGps", false);
|
||||
Log.d(TAG, "Gps pref set to: " + gpsPref);
|
||||
return gpsPref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a LocationManager to listen for current location
|
||||
*/
|
||||
protected void registerLocationManager() {
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
criteria = new Criteria();
|
||||
provider = locationManager.getBestProvider(criteria, true);
|
||||
myLocationListener = new MyLocationListener();
|
||||
|
||||
locationManager.requestLocationUpdates(provider, 400, 1, myLocationListener);
|
||||
Location location = locationManager.getLastKnownLocation(provider);
|
||||
if (location != null) {
|
||||
myLocationListener.onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unregisterLocationManager() {
|
||||
locationManager.removeUpdates(myLocationListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts geolocation of image from EXIF data.
|
||||
* @return coordinates of image as string (needs to be passed as a String in API query)
|
||||
*/
|
||||
public String getCoords(boolean useGPS) {
|
||||
|
||||
ExifInterface exif;
|
||||
String latitude = "";
|
||||
String longitude = "";
|
||||
String latitude_ref = "";
|
||||
String longitude_ref = "";
|
||||
String decimalCoords = "";
|
||||
|
||||
try {
|
||||
exif = new ExifInterface(filePath);
|
||||
} catch (IOException e) {
|
||||
Log.w("Image", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null && useGPS) {
|
||||
registerLocationManager();
|
||||
|
||||
imageCoordsExists = false;
|
||||
Log.d(TAG, "EXIF data has no location info");
|
||||
|
||||
//Check what user's preference is for automatic location detection
|
||||
boolean gpsPrefEnabled = gpsPreferenceEnabled();
|
||||
|
||||
if (gpsPrefEnabled) {
|
||||
Log.d(TAG, "Current location values: Lat = " + currentLatitude + " Long = " + currentLongitude);
|
||||
return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
|
||||
} else {
|
||||
// No coords found
|
||||
return null;
|
||||
}
|
||||
} else if(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
|
||||
return null;
|
||||
} else {
|
||||
imageCoordsExists = true;
|
||||
Log.d(TAG, "EXIF data has location info");
|
||||
|
||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
Log.d("Image", "Latitude: " + latitude + " " + latitude_ref);
|
||||
Log.d("Image", "Longitude: " + longitude + " " + longitude_ref);
|
||||
|
||||
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
|
||||
return decimalCoords;
|
||||
}
|
||||
}
|
||||
|
||||
private class MyLocationListener implements LocationListener {
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
currentLatitude = location.getLatitude();
|
||||
currentLongitude = location.getLongitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
Log.d(TAG, provider + "'s status changed to " + status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
Log.d(TAG, "Provider " + provider + " enabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
Log.d(TAG, "Provider " + provider + " disabled");
|
||||
}
|
||||
}
|
||||
|
||||
public double getDecLatitude() {
|
||||
return decLatitude;
|
||||
}
|
||||
|
||||
public double getDecLongitude() {
|
||||
return decLongitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts format of geolocation into decimal coordinates as required by MediaWiki API
|
||||
* @return the coordinates in decimals
|
||||
*/
|
||||
private String getDecimalCoords(String latitude, String latitude_ref, String longitude, String longitude_ref) {
|
||||
|
||||
if (latitude_ref.equals("N")) {
|
||||
decLatitude = convertToDegree(latitude);
|
||||
} else {
|
||||
decLatitude = 0 - convertToDegree(latitude);
|
||||
}
|
||||
|
||||
if (longitude_ref.equals("E")) {
|
||||
decLongitude = convertToDegree(longitude);
|
||||
} else {
|
||||
decLongitude = 0 - convertToDegree(longitude);
|
||||
}
|
||||
|
||||
String decimalCoords = String.valueOf(decLatitude) + "|" + String.valueOf(decLongitude);
|
||||
Log.d("Coords", "Latitude and Longitude are " + decimalCoords);
|
||||
return decimalCoords;
|
||||
}
|
||||
|
||||
private double convertToDegree(String stringDMS){
|
||||
double result;
|
||||
String[] DMS = stringDMS.split(",", 3);
|
||||
|
||||
String[] stringD = DMS[0].split("/", 2);
|
||||
double d0 = Double.parseDouble(stringD[0]);
|
||||
double d1 = Double.parseDouble(stringD[1]);
|
||||
double degrees = d0/d1;
|
||||
|
||||
String[] stringM = DMS[1].split("/", 2);
|
||||
double m0 = Double.parseDouble(stringM[0]);
|
||||
double m1 = Double.parseDouble(stringM[1]);
|
||||
double minutes = m0/m1;
|
||||
|
||||
String[] stringS = DMS[2].split("/", 2);
|
||||
double s0 = Double.parseDouble(stringS[0]);
|
||||
double s1 = Double.parseDouble(stringS[1]);
|
||||
double seconds = s0/s1;
|
||||
|
||||
result = degrees + (minutes/60) + (seconds/3600);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.database.DataSetObserver;
|
||||
import android.net.*;
|
||||
import android.os.*;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.*;
|
||||
|
||||
import fr.free.nrw.commons.*;
|
||||
import fr.free.nrw.commons.auth.*;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.contributions.*;
|
||||
import fr.free.nrw.commons.media.*;
|
||||
|
||||
public class MultipleShareActivity
|
||||
extends AuthenticatedActivity
|
||||
implements MediaDetailPagerFragment.MediaDetailProvider,
|
||||
AdapterView.OnItemClickListener,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
|
||||
CategorizationFragment.OnCategoriesSaveHandler {
|
||||
private CommonsApplication app;
|
||||
private ArrayList<Contribution> photosList = null;
|
||||
|
||||
private MultipleUploadListFragment uploadsList;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private UploadController uploadController;
|
||||
|
||||
public MultipleShareActivity() {
|
||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return photosList.get(i);
|
||||
}
|
||||
|
||||
public int getTotalMediaCount() {
|
||||
if(photosList == null) {
|
||||
return 0;
|
||||
}
|
||||
return photosList.size();
|
||||
}
|
||||
|
||||
public void notifyDatasetChanged() {
|
||||
if(uploadsList != null) {
|
||||
uploadsList.notifyDatasetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
// fixme implement me if needed
|
||||
}
|
||||
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
// fixme implement me if needed
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int index, long item) {
|
||||
showDetail(index);
|
||||
|
||||
}
|
||||
|
||||
public void OnMultipleUploadInitiated() {
|
||||
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
|
||||
dialog.setIndeterminate(false);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
dialog.setMax(photosList.size());
|
||||
dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size()));
|
||||
dialog.show();
|
||||
|
||||
for(int i = 0; i < photosList.size(); i++) {
|
||||
Contribution up = photosList.get(i);
|
||||
final int uploadCount = i + 1; // Goddamn Java
|
||||
|
||||
uploadController.startUpload(up, new UploadController.ContributionUploadProgress() {
|
||||
public void onUploadStarted(Contribution contribution) {
|
||||
dialog.setProgress(uploadCount);
|
||||
if(uploadCount == photosList.size()) {
|
||||
dialog.dismiss();
|
||||
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
|
||||
startingToast.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadsList.setImageOnlyMode(true);
|
||||
|
||||
categorizationFragment = (CategorizationFragment) this.getSupportFragmentManager().findFragmentByTag("categorization");
|
||||
if(categorizationFragment == null) {
|
||||
categorizationFragment = new CategorizationFragment();
|
||||
}
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = this.getCurrentFocus();
|
||||
if (target != null) {
|
||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization")
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void onCategoriesSave(ArrayList<String> categories) {
|
||||
if(categories.size() > 0) {
|
||||
ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
|
||||
for(Contribution contribution: photosList) {
|
||||
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
|
||||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
|
||||
categoriesSequence.setContentProviderClient(client);
|
||||
categoriesSequence.save();
|
||||
}
|
||||
}
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("categories-count", categories.size())
|
||||
.param("files-count", photosList.size())
|
||||
.param("source", Contribution.SOURCE_EXTERNAL)
|
||||
.param("result", "queued")
|
||||
.log();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch(item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if(mediaDetails.isVisible()) {
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
uploadController = new UploadController(this);
|
||||
|
||||
setContentView(R.layout.activity_multiple_uploads);
|
||||
app = (CommonsApplication)this.getApplicationContext();
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
|
||||
}
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
requestAuthToken();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
uploadController.cleanup();
|
||||
}
|
||||
|
||||
private void showDetail(int i) {
|
||||
if(mediaDetails == null ||!mediaDetails.isVisible()) {
|
||||
mediaDetails = new MediaDetailPagerFragment(true);
|
||||
this.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.uploadsFragmentContainer, mediaDetails)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
this.getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
mediaDetails.showImage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelableArrayList("uploadsList", photosList);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
app.getApi().setAuthCookie(authCookie);
|
||||
Intent intent = getIntent();
|
||||
|
||||
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
|
||||
if(photosList == null) {
|
||||
photosList = new ArrayList<Contribution>();
|
||||
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
for(int i=0; i < urisList.size(); i++) {
|
||||
Contribution up = new Contribution();
|
||||
Uri uri = urisList.get(i);
|
||||
up.setLocalUri(uri);
|
||||
up.setTag("mimeType", intent.getType());
|
||||
up.setTag("sequence", i);
|
||||
up.setSource(Contribution.SOURCE_EXTERNAL);
|
||||
up.setMultiple(true);
|
||||
photosList.add(up);
|
||||
}
|
||||
}
|
||||
|
||||
uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList");
|
||||
if(uploadsList == null) {
|
||||
uploadsList = new MultipleUploadListFragment();
|
||||
this.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.uploadsFragmentContainer, uploadsList, "uploadsList")
|
||||
.commit();
|
||||
}
|
||||
setTitle(getResources().getQuantityString(R.plurals.multiple_uploads_title, photosList.size(), photosList.size()));
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
super.onAuthFailure();
|
||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||
failureToast.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if(categorizationFragment != null && categorizationFragment.isVisible()) {
|
||||
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
|
||||
.param("files-count", photosList.size())
|
||||
.param("source", Contribution.SOURCE_EXTERNAL)
|
||||
.param("result", "cancelled")
|
||||
.log();
|
||||
} else {
|
||||
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
|
||||
.param("multiple", true)
|
||||
.param("result", "cancelled")
|
||||
.log();
|
||||
}
|
||||
}
|
||||
|
||||
public void onBackStackChanged() {
|
||||
if(mediaDetails != null && mediaDetails.isVisible()) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} else {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.*;
|
||||
import android.graphics.*;
|
||||
import android.net.*;
|
||||
import android.os.*;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.*;
|
||||
import android.util.*;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.*;
|
||||
import com.nostra13.universalimageloader.core.*;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.*;
|
||||
import fr.free.nrw.commons.media.*;
|
||||
|
||||
|
||||
public class MultipleUploadListFragment extends Fragment {
|
||||
|
||||
public interface OnMultipleUploadInitiatedHandler {
|
||||
public void OnMultipleUploadInitiated();
|
||||
}
|
||||
|
||||
private GridView photosGrid;
|
||||
private PhotoDisplayAdapter photosAdapter;
|
||||
private EditText baseTitle;
|
||||
|
||||
private Point photoSize;
|
||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
|
||||
|
||||
private DisplayImageOptions uploadDisplayOptions;
|
||||
|
||||
private boolean imageOnlyMode;
|
||||
|
||||
private static class UploadHolderView {
|
||||
Uri imageUri;
|
||||
|
||||
ImageView image;
|
||||
TextView title;
|
||||
|
||||
RelativeLayout overlay;
|
||||
}
|
||||
|
||||
private class PhotoDisplayAdapter extends BaseAdapter {
|
||||
|
||||
public int getCount() {
|
||||
return detailProvider.getTotalMediaCount();
|
||||
}
|
||||
|
||||
public Object getItem(int i) {
|
||||
return detailProvider.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
public long getItemId(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||
UploadHolderView holder;
|
||||
|
||||
if(view == null) {
|
||||
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
|
||||
holder = new UploadHolderView();
|
||||
holder.image = (ImageView) view.findViewById(R.id.uploadImage);
|
||||
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
|
||||
holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
|
||||
|
||||
holder.image.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, photoSize.y));
|
||||
|
||||
view.setTag(holder);
|
||||
} else {
|
||||
holder = (UploadHolderView)view.getTag();
|
||||
}
|
||||
|
||||
|
||||
Contribution up = (Contribution)this.getItem(i);
|
||||
|
||||
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
|
||||
ImageLoader.getInstance().displayImage(up.getLocalUri().toString(), holder.image, uploadDisplayOptions);
|
||||
holder.imageUri = up.getLocalUri();
|
||||
}
|
||||
|
||||
if(!imageOnlyMode) {
|
||||
holder.overlay.setVisibility(View.VISIBLE);
|
||||
holder.title.setText(up.getFilename());
|
||||
} else {
|
||||
holder.overlay.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
return view;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = getView().findFocus();
|
||||
if (target != null) {
|
||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Wrong result type
|
||||
private Point calculatePicDimension(int count) {
|
||||
DisplayMetrics screenMetrics = getResources().getDisplayMetrics();
|
||||
int screenWidth = screenMetrics.widthPixels;
|
||||
int screenHeight = screenMetrics.heightPixels;
|
||||
|
||||
int picWidth = Math.min((int) Math.sqrt(screenWidth * screenHeight / count), screenWidth);
|
||||
picWidth = Math.min((int)(192 * screenMetrics.density), Math.max((int) (120 * screenMetrics.density), picWidth / 48 * 48));
|
||||
int picHeight = Math.min(picWidth, (int)(192 * screenMetrics.density)); // Max Height is same as Contributions list
|
||||
return new Point(picWidth, picHeight);
|
||||
|
||||
}
|
||||
|
||||
public void notifyDatasetChanged() {
|
||||
if(photosAdapter != null) {
|
||||
photosAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageOnlyMode(boolean mode) {
|
||||
imageOnlyMode = mode;
|
||||
if(imageOnlyMode) {
|
||||
baseTitle.setVisibility(View.GONE);
|
||||
} else {
|
||||
baseTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
photosAdapter.notifyDataSetChanged();
|
||||
photosGrid.setEnabled(!mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, null);
|
||||
photosGrid = (GridView)view.findViewById(R.id.multipleShareBackground);
|
||||
baseTitle = (EditText)view.findViewById(R.id.multipleBaseTitle);
|
||||
|
||||
|
||||
photosAdapter = new PhotoDisplayAdapter();
|
||||
photosGrid.setAdapter(photosAdapter);
|
||||
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
|
||||
photoSize = calculatePicDimension(detailProvider.getTotalMediaCount());
|
||||
photosGrid.setColumnWidth(photoSize.x);
|
||||
|
||||
baseTitle.addTextChangedListener(new TextWatcher() {
|
||||
public void beforeTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
|
||||
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
|
||||
for(int i = 0; i < detailProvider.getTotalMediaCount(); i++) {
|
||||
Contribution up = (Contribution) detailProvider.getMediaAtPosition(i);
|
||||
Boolean isDirty = (Boolean)up.getTag("isDirty");
|
||||
if(isDirty == null || !isDirty) {
|
||||
if(!TextUtils.isEmpty(charSequence)) {
|
||||
up.setFilename(charSequence.toString() + " - " + ((Integer)up.getTag("sequence") + 1));
|
||||
} else {
|
||||
up.setFilename("");
|
||||
}
|
||||
}
|
||||
}
|
||||
detailProvider.notifyDatasetChanged();
|
||||
|
||||
}
|
||||
|
||||
public void afterTextChanged(Editable editable) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, android.view.MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
menu.clear();
|
||||
inflater.inflate(R.menu.fragment_multiple_upload_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch(item.getItemId()) {
|
||||
case R.id.menu_upload_multiple:
|
||||
multipleUploadInitiatedHandler.OnMultipleUploadInitiated();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
uploadDisplayOptions = Utils.getGenericDisplayOptions().build();
|
||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
|
||||
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
257
app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
Normal file
257
app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.Cache;
|
||||
import com.android.volley.NetworkResponse;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.HttpHeaderParser;
|
||||
import com.android.volley.toolbox.JsonRequest;
|
||||
import com.android.volley.toolbox.Volley;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
|
||||
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
|
||||
* of relevant categories.
|
||||
*/
|
||||
public class MwVolleyApi {
|
||||
|
||||
private static RequestQueue REQUEST_QUEUE;
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private Context context;
|
||||
private String coordsLog;
|
||||
|
||||
protected static Set<String> categorySet;
|
||||
private static List<String> categoryList;
|
||||
|
||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
||||
private static final String TAG = MwVolleyApi.class.getName();
|
||||
|
||||
public MwVolleyApi(Context context) {
|
||||
this.context = context;
|
||||
categorySet = new HashSet<String>();
|
||||
}
|
||||
|
||||
public static List<String> getGpsCat() {
|
||||
return categoryList;
|
||||
}
|
||||
|
||||
public static void setGpsCat(List cachedList) {
|
||||
categoryList = new ArrayList<String>();
|
||||
categoryList.addAll(cachedList);
|
||||
Log.d(TAG, "Setting GPS cats from cache: " + categoryList.toString());
|
||||
}
|
||||
|
||||
public void request(String coords) {
|
||||
coordsLog = coords;
|
||||
String apiUrl = buildUrl(coords);
|
||||
Log.d("Image", "URL: " + apiUrl);
|
||||
|
||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
||||
new LogResponseListener<QueryResponse>(), new LogResponseErrorListener());
|
||||
getQueue().add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds URL with image coords for MediaWiki API calls
|
||||
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
|
||||
* @param coords Coordinates to build query with
|
||||
* @return URL for API query
|
||||
*/
|
||||
private String buildUrl (String coords){
|
||||
|
||||
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
|
||||
|
||||
builder.appendPath("w")
|
||||
.appendPath("api.php")
|
||||
.appendQueryParameter("action", "query")
|
||||
.appendQueryParameter("prop", "categories|coordinates|pageprops")
|
||||
.appendQueryParameter("format", "json")
|
||||
.appendQueryParameter("clshow", "!hidden")
|
||||
.appendQueryParameter("coprop", "type|name|dim|country|region|globe")
|
||||
.appendQueryParameter("codistancefrompoint", coords)
|
||||
.appendQueryParameter("generator", "geosearch")
|
||||
.appendQueryParameter("ggscoord", coords)
|
||||
.appendQueryParameter("ggsradius", "10000")
|
||||
.appendQueryParameter("ggslimit", "10")
|
||||
.appendQueryParameter("ggsnamespace", "6")
|
||||
.appendQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
||||
.appendQueryParameter("ggsprimary", "all")
|
||||
.appendQueryParameter("formatversion", "2");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private synchronized RequestQueue getQueue() {
|
||||
return getQueue(context);
|
||||
}
|
||||
|
||||
private static RequestQueue getQueue(Context context) {
|
||||
if (REQUEST_QUEUE == null) {
|
||||
REQUEST_QUEUE = Volley.newRequestQueue(context.getApplicationContext());
|
||||
}
|
||||
return REQUEST_QUEUE;
|
||||
}
|
||||
|
||||
private static class LogResponseListener<T> implements Response.Listener<T> {
|
||||
private static final String TAG = LogResponseListener.class.getName();
|
||||
|
||||
@Override
|
||||
public void onResponse(T response) {
|
||||
Log.d(TAG, response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class LogResponseErrorListener implements Response.ErrorListener {
|
||||
private static final String TAG = LogResponseErrorListener.class.getName();
|
||||
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Log.e(TAG, error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
||||
private static final String TAG = QueryRequest.class.getName();
|
||||
|
||||
public QueryRequest(String url,
|
||||
Response.Listener<QueryResponse> listener,
|
||||
Response.ErrorListener errorListener) {
|
||||
super(Request.Method.GET, url, null, listener, errorListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Response<QueryResponse> parseNetworkResponse(NetworkResponse response) {
|
||||
String json = parseString(response);
|
||||
QueryResponse queryResponse = GSON.fromJson(json, QueryResponse.class);
|
||||
return Response.success(queryResponse, cacheEntry(response));
|
||||
}
|
||||
|
||||
private Cache.Entry cacheEntry(NetworkResponse response) {
|
||||
return HttpHeaderParser.parseCacheHeaders(response);
|
||||
}
|
||||
|
||||
private String parseString(NetworkResponse response) {
|
||||
try {
|
||||
return new String(response.data, HttpHeaderParser.parseCharset(response.headers));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return new String(response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class GpsCatExists {
|
||||
private static boolean gpsCatExists;
|
||||
|
||||
public static void setGpsCatExists(boolean gpsCat) {
|
||||
gpsCatExists = gpsCat;
|
||||
}
|
||||
|
||||
public static boolean getGpsCatExists() {
|
||||
return gpsCatExists;
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryResponse {
|
||||
private Query query = new Query();
|
||||
|
||||
private String printSet() {
|
||||
if (categorySet == null || categorySet.isEmpty()) {
|
||||
GpsCatExists.setGpsCatExists(false);
|
||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
||||
return "No collection of categories";
|
||||
} else {
|
||||
GpsCatExists.setGpsCatExists(true);
|
||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
||||
return "CATEGORIES FOUND" + categorySet.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (query != null) {
|
||||
return "query=" + query.toString() + "\n" + printSet();
|
||||
} else {
|
||||
return "No pages found";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Query {
|
||||
private Page [] pages;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("pages=" + "\n");
|
||||
if (pages != null) {
|
||||
for (Page page : pages) {
|
||||
builder.append(page.toString());
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.replace(builder.length() - 1, builder.length(), "");
|
||||
return builder.toString();
|
||||
} else {
|
||||
return "No pages found";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class Page {
|
||||
private int pageid;
|
||||
private int ns;
|
||||
private String title;
|
||||
private Category[] categories;
|
||||
private Category category;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder builder = new StringBuilder("PAGEID=" + pageid + " ns=" + ns + " title=" + title + "\n" + " CATEGORIES= ");
|
||||
|
||||
if (categories == null || categories.length == 0) {
|
||||
builder.append("no categories exist\n");
|
||||
} else {
|
||||
for (Category category : categories) {
|
||||
builder.append(category.toString());
|
||||
builder.append("\n");
|
||||
if (category != null) {
|
||||
String categoryString = category.toString().replace("Category:", "");
|
||||
categorySet.add(categoryString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categoryList = new ArrayList<String>(categorySet);
|
||||
builder.replace(builder.length() - 1, builder.length(), "");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Category {
|
||||
private String title;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
394
app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
Normal file
394
app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
|
||||
/**
|
||||
* Activity for the title/desc screen after image is selected. Also starts processing image
|
||||
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
|
||||
*/
|
||||
public class ShareActivity
|
||||
extends AuthenticatedActivity
|
||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||
CategorizationFragment.OnCategoriesSaveHandler {
|
||||
|
||||
private static final String TAG = ShareActivity.class.getName();
|
||||
|
||||
private SingleUploadFragment shareView;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private CommonsApplication app;
|
||||
|
||||
private String source;
|
||||
private String mimeType;
|
||||
private String mediaUriString;
|
||||
|
||||
private Uri mediaUri;
|
||||
private Contribution contribution;
|
||||
private ImageView backgroundImageView;
|
||||
private UploadController uploadController;
|
||||
|
||||
private CommonsApplication cacheObj;
|
||||
private boolean cacheFound;
|
||||
|
||||
private GPSExtractor imageObj;
|
||||
private String filePath;
|
||||
private String decimalCoords;
|
||||
|
||||
private boolean useNewPermissions = false;
|
||||
private boolean storagePermission = false;
|
||||
private boolean locationPermission = false;
|
||||
|
||||
public ShareActivity() {
|
||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
public void uploadActionInitiated(String title, String description) {
|
||||
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
|
||||
startingToast.show();
|
||||
|
||||
if (cacheFound == false) {
|
||||
//Has to be called after apiCall.request()
|
||||
app.cacheData.cacheCategory();
|
||||
Log.d(TAG, "Cache the categories found");
|
||||
}
|
||||
|
||||
uploadController.startUpload(title, mediaUri, description, mimeType, source, new UploadController.ContributionUploadProgress() {
|
||||
public void onUploadStarted(Contribution contribution) {
|
||||
ShareActivity.this.contribution = contribution;
|
||||
showPostUpload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showPostUpload() {
|
||||
if(categorizationFragment == null) {
|
||||
categorizationFragment = new CategorizationFragment();
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.single_upload_fragment_container, categorizationFragment, "categorization")
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void onCategoriesSave(ArrayList<String> categories) {
|
||||
if(categories.size() > 0) {
|
||||
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
|
||||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
categoriesSequence.setContentProviderClient(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
|
||||
categoriesSequence.save();
|
||||
}
|
||||
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||
|
||||
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("categories-count", categories.size())
|
||||
.param("files-count", 1)
|
||||
.param("source", contribution.getSource())
|
||||
.param("result", "queued")
|
||||
.log();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if(contribution != null) {
|
||||
outState.putParcelable("contribution", contribution);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if(categorizationFragment != null && categorizationFragment.isVisible()) {
|
||||
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
|
||||
.param("files-count", 1)
|
||||
.param("source", contribution.getSource())
|
||||
.param("result", "cancelled")
|
||||
.log();
|
||||
} else {
|
||||
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
|
||||
.param("multiple", true)
|
||||
.param("result", "cancelled")
|
||||
.log();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
super.onAuthCookieAcquired(authCookie);
|
||||
app.getApi().setAuthCookie(authCookie);
|
||||
|
||||
|
||||
shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||
if(shareView == null && categorizationFragment == null) {
|
||||
shareView = new SingleUploadFragment();
|
||||
this.getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.single_upload_fragment_container, shareView, "shareView")
|
||||
.commit();
|
||||
}
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
super.onAuthFailure();
|
||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||
failureToast.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
||||
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
uploadController = new UploadController(this);
|
||||
setContentView(R.layout.activity_share);
|
||||
|
||||
app = (CommonsApplication)this.getApplicationContext();
|
||||
backgroundImageView = (ImageView)findViewById(R.id.backgroundImage);
|
||||
|
||||
Intent intent = getIntent();
|
||||
|
||||
if(intent.getAction().equals(Intent.ACTION_SEND)) {
|
||||
mediaUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if(intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
||||
} else {
|
||||
source = Contribution.SOURCE_EXTERNAL;
|
||||
}
|
||||
mimeType = intent.getType();
|
||||
}
|
||||
|
||||
if (mediaUri != null) {
|
||||
mediaUriString = mediaUri.toString();
|
||||
ImageLoader.getInstance().displayImage(mediaUriString, backgroundImageView);
|
||||
}
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
contribution = savedInstanceState.getParcelable("contribution");
|
||||
}
|
||||
requestAuthToken();
|
||||
|
||||
|
||||
Log.d(TAG, "Uri: " + mediaUriString);
|
||||
Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
useNewPermissions = true;
|
||||
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
storagePermission = true;
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
locationPermission = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check storage permissions if marshmallow or newer
|
||||
if (useNewPermissions && (!storagePermission || !locationPermission)) {
|
||||
if (!storagePermission && !locationPermission) {
|
||||
String permissionRationales = getResources().getString(R.string.storage_permission_rationale) + "\n" + getResources().getString(R.string.location_permission_rationale);
|
||||
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), permissionRationales,
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.ok, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ActivityCompat.requestPermissions(ShareActivity.this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION}, 3);
|
||||
}
|
||||
});
|
||||
snackbar.show();
|
||||
View snackbarView = snackbar.getView();
|
||||
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
|
||||
textView.setMaxLines(3);
|
||||
} else if (!storagePermission) {
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.storage_permission_rationale,
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.ok, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ActivityCompat.requestPermissions(ShareActivity.this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
|
||||
}
|
||||
}).show();
|
||||
} else if (!locationPermission) {
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.location_permission_rationale,
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.ok, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ActivityCompat.requestPermissions(ShareActivity.this,
|
||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
} else if (useNewPermissions && storagePermission && !locationPermission) {
|
||||
getFileMetadata();
|
||||
} else if(!useNewPermissions || (storagePermission && locationPermission)) {
|
||||
getFileMetadata();
|
||||
getLocationData();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String permissions[], int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
// 1 = Storage
|
||||
case 1: {
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
getFileMetadata();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 2 = Location
|
||||
case 2: {
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
getLocationData();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 3 = Storage + Location
|
||||
case 3: {
|
||||
if (grantResults.length > 1
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
getFileMetadata();
|
||||
}
|
||||
if (grantResults.length > 1
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
getLocationData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getFileMetadata() {
|
||||
filePath = FileUtils.getPath(this, mediaUri);
|
||||
Log.d(TAG, "Filepath: " + filePath);
|
||||
Log.d(TAG, "Calling GPSExtractor");
|
||||
imageObj = new GPSExtractor(filePath, this);
|
||||
|
||||
if (filePath != null && !filePath.equals("")) {
|
||||
// Gets image coords from exif data
|
||||
decimalCoords = imageObj.getCoords(false);
|
||||
useImageCoords();
|
||||
}
|
||||
}
|
||||
|
||||
public void getLocationData() {
|
||||
if(imageObj == null) {
|
||||
imageObj = new GPSExtractor(filePath, this);
|
||||
}
|
||||
|
||||
decimalCoords = imageObj.getCoords(true);
|
||||
useImageCoords();
|
||||
}
|
||||
|
||||
public void useImageCoords() {
|
||||
if(decimalCoords != null) {
|
||||
Log.d(TAG, "Decimal coords of image: " + decimalCoords);
|
||||
|
||||
// Only set cache for this point if image has coords
|
||||
if (imageObj.imageCoordsExists) {
|
||||
double decLongitude = imageObj.getDecLongitude();
|
||||
double decLatitude = imageObj.getDecLatitude();
|
||||
app.cacheData.setQtPoint(decLongitude, decLatitude);
|
||||
}
|
||||
|
||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
||||
|
||||
List displayCatList = app.cacheData.findCategory();
|
||||
boolean catListEmpty = displayCatList.isEmpty();
|
||||
|
||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||
if (catListEmpty) {
|
||||
cacheFound = false;
|
||||
apiCall.request(decimalCoords);
|
||||
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString());
|
||||
} else {
|
||||
cacheFound = true;
|
||||
Log.d(TAG, "Cache found, setting categoryList in MwVolleyApi to " + displayCatList.toString());
|
||||
MwVolleyApi.setGpsCat(displayCatList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
try {
|
||||
imageObj.unregisterLocationManager();
|
||||
Log.d(TAG, "Unregistered locationManager");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
Log.d(TAG, "locationManager does not exist, not unregistered");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
uploadController.cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import fr.free.nrw.commons.Prefs;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
|
||||
public class SingleUploadFragment extends Fragment {
|
||||
|
||||
public interface OnUploadActionInitiated {
|
||||
void uploadActionInitiated(String title, String description);
|
||||
}
|
||||
|
||||
private EditText titleEdit;
|
||||
private EditText descEdit;
|
||||
private TextView licenseSummaryView;
|
||||
|
||||
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.activity_share, menu);
|
||||
if(titleEdit != null) {
|
||||
menu.findItem(R.id.menu_upload_single).setEnabled(titleEdit.getText().length() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_upload_single:
|
||||
uploadActionInitiatedHandler.uploadActionInitiated(titleEdit.getText().toString(), descEdit.getText().toString());
|
||||
return true;
|
||||
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_single_upload, null);
|
||||
|
||||
titleEdit = (EditText)rootView.findViewById(R.id.titleEdit);
|
||||
descEdit = (EditText)rootView.findViewById(R.id.descEdit);
|
||||
licenseSummaryView = (TextView)rootView.findViewById(R.id.share_license_summary);
|
||||
|
||||
TextWatcher uploadEnabler = new TextWatcher() {
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
|
||||
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {}
|
||||
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if(getActivity() != null) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
titleEdit.addTextChangedListener(uploadEnabler);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
final String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA);
|
||||
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
|
||||
|
||||
// Open license page on touch
|
||||
licenseSummaryView.setOnTouchListener(new View.OnTouchListener() {
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(Utils.licenseUrlFor(license)));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
uploadActionInitiatedHandler = (OnUploadActionInitiated) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = getView().findFocus();
|
||||
if (target != null) {
|
||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.Prefs;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
|
||||
public class UploadController {
|
||||
private UploadService uploadService;
|
||||
|
||||
private final Activity activity;
|
||||
final CommonsApplication app;
|
||||
|
||||
public interface ContributionUploadProgress {
|
||||
void onUploadStarted(Contribution contribution);
|
||||
}
|
||||
|
||||
public UploadController(Activity activity) {
|
||||
this.activity = activity;
|
||||
app = (CommonsApplication)activity.getApplicationContext();
|
||||
}
|
||||
|
||||
private boolean isUploadServiceConnected;
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
// this should never happen
|
||||
throw new RuntimeException("UploadService died but the rest of the process did not!");
|
||||
}
|
||||
};
|
||||
|
||||
public void prepareService() {
|
||||
Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
activity.startService(uploadServiceIntent);
|
||||
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
if(isUploadServiceConnected) {
|
||||
activity.unbindService(uploadServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, ContributionUploadProgress onComplete) {
|
||||
Contribution contribution;
|
||||
|
||||
String title = rawTitle;
|
||||
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
|
||||
// People are used to ".jpg" more than ".jpeg" which the system gives us.
|
||||
if (extension != null && extension.toLowerCase().equals("jpeg")) {
|
||||
extension = "jpg";
|
||||
}
|
||||
if(extension != null && !title.toLowerCase().endsWith(extension.toLowerCase())) {
|
||||
title += "." + extension;
|
||||
}
|
||||
|
||||
|
||||
contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY);
|
||||
|
||||
contribution.setTag("mimeType", mimeType);
|
||||
contribution.setSource(source);
|
||||
|
||||
startUpload(contribution, onComplete);
|
||||
}
|
||||
|
||||
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
|
||||
if(TextUtils.isEmpty(contribution.getCreator())) {
|
||||
contribution.setCreator(app.getCurrentAccount().name);
|
||||
}
|
||||
|
||||
if(contribution.getDescription() == null) {
|
||||
contribution.setDescription("");
|
||||
}
|
||||
|
||||
String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA);
|
||||
contribution.setLicense(license);
|
||||
|
||||
Utils.executeAsyncTask(new AsyncTask<Void, Void, Contribution>() {
|
||||
|
||||
// Fills up missing information about Contributions
|
||||
// Only does things that involve some form of IO
|
||||
// Runs in background thread
|
||||
@Override
|
||||
protected Contribution doInBackground(Void... voids /* stare into you */) {
|
||||
long length;
|
||||
try {
|
||||
if(contribution.getDataLength() <= 0) {
|
||||
length = activity.getContentResolver().openAssetFileDescriptor(contribution.getLocalUri(), "r").getLength();
|
||||
if(length == -1) {
|
||||
// Let us find out the long way!
|
||||
length = Utils.countBytes(activity.getContentResolver().openInputStream(contribution.getLocalUri()));
|
||||
}
|
||||
contribution.setDataLength(length);
|
||||
}
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
String mimeType = (String)contribution.getTag("mimeType");
|
||||
if(mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
|
||||
mimeType = activity.getContentResolver().getType(contribution.getLocalUri());
|
||||
if(mimeType != null) {
|
||||
contribution.setTag("mimeType", mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null) {
|
||||
Cursor cursor = activity.getContentResolver().query(contribution.getLocalUri(),
|
||||
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
|
||||
if(cursor != null && cursor.getCount() != 0) {
|
||||
cursor.moveToFirst();
|
||||
Date dateCreated = new Date(cursor.getLong(0));
|
||||
Date epochStart = new Date(0);
|
||||
if(dateCreated.equals(epochStart) || dateCreated.before(epochStart)) {
|
||||
// If date is incorrect (1st second of unix time) then set it to the current date
|
||||
dateCreated = new Date();
|
||||
}
|
||||
contribution.setDateCreated(dateCreated);
|
||||
} else {
|
||||
contribution.setDateCreated(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
return contribution;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Contribution contribution) {
|
||||
super.onPostExecute(contribution);
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
||||
onComplete.onUploadStarted(contribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
325
app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
Normal file
325
app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.graphics.*;
|
||||
import android.os.Bundle;
|
||||
import fr.free.nrw.commons.*;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import org.mediawiki.api.*;
|
||||
import in.yuvi.http.fluent.ProgressListener;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.*;
|
||||
import android.widget.*;
|
||||
|
||||
import fr.free.nrw.commons.contributions.*;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
|
||||
public class UploadService extends HandlerService<Contribution> {
|
||||
|
||||
private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload";
|
||||
|
||||
public static final int ACTION_UPLOAD_FILE = 1;
|
||||
|
||||
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
||||
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
|
||||
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private ContentProviderClient contributionsProviderClient;
|
||||
private CommonsApplication app;
|
||||
|
||||
private NotificationCompat.Builder curProgressNotification;
|
||||
|
||||
private int toUpload;
|
||||
|
||||
// DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING
|
||||
// See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification
|
||||
// Seriously, Android?
|
||||
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
|
||||
public static final int NOTIFICATION_UPLOAD_COMPLETE = 2;
|
||||
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
|
||||
|
||||
public UploadService() {
|
||||
super("UploadService");
|
||||
}
|
||||
|
||||
private class NotificationUpdateProgressListener implements ProgressListener {
|
||||
|
||||
String notificationTag;
|
||||
boolean notificationTitleChanged;
|
||||
Contribution contribution;
|
||||
|
||||
String notificationProgressTitle;
|
||||
String notificationFinishingTitle;
|
||||
|
||||
public NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle, String notificationFinishingTitle, Contribution contribution) {
|
||||
this.notificationTag = notificationTag;
|
||||
this.notificationProgressTitle = notificationProgressTitle;
|
||||
this.notificationFinishingTitle = notificationFinishingTitle;
|
||||
this.contribution = contribution;
|
||||
}
|
||||
|
||||
public void onProgress(long transferred, long total) {
|
||||
Log.d("Commons", String.format("Uploaded %d of %d", transferred, total));
|
||||
if(!notificationTitleChanged) {
|
||||
curProgressNotification.setContentTitle(notificationProgressTitle);
|
||||
notificationTitleChanged = true;
|
||||
contribution.setState(Contribution.STATE_IN_PROGRESS);
|
||||
}
|
||||
if(transferred == total) {
|
||||
// Completed!
|
||||
curProgressNotification.setContentTitle(notificationFinishingTitle);
|
||||
curProgressNotification.setProgress(0, 100, true);
|
||||
} else {
|
||||
curProgressNotification.setProgress(100, (int) (((double) transferred / (double) total) * 100), false);
|
||||
}
|
||||
startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||
|
||||
contribution.setTransferred(transferred);
|
||||
contribution.save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
contributionsProviderClient.release();
|
||||
Log.d("Commons", "ZOMG I AM BEING KILLED HALP!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
app = (CommonsApplication) this.getApplicationContext();
|
||||
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handle(int what, Contribution contribution) {
|
||||
switch(what) {
|
||||
case ACTION_UPLOAD_FILE:
|
||||
uploadContribution(contribution);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown value for what");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queue(int what, Contribution contribution) {
|
||||
switch (what) {
|
||||
case ACTION_UPLOAD_FILE:
|
||||
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
contribution.setTransferred(0);
|
||||
contribution.setContentProviderClient(contributionsProviderClient);
|
||||
|
||||
contribution.save();
|
||||
toUpload++;
|
||||
if (curProgressNotification != null && toUpload != 1) {
|
||||
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
|
||||
Log.d("Commons", String.format("%d uploads left", toUpload));
|
||||
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||
}
|
||||
|
||||
super.queue(what, contribution);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown value for what");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean freshStart = true;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if(intent.getAction() == ACTION_START_SERVICE && freshStart) {
|
||||
ContentValues failedValues = new ContentValues();
|
||||
failedValues.put(Contribution.Table.COLUMN_STATE, Contribution.STATE_FAILED);
|
||||
|
||||
int updated = getContentResolver().update(ContributionsContentProvider.BASE_URI,
|
||||
failedValues,
|
||||
Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
|
||||
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
|
||||
);
|
||||
Log.d("Commons", "Set " + updated + " uploads to failed");
|
||||
Log.d("Commons", "Flags is" + flags + " id is" + startId);
|
||||
freshStart = false;
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
|
||||
private void uploadContribution(Contribution contribution) {
|
||||
MWApi api = app.getApi();
|
||||
|
||||
ApiResult result;
|
||||
InputStream file = null;
|
||||
|
||||
String notificationTag = contribution.getLocalUri().toString();
|
||||
|
||||
try {
|
||||
file = this.getContentResolver().openInputStream(contribution.getLocalUri());
|
||||
} catch(FileNotFoundException e) {
|
||||
Log.d("Exception", "File not found");
|
||||
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
||||
fileNotFound.show();
|
||||
}
|
||||
|
||||
Log.d("Commons", "Before execution!");
|
||||
curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(String.format(getString(R.string.upload_progress_notification_title_start), contribution.getDisplayTitle()))
|
||||
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
|
||||
.setOngoing(true)
|
||||
.setProgress(100, 0, true)
|
||||
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, ContributionsActivity.class), 0))
|
||||
.setTicker(String.format(getString(R.string.upload_progress_notification_title_in_progress), contribution.getDisplayTitle()));
|
||||
|
||||
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||
|
||||
try {
|
||||
String filename = findUniqueFilename(contribution.getFilename());
|
||||
if(!api.validateLogin()) {
|
||||
// Need to revalidate!
|
||||
if(app.revalidateAuthToken()) {
|
||||
Log.d("Commons", "Successfully revalidated token!");
|
||||
} else {
|
||||
Log.d("Commons", "Unable to revalidate :(");
|
||||
// TODO: Put up a new notification, ask them to re-login
|
||||
stopForeground(true);
|
||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||
failureToast.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(notificationTag,
|
||||
String.format(getString(R.string.upload_progress_notification_title_in_progress), contribution.getDisplayTitle()),
|
||||
String.format(getString(R.string.upload_progress_notification_title_finishing), contribution.getDisplayTitle()),
|
||||
contribution
|
||||
);
|
||||
result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
|
||||
|
||||
|
||||
Log.d("Commons", "Response is" + Utils.getStringFromDOM(result.getDocument()));
|
||||
|
||||
curProgressNotification = null;
|
||||
|
||||
|
||||
String resultStatus = result.getString("/api/upload/@result");
|
||||
if(!resultStatus.equals("Success")) {
|
||||
String errorCode = result.getString("/api/error/@code");
|
||||
showFailedNotification(contribution);
|
||||
fr.free.nrw.commons.EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("source", contribution.getSource())
|
||||
.param("multiple", contribution.getMultiple())
|
||||
.param("result", errorCode)
|
||||
.param("filename", contribution.getFilename())
|
||||
.log();
|
||||
} else {
|
||||
Date dateUploaded = null;
|
||||
dateUploaded = Utils.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");
|
||||
contribution.setFilename(canonicalFilename);
|
||||
contribution.setImageUrl(imageUrl);
|
||||
contribution.setState(Contribution.STATE_COMPLETED);
|
||||
contribution.setDateUploaded(dateUploaded);
|
||||
contribution.save();
|
||||
|
||||
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
|
||||
.param("username", app.getCurrentAccount().name)
|
||||
.param("source", contribution.getSource()) //FIXME
|
||||
.param("filename", contribution.getFilename())
|
||||
.param("multiple", contribution.getMultiple())
|
||||
.param("result", "success")
|
||||
.log();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
Log.d("Commons", "I have a network fuckup");
|
||||
showFailedNotification(contribution);
|
||||
return;
|
||||
} finally {
|
||||
toUpload--;
|
||||
if(toUpload == 0) {
|
||||
// Sync modifications right after all uplaods are processed
|
||||
ContentResolver.requestSync(((CommonsApplication) getApplicationContext()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showFailedNotification(Contribution contribution) {
|
||||
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
|
||||
.setTicker(String.format(getString(R.string.upload_failed_notification_title), contribution.getDisplayTitle()))
|
||||
.setContentTitle(String.format(getString(R.string.upload_failed_notification_title), contribution.getDisplayTitle()))
|
||||
.setContentText(getString(R.string.upload_failed_notification_subtitle))
|
||||
.build();
|
||||
notificationManager.notify(NOTIFICATION_UPLOAD_FAILED, failureNotification);
|
||||
|
||||
contribution.setState(Contribution.STATE_FAILED);
|
||||
contribution.save();
|
||||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
return findUniqueFilename(fileName, 1);
|
||||
}
|
||||
|
||||
private String findUniqueFilename(String fileName, int sequenceNumber) throws IOException {
|
||||
String sequenceFileName;
|
||||
if (sequenceNumber == 1) {
|
||||
sequenceFileName = fileName;
|
||||
} else {
|
||||
if (fileName.indexOf('.') == -1) {
|
||||
// We really should have appended a file type suffix already.
|
||||
// But... we might not.
|
||||
sequenceFileName = fileName + " " + sequenceNumber;
|
||||
} else {
|
||||
Pattern regex = Pattern.compile("^(.*)(\\..+?)$");
|
||||
Matcher regexMatcher = regex.matcher(fileName);
|
||||
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
||||
}
|
||||
}
|
||||
Log.d("Commons", "checking for uniqueness of name " + sequenceFileName);
|
||||
|
||||
if (fileExistsWithName(sequenceFileName)) {
|
||||
return findUniqueFilename(fileName, sequenceNumber + 1);
|
||||
} else {
|
||||
return sequenceFileName;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fileExistsWithName(String fileName) throws IOException {
|
||||
MWApi api = app.getApi();
|
||||
ApiResult result;
|
||||
|
||||
result = api.action("query")
|
||||
.param("prop", "imageinfo")
|
||||
.param("titles", "File:" + fileName)
|
||||
.get();
|
||||
|
||||
ArrayList<ApiResult> nodes = result.getNodes("/api/query/pages/page/imageinfo");
|
||||
return nodes.size() > 0;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue