Synced branch to master

This commit is contained in:
Vishan Seru 2017-10-10 19:51:29 +05:30
commit 6b96b65203
123 changed files with 1667 additions and 930 deletions

View file

@ -13,7 +13,6 @@ import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.preference.PreferenceManager;
import android.support.v4.util.LruCache;
import android.util.Log;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.stetho.Stetho;
@ -35,7 +34,6 @@ import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.FileUtils;
import timber.log.Timber;
@ -51,12 +49,6 @@ import timber.log.Timber;
public class CommonsApplication extends Application {
private Account currentAccount = null; // Unlike a savings account...
public static final String API_URL = "https://commons.wikimedia.org/w/api.php";
public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/commons";
public static final String HOME_URL = "https://commons.wikimedia.org/wiki/";
public static final String MOBILE_HOME_URL = "https://commons.m.wikimedia.org/wiki/";
public static final String EVENTLOG_URL = "https://www.wikimedia.org/beacon/event";
public static final String EVENTLOG_WIKI = "commonswiki";
public static final Object[] EVENT_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5334329L};
public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5257721L};
@ -92,7 +84,7 @@ public class CommonsApplication extends Application {
public MediaWikiApi getMWApi() {
if (api == null) {
api = new ApacheHttpClientMediaWikiApi(API_URL);
api = new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
}
return api;
}
@ -155,10 +147,10 @@ public class CommonsApplication extends Application {
* @return Account|null
*/
public Account getCurrentAccount() {
if(currentAccount == null) {
if (currentAccount == null) {
AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
if(allAccounts.length != 0) {
if (allAccounts.length != 0) {
currentAccount = allAccounts[0];
}
}
@ -169,7 +161,7 @@ public class CommonsApplication extends Application {
AccountManager accountManager = AccountManager.get(this);
Account curAccount = getCurrentAccount();
if(curAccount == null) {
if (curAccount == null) {
return false; // This should never happen
}
@ -190,7 +182,7 @@ public class CommonsApplication extends Application {
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
public void clearApplicationData(Context context, NavigationBaseActivity.LogoutListener logoutListener) {
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
if (applicationDirectory.exists()) {
@ -222,10 +214,8 @@ public class CommonsApplication extends Application {
setIndex(getIndex() + 1);
try {
if (accountManagerFuture != null) {
if (accountManagerFuture.getResult()) {
Timber.d("Account removed successfully.");
}
if (accountManagerFuture != null && accountManagerFuture.getResult()) {
Timber.d("Account removed successfully.");
}
} catch (OperationCanceledException | IOException | AuthenticatorException e) {
e.printStackTrace();
@ -234,11 +224,13 @@ public class CommonsApplication extends Application {
if (getIndex() == allAccounts.length) {
Timber.d("All accounts have been removed");
//TODO: fix preference manager
PreferenceManager.getDefaultSharedPreferences(getInstance()).edit().clear().commit();
PreferenceManager.getDefaultSharedPreferences(getInstance())
.edit().clear().commit();
SharedPreferences preferences = context
.getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
preferences.edit().clear().commit();
context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().clear().commit();
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit().clear().commit();
preferences.edit().putBoolean("firstrun", false).apply();
updateAllDatabases();
currentAccount = null;
@ -265,4 +257,8 @@ public class CommonsApplication extends Application {
Category.Table.onDelete(db);
Contribution.Table.onDelete(db);
}
public interface LogoutListener {
void onLogoutComplete();
}
}

View file

@ -3,10 +3,10 @@ package fr.free.nrw.commons;
import android.support.annotation.Nullable;
public class License {
String key;
String template;
String url;
String name;
private String key;
private String template;
private String url;
private String name;
public License(String key, String template, String url, String name) {
if (key == null) {

View file

@ -10,18 +10,16 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class LicenseList {
Map<String, License> licenses = new HashMap<>();
Resources res;
private static String XMLNS_LICENSE = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
private Map<String, License> licenses = new HashMap<>();
private Resources res;
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
while (Utils.xmlFastForward(parser, XMLNS_LICENSE, "license")) {
String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
while (Utils.xmlFastForward(parser, namespace, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
@ -31,10 +29,6 @@ public class LicenseList {
}
}
public Set<String> keySet() {
return licenses.keySet();
}
public Collection<License> values() {
return licenses.values();
}
@ -44,7 +38,7 @@ public class LicenseList {
}
@Nullable
public License licenseForTemplate(String template) {
License licenseForTemplate(String template) {
String ucTemplate = new PageTitle(template).getDisplayText();
for (License license : values()) {
if (ucTemplate.equals(new PageTitle(license.getTemplate()).getDisplayText())) {
@ -54,26 +48,16 @@ public class LicenseList {
return null;
}
public String nameIdForTemplate(String template) {
private String nameIdForTemplate(String template) {
// hack :D (converts dashes and periods to underscores)
// cc-by-sa-3.0 -> cc_by_sa_3_0
return "license_name_" + template.toLowerCase(Locale.ENGLISH).replace("-", "_").replace(".", "_");
return "license_name_" + template.toLowerCase(Locale.ENGLISH).replace("-",
"_").replace(".", "_");
}
private int stringIdByName(String stringId) {
return res.getIdentifier("fr.free.nrw.commons:string/" + stringId, null, null);
}
public String nameForTemplate(String template) {
//Log.d("Commons", "LicenseList.nameForTemplate: template: " + template);
String stringId = nameIdForTemplate(template);
//Log.d("Commons", "LicenseList.nameForTemplate: stringId: " + stringId);
int nameId = stringIdByName(stringId);
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
if(nameId != 0) {
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
return res.getString(nameId);
}
return template;
private String nameForTemplate(String template) {
int nameId = res.getIdentifier("fr.free.nrw.commons:string/"
+ nameIdForTemplate(template), null, null);
return (nameId != 0) ? res.getString(nameId) : template;
}
}

View file

@ -29,12 +29,66 @@ public class Media implements Parcelable {
}
};
private static Pattern displayTitlePattern = Pattern.compile("(.*)(\\.\\w+)", Pattern.CASE_INSENSITIVE);
// Primary metadata fields
protected Uri localUri;
protected String imageUrl;
protected String filename;
protected String description; // monolingual description on input...
protected long dataLength;
protected Date dateCreated;
protected @Nullable Date dateUploaded;
protected int width;
protected int height;
protected String license;
protected String creator;
protected ArrayList<String> categories; // as loaded at runtime?
private Map<String, String> descriptions; // multilingual descriptions as loaded
private HashMap<String, Object> tags = new HashMap<>();
private @Nullable LatLng coordinates;
protected Media() {
this.categories = new ArrayList<>();
this.descriptions = new HashMap<>();
}
private HashMap<String, Object> tags = new HashMap<>();
public Media(String filename) {
this();
this.filename = filename;
}
public Media(Uri localUri, String imageUrl, String filename, String description,
long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
}
@SuppressWarnings("unchecked")
public Media(Parcel in) {
localUri = in.readParcelable(Uri.class.getClassLoader());
imageUrl = in.readString();
filename = in.readString();
description = in.readString();
dataLength = in.readLong();
dateCreated = (Date) in.readSerializable();
dateUploaded = (Date) in.readSerializable();
creator = in.readString();
tags = (HashMap<String, Object>) in.readSerializable();
width = in.readInt();
height = in.readInt();
license = in.readString();
if (categories != null) {
in.readStringList(categories);
}
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
public Object getTag(String key) {
return tags.get(key);
@ -44,15 +98,14 @@ public class Media implements Parcelable {
tags.put(key, value);
}
public static Pattern displayTitlePattern = Pattern.compile("(.*)(\\.\\w+)", Pattern.CASE_INSENSITIVE);
public String getDisplayTitle() {
if(filename == null) {
public String getDisplayTitle() {
if (filename == null) {
return "";
}
// FIXME: Gross hack bercause my regex skills suck maybe or I am too lazy who knows
String title = getFilePageTitle().getDisplayText().replaceFirst("^File:", "");
Matcher matcher = displayTitlePattern.matcher(title);
if(matcher.matches()) {
if (matcher.matches()) {
return matcher.group(1);
} else {
return title;
@ -68,7 +121,7 @@ public class Media implements Parcelable {
}
public String getImageUrl() {
if(imageUrl == null) {
if (imageUrl == null) {
imageUrl = Utils.makeThumbBaseUrl(this.getFilename());
}
return imageUrl;
@ -86,6 +139,10 @@ public class Media implements Parcelable {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public long getDataLength() {
return dataLength;
}
@ -102,7 +159,8 @@ public class Media implements Parcelable {
this.dateCreated = date;
}
public @Nullable Date getDateUploaded() {
public @Nullable
Date getDateUploaded() {
return dateUploaded;
}
@ -138,7 +196,8 @@ public class Media implements Parcelable {
this.license = license;
}
public @Nullable LatLng getCoordinates() {
public @Nullable
LatLng getCoordinates() {
return coordinates;
}
@ -146,24 +205,9 @@ public class Media implements Parcelable {
this.coordinates = coordinates;
}
// Primary metadata fields
protected Uri localUri;
protected String imageUrl;
protected String filename;
protected String description; // monolingual description on input...
protected long dataLength;
protected Date dateCreated;
protected @Nullable Date dateUploaded;
protected int width;
protected int height;
protected String license;
private @Nullable LatLng coordinates;
protected String creator;
protected ArrayList<String> categories; // as loaded at runtime?
protected Map<String, String> descriptions; // multilingual descriptions as loaded
@SuppressWarnings("unchecked")
public ArrayList<String> getCategories() {
return (ArrayList<String>)categories.clone(); // feels dirty
return (ArrayList<String>) categories.clone(); // feels dirty
}
public void setCategories(List<String> categories) {
@ -171,7 +215,7 @@ public class Media implements Parcelable {
this.categories.addAll(categories);
}
public void setDescriptions(Map<String,String> descriptions) {
void setDescriptions(Map<String, String> descriptions) {
for (String key : this.descriptions.keySet()) {
this.descriptions.remove(key);
}
@ -196,23 +240,6 @@ public class Media implements Parcelable {
}
}
public Media(String filename) {
this();
this.filename = filename;
}
public Media(Uri localUri, String imageUrl, String filename, String description, long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
}
@Override
public int describeContents() {
return 0;
@ -235,27 +262,4 @@ public class Media implements Parcelable {
parcel.writeStringList(categories);
parcel.writeMap(descriptions);
}
public Media(Parcel in) {
localUri = in.readParcelable(Uri.class.getClassLoader());
imageUrl = in.readString();
filename = in.readString();
description = in.readString();
dataLength = in.readLong();
dateCreated = (Date) in.readSerializable();
dateUploaded = (Date) in.readSerializable();
creator = in.readString();
tags = (HashMap<String, Object>)in.readSerializable();
width = in.readInt();
height = in.readInt();
license = in.readString();
if (categories != null) {
in.readStringList(categories);
}
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
public void setDescription(String description) {
this.description = description;
}
}

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons;
import android.support.annotation.Nullable;
import org.mediawiki.api.ApiResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

View file

@ -58,7 +58,7 @@ public class PageTitle {
*/
@NonNull
public Uri getCanonicalUri() {
String uriStr = CommonsApplication.HOME_URL + Uri.encode(getPrefixedText(), ":/");
String uriStr = BuildConfig.HOME_URL + Uri.encode(getPrefixedText(), ":/");
return Uri.parse(uriStr);
}
@ -71,7 +71,7 @@ public class PageTitle {
*/
@NonNull
public Uri getMobileUri() {
String uriStr = CommonsApplication.MOBILE_HOME_URL + Uri.encode(getPrefixedText(), ":/");
String uriStr = BuildConfig.MOBILE_HOME_URL + Uri.encode(getPrefixedText(), ":/");
return Uri.parse(uriStr);
}

View file

@ -40,7 +40,6 @@ import javax.xml.transform.stream.StreamResult;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class Utils {
// Get SHA1 of file from input stream
@ -80,10 +79,12 @@ public class Utils {
}
}
/** Fix Html.fromHtml is deprecated problem
/**
* Fix Html.fromHtml is deprecated problem
*
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
* */
*/
public static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
@ -127,7 +128,7 @@ public class Utils {
public static String makeThumbBaseUrl(String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", CommonsApplication.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
}
public static String getStringFromDOM(Node dom) {
@ -241,9 +242,9 @@ public class Utils {
public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
try {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() == XmlPullParser.START_TAG &&
parser.getNamespace().equals(namespace) &&
parser.getName().equals(element)) {
if (parser.getEventType() == XmlPullParser.START_TAG
&& parser.getNamespace().equals(namespace)
&& parser.getName().equals(element)) {
// We found it!
return true;
}
@ -266,7 +267,8 @@ public class Utils {
extension = "jpg";
}
title = jpegPattern.matcher(title).replaceFirst(".jpg");
if (extension != null && !title.toLowerCase(Locale.getDefault()).endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
if (extension != null && !title.toLowerCase(Locale.getDefault())
.endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
title += "." + extension;
}
return title;
@ -277,6 +279,6 @@ public class Utils {
}
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme",false);
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}
}

View file

@ -14,11 +14,11 @@ import timber.log.Timber;
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
String accountType;
private String accountType;
CommonsApplication app;
private String authCookie;
public AuthenticatedActivity() {
this.accountType = AccountUtil.accountType();
}
@ -28,9 +28,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
cookie -> onAuthCookieAcquired(cookie),
throwable -> onAuthFailure());
.subscribe(this::onAuthCookieAcquired, throwable -> onAuthFailure());
}
private void addAccount(AccountManager accountManager) {
@ -55,24 +53,24 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
}
protected void requestAuthToken() {
if(authCookie != null) {
if (authCookie != null) {
onAuthCookieAcquired(authCookie);
return;
}
AccountManager accountManager = AccountManager.get(this);
Account curAccount = app.getCurrentAccount();
if(curAccount == null) {
if (curAccount == null) {
addAccount(accountManager);
} else {
getAuthCookie(curAccount, accountManager);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = CommonsApplication.getInstance();
if(savedInstanceState != null) {
if (savedInstanceState != null) {
authCookie = savedInstanceState.getString("authCookie");
}
}
@ -84,5 +82,6 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
}
protected abstract void onAuthCookieAcquired(String authCookie);
protected abstract void onAuthFailure();
}

View file

@ -8,10 +8,8 @@ import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

View file

@ -6,6 +6,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber;
@ -24,16 +25,17 @@ public class SignupActivity extends BaseActivity {
webView.setWebViewClient(new MyWebViewClient());
WebSettings webSettings = webView.getSettings();
//Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can trust Wikimedia's site... right?
/*Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can
trust Wikimedia's site... right?*/
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes");
webView.loadUrl(BuildConfig.SIGNUP_LANDING_URL);
}
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) {
if (url.equals(BuildConfig.SIGNUP_SUCCESS_REDIRECTION_URL)) {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url);

View file

@ -16,21 +16,32 @@ import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION;
import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;
import static android.accounts.AccountManager.KEY_ERROR_CODE;
import static android.accounts.AccountManager.KEY_ERROR_MESSAGE;
import static android.accounts.AccountManager.KEY_INTENT;
import static fr.free.nrw.commons.auth.LoginActivity.PARAM_USERNAME;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
private Context context;
public WikiAccountAuthenticator(Context context) {
WikiAccountAuthenticator(Context context) {
super(context);
this.context = context;
}
private Bundle unsupportedOperation() {
Bundle bundle = new Bundle();
bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
bundle.putInt(KEY_ERROR_CODE, ERROR_CODE_UNSUPPORTED_OPERATION);
// HACK: the docs indicate that this is a required key bit it's not displayed to the user.
bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "");
bundle.putString(KEY_ERROR_MESSAGE, "");
return bundle;
}
@ -54,10 +65,10 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
private Bundle addAccount(AccountAuthenticatorResponse response) {
Intent Intent = new Intent(context, LoginActivity.class);
Intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
Intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, Intent);
bundle.putParcelable(KEY_INTENT, Intent);
return bundle;
}
@ -78,14 +89,16 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
//TODO add 2fa support here
String result = api.login(username, password);
if(result.equals("PASS")) {
if (result.equals("PASS")) {
return api.getAuthCookie();
} else {
return null;
}
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle options) throws NetworkErrorException {
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(context);
@ -101,9 +114,9 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
}
if (authCookie != null) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
result.putString(AccountManager.KEY_AUTHTOKEN, authCookie);
result.putString(KEY_ACCOUNT_NAME, account.name);
result.putString(KEY_ACCOUNT_TYPE, AccountUtil.accountType());
result.putString(KEY_AUTHTOKEN, authCookie);
return result;
}
}
@ -112,10 +125,10 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
// need to re-prompt them for their credentials. We do that by creating
// an intent to display our AuthenticatorActivity panel.
final Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(LoginActivity.PARAM_USERNAME, account.name);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(PARAM_USERNAME, account.name);
intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
bundle.putParcelable(KEY_INTENT, intent);
return bundle;
}
@ -133,7 +146,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
@NonNull Account account, @NonNull String[] features)
throws NetworkErrorException {
Bundle bundle = new Bundle();
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
bundle.putBoolean(KEY_BOOLEAN_RESULT, false);
return bundle;
}
@ -141,8 +154,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
@Override
public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @Nullable String authTokenType,
@Nullable Bundle options)
throws NetworkErrorException {
@Nullable Bundle options) throws NetworkErrorException {
return unsupportedOperation();
}

View file

@ -32,7 +32,7 @@ public class CacheController {
public void cacheCategory() {
List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat());
pointCatList.addAll(MwVolleyApi.getGpsCat());
Timber.d("Categories being cached: %s", pointCatList);
} else {
Timber.d("No categories found, so no categories cached");

View file

@ -31,15 +31,12 @@ class CategoriesRenderer extends Renderer<CategoryItem> {
@Override
protected void hookListeners(View view) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CategoryItem item = getContent();
item.setSelected(!item.isSelected());
checkedView.setChecked(item.isSelected());
if (listener != null) {
listener.categoryClicked(item);
}
view.setOnClickListener(v -> {
CategoryItem item = getContent();
item.setSelected(!item.isSelected());
checkedView.setChecked(item.isSelected());
if (listener != null) {
listener.categoryClicked(item);
}
});
}

View file

@ -25,6 +25,7 @@ import com.pedrogomez.renderers.RVRendererAdapter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -36,6 +37,7 @@ import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -97,6 +99,7 @@ public class CategorizationFragment extends Fragment {
categoriesCache = new HashMap<>();
if (savedInstanceState != null) {
items.addAll(savedInstanceState.getParcelableArrayList("currentCategories"));
//noinspection unchecked
categoriesCache.putAll((HashMap<String, ArrayList<String>>) savedInstanceState
.getSerializable("categoriesCache"));
}
@ -106,7 +109,7 @@ public class CategorizationFragment extends Fragment {
RxTextView.textChanges(categoriesFilter)
.takeUntil(RxView.detaches(categoriesFilter))
.debounce(300, TimeUnit.MILLISECONDS)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(filter -> updateCategoryList(filter.toString()));
return rootView;
@ -194,16 +197,15 @@ public class CategorizationFragment extends Fragment {
.concatWith(
searchAll(filter)
.mergeWith(searchCategories(filter))
.concatWith( TextUtils.isEmpty(filter)
.concatWith(TextUtils.isEmpty(filter)
? defaultCategories() : Observable.empty())
)
.filter(categoryItem -> !containsYear(categoryItem.getName()))
.distinct()
.sorted(sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
s -> categoriesAdapter.add(s),
throwable -> Timber.e(throwable),
() -> {
s -> categoriesAdapter.add(s), Timber::e, () -> {
categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE);
@ -222,6 +224,12 @@ public class CategorizationFragment extends Fragment {
);
}
private Comparator<CategoryItem> sortBySimilarity(final String filter) {
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
return (firstItem, secondItem) -> stringSimilarityComparator
.compare(firstItem.getName(), secondItem.getName());
}
private List<String> getStringList(List<CategoryItem> input) {
List<String> output = new ArrayList<>();
for (CategoryItem item : input) {

View file

@ -15,31 +15,39 @@ import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
import static fr.free.nrw.commons.data.Category.Table.ALL_FIELDS;
import static fr.free.nrw.commons.data.Category.Table.COLUMN_ID;
import static fr.free.nrw.commons.data.Category.Table.TABLE_NAME;
public class CategoryContentProvider extends ContentProvider {
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
// For URI matcher
private static final int CATEGORIES = 1;
private static final int CATEGORIES_ID = 2;
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
private static final String BASE_PATH = "categories";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CATEGORIES);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
}
private DBOpenHelper dbOpenHelper;
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
private DBOpenHelper dbOpenHelper;
@SuppressWarnings("ConstantConditions")
@Override
public boolean onCreate() {
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
CommonsApplication app = ((CommonsApplication) getContext().getApplicationContext());
dbOpenHelper = app.getDBOpenHelper();
return false;
}
@ -48,23 +56,23 @@ public class CategoryContentProvider extends ContentProvider {
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Category.Table.TABLE_NAME);
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch(uriType) {
switch (uriType) {
case CATEGORIES:
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CATEGORIES_ID:
cursor = queryBuilder.query(db,
Category.Table.ALL_FIELDS,
ALL_FIELDS,
"_id = ?",
new String[] { uri.getLastPathSegment() },
new String[]{uri.getLastPathSegment()},
null,
null,
sortOrder
@ -92,7 +100,7 @@ public class CategoryContentProvider extends ContentProvider {
long id;
switch (uriType) {
case CATEGORIES:
id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues);
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@ -115,9 +123,9 @@ public class CategoryContentProvider extends ContentProvider {
sqlDB.beginTransaction();
switch (uriType) {
case CATEGORIES:
for(ContentValues value: values) {
for (ContentValues value : values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(Category.Table.TABLE_NAME, null, value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@ -134,24 +142,25 @@ public class CategoryContentProvider extends ContentProvider {
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
SQL Injection warnings: First, note that we're not exposing this to the
outside world (exported="false"). Even then, we should make sure to sanitize
all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
In here, the only concat created argument is for id. It is cast to an int,
and will error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case CATEGORIES_ID:
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Category.Table.TABLE_NAME,
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
Category.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");

View file

@ -127,7 +127,7 @@ public class Contribution extends Media {
}
public String getPageContents() {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
buffer

View file

@ -1,30 +1,39 @@
package fr.free.nrw.commons.contributions;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.FileProvider;
import java.io.File;
import java.util.Date;
import java.util.List;
import fr.free.nrw.commons.upload.ShareActivity;
import fr.free.nrw.commons.upload.UploadService;
import timber.log.Timber;
public class ContributionController {
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.EXTRA_STREAM;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
class ContributionController {
private static final int SELECT_FROM_GALLERY = 1;
private static final int SELECT_FROM_CAMERA = 2;
private Fragment fragment;
private Activity activity;
private final static int SELECT_FROM_GALLERY = 1;
private final static int SELECT_FROM_CAMERA = 2;
public ContributionController(Fragment fragment) {
ContributionController(Fragment fragment) {
this.fragment = fragment;
this.activity = fragment.getActivity();
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
@ -34,43 +43,59 @@ public class ContributionController {
File photoFile = new File(fragment.getContext().getCacheDir() + "/images",
new Date().getTime() + ".jpg");
photoFile.getParentFile().mkdirs();
Context applicationContext = fragment.getActivity().getApplicationContext();
return FileProvider.getUriForFile(
fragment.getContext(),
fragment.getActivity().getApplicationContext().getPackageName() + ".provider",
applicationContext.getPackageName() + ".provider",
photoFile);
}
public void startCameraCapture() {
private static void requestWritePermission(Context context, Intent intent, Uri uri) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureUri = reGenerateImageCaptureUriInCache();
takePictureIntent.setFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Intent.setFlags doesn't work for API level <20
requestWritePermission(fragment.getContext(), takePictureIntent, lastGeneratedCaptureUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureUri);
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
public void startGalleryPick() {
//FIXME: Starts gallery (opens Google Photos)
Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
}
public void handleImagePicked(int requestCode, Intent data) {
void handleImagePicked(int requestCode, Intent data) {
FragmentActivity activity = fragment.getActivity();
Intent shareIntent = new Intent(activity, ShareActivity.class);
shareIntent.setAction(Intent.ACTION_SEND);
switch(requestCode) {
shareIntent.setAction(ACTION_SEND);
switch (requestCode) {
case SELECT_FROM_GALLERY:
//Handles image picked from gallery
Uri imageData = data.getData();
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(Intent.EXTRA_STREAM, imageData);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY);
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
break;
case SELECT_FROM_CAMERA:
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
break;
}
Timber.i("Image selected");
@ -81,12 +106,14 @@ public class ContributionController {
}
}
public void saveState(Bundle outState) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureUri);
void saveState(Bundle outState) {
if (outState != null) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureUri);
}
}
public void loadState(Bundle savedInstanceState) {
if(savedInstanceState != null) {
void loadState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
lastGeneratedCaptureUri = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.contributions;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@ -38,13 +37,17 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class ContributionsActivity
extends AuthenticatedActivity
implements LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher {
import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUTHORITY;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
public class ContributionsActivity extends AuthenticatedActivity
implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher {
private Cursor allContributions;
private ContributionsListFragment contributionsList;
@ -63,14 +66,18 @@ public class ContributionsActivity
This is why Contribution.STATE_COMPLETED is -1.
*/
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, "
+ Contribution.Table.COLUMN_UPLOADED + " DESC , ("
+ Contribution.Table.COLUMN_TIMESTAMP + " * "
+ Contribution.Table.COLUMN_STATE + ")";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
.getService();
isUploadServiceConnected = true;
}
@ -86,7 +93,7 @@ public class ContributionsActivity
compositeDisposable.clear();
getSupportFragmentManager().removeOnBackStackChangedListener(this);
super.onDestroy();
if(isUploadServiceConnected) {
if (isUploadServiceConnected) {
unbindService(uploadServiceConnection);
}
}
@ -96,9 +103,9 @@ public class ContributionsActivity
super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isSettingsChanged =
sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
editor.apply();
if (isSettingsChanged) {
refreshSource();
@ -107,14 +114,16 @@ public class ContributionsActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here!
ContentResolver.requestSync(CommonsApplication.getInstance().getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
// Do a sync every time we get here!
CommonsApplication app = ((CommonsApplication) getApplication());
requestSync(app.getCurrentAccount(), AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
allContributions = getContentResolver().query(ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
allContributions = getContentResolver().query(BASE_URI, ALL_FIELDS,
CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
getSupportLoaderManager().initLoader(0, null, this);
}
@ -127,12 +136,13 @@ public class ContributionsActivity
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
contributionsList = (ContributionsListFragment)getSupportFragmentManager()
FragmentManager supportFragmentManager = getSupportFragmentManager();
contributionsList = (ContributionsListFragment) supportFragmentManager
.findFragmentById(R.id.contributionsListFragment);
getSupportFragmentManager().addOnBackStackChangedListener(this);
supportFragmentManager.addOnBackStackChangedListener(this);
if (savedInstanceState != null) {
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager()
mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
}
requestAuthToken();
@ -143,21 +153,25 @@ public class ContributionsActivity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("mediaDetailsVisible", (mediaDetails != null && mediaDetails.isVisible()));
boolean mediaDetailsVisible = mediaDetails != null && mediaDetails.isVisible();
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
}
/** Replace whatever is in the current contributionsFragmentContainer view with mediaDetailPagerFragment,
/ and preserve previous state in back stack.
/ Called when user selects a contribution. */
/**
* Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack.
* Called when user selects a contribution.
*/
private void showDetail(int i) {
if(mediaDetails == null ||!mediaDetails.isVisible()) {
if (mediaDetails == null || !mediaDetails.isVisible()) {
mediaDetails = new MediaDetailPagerFragment();
this.getSupportFragmentManager()
FragmentManager supportFragmentManager = getSupportFragmentManager();
supportFragmentManager
.beginTransaction()
.replace(R.id.contributionsFragmentContainer, mediaDetails)
.addToBackStack(null)
.commit();
this.getSupportFragmentManager().executePendingTransactions();
supportFragmentManager.executePendingTransactions();
}
mediaDetails.showImage(i);
}
@ -165,7 +179,7 @@ public class ContributionsActivity
public void retryUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = Contribution.fromCursor(allContributions);
if(c.getState() == Contribution.STATE_FAILED) {
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toContentValues());
} else {
@ -176,9 +190,9 @@ public class ContributionsActivity
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = Contribution.fromCursor(allContributions);
if(c.getState() == Contribution.STATE_FAILED) {
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toContentValues());
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(AUTHORITY));
c.delete();
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toContentValues());
@ -187,9 +201,9 @@ public class ContributionsActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case android.R.id.home:
if(mediaDetails.isVisible()) {
if (mediaDetails.isVisible()) {
getSupportFragmentManager().popBackStack();
}
return true;
@ -215,21 +229,20 @@ public class ContributionsActivity
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
SharedPreferences sharedPref =
PreferenceManager.getDefaultSharedPreferences(this);
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
return new CursorLoader(this, ContributionsContentProvider.BASE_URI,
Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null,
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI,
ALL_FIELDS, CONTRIBUTION_SELECTION, null,
CONTRIBUTION_SORT + "LIMIT " + uploads);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if(contributionsList.getAdapter() == null) {
contributionsList
.setAdapter(new ContributionsListAdapter(getApplicationContext(), cursor, 0));
if (contributionsList.getAdapter() == null) {
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
cursor, 0));
} else {
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
}
setUploadCount();
@ -249,34 +262,32 @@ public class ContributionsActivity
if (contributionsList.getAdapter() == null) {
// not yet ready to return data
return null;
} else {
} else {
return Contribution.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
}
}
@Override
public int getTotalMediaCount() {
if(contributionsList.getAdapter() == null) {
if (contributionsList.getAdapter() == null) {
return 0;
}
return contributionsList.getAdapter().getCount();
}
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
CommonsApplication application = CommonsApplication.getInstance();
CommonsApplication app = ((CommonsApplication) getApplication());
compositeDisposable.add(
CommonsApplication.getInstance().getMWApi()
.getUploadCount(application.getCurrentAccount().name)
app.getMWApi()
.getUploadCount(app.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uploadCount ->
getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount,
uploadCount)),
throwable -> Timber.e(throwable, "Fetching upload count failed")
uploadCount -> getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed")
)
);
}
@ -332,8 +343,7 @@ public class ContributionsActivity
}
public static void startYourself(Context context) {
Intent contributionsIntent = new Intent(context, ContributionsActivity.class);
context.startActivity(contributionsIntent);
context.startActivity(new Intent(context, ContributionsActivity.class));
}
}

View file

@ -13,17 +13,20 @@ import android.text.TextUtils;
import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
public class ContributionsContentProvider extends ContentProvider{
import static android.content.UriMatcher.NO_MATCH;
import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.Contribution.Table.TABLE_NAME;
public class ContributionsContentProvider extends ContentProvider {
private static final int CONTRIBUTIONS = 1;
private static final int CONTRIBUTIONS_ID = 2;
public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
private static final String BASE_PATH = "contributions";
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTRIBUTIONS);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID);
@ -38,25 +41,29 @@ public class ContributionsContentProvider extends ContentProvider{
return false;
}
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Contribution.Table.TABLE_NAME);
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = CommonsApplication.getInstance().getDBOpenHelper().getReadableDatabase();
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
SQLiteDatabase db = app.getDBOpenHelper().getReadableDatabase();
Cursor cursor;
switch(uriType) {
switch (uriType) {
case CONTRIBUTIONS:
cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CONTRIBUTIONS_ID:
cursor = queryBuilder.query(db,
Contribution.Table.ALL_FIELDS,
ALL_FIELDS,
"_id = ?",
new String[] { uri.getLastPathSegment() },
new String[]{uri.getLastPathSegment()},
null,
null,
sortOrder
@ -76,14 +83,16 @@ public class ContributionsContentProvider extends ContentProvider{
return null;
}
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
long id = 0;
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
long id;
switch (uriType) {
case CONTRIBUTIONS:
id = sqlDB.insert(Contribution.Table.TABLE_NAME, null, contentValues);
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@ -92,19 +101,21 @@ public class ContributionsContentProvider extends ContentProvider{
return Uri.parse(BASE_URI + "/" + id);
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows = 0;
int rows;
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = CommonsApplication.getInstance().getDBOpenHelper().getReadableDatabase();
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
switch(uriType) {
switch (uriType) {
case CONTRIBUTIONS_ID:
Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
rows = db.delete(Contribution.Table.TABLE_NAME,
rows = sqlDB.delete(TABLE_NAME,
"_id = ?",
new String[] { uri.getLastPathSegment() }
new String[]{uri.getLastPathSegment()}
);
break;
default:
@ -114,17 +125,19 @@ public class ContributionsContentProvider extends ContentProvider{
return rows;
}
@SuppressWarnings("ConstantConditions")
@Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case CONTRIBUTIONS:
for(ContentValues value: values) {
for (ContentValues value : values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@ -136,35 +149,37 @@ public class ContributionsContentProvider extends ContentProvider{
return values.length;
}
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
Even then, we should make sure to sanitize all user input appropriately.
Input that passes through ContentValuesshould be fine. So only issues are those that pass
in via concating.
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
In here, the only concat created argument is for id. It is cast to an int, and will
error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
int rowsUpdated = 0;
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case CONTRIBUTIONS:
rowsUpdated = sqlDB.update(Contribution.Table.TABLE_NAME,
contentValues,
selection,
selectionArgs);
rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs);
break;
case CONTRIBUTIONS_ID:
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Contribution.Table.TABLE_NAME,
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
Contribution.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
break;
default:

View file

@ -1,13 +1,11 @@
package fr.free.nrw.commons.contributions;
import android.Manifest;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
@ -31,18 +29,20 @@ import fr.free.nrw.commons.nearby.NearbyActivity;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.View.GONE;
public class ContributionsListFragment extends Fragment {
public interface SourceRefresher {
void refreshSource();
}
@BindView(R.id.contributionsList) GridView contributionsList;
@BindView(R.id.waitingMessage) TextView waitingMessage;
@BindView(R.id.emptyMessage) TextView emptyMessage;
@BindView(R.id.contributionsList)
GridView contributionsList;
@BindView(R.id.waitingMessage)
TextView waitingMessage;
@BindView(R.id.emptyMessage)
TextView emptyMessage;
private ContributionController controller;
@Override
@ -50,21 +50,21 @@ public class ContributionsListFragment extends Fragment {
View v = inflater.inflate(R.layout.fragment_contributions, container, false);
ButterKnife.bind(this, v);
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
if(savedInstanceState != null) {
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
if (savedInstanceState != null) {
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
}
//TODO: Should this be in onResume?
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified);
if (lastModified.equals("")) {
waitingMessage.setVisibility(View.VISIBLE);
} else {
waitingMessage.setVisibility(View.GONE);
waitingMessage.setVisibility(GONE);
}
return v;
@ -93,7 +93,7 @@ public class ContributionsListFragment extends Fragment {
//FIXME: must get the file data for Google Photos when receive the intent answer, in the onActivityResult method
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",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data);
@ -105,7 +105,7 @@ public class ContributionsListFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.menu_from_gallery:
//Gallery crashes before reach ShareActivity screen so must implement permissions check here
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -113,7 +113,7 @@ public class ContributionsListFragment extends Fragment {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(),
READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
!= PERMISSION_GRANTED) {
// Should we show an explanation?
if (shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
@ -123,7 +123,7 @@ public class ContributionsListFragment extends Fragment {
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.storage_permission_rationale))
.setMessage(getString(R.string.read_storage_permission_rationale))
.setPositiveButton("OK", (dialog, which) -> {
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
dialog.dismiss();
@ -155,7 +155,42 @@ public class ContributionsListFragment extends Fragment {
return true;
case R.id.menu_from_camera:
controller.startCameraCapture();
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(CommonsApplication.getInstance());
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.write_storage_permission_rationale))
.setPositiveButton("OK", (dialog, which) -> {
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
dialog.dismiss();
})
.setNegativeButton("Cancel", null)
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
3);
// MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
controller.startCameraCapture();
return true;
}
} else {
controller.startCameraCapture();
return true;
}
return true;
default:
return super.onOptionsItemSelected(item);
@ -163,14 +198,15 @@ public class ContributionsListFragment extends Fragment {
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = "
+ permissions + " grant =" + grantResults);
switch (requestCode) {
// 1 = Storage allowed when gallery selected
case 1: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Call controller.startGalleryPick()");
controller.startGalleryPick();
}
@ -178,13 +214,19 @@ public class ContributionsListFragment extends Fragment {
break;
// 2 = Location allowed when 'nearby places' selected
case 2: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Location permission granted");
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
startActivity(nearbyIntent);
}
}
break;
case 3: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Call controller.startCameraCapture()");
controller.startCameraCapture();
}
}
}
}
@ -193,7 +235,8 @@ public class ContributionsListFragment extends Fragment {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu);
if (!CommonsApplication.getInstance().deviceHasCamera()) {
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
if (!app.deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false);
}
}
@ -217,6 +260,10 @@ public class ContributionsListFragment extends Fragment {
}
protected void clearSyncMessage() {
waitingMessage.setVisibility(View.GONE);
waitingMessage.setVisibility(GONE);
}
public interface SourceRefresher {
void refreshSource();
}
}

View file

@ -23,8 +23,18 @@ import fr.free.nrw.commons.mwapi.LogEventResult;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String[] existsQuery = {COLUMN_FILENAME};
private static final String existsSelection = COLUMN_FILENAME + " = ?";
private static final ContentValues[] EMPTY = {};
private static int COMMIT_THRESHOLD = 10;
public ContributionsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@ -36,39 +46,38 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
return limit; // FIXME: Parameterize!
}
private static final String[] existsQuery = { Contribution.Table.COLUMN_FILENAME };
private static final String existsSelection = Contribution.Table.COLUMN_FILENAME + " = ?";
private boolean fileExists(ContentProviderClient client, String filename) {
Cursor cursor = null;
try {
cursor = client.query(ContributionsContentProvider.BASE_URI,
cursor = client.query(BASE_URI,
existsQuery,
existsSelection,
new String[] { filename },
new String[]{filename},
""
);
return cursor.getCount() != 0;
return cursor != null && cursor.getCount() != 0;
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
if ( cursor != null ) {
if (cursor != null) {
cursor.close();
}
}
}
@Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient contentProviderClient, SyncResult syncResult) {
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
String user = account.name;
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
SharedPreferences prefs = getContext().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date();
LogEventResult result;
Boolean done = false;
String queryContinue = null;
while(!done) {
while (!done) {
try {
result = api.logEvents(user, lastModified, queryContinue, getLimit());
@ -90,19 +99,21 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
continue;
}
String filename = image.getFilename();
if(fileExists(contentProviderClient, filename)) {
if (fileExists(contentProviderClient, filename)) {
Timber.d("Skipping %s", filename);
continue;
}
String thumbUrl = Utils.makeThumbBaseUrl(filename);
Date dateUpdated = image.getDateUpdated();
Contribution contrib = new Contribution(null, thumbUrl, filename, "", -1, dateUpdated, dateUpdated, user, "", "");
contrib.setState(Contribution.STATE_COMPLETED);
Contribution contrib = new Contribution(null, thumbUrl, filename,
"", -1, dateUpdated, dateUpdated, user,
"", "");
contrib.setState(STATE_COMPLETED);
imageValues.add(contrib.toContentValues());
if(imageValues.size() % COMMIT_THRESHOLD == 0) {
if (imageValues.size() % COMMIT_THRESHOLD == 0) {
try {
contentProviderClient.bulkInsert(ContributionsContentProvider.BASE_URI, imageValues.toArray(new ContentValues[]{}));
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@ -110,20 +121,21 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
}
}
if(imageValues.size() != 0) {
if (imageValues.size() != 0) {
try {
contentProviderClient.bulkInsert(ContributionsContentProvider.BASE_URI, imageValues.toArray(new ContentValues[]{}));
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
queryContinue = result.getQueryContinue();
if(TextUtils.isEmpty(queryContinue)) {
if (TextUtils.isEmpty(queryContinue)) {
done = true;
}
}
prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
Timber.d("Oh hai, everyone! Look, a kitty!");
}
}

View file

@ -276,13 +276,13 @@ public class MediaDetailFragment extends Fragment {
categoryContainer.removeAllViews();
// @fixme add the category items
for (String cat : categoryNames) {
View catLabel = buildCatLabel(cat);
View catLabel = buildCatLabel(cat, categoryContainer);
categoryContainer.addView(catLabel);
}
}
private View buildCatLabel(final String catName) {
final View item = getLayoutInflater(null).inflate(R.layout.detail_category_item, null, false);
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
final CompatTextView textView = (CompatTextView)item.findViewById(R.id.mediaDetailCategoryItemText);
textView.setText(catName);

View file

@ -1,11 +1,8 @@
package fr.free.nrw.commons.media;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
@ -34,23 +31,16 @@ import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.EventLog;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.CommonsApplication.EVENT_SHARE_ATTEMPT;
public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPageChangeListener {
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);
int getTotalMediaCount();
void notifyDatasetChanged();
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
}
private ViewPager pager;
private Boolean editable;
private CommonsApplication app;
public MediaDetailPagerFragment() {
this(false);
@ -61,30 +51,10 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
this.editable = editable;
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
public MediaDetailAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
if (i == 0) {
// See bug https://code.google.com/p/android/issues/detail?id=27526
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
}
return MediaDetailFragment.forMedia(i, editable);
}
@Override
public int getCount() {
return ((MediaDetailProvider)getActivity()).getTotalMediaCount();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false);
pager = (ViewPager) view.findViewById(R.id.mediaDetailsPager);
pager.addOnPageChangeListener(this);
@ -120,18 +90,18 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable");
}
app = CommonsApplication.getInstance();
setHasOptionsMenu(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
MediaDetailProvider provider = (MediaDetailProvider)getActivity();
MediaDetailProvider provider = (MediaDetailProvider) getActivity();
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.menu_share_current_image:
// Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252
EventLog.schema(CommonsApplication.EVENT_SHARE_ATTEMPT)
CommonsApplication app = (CommonsApplication) getActivity().getApplication();
EventLog.schema(EVENT_SHARE_ATTEMPT)
.param("username", app.getCurrentAccount().name)
.param("filename", m.getFilename())
.log();
@ -139,7 +109,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
case R.id.menu_browser_current_image:
// View in browser
Intent viewIntent = new Intent();
viewIntent.setAction(Intent.ACTION_VIEW);
viewIntent.setAction(ACTION_VIEW);
viewIntent.setData(m.getFilePageTitle().getMobileUri());
startActivity(viewIntent);
return true;
@ -149,12 +119,12 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
return true;
case R.id.menu_retry_current_image:
// Retry
((ContributionsActivity)getActivity()).retryUpload(pager.getCurrentItem());
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
case R.id.menu_cancel_current_image:
// todo: delete image
((ContributionsActivity)getActivity()).deleteUpload(pager.getCurrentItem());
((ContributionsActivity) getActivity()).deleteUpload(pager.getCurrentItem());
getActivity().getSupportFragmentManager().popBackStack();
return true;
default:
@ -170,7 +140,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
*/
private void downloadMedia(Media m) {
String imageUrl = m.getImageUrl(),
fileName = m.getFilename();
fileName = m.getFilename();
// Strip 'File:' from beginning of filename, we really shouldn't store it
fileName = fileName.replaceFirst("^File:", "");
Uri imageUri = Uri.parse(imageUrl);
@ -185,14 +155,15 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
req.allowScanningByMediaScanner();
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
Snackbar.make(getView(), R.string.storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1)).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !(ContextCompat.checkSelfPermission(getContext(),
READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
Snackbar.make(getView(), R.string.read_storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{READ_EXTERNAL_STORAGE}, 1)).show();
} else {
final DownloadManager manager = (DownloadManager)getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(req);
((DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE)).enqueue(req);
}
}
@ -202,7 +173,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
menu.clear(); // see http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_image_detail, menu);
if (pager != null) {
MediaDetailProvider provider = (MediaDetailProvider)getActivity();
MediaDetailProvider provider = (MediaDetailProvider) getActivity();
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
if (m != null) {
// Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
@ -225,8 +196,8 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
}
if (m instanceof Contribution) {
Contribution c = (Contribution)m;
switch(c.getState()) {
Contribution c = (Contribution) m;
switch (c.getState()) {
case Contribution.STATE_FAILED:
menu.findItem(R.id.menu_retry_current_image).setEnabled(true).setVisible(true);
menu.findItem(R.id.menu_cancel_current_image).setEnabled(true).setVisible(true);
@ -267,6 +238,39 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
@Override
public void onPageScrollStateChanged(int i) {
}
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);
int getTotalMediaCount();
void notifyDatasetChanged();
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
public MediaDetailAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
if (i == 0) {
// See bug https://code.google.com/p/android/issues/detail?id=27526
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
}
return MediaDetailFragment.forMedia(i, editable);
}
@Override
public int getCount() {
return ((MediaDetailProvider) getActivity()).getTotalMediaCount();
}
}
}

View file

@ -33,7 +33,7 @@ public class CategoryModifier extends PageModifier {
JSONArray categories;
categories = params.optJSONArray(PARAM_CATEGORIES);
StringBuffer categoriesString = new StringBuffer();
StringBuilder categoriesString = new StringBuilder();
for(int i=0; i < categories.length(); i++) {
String category = categories.optString(i);
categoriesString.append("\n[[Category:").append(category).append("]]");

View file

@ -27,7 +27,7 @@ public class ModifierSequence {
public ModifierSequence(Uri mediaUri, JSONObject data) {
this(mediaUri);
JSONArray modifiersJSON = data.optJSONArray("modifiers");
for(int i=0; i< modifiersJSON.length(); i++) {
for (int i=0; i< modifiersJSON.length(); i++) {
modifiers.add(PageModifier.fromJSON(modifiersJSON.optJSONObject(i)));
}
}
@ -41,14 +41,14 @@ public class ModifierSequence {
}
public String executeModifications(String pageName, String pageContents) {
for(PageModifier modifier: modifiers) {
for (PageModifier modifier: modifiers) {
pageContents = modifier.doModification(pageName, pageContents);
}
return pageContents;
}
public String getEditSummary() {
StringBuffer editSummary = new StringBuffer();
StringBuilder editSummary = new StringBuilder();
for(PageModifier modifier: modifiers) {
editSummary.append(modifier.getEditSumary()).append(" ");
}
@ -60,7 +60,7 @@ public class ModifierSequence {
JSONObject data = new JSONObject();
try {
JSONArray modifiersJSON = new JSONArray();
for(PageModifier modifier: modifiers) {
for (PageModifier modifier: modifiers) {
modifiersJSON.put(modifier.toJSON());
}
data.put("modifiers", modifiersJSON);
@ -81,7 +81,8 @@ public class ModifierSequence {
// Hardcoding column positions!
ModifierSequence ms = null;
try {
ms = new ModifierSequence(Uri.parse(cursor.getString(1)), new JSONObject(cursor.getString(2)));
ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
new JSONObject(cursor.getString(2)));
} catch (JSONException e) {
throw new RuntimeException(e);
}

View file

@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
@ -235,8 +236,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return categories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override
@ -265,15 +265,14 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return categories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override
@NonNull
public Observable<String> searchTitles(String title, int searchCatsLimit) {
return Single.fromCallable(() -> {
ArrayList<ApiResult> categoryNodes = null;
return Single.fromCallable((Callable<List<String>>) () -> {
ArrayList<ApiResult> categoryNodes;
try {
categoryNodes = api.action("query")
@ -287,7 +286,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.getNodes("/api/query/search/p/@title");
} catch (IOException e) {
Timber.e("Failed to obtain searchTitles", e);
return new ArrayList();
return Collections.emptyList();
}
if (categoryNodes == null) {
@ -302,8 +301,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return titleCategories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override
@ -392,7 +390,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
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");
if (!resultStatus.equals("Success")) {

View file

@ -41,12 +41,12 @@ public class LogBuilder {
try {
fullData.put("schema", schema);
fullData.put("revision", rev);
fullData.put("wiki", CommonsApplication.EVENTLOG_WIKI);
fullData.put("wiki", BuildConfig.EVENTLOG_WIKI);
data.put("device", EventLog.DEVICE);
data.put("platform", "Android/" + Build.VERSION.RELEASE);
data.put("appversion", "Android/" + BuildConfig.VERSION_NAME);
fullData.put("event", data);
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
return new URL(BuildConfig.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
} catch (MalformedURLException | JSONException e) {
throw new RuntimeException(e);
}

View file

@ -5,7 +5,6 @@ import android.support.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.Single;

View file

@ -10,7 +10,6 @@ import com.mapbox.mapboxsdk.annotations.IconFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

View file

@ -63,10 +63,10 @@ public class NearbyInfoDialog extends OverlayDialog {
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
overflowButton.setOnClickListener(this::popupMenuListener);
overflowButton.setOnClickListener(v -> popupMenuListener());
}
private void popupMenuListener(View v) {
private void popupMenuListener() {
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
popupMenu.inflate(R.menu.nearby_info_dialog_options);

View file

@ -4,7 +4,6 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -13,7 +12,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.annotations.PolygonOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
@ -22,7 +20,6 @@ import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.services.android.telemetry.MapboxTelemetry;
import java.lang.reflect.Type;

View file

@ -1,13 +1,11 @@
package fr.free.nrw.commons.settings;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;

View file

@ -24,15 +24,13 @@ import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import timber.log.Timber;
public class NavigationBaseActivity extends BaseActivity
public abstract class NavigationBaseActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener {
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.navigation_view)
NavigationView navigationView;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
@ -121,16 +119,9 @@ public class NavigationBaseActivity extends BaseActivity
.setMessage(R.string.logout_verification)
.setCancelable(false)
.setPositiveButton(R.string.yes, (dialog, which) -> {
((CommonsApplication) getApplicationContext())
.clearApplicationData(NavigationBaseActivity.this, () -> {
Timber.d("Logout complete callback received.");
Intent nearbyIntent = new Intent(
NavigationBaseActivity.this, LoginActivity.class);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(nearbyIntent);
finish();
});
BaseLogoutListener logoutListener = new BaseLogoutListener();
CommonsApplication app = (CommonsApplication) getApplication();
app.clearApplicationData(this, logoutListener);
})
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.show();
@ -140,7 +131,16 @@ public class NavigationBaseActivity extends BaseActivity
}
}
public interface LogoutListener {
void onLogoutComplete();
private class BaseLogoutListener implements CommonsApplication.LogoutListener {
@Override
public void onLogoutComplete() {
Timber.d("Logout complete callback received.");
Intent nearbyIntent = new Intent(
NavigationBaseActivity.this, LoginActivity.class);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(nearbyIntent);
finish();
}
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;

View file

@ -63,7 +63,7 @@ public class MultipleShareActivity
@Override
public int getTotalMediaCount() {
if(photosList == null) {
if (photosList == null) {
return 0;
}
return photosList.size();
@ -71,7 +71,7 @@ public class MultipleShareActivity
@Override
public void notifyDatasetChanged() {
if(uploadsList != null) {
if (uploadsList != null) {
uploadsList.notifyDatasetChanged();
}
}
@ -145,7 +145,7 @@ public class MultipleShareActivity
uploadsList.setImageOnlyMode(true);
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
if(categorizationFragment == null) {
if (categorizationFragment == null) {
categorizationFragment = new CategorizationFragment();
}
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
@ -162,7 +162,7 @@ public class MultipleShareActivity
@Override
public void onCategoriesSave(List<String> categories) {
if(categories.size() > 0) {
if (categories.size() > 0) {
ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
for(Contribution contribution: photosList) {
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
@ -191,7 +191,7 @@ public class MultipleShareActivity
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case android.R.id.home:
if(mediaDetails.isVisible()) {
if (mediaDetails.isVisible()) {
getSupportFragmentManager().popBackStack();
}
return true;
@ -209,7 +209,7 @@ public class MultipleShareActivity
ButterKnife.bind(this);
initDrawer();
if(savedInstanceState != null) {
if (savedInstanceState != null) {
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
}
@ -225,7 +225,7 @@ public class MultipleShareActivity
}
private void showDetail(int i) {
if(mediaDetails == null ||!mediaDetails.isVisible()) {
if (mediaDetails == null ||!mediaDetails.isVisible()) {
mediaDetails = new MediaDetailPagerFragment(true);
getSupportFragmentManager()
.beginTransaction()
@ -248,8 +248,8 @@ public class MultipleShareActivity
app.getMWApi().setAuthCookie(authCookie);
Intent intent = getIntent();
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
if(photosList == null) {
if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
if (photosList == null) {
photosList = new ArrayList<>();
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
for(int i=0; i < urisList.size(); i++) {
@ -265,7 +265,7 @@ public class MultipleShareActivity
}
uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList");
if(uploadsList == null) {
if (uploadsList == null) {
uploadsList = new MultipleUploadListFragment();
getSupportFragmentManager()
.beginTransaction()
@ -287,7 +287,7 @@ public class MultipleShareActivity
@Override
public void onBackPressed() {
super.onBackPressed();
if(categorizationFragment != null && categorizationFragment.isVisible()) {
if (categorizationFragment != null && categorizationFragment.isVisible()) {
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
.param("username", app.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
@ -307,7 +307,7 @@ public class MultipleShareActivity
@Override
public void onBackStackChanged() {
if(mediaDetails != null && mediaDetails.isVisible()) {
if (mediaDetails != null && mediaDetails.isVisible()) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);

View file

@ -77,8 +77,8 @@ public class MultipleUploadListFragment extends Fragment {
public View getView(int i, View view, ViewGroup viewGroup) {
UploadHolderView holder;
if(view == null) {
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.layout_upload_item, viewGroup, false);
holder = new UploadHolderView();
holder.image = (SimpleDraweeView) view.findViewById(R.id.uploadImage);
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
@ -94,17 +94,17 @@ public class MultipleUploadListFragment extends Fragment {
.build());
view.setTag(holder);
} else {
holder = (UploadHolderView)view.getTag();
holder = (UploadHolderView) view.getTag();
}
Contribution up = (Contribution)this.getItem(i);
Contribution up = (Contribution) this.getItem(i);
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
if (holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
holder.image.setImageURI(up.getLocalUri().toString());
holder.imageUri = up.getLocalUri();
}
if(!imageOnlyMode) {
if (!imageOnlyMode) {
holder.overlay.setVisibility(View.VISIBLE);
holder.title.setText(up.getFilename());
} else {
@ -134,21 +134,21 @@ public class MultipleUploadListFragment extends Fragment {
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
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) {
if (photosAdapter != null) {
photosAdapter.notifyDataSetChanged();
}
}
public void setImageOnlyMode(boolean mode) {
imageOnlyMode = mode;
if(imageOnlyMode) {
if (imageOnlyMode) {
baseTitle.setVisibility(View.GONE);
} else {
baseTitle.setVisibility(View.VISIBLE);
@ -159,13 +159,13 @@ public class MultipleUploadListFragment extends Fragment {
@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);
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
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());
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
photoSize = calculatePicDimension(detailProvider.getTotalMediaCount());
photosGrid.setColumnWidth(photoSize.x);
@ -188,7 +188,7 @@ public class MultipleUploadListFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.menu_upload_multiple:
multipleUploadInitiatedHandler.OnMultipleUploadInitiated();
return true;
@ -200,7 +200,7 @@ public class MultipleUploadListFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
setHasOptionsMenu(true);
@ -213,12 +213,12 @@ public class MultipleUploadListFragment extends Fragment {
@Override
public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
for(int i = 0; i < detailProvider.getTotalMediaCount(); i++) {
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));
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("");
}

View file

@ -3,12 +3,14 @@ package fr.free.nrw.commons.upload;
import android.Manifest;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@ -24,10 +26,10 @@ import android.widget.Toast;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -53,9 +55,9 @@ import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
* 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,
public class ShareActivity
extends AuthenticatedActivity
implements SingleUploadFragment.OnUploadActionInitiated,
OnCategoriesSaveHandler {
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
@ -119,7 +121,7 @@ public class ShareActivity
// and permission is not obtained.
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED);
!= PackageManager.PERMISSION_GRANTED);
}
private void uploadBegins() {
@ -145,7 +147,7 @@ public class ShareActivity
}
private void showPostUpload() {
if(categorizationFragment == null) {
if (categorizationFragment == null) {
categorizationFragment = new CategorizationFragment();
}
getSupportFragmentManager().beginTransaction()
@ -155,7 +157,7 @@ public class ShareActivity
@Override
public void onCategoriesSave(List<String> categories) {
if(categories.size() > 0) {
if (categories.size() > 0) {
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
@ -181,7 +183,7 @@ public class ShareActivity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(contribution != null) {
if (contribution != null) {
outState.putParcelable("contribution", contribution);
}
}
@ -189,7 +191,7 @@ public class ShareActivity
@Override
public void onBackPressed() {
super.onBackPressed();
if(categorizationFragment != null && categorizationFragment.isVisible()) {
if (categorizationFragment != null && categorizationFragment.isVisible()) {
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
.param("username", app.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
@ -228,7 +230,7 @@ public class ShareActivity
ButterKnife.bind(this);
initBack();
app = CommonsApplication.getInstance();
backgroundImageView = (SimpleDraweeView)findViewById(R.id.backgroundImage);
backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage);
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
@ -254,7 +256,7 @@ public class ShareActivity
backgroundImageView.setImageURI(mediaUri);
}
if (savedInstanceState != null) {
if (savedInstanceState != null) {
contribution = savedInstanceState.getParcelable("contribution");
}
@ -279,7 +281,7 @@ public class ShareActivity
if (useNewPermissions && (!storagePermitted || !locationPermitted)) {
if (!storagePermitted && !locationPermitted) {
String permissionRationales =
getResources().getString(R.string.storage_permission_rationale) + "\n"
getResources().getString(R.string.read_storage_permission_rationale) + "\n"
+ getResources().getString(R.string.location_permission_rationale);
snackbar = requestPermissionUsingSnackBar(
permissionRationales,
@ -292,7 +294,7 @@ public class ShareActivity
textView.setMaxLines(3);
} else if (!storagePermitted) {
requestPermissionUsingSnackBar(
getString(R.string.storage_permission_rationale),
getString(R.string.read_storage_permission_rationale),
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_PERM_ON_CREATE_STORAGE);
} else if (!locationPermitted) {
@ -307,7 +309,7 @@ public class ShareActivity
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
if(shareView == null && categorizationFragment == null) {
if (shareView == null && categorizationFragment == null) {
shareView = new SingleUploadFragment();
getSupportFragmentManager()
.beginTransaction()
@ -417,12 +419,27 @@ public class ShareActivity
// in older devices getPath() may fail depending on the source URI
// creating and using a copy of the file seems to work instead.
// TODO: there might be a more proper solution than this
String copyPath = getApplicationContext().getCacheDir().getAbsolutePath()
+ "/" + new Date().getTime() + ".jpg";
String copyPath = null;
try {
ParcelFileDescriptor descriptor
= getContentResolver().openFileDescriptor(mediaUri, "r");
if (descriptor != null) {
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(CommonsApplication.getInstance());
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
if (useExtStorage) {
copyPath = Environment.getExternalStorageDirectory().toString()
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
newFile.mkdir();
FileUtils.copy(
descriptor.getFileDescriptor(),
copyPath);
Timber.d("Filepath (copied): %s", copyPath);
return copyPath;
}
copyPath = getApplicationContext().getCacheDir().getAbsolutePath()
+ "/" + new Date().getTime() + ".jpg";
FileUtils.copy(
descriptor.getFileDescriptor(),
copyPath);
@ -439,6 +456,7 @@ public class ShareActivity
/**
* Gets coordinates for category suggestions, either from EXIF data or user location
*
* @param gpsEnabled if true use GPS
*/
private void getFileMetadata(boolean gpsEnabled) {
@ -474,7 +492,7 @@ public class ShareActivity
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
*/
public void useImageCoords() {
if(decimalCoords != null) {
if (decimalCoords != null) {
Timber.d("Decimal coords of image: %s", decimalCoords);
// Only set cache for this point if image has coords
@ -508,8 +526,7 @@ public class ShareActivity
try {
imageObj.unregisterLocationManager();
Timber.d("Unregistered locationManager");
}
catch (NullPointerException e) {
} catch (NullPointerException e) {
Timber.d("locationManager does not exist, not unregistered");
}
}
@ -524,7 +541,7 @@ public class ShareActivity
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(categorizationFragment!=null && categorizationFragment.isVisible()) {
if (categorizationFragment != null && categorizationFragment.isVisible()) {
categorizationFragment.showBackButtonDialog();
} else {
onBackPressed();

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
@ -39,13 +38,10 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class SingleUploadFragment extends Fragment {
private SharedPreferences prefs;
private String license;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
public class SingleUploadFragment extends Fragment {
@BindView(R.id.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit;
@ -53,13 +49,15 @@ public class SingleUploadFragment extends Fragment {
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
private SharedPreferences prefs;
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher();
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_share, menu);
if(titleEdit != null) {
if (titleEdit != null) {
menu.findItem(R.id.menu_upload_single).setEnabled(titleEdit.getText().length() != 0);
}
}
@ -88,8 +86,9 @@ public class SingleUploadFragment extends Fragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_single_upload, null);
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);
@ -112,10 +111,10 @@ public class SingleUploadFragment extends Fragment {
Timber.d(license);
ArrayAdapter<String> adapter;
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",false)) {
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme", false)) {
// dark theme
adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_dropdown_item, licenseItems);
}else {
} else {
// light theme
adapter = new ArrayAdapter<>(getActivity(), R.layout.light_simple_spinner_dropdown_item, licenseItems);
}
@ -146,26 +145,27 @@ public class SingleUploadFragment extends Fragment {
super.onDestroyView();
}
@OnItemSelected(R.id.licenseSpinner) void onLicenseSelected(AdapterView<?> parent, View view, int position, long id) {
@OnItemSelected(R.id.licenseSpinner)
void onLicenseSelected(AdapterView<?> parent, View view, int position, long id) {
String licenseName = parent.getItemAtPosition(position).toString();
// Set selected color to white because it should be readable on random images.
TextView selectedText = (TextView) licenseSpinner.getChildAt(0);
if (selectedText != null ) {
if (selectedText != null) {
selectedText.setTextColor(Color.WHITE);
selectedText.setBackgroundColor(Color.TRANSPARENT);
}
String license;
if(getString(R.string.license_name_cc0).equals(licenseName)) {
if (getString(R.string.license_name_cc0).equals(licenseName)) {
license = Prefs.Licenses.CC0;
} else if(getString(R.string.license_name_cc_by).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_3;
} else if(getString(R.string.license_name_cc_by_sa).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_sa).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_SA_3;
} else if(getString(R.string.license_name_cc_by_four).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_four).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_4;
} else if(getString(R.string.license_name_cc_by_sa_four).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_sa_four).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_SA_4;
} else {
throw new IllegalStateException("Unknown licenseName: " + licenseName);
@ -177,10 +177,9 @@ public class SingleUploadFragment extends Fragment {
editor.commit();
}
@OnTouch(R.id.share_license_summary) boolean showLicence(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
@OnTouch(R.id.share_license_summary)
boolean showLicence(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == ACTION_DOWN) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(Utils.licenseUrlFor(license)));
@ -191,7 +190,8 @@ public class SingleUploadFragment extends Fragment {
}
}
@OnClick(R.id.titleDescButton) void setTitleDescButton() {
@OnClick(R.id.titleDescButton)
void setTitleDescButton() {
//Retrieve last title and desc entered
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
String title = titleDesc.getString("Title", "");
@ -205,57 +205,41 @@ public class SingleUploadFragment extends Fragment {
/**
* Copied from https://stackoverflow.com/a/26269435/8065933
*/
@OnTouch
(R.id.titleEdit) boolean titleInfo(View view, MotionEvent motionEvent) {
@OnTouch(R.id.titleEdit)
boolean titleInfo(View view, MotionEvent motionEvent) {
//Should replace right with end to support different right-to-left languages as well
final int value = titleEdit.getRight() - titleEdit.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == motionEvent.ACTION_UP && motionEvent.getRawX() >= value) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.media_detail_title);
builder.setMessage(R.string.title_info);
builder.setCancelable(true);
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.media_detail_title)
.setMessage(R.string.title_info)
.setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create()
.show();
return true;
}
return false;
}
@OnTouch
(R.id.descEdit) boolean descriptionInfo(View view, MotionEvent motionEvent) {
@OnTouch(R.id.descEdit)
boolean descriptionInfo(View view, MotionEvent motionEvent) {
final int value = descEdit.getRight() - descEdit.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == motionEvent.ACTION_UP && motionEvent.getRawX() >= value) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.media_detail_description);
builder.setMessage(R.string.description_info);
builder.setCancelable(true);
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
return true;
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.media_detail_description)
.setMessage(R.string.description_info)
.setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create()
.show();
return true;
}
return false;
}
private void setLicenseSummary(String license) {
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
}
@ -279,16 +263,22 @@ public class SingleUploadFragment extends Fragment {
}
}
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
private class TitleTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void afterTextChanged(Editable editable) {
if(getActivity() != null) {
if (getActivity() != null) {
getActivity().invalidateOptionsMenu();
}
}

View file

@ -15,7 +15,7 @@ public class FileUtils {
* @return the content of the file
*/
public static String readFromResource(String fileName) throws IOException {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons.utils;
import java.util.Comparator;
import info.debatty.java.stringsimilarity.Levenshtein;
public class StringSortingUtils {
private StringSortingUtils() {
//no-op
}
/**
* Returns Comparator for sorting strings by its similarity with Levenshtein
* algorithm. By using this Comparator we get results from the highest to
* the lowest match.
*
* @param filter pattern to compare similarity
* @return Comparator with string similarity
*/
public static Comparator<String> sortBySimilarity(final String filter) {
return (firstItem, secondItem) -> {
double firstItemSimilarity = calculateSimilarity(firstItem, filter);
double secondItemSimilarity = calculateSimilarity(secondItem, filter);
return (int) Math.signum(secondItemSimilarity - firstItemSimilarity);
};
}
private static double calculateSimilarity(String firstString, String secondString) {
String longer = firstString.toLowerCase();
String shorter = secondString.toLowerCase();
if (firstString.length() < secondString.length()) {
longer = secondString;
shorter = firstString;
}
int longerLength = longer.length();
if (longerLength == 0) {
return 1.0;
}
double distanceBetweenStrings = new Levenshtein().distance(longer, shorter);
return (longerLength - distanceBetweenStrings) / (double) longerLength;
}
}