Merge remote-tracking branch 'refs/remotes/origin/2.8-release'

This commit is contained in:
misaochan 2018-08-04 19:29:50 +10:00
commit 18fbd47535
36 changed files with 648 additions and 161 deletions

View file

@ -122,7 +122,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
} }
debug { debug {
applicationIdSuffix ".debug"
testCoverageEnabled true testCoverageEnabled true
versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion() versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion()
} }
@ -131,6 +130,9 @@ android {
flavorDimensions 'tier' flavorDimensions 'tier'
productFlavors { productFlavors {
prod { prod {
applicationId 'fr.free.nrw.commons'
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\"" buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\"" buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\"" buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
@ -144,10 +146,18 @@ android {
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\""
dimension 'tier' dimension 'tier'
} }
beta { beta {
applicationId 'fr.free.nrw.commons.beta'
// What values do we need to hit the BETA versions of the site / api ? // What values do we need to hit the BETA versions of the site / api ?
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\"" buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\"" buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
@ -162,6 +172,12 @@ android {
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"" buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.beta.categories.contentprovider\""
dimension 'tier' dimension 'tier'
} }
} }

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_type">fr.free.nrw.commons.beta</string>
<string name="contribution_authority">fr.free.nrw.commons.beta.contributions.contentprovider</string>
<string name="modification_authority">fr.free.nrw.commons.beta.modifications.contentprovider</string>
<string name="category_authority">fr.free.nrw.commons.beta.categories.contentprovider</string>
</resources>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="placeholder_place_distance">Overlay</string>
<string name="placeholder_place_name">Name</string>
<string name="placeholder_place_description">Description</string>
</resources>

View file

@ -168,21 +168,21 @@
<provider <provider
android:name=".contributions.ContributionsContentProvider" android:name=".contributions.ContributionsContentProvider"
android:authorities="fr.free.nrw.commons.contributions.contentprovider" android:authorities="${applicationId}.contributions.contentprovider"
android:exported="false" android:exported="false"
android:label="@string/provider_contributions" android:label="@string/provider_contributions"
android:syncable="true" /> android:syncable="true" />
<provider <provider
android:name=".modifications.ModificationsContentProvider" android:name=".modifications.ModificationsContentProvider"
android:authorities="fr.free.nrw.commons.modifications.contentprovider" android:authorities="${applicationId}.modifications.contentprovider"
android:exported="false" android:exported="false"
android:label="@string/provider_modifications" android:label="@string/provider_modifications"
android:syncable="true" /> android:syncable="true" />
<provider <provider
android:name=".category.CategoryContentProvider" android:name=".category.CategoryContentProvider"
android:authorities="fr.free.nrw.commons.categories.contentprovider" android:authorities="${applicationId}.categories.contentprovider"
android:exported="false" android:exported="false"
android:label="@string/provider_categories" android:label="@string/provider_categories"
android:syncable="false" /> android:syncable="false" />

View file

@ -27,6 +27,7 @@ import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.modifications.ModifierSequenceDao; import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.ContributionUtils;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -68,7 +69,6 @@ public class CommonsApplication extends MultiDexApplication {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
ApplicationlessInjection ApplicationlessInjection
.getInstance(this) .getInstance(this)
.getCommonsApplicationComponent() .getCommonsApplicationComponent()
@ -81,6 +81,8 @@ public class CommonsApplication extends MultiDexApplication {
if (setupLeakCanary() == RefWatcher.DISABLED) { if (setupLeakCanary() == RefWatcher.DISABLED) {
return; return;
} }
// Empty temp directory in case some temp files are created and never removed.
ContributionUtils.emptyTemporaryDirectory();
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());

View file

@ -51,16 +51,10 @@ public class MediaWikiImageView extends SimpleDraweeView {
return; return;
} }
if(media.getFilename() != null) { if (media.getFilename() != null && thumbnailUrlCache.get(media.getFilename()) != null) {
if (thumbnailUrlCache.get(media.getFilename()) != null) { setImageUrl(thumbnailUrlCache.get(media.getFilename()));
setImageUrl(thumbnailUrlCache.get(media.getFilename())); } else {
} else { setImageUrl(null);
setImageUrl(null);
currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
currentThumbnailTask.execute(media.getFilename());
}
} else { // local image
setImageUrl(media.getLocalUri().toString());
currentThumbnailTask = new ThumbnailFetchTask(media, mwApi); currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
currentThumbnailTask.execute(media.getFilename()); currentThumbnailTask.execute(media.getFilename());
} }

View file

@ -1,20 +1,6 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import timber.log.Timber;
import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
public class AccountUtil { public class AccountUtil {
@ -27,38 +13,4 @@ public class AccountUtil {
this.context = context; this.context = context;
} }
public void createAccount(@Nullable AccountAuthenticatorResponse response,
String username, String password) {
Account account = new Account(username, ACCOUNT_TYPE);
boolean created = accountManager().addAccountExplicitly(account, password, null);
Timber.d("account creation " + (created ? "successful" : "failure"));
if (created) {
if (response != null) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, username);
bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(bundle);
}
} else {
if (response != null) {
response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
}
Timber.d("account creation failure");
}
// FIXME: If the user turns it off, it shouldn't be auto turned back on
ContentResolver.setSyncAutomatically(account, CONTRIBUTION_AUTHORITY, true); // Enable sync by default!
ContentResolver.setSyncAutomatically(account, MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
}
private AccountManager accountManager() {
return AccountManager.get(context);
}
} }

View file

@ -63,7 +63,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username"; public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
@Inject MediaWikiApi mwApi; @Inject MediaWikiApi mwApi;
@Inject AccountUtil accountUtil;
@Inject SessionManager sessionManager; @Inject SessionManager sessionManager;
@Inject @Named("application_preferences") SharedPreferences prefs; @Inject @Named("application_preferences") SharedPreferences prefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs; @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@ -248,7 +247,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
} }
} }
accountUtil.createAccount(response, username, password); sessionManager.createAccount(response, username, password);
startMainActivity(); startMainActivity();
} }

View file

@ -1,18 +1,24 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.Observable; import io.reactivex.Observable;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE; import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
/** /**
* Manage the current logged in user session. * Manage the current logged in user session.
@ -23,13 +29,57 @@ public class SessionManager {
private Account currentAccount; // Unlike a savings account... ;-) private Account currentAccount; // Unlike a savings account... ;-)
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
public SessionManager(Context context, MediaWikiApi mediaWikiApi, SharedPreferences sharedPreferences) {
public SessionManager(Context context,
MediaWikiApi mediaWikiApi,
SharedPreferences sharedPreferences) {
this.context = context; this.context = context;
this.mediaWikiApi = mediaWikiApi; this.mediaWikiApi = mediaWikiApi;
this.currentAccount = null; this.currentAccount = null;
this.sharedPreferences = sharedPreferences; this.sharedPreferences = sharedPreferences;
} }
/**
* Creata a new account
*
* @param response
* @param username
* @param password
*/
public void createAccount(@Nullable AccountAuthenticatorResponse response,
String username, String password) {
Account account = new Account(username, BuildConfig.ACCOUNT_TYPE);
boolean created = accountManager().addAccountExplicitly(account, password, null);
Timber.d("account creation " + (created ? "successful" : "failure"));
if (created) {
if (response != null) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, username);
bundle.putString(KEY_ACCOUNT_TYPE, BuildConfig.ACCOUNT_TYPE);
response.onResult(bundle);
}
} else {
if (response != null) {
response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
}
Timber.d("account creation failure");
}
// FIXME: If the user turns it off, it shouldn't be auto turned back on
ContentResolver.setSyncAutomatically(account, BuildConfig.CONTRIBUTION_AUTHORITY, true); // Enable sync by default!
ContentResolver.setSyncAutomatically(account, BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
}
private AccountManager accountManager() {
return AccountManager.get(context);
}
/** /**
* @return Account|null * @return Account|null
*/ */
@ -37,7 +87,7 @@ public class SessionManager {
public Account getCurrentAccount() { public Account getCurrentAccount() {
if (currentAccount == null) { if (currentAccount == null) {
AccountManager accountManager = AccountManager.get(context); AccountManager accountManager = AccountManager.get(context);
Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE); Account[] allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE);
if (allAccounts.length != 0) { if (allAccounts.length != 0) {
currentAccount = allAccounts[0]; currentAccount = allAccounts[0];
} }
@ -53,7 +103,7 @@ public class SessionManager {
return false; // This should never happen return false; // This should never happen
} }
accountManager.invalidateAuthToken(ACCOUNT_TYPE, mediaWikiApi.getAuthCookie()); accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, mediaWikiApi.getAuthCookie());
String authCookie = getAuthCookie(); String authCookie = getAuthCookie();
if (authCookie == null) { if (authCookie == null) {
@ -92,7 +142,7 @@ public class SessionManager {
public Completable clearAllAccounts() { public Completable clearAllAccounts() {
AccountManager accountManager = AccountManager.get(context); AccountManager accountManager = AccountManager.get(context);
Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE); Account[] allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE);
return Completable.fromObservable(Observable.fromArray(allAccounts) return Completable.fromObservable(Observable.fromArray(allAccounts)
.map(a -> accountManager.removeAccount(a, null, null).getResult())) .map(a -> accountManager.removeAccount(a, null, null).getResult()))
.doOnComplete(() -> currentAccount = null); .doOnComplete(() -> currentAccount = null);

View file

@ -12,6 +12,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
@ -19,7 +20,7 @@ import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE; import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
private static final String[] SYNC_AUTHORITIES = {ContributionsContentProvider.CONTRIBUTION_AUTHORITY, ModificationsContentProvider.MODIFICATIONS_AUTHORITY}; private static final String[] SYNC_AUTHORITIES = {BuildConfig.CONTRIBUTION_AUTHORITY, BuildConfig.MODIFICATION_AUTHORITY};
@NonNull @NonNull
private final Context context; private final Context context;

View file

@ -11,6 +11,7 @@ import android.text.TextUtils;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider; import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber; import timber.log.Timber;
@ -22,19 +23,18 @@ import static fr.free.nrw.commons.category.CategoryDao.Table.TABLE_NAME;
public class CategoryContentProvider extends CommonsDaggerContentProvider { public class CategoryContentProvider extends CommonsDaggerContentProvider {
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
// For URI matcher // For URI matcher
private static final int CATEGORIES = 1; private static final int CATEGORIES = 1;
private static final int CATEGORIES_ID = 2; private static final int CATEGORIES_ID = 2;
private static final String BASE_PATH = "categories"; private static final String BASE_PATH = "categories";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.CATEGORY_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH); private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
static { static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CATEGORIES); uriMatcher.addURI(BuildConfig.CATEGORY_AUTHORITY, BASE_PATH, CATEGORIES);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID); uriMatcher.addURI(BuildConfig.CATEGORY_AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
} }
public static Uri uriForId(int id) { public static Uri uriForId(int id) {

View file

@ -46,6 +46,7 @@ public class Contribution extends Media {
private String decimalCoords; private String decimalCoords;
private boolean isMultiple; private boolean isMultiple;
private String wikiDataEntityId; private String wikiDataEntityId;
private Uri contentProviderUri;
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp, public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
int state, long dataLength, Date dateUploaded, long transferred, int state, long dataLength, Date dateUploaded, long transferred,
@ -236,4 +237,12 @@ public class Contribution extends Media {
public void setWikiDataEntityId(String wikiDataEntityId) { public void setWikiDataEntityId(String wikiDataEntityId) {
this.wikiDataEntityId = wikiDataEntityId; this.wikiDataEntityId = wikiDataEntityId;
} }
public void setContentProviderUri(Uri contentProviderUri) {
this.contentProviderUri = contentProviderUri;
}
public Uri getContentProviderUri() {
return contentProviderUri;
}
} }

View file

@ -7,9 +7,11 @@ import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.util.Log;
import java.io.File; import java.io.File;
import java.util.Date; import java.util.Date;
@ -28,8 +30,8 @@ import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_
public class ContributionController { public class ContributionController {
private static final int SELECT_FROM_GALLERY = 1; public static final int SELECT_FROM_GALLERY = 1;
private static final int SELECT_FROM_CAMERA = 2; public static final int SELECT_FROM_CAMERA = 2;
private Fragment fragment; private Fragment fragment;
@ -91,8 +93,7 @@ public class ContributionController {
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY); fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
} }
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload, String wikiDataEntityId) { public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDirectUpload, String wikiDataEntityId) {
Timber.d("Is direct upload %s and the Wikidata entity ID is %s", isDirectUpload, wikiDataEntityId);
FragmentActivity activity = fragment.getActivity(); FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult()"); Timber.d("handleImagePicked() called with onActivityResult()");
Intent shareIntent = new Intent(activity, ShareActivity.class); Intent shareIntent = new Intent(activity, ShareActivity.class);
@ -100,7 +101,7 @@ public class ContributionController {
switch (requestCode) { switch (requestCode) {
case SELECT_FROM_GALLERY: case SELECT_FROM_GALLERY:
//Handles image picked from gallery //Handles image picked from gallery
Uri imageData = data.getData(); Uri imageData = uri;
shareIntent.setType(activity.getContentResolver().getType(imageData)); shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData); shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY); shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);

View file

@ -38,6 +38,7 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.quiz.QuizChecker; import fr.free.nrw.commons.quiz.QuizChecker;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
@ -110,7 +111,7 @@ public class ContributionsActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here! // Do a sync everytime we get here!
requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.CONTRIBUTION_AUTHORITY, new Bundle()); requestSync(sessionManager.getCurrentAccount(), BuildConfig.CONTRIBUTION_AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class); Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent); startService(uploadServiceIntent);
@ -199,6 +200,9 @@ public class ContributionsActivity
Contribution c = contributionDao.fromCursor(allContributions); Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) { if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toString()); Timber.d("Deleting failed contrib %s", c.toString());
// If upload fails and then user decides to cancel upload at all, which means contribution
// object will be deleted. So we have to delete temp file for that contribution.
ContributionUtils.removeTemporaryFile(c.getLocalUri());
contributionDao.delete(c); contributionDao.delete(c);
} else { } else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toString()); Timber.d("Skipping deletion for non-failed contrib %s", c.toString());

View file

@ -11,6 +11,7 @@ import android.text.TextUtils;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider; import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber; import timber.log.Timber;
@ -25,13 +26,12 @@ public class ContributionsContentProvider extends CommonsDaggerContentProvider {
private static final int CONTRIBUTIONS_ID = 2; private static final int CONTRIBUTIONS_ID = 2;
private static final String BASE_PATH = "contributions"; private static final String BASE_PATH = "contributions";
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH); private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
public static final String CONTRIBUTION_AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
public static final Uri BASE_URI = Uri.parse("content://" + CONTRIBUTION_AUTHORITY + "/" + BASE_PATH); public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.CONTRIBUTION_AUTHORITY + "/" + BASE_PATH);
static { static {
uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH, CONTRIBUTIONS); uriMatcher.addURI(BuildConfig.CONTRIBUTION_AUTHORITY, BASE_PATH, CONTRIBUTIONS);
uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID); uriMatcher.addURI(BuildConfig.CONTRIBUTION_AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID);
} }
public static Uri uriForId(int id) { public static Uri uriForId(int id) {

View file

@ -3,11 +3,13 @@ package fr.free.nrw.commons.contributions;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -31,6 +33,7 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.utils.ContributionUtils;
import timber.log.Timber; import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@ -117,7 +120,13 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, false, null); if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, false, null);
} else {
controller.handleImagePicked(requestCode, data.getData(), false, null);
}
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -12,6 +12,7 @@ import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
@ -23,15 +24,11 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener;
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl; import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.RECENT_SEARCH_AUTHORITY; import static fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.RECENT_SEARCH_AUTHORITY;
import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
@Module @Module
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public class CommonsApplicationModule { public class CommonsApplicationModule {
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
private Context applicationContext; private Context applicationContext;
public CommonsApplicationModule(Context applicationContext) { public CommonsApplicationModule(Context applicationContext) {
@ -51,7 +48,7 @@ public class CommonsApplicationModule {
@Provides @Provides
@Named("category") @Named("category")
public ContentProviderClient provideCategoryContentProviderClient(Context context) { public ContentProviderClient provideCategoryContentProviderClient(Context context) {
return context.getContentResolver().acquireContentProviderClient(CATEGORY_AUTHORITY); return context.getContentResolver().acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY);
} }
/** /**
@ -69,13 +66,13 @@ public class CommonsApplicationModule {
@Provides @Provides
@Named("contribution") @Named("contribution")
public ContentProviderClient provideContributionContentProviderClient(Context context) { public ContentProviderClient provideContributionContentProviderClient(Context context) {
return context.getContentResolver().acquireContentProviderClient(CONTRIBUTION_AUTHORITY); return context.getContentResolver().acquireContentProviderClient(BuildConfig.CONTRIBUTION_AUTHORITY);
} }
@Provides @Provides
@Named("modification") @Named("modification")
public ContentProviderClient provideModificationContentProviderClient(Context context) { public ContentProviderClient provideModificationContentProviderClient(Context context) {
return context.getContentResolver().acquireContentProviderClient(MODIFICATIONS_AUTHORITY); return context.getContentResolver().acquireContentProviderClient(BuildConfig.MODIFICATION_AUTHORITY);
} }
@Provides @Provides

View file

@ -11,6 +11,7 @@ import android.text.TextUtils;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider; import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber; import timber.log.Timber;
@ -22,15 +23,14 @@ public class ModificationsContentProvider extends CommonsDaggerContentProvider {
private static final int MODIFICATIONS = 1; private static final int MODIFICATIONS = 1;
private static final int MODIFICATIONS_ID = 2; private static final int MODIFICATIONS_ID = 2;
public static final String MODIFICATIONS_AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider";
public static final String BASE_PATH = "modifications"; public static final String BASE_PATH = "modifications";
public static final Uri BASE_URI = Uri.parse("content://" + MODIFICATIONS_AUTHORITY + "/" + BASE_PATH); public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.MODIFICATION_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static { static {
uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH, MODIFICATIONS); uriMatcher.addURI(BuildConfig.MODIFICATION_AUTHORITY, BASE_PATH, MODIFICATIONS);
uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID); uriMatcher.addURI(BuildConfig.MODIFICATION_AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID);
} }
public static Uri uriForId(int id) { public static Uri uriForId(int id) {

View file

@ -13,6 +13,7 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionDao; import fr.free.nrw.commons.contributions.ContributionDao;
@ -77,7 +78,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
ContentProviderClient contributionsClient = null; ContentProviderClient contributionsClient = null;
try { try {
contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.CONTRIBUTION_AUTHORITY); contributionsClient = getContext().getContentResolver().acquireContentProviderClient(BuildConfig.CONTRIBUTION_AUTHORITY);
while (!allModifications.isAfterLast()) { while (!allModifications.isAfterLast()) {
ModifierSequence sequence = modifierSequenceDao.fromCursor(allModifications); ModifierSequence sequence = modifierSequenceDao.fromCursor(allModifications);

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.mwapi;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -51,6 +52,7 @@ import fr.free.nrw.commons.category.CategoryImageUtils;
import fr.free.nrw.commons.category.QueryContinue; import fr.free.nrw.commons.category.QueryContinue;
import fr.free.nrw.commons.notification.Notification; import fr.free.nrw.commons.notification.Notification;
import fr.free.nrw.commons.notification.NotificationUtils; import fr.free.nrw.commons.notification.NotificationUtils;
import fr.free.nrw.commons.utils.ContributionUtils;
import in.yuvi.http.fluent.Http; import in.yuvi.http.fluent.Http;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -92,6 +94,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent()); params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params); httpClient = new DefaultHttpClient(cm, params);
httpClient.addRequestInterceptor(NetworkInterceptors.getHttpRequestInterceptor());
api = new MWApi(apiURL, httpClient); api = new MWApi(apiURL, httpClient);
wikidataApi = new MWApi(wikidatApiURL, httpClient); wikidataApi = new MWApi(wikidatApiURL, httpClient);
this.defaultPreferences = defaultPreferences; this.defaultPreferences = defaultPreferences;
@ -855,17 +858,23 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
long dataLength, long dataLength,
String pageContents, String pageContents,
String editSummary, String editSummary,
final ProgressListener progressListener) throws IOException { final ProgressListener progressListener,
Uri fileUri,
Uri contentProviderUri) throws IOException {
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress); ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
Log.e("WTF", "Result: " + result.toString()); Log.e("WTF", "Result: " + result.toString());
String resultStatus = result.getString("/api/upload/@result"); String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) { if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code"); String errorCode = result.getString("/api/error/@code");
Timber.e(errorCode); Timber.e(errorCode);
return new UploadResult(resultStatus, errorCode); return new UploadResult(resultStatus, errorCode);
} else { } else {
// If success we have to remove file from temp directory
ContributionUtils.removeTemporaryFile(fileUri);
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
String imageUrl = result.getString("/api/upload/imageinfo/@url"); String imageUrl = result.getString("/api/upload/imageinfo/@url");
@ -873,7 +882,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
} }
} }
@Override @Override
@NonNull @NonNull
public Single<Integer> getUploadCount(String userName) { public Single<Integer> getUploadCount(String userName) {

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.mwapi; package fr.free.nrw.commons.mwapi;
import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -54,7 +55,7 @@ public interface MediaWikiApi {
List<String> searchCategory(String title, int offset); List<String> searchCategory(String title, int offset);
@NonNull @NonNull
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException; UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener, Uri fileUri, Uri contentProviderUri) throws IOException;
@Nullable @Nullable
String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException; String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException;

View file

@ -0,0 +1,85 @@
package fr.free.nrw.commons.mwapi;
import android.support.annotation.NonNull;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.impl.client.ClientParamsStack;
import org.apache.http.params.HttpParamsNames;
import org.apache.http.protocol.HttpContext;
import java.util.HashSet;
import java.util.Set;
import timber.log.Timber;
public class NetworkInterceptors {
/**
* Interceptor to log the HTTP request
*/
@NonNull
public static HttpRequestInterceptor getHttpRequestInterceptor() {
return (HttpRequest request, HttpContext httpContext) -> {
Timber.v("<<<<<<<<<<<<<< START OF REQUEST LOGGING [%s] >>>>>>>>>>>>", request.getRequestLine().getUri());
Timber.v("Request line:\n %s", request.getRequestLine().toString());
logRequestParams(request);
logRequestHeaders(request);
Timber.v("Protocol version:\n %s", request.getProtocolVersion());
Timber.v("<<<<<<<<<<<<<< END OF REQUEST LOGGING [%s] >>>>>>>>>>>>", request.getRequestLine().getUri());
};
}
/**
* Log all request params from a HTTPRequest
* @param request
*/
private static void logRequestParams(HttpRequest request) {
Set<String> names = new HashSet<>();
if (request.getParams() instanceof ClientParamsStack) {
ClientParamsStack cps = (ClientParamsStack) request.getParams();
if (cps.getApplicationParams() != null
&& cps.getRequestParams() instanceof HttpParamsNames) {
names.addAll(((HttpParamsNames) cps.getApplicationParams()).getNames());
}
if (cps.getClientParams() != null
&& cps.getClientParams() instanceof HttpParamsNames) {
names.addAll(((HttpParamsNames) cps.getClientParams()).getNames());
}
if (cps.getRequestParams() != null
&& cps.getRequestParams() instanceof HttpParamsNames) {
names.addAll(((HttpParamsNames) cps.getRequestParams()).getNames());
}
if (cps.getOverrideParams() != null
&& cps.getRequestParams() instanceof HttpParamsNames) {
names.addAll(((HttpParamsNames) cps.getOverrideParams()).getNames());
}
} else {
HttpParamsNames params = (HttpParamsNames) request.getParams();
names = params.getNames();
}
Timber.v("<<<<<<<<<<<<<< REQUEST PARAMS >>>>>>>>>>>>");
for (String name : names) {
Timber.v("Param >> %s: %s", name, request.getParams().getParameter(name));
}
Timber.v("<<<<<<<<<<<<<< REQUEST PARAMS >>>>>>>>>>>>");
}
/**
* Log all headers from a HTTPRequest
* @param request
*/
private static void logRequestHeaders(HttpRequest request) {
Header[] headerFields = request.getAllHeaders();
Timber.v("<<<<<<<<<<<<<< HEADERS >>>>>>>>>>>>");
for (int e = 0; e < request.getAllHeaders().length; e++) {
Timber.v("Header >> %s: %s", headerFields[e].getName(), headerFields[e].getValue());
}
Timber.v("<<<<<<<<<<<<<< HEADERS >>>>>>>>>>>>");
}
}

View file

@ -30,6 +30,7 @@ import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber; import timber.log.Timber;
@ -147,7 +148,13 @@ public class NearbyListFragment extends DaggerFragment {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true, directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null)); if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, true, null);
} else {
controller.handleImagePicked(requestCode, data.getData(), true, null);
}
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -56,6 +56,7 @@ import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.ContributionController; import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.UriDeserializer;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber; import timber.log.Timber;
@ -765,10 +766,17 @@ public class NearbyMapFragment extends DaggerFragment {
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data, true, directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null)); if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, true, null);
} else {
controller.handleImagePicked(requestCode, data.getData(), true, null);
}
} else { } else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s", Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data); requestCode, resultCode, data);

View file

@ -14,6 +14,7 @@ import android.provider.DocumentsContract;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;

View file

@ -31,6 +31,7 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
@ -45,6 +46,8 @@ import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao; import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.ExternalStorageUtils;
import timber.log.Timber; import timber.log.Timber;
//TODO: We should use this class to see how multiple uploads are handled, and then REMOVE it. //TODO: We should use this class to see how multiple uploads are handled, and then REMOVE it.
@ -54,7 +57,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
AdapterView.OnItemClickListener, AdapterView.OnItemClickListener,
FragmentManager.OnBackStackChangedListener, FragmentManager.OnBackStackChangedListener,
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler, MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
OnCategoriesSaveHandler { OnCategoriesSaveHandler,
ActivityCompat.OnRequestPermissionsResultCallback{
@Inject @Inject
MediaWikiApi mwApi; MediaWikiApi mwApi;
@ -75,6 +79,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
private CategorizationFragment categorizationFragment; private CategorizationFragment categorizationFragment;
private boolean locationPermitted = false; private boolean locationPermitted = false;
private boolean isMultipleUploadsPrepared = false;
private boolean isMultipleUploadsFinalised = false; // Checks is user clicked to upload button or regret before this phase
@Override @Override
public Media getMediaAtPosition(int i) { public Media getMediaAtPosition(int i) {
@ -113,30 +119,25 @@ public class MultipleShareActivity extends AuthenticatedActivity
@Override @Override
public void OnMultipleUploadInitiated() { public void OnMultipleUploadInitiated() {
// No need to request external permission here, because if user can reach this point, then she permission granted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Timber.d("OnMultipleUploadInitiated");
//Check for Storage permission that is required for upload. Do not allow user to proceed without permission, otherwise will crash multipleUploadBegins();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
} else {
multipleUploadBegins();
}
} else {
multipleUploadBegins();
}
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == 1 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
multipleUploadBegins(); Timber.d("onRequestPermissionsResult external storage permission granted");
prepareMultipleUpoadList();
} else {
// Permission is not granted, close activity
finish();
} }
} }
private void multipleUploadBegins() { private void multipleUploadBegins() {
Timber.d("Multiple upload begins"); Timber.d("Multiple upload begins");
final ProgressDialog dialog = new ProgressDialog(this); final ProgressDialog dialog = new ProgressDialog(this);
dialog.setIndeterminate(false); dialog.setIndeterminate(false);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
@ -174,6 +175,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization") .add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization")
.commitAllowingStateLoss(); .commitAllowingStateLoss();
isMultipleUploadsFinalised = true;
//See http://stackoverflow.com/questions/7469082/getting-exception-illegalstateexception-can-not-perform-this-action-after-onsa //See http://stackoverflow.com/questions/7469082/getting-exception-illegalstateexception-can-not-perform-this-action-after-onsa
} }
@ -191,7 +193,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
} }
// FIXME: Make sure that the content provider is up // 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 // 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(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default! ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
finish(); finish();
} }
@ -253,13 +255,40 @@ public class MultipleShareActivity extends AuthenticatedActivity
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); /* This will be true if permission request is granted before we request. Otherwise we will
outState.putParcelableArrayList("uploadsList", photosList); * explicitly call operations under this method again.
*/
if (isMultipleUploadsPrepared) {
super.onSaveInstanceState(outState);
Timber.d("onSaveInstanceState multiple uploads is prepared, permission granted");
outState.putParcelableArrayList("uploadsList", photosList);
} else {
Timber.d("onSaveInstanceState multiple uploads is not prepared, permission not granted");
return;
}
} }
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
// Multiple uploads prepared boolean is used to decide when to call multipleUploadsBegin()
isMultipleUploadsFinalised = false;
isMultipleUploadsPrepared = false;
mwApi.setAuthCookie(authCookie); mwApi.setAuthCookie(authCookie);
if (!ExternalStorageUtils.isStoragePermissionGranted(this)) {
ExternalStorageUtils.requestExternalStoragePermission(this);
isMultipleUploadsPrepared = false;
return; // Postpone operation to do after gettion permission
} else {
isMultipleUploadsPrepared = true;
prepareMultipleUpoadList();
}
}
/**
* Prepares a list from files will be uploaded. Saves these files temporarily to external
* storage. Adds them to uploads list
*/
private void prepareMultipleUpoadList() {
Intent intent = getIntent(); Intent intent = getIntent();
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) { if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
@ -269,6 +298,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
for (int i = 0; i < urisList.size(); i++) { for (int i = 0; i < urisList.size(); i++) {
Contribution up = new Contribution(); Contribution up = new Contribution();
Uri uri = urisList.get(i); Uri uri = urisList.get(i);
// Use temporarily saved file Uri instead
uri = ContributionUtils.saveFileBeingUploadedTemporarily(this, uri);
up.setLocalUri(uri); up.setLocalUri(uri);
up.setTag("mimeType", intent.getType()); up.setTag("mimeType", intent.getType());
up.setTag("sequence", i); up.setTag("sequence", i);
@ -350,4 +381,24 @@ public class MultipleShareActivity extends AuthenticatedActivity
return null; return null;
} }
// If on back pressed before sharing
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
protected void onStop() {
// Remove saved files if activity is stopped before upload operation, ie user changed mind
if (!isMultipleUploadsFinalised) {
if (photosList != null) {
for (Contribution contribution : photosList) {
Timber.d("User changed mind, didn't click to upload button, deleted file: "+contribution.getLocalUri());
ContributionUtils.removeTemporaryFile(contribution.getLocalUri());
}
}
}
super.onStop();
}
} }

View file

@ -22,7 +22,9 @@ import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -34,6 +36,8 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.github.chrisbanes.photoview.PhotoView; import com.github.chrisbanes.photoview.PhotoView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -46,6 +50,7 @@ import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick; import butterknife.OnClick;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.auth.LoginActivity;
@ -61,6 +66,8 @@ import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.CategoryApi; import fr.free.nrw.commons.mwapi.CategoryApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.ContributionUtils;
import fr.free.nrw.commons.utils.ExternalStorageUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber; import timber.log.Timber;
@ -76,7 +83,9 @@ import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_
public class ShareActivity public class ShareActivity
extends AuthenticatedActivity extends AuthenticatedActivity
implements SingleUploadFragment.OnUploadActionInitiated, implements SingleUploadFragment.OnUploadActionInitiated,
OnCategoriesSaveHandler { OnCategoriesSaveHandler,
ActivityCompat.OnRequestPermissionsResultCallback {
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4; private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
//Had to make them class variables, to extract out the click listeners, also I see no harm in this //Had to make them class variables, to extract out the click listeners, also I see no harm in this
final Rect startBounds = new Rect(); final Rect startBounds = new Rect();
@ -119,6 +128,7 @@ public class ShareActivity
private String mimeType; private String mimeType;
private CategorizationFragment categorizationFragment; private CategorizationFragment categorizationFragment;
private Uri mediaUri; private Uri mediaUri;
private Uri contentProviderUri;
private Contribution contribution; private Contribution contribution;
private GPSExtractor gpsObj; private GPSExtractor gpsObj;
private String decimalCoords; private String decimalCoords;
@ -135,9 +145,12 @@ public class ShareActivity
private long ShortAnimationDuration; private long ShortAnimationDuration;
private boolean isFABOpen = false; private boolean isFABOpen = false;
private float startScaleFinal; private float startScaleFinal;
private Bundle savedInstanceState;
private boolean isUploadFinalised = false; // Checks is user clicked to upload button or regret before this phase
private boolean isZoom = false; private boolean isZoom = false;
/** /**
* Called when user taps the submit button. * Called when user taps the submit button.
* Requests Storage permission, if needed. * Requests Storage permission, if needed.
@ -183,6 +196,7 @@ public class ShareActivity
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri) return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) && (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED); != PackageManager.PERMISSION_GRANTED);
//return false;
} }
@ -203,13 +217,12 @@ public class ShareActivity
Timber.d("Cache the categories found"); Timber.d("Cache the categories found");
} }
uploadController.startUpload(title,mediaUri,description,mimeType,source,decimalCoords,wikiDataEntityId,c -> uploadController.startUpload(title, contentProviderUri, mediaUri, description, mimeType, source, decimalCoords, wikiDataEntityId, c -> {
ShareActivity.this.contribution = c;
{ showPostUpload();
ShareActivity.this.contribution = c; });
showPostUpload(); isUploadFinalised = true;
}); }
}
/** /**
* Starts CategorizationFragment after uploadBegins. * Starts CategorizationFragment after uploadBegins.
@ -241,7 +254,7 @@ public class ShareActivity
// FIXME: Make sure that the content provider is up // 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 // 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(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default! ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
finish(); finish();
} }
@ -270,7 +283,7 @@ public class ShareActivity
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
isUploadFinalised = false;
setContentView(R.layout.activity_share); setContentView(R.layout.activity_share);
ButterKnife.bind(this); ButterKnife.bind(this);
initBack(); initBack();
@ -281,9 +294,29 @@ public class ShareActivity
.setFailureImage(VectorDrawableCompat.create(getResources(), .setFailureImage(VectorDrawableCompat.create(getResources(),
R.drawable.ic_error_outline_black_24dp, getTheme())) R.drawable.ic_error_outline_black_24dp, getTheme()))
.build()); .build());
if (!ExternalStorageUtils.isStoragePermissionGranted(this)) {
this.savedInstanceState = savedInstanceState;
ExternalStorageUtils.requestExternalStoragePermission(this);
return; // Postpone operation to do after getting permission
} else {
receiveImageIntent();
createContributionWithReceivedIntent(savedInstanceState);
}
}
receiveImageIntent(); @Override
protected void onStop() {
// If upload is not finalised with failure or success, but contribution is created,
// we have to remove temp file, to prevent using unnecessary memory
if (!isUploadFinalised) {
if (mediaUri != null) {
ContributionUtils.removeTemporaryFile(mediaUri);
}
}
super.onStop();
}
private void createContributionWithReceivedIntent(Bundle savedInstanceState) {
if (savedInstanceState != null) { if (savedInstanceState != null) {
contribution = savedInstanceState.getParcelable("contribution"); contribution = savedInstanceState.getParcelable("contribution");
} }
@ -318,6 +351,11 @@ public class ShareActivity
if (Intent.ACTION_SEND.equals(intent.getAction())) { if (Intent.ACTION_SEND.equals(intent.getAction())) {
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
contentProviderUri = mediaUri;
mediaUri = ContributionUtils.saveFileBeingUploadedTemporarily(this, mediaUri);
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) { if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
source = intent.getStringExtra(UploadService.EXTRA_SOURCE); source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
} else { } else {
@ -400,17 +438,20 @@ public class ShareActivity
*/ */
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) { if (requestCode == 1 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Storage (from submit button) - this needs to be separate from (1) because only the Timber.d("onRequestPermissionsResult external storage permission granted");
// submit button should bring user to next screen // You can receive image intent and save image to a temp file only if ext storage permission is granted
case REQUEST_PERM_ON_SUBMIT_STORAGE: { receiveImageIntent();
if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { createContributionWithReceivedIntent(savedInstanceState);
checkIfFileExists();
//Uploading only begins if storage permission granted from arrow icon if (requestCode == REQUEST_PERM_ON_SUBMIT_STORAGE) {
uploadBegins(); checkIfFileExists();
} //Uploading only begins if storage permission granted from arrow icon
uploadBegins();
} }
} else {
finish();
} }
} }

View file

@ -15,9 +15,10 @@ import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.widget.Toast; import android.util.Log;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date; import java.util.Date;
@ -100,7 +101,7 @@ public class UploadController {
* @param wikiDataEntityId * @param wikiDataEntityId
* @param onComplete the progress tracker * @param onComplete the progress tracker
*/ */
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) { public void startUpload(String title, Uri contentProviderUri, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
Contribution contribution; Contribution contribution;
@ -133,6 +134,7 @@ public class UploadController {
contribution.setTag("mimeType", mimeType); contribution.setTag("mimeType", mimeType);
contribution.setSource(source); contribution.setSource(source);
contribution.setWikiDataEntityId(wikiDataEntityId); contribution.setWikiDataEntityId(wikiDataEntityId);
contribution.setContentProviderUri(contentProviderUri);
} }
@ -168,9 +170,12 @@ public class UploadController {
long length; long length;
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
try { try {
//TODO: understand do we really need this code
if (contribution.getDataLength() <= 0) { if (contribution.getDataLength() <= 0) {
Log.d("deneme","UploadController/doInBackground, contribution.getLocalUri():"+contribution.getLocalUri());
AssetFileDescriptor assetFileDescriptor = contentResolver AssetFileDescriptor assetFileDescriptor = contentResolver
.openAssetFileDescriptor(contribution.getLocalUri(), "r"); .openAssetFileDescriptor(Uri.fromFile(new File(contribution.getLocalUri().getPath())), "r");
if (assetFileDescriptor != null) { if (assetFileDescriptor != null) {
length = assetFileDescriptor.getLength(); length = assetFileDescriptor.getLength();
if (length == -1) { if (length == -1) {
@ -220,7 +225,7 @@ public class UploadController {
contribution.setDateCreated(new Date()); contribution.setDateCreated(new Date());
} }
} }
return contribution; return contribution;
} }
@Override @Override

View file

@ -10,9 +10,12 @@ import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -23,6 +26,7 @@ import java.util.regex.Pattern;
import javax.inject.Inject; import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
@ -179,13 +183,15 @@ public class UploadService extends HandlerService<Contribution> {
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
private void uploadContribution(Contribution contribution) { private void uploadContribution(Contribution contribution) {
InputStream file; InputStream fileInputStream;
String notificationTag = contribution.getLocalUri().toString(); String notificationTag = contribution.getLocalUri().toString();
try { try {
//FIXME: Google Photos bug //FIXME: Google Photos bug
file = this.getContentResolver().openInputStream(contribution.getLocalUri()); File file1 = new File(contribution.getLocalUri().getPath());
fileInputStream = new FileInputStream(file1);
//fileInputStream = this.getContentResolver().openInputStream(contribution.getLocalUri());
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Timber.d("File not found"); Timber.d("File not found");
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG); Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
@ -193,9 +199,9 @@ public class UploadService extends HandlerService<Contribution> {
return; return;
} }
//As the file is null there's no point in continuing the upload process //As the fileInputStream is null there's no point in continuing the upload process
//mwapi.upload accepts a NonNull input stream //mwapi.upload accepts a NonNull input stream
if(file == null) { if(fileInputStream == null) {
Timber.d("File not found"); Timber.d("File not found");
return; return;
} }
@ -243,7 +249,7 @@ public class UploadService extends HandlerService<Contribution> {
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()), getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
contribution contribution
); );
UploadResult uploadResult = mwApi.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); UploadResult uploadResult = mwApi.uploadFile(filename, fileInputStream, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater, contribution.getLocalUri(), contribution.getContentProviderUri());
Timber.d("Response is %s", uploadResult.toString()); Timber.d("Response is %s", uploadResult.toString());
@ -272,7 +278,7 @@ public class UploadService extends HandlerService<Contribution> {
toUpload--; toUpload--;
if (toUpload == 0) { if (toUpload == 0) {
// Sync modifications right after all uplaods are processed // Sync modifications right after all uplaods are processed
ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, new Bundle()); ContentResolver.requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, new Bundle());
stopForeground(true); stopForeground(true);
} }
} }

View file

@ -0,0 +1,95 @@
package fr.free.nrw.commons.utils;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import java.io.File;
import java.util.Random;
import timber.log.Timber;
/**
* This class includes utility methods for uploading process of images.
*/
public class ContributionUtils {
private static String TEMP_EXTERNAL_DIRECTORY =
android.os.Environment.getExternalStorageDirectory().getPath()+
File.separatorChar+"UploadingByCommonsApp";
/**
* Saves images temporarily to a fixed folder and use Uri of that file during upload process.
* Otherwise, temporary Uri provided by content provider sometimes points to a null space and
* consequently upload fails. See: issue #1400A and E.
* Not: Saved image will be deleted, our directory will be empty after upload process.
* @return URI of saved image
*/
public static Uri saveFileBeingUploadedTemporarily(Context context, Uri URIfromContentProvider) {
// TODO add exceptions for Google Drive URİ is needed
Uri result = null;
if (FileUtils.checkIfDirectoryExists(TEMP_EXTERNAL_DIRECTORY)) {
String destinationFilename = decideTempDestinationFileName();
result = FileUtils.saveFileFromURI(context, URIfromContentProvider, destinationFilename);
} else { // If directory doesn't exist, create it and recursive call current method to check again
File file = new File(TEMP_EXTERNAL_DIRECTORY);
if (file.mkdirs()) {
Timber.d("saveFileBeingUploadedTemporarily() parameters: URI from Content Provider %s", URIfromContentProvider);
result = saveFileBeingUploadedTemporarily(context, URIfromContentProvider); // If directory is created
} else { //An error occurred to create directory
Timber.e("saveFileBeingUploadedTemporarily() parameters: URI from Content Provider %s", URIfromContentProvider);
}
}
return result;
}
/**
* Removes temp file created during upload
* @param tempFileUri
*/
public static void removeTemporaryFile(Uri tempFileUri) {
//TODO: do I have to notify file system about deletion?
File tempFile = new File(tempFileUri.getPath());
if (tempFile.exists()) {
boolean isDeleted= tempFile.delete();
Timber.e("removeTemporaryFile() parameters: URI tempFileUri %s, deleted status %b", tempFileUri, isDeleted);
}
}
private static String decideTempDestinationFileName() {
int i = 0;
while (true) {
if (new File(TEMP_EXTERNAL_DIRECTORY +File.separatorChar+i+"_tmp").exists()) {
// This file is in use, try enother file
i++;
} else {
// Use time stamp for file name, so that two temporary file never has same file name
// to prevent previous file reference bug
Long tsLong = System.currentTimeMillis()/1000;
String ts = tsLong.toString();
// For multiple uploads, time randomisation should be combined with another random
// parameter, since they created at same time
int multipleUploadRandomParameter = new Random().nextInt(100);
return TEMP_EXTERNAL_DIRECTORY +File.separatorChar+ts+multipleUploadRandomParameter+"_tmp";
}
}
}
public static void emptyTemporaryDirectory() {
File dir = new File(TEMP_EXTERNAL_DIRECTORY);
if (dir.isDirectory())
{
String[] children = dir.list();
if (children != null && children.length >0) {
for (int i = 0; i < children.length; i++)
{
new File(dir, children[i]).delete();
}
}
}
}
}

View file

@ -0,0 +1,47 @@
package fr.free.nrw.commons.utils;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import timber.log.Timber;
/**
* Created by root on 23.07.2018.
*/
public class ExternalStorageUtils {
/**
* Checks if external storage permission is granted
* @param context activity we are on
* @return true if permission is granted, false if not
*/
public static boolean isStoragePermissionGranted(Context context) {
if (Build.VERSION.SDK_INT >= 23) {
if (context.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Timber.d("External storage permission granted, API >= 23");
return true;
} else {
Timber.d("External storage permission not granted, API >= 23");
return false;
}
} else { //permission is automatically granted on sdk<23 upon installation
Timber.d("External storage permission granted before, API < 23");
return true;
}
}
/**
* Requests external storage permission
* @param context activity we are on
*/
public static void requestExternalStoragePermission(Context context) {
Timber.d("External storage permission requested");
ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}

View file

@ -0,0 +1,89 @@
package fr.free.nrw.commons.utils;
import android.content.Context;
import android.net.Uri;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
/**
* Created for file operations
*/
public class FileUtils {
/**
* Saves file from source URI to destination.
* @param sourceUri Uri which points to file to be saved
* @param destinationFilename where file will be located at
* @return Uri points to file saved
*/
public static Uri saveFileFromURI(Context context, Uri sourceUri, String destinationFilename) {
File file = new File(destinationFilename);
if (file.exists()) {
file.delete();
}
InputStream in = null;
OutputStream out = null;
try {
in = context.getContentResolver().openInputStream(sourceUri);
out = new FileOutputStream(new File(destinationFilename));
byte[] buf = new byte[1024];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return Uri.parse("file://" + destinationFilename);
}
/**
* Checks if directory exists
* @param pathToCheck path of directory to check
* @return true if directory exists, false otherwise
*/
public static boolean checkIfDirectoryExists(String pathToCheck) {
File director = new File(pathToCheck);
if(director.exists() && director.isDirectory()) {
return true;
} else {
return false;
}
}
/**
* Creates new directory.
* @param pathToCreateAt where directory will be created at
* @return true if directory is created, false if an error occured, or already exists.
*/
public static boolean createDirectory(String pathToCreateAt) {
File directory = new File(pathToCreateAt);
if (!directory.exists()) {
return directory.mkdirs(); //true if directory is created
} else {
return false; //false if file already exists
}
}
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="fr.free.nrw.commons" android:accountType="@string/account_type"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:smallIcon="@drawable/ic_launcher" /> android:smallIcon="@drawable/ic_launcher" />

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="fr.free.nrw.commons.contributions.contentprovider" android:contentAuthority="@string/contribution_authority"
android:accountType="fr.free.nrw.commons" android:accountType="@string/account_type"
android:supportsUploading="false" android:supportsUploading="false"
android:userVisible="true" android:userVisible="true"
android:isAlwaysSyncable="true" android:isAlwaysSyncable="true"

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="fr.free.nrw.commons.modifications.contentprovider" android:contentAuthority="@string/modification_authority"
android:accountType="fr.free.nrw.commons" android:accountType="@string/account_type"
android:supportsUploading="true" android:supportsUploading="true"
android:userVisible="true" android:userVisible="true"
android:isAlwaysSyncable="true" android:isAlwaysSyncable="true"

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_type">fr.free.nrw.commons</string>
<string name="contribution_authority">fr.free.nrw.commons.contributions.contentprovider</string>
<string name="modification_authority">fr.free.nrw.commons.modifications.contentprovider</string>
<string name="category_authority">fr.free.nrw.commons.categories.contentprovider</string>
</resources>