diff --git a/app/build.gradle b/app/build.gradle
index 1de265ccf..2bb4341f5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -68,6 +68,11 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
+
+ implementation 'com.google.dagger:dagger:2.11'
+ implementation 'com.google.dagger:dagger-android-support:2.11'
+ annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
+ annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'
}
android {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 872d02b9e..9a232d1b9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,18 +2,18 @@
package="fr.free.nrw.commons">
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -32,22 +32,25 @@
android:finishOnTaskLaunch="true" />
+ android:name="org.acra.CrashReportDialog"
+ android:excludeFromRecents="true"
+ android:finishOnTaskLaunch="true"
+ android:launchMode="singleInstance"
+ android:theme="@android:style/Theme.Dialog" />
+
+
-
-
+
+
-
-
+
+
+
+ android:label="@string/app_name">
@@ -55,11 +58,11 @@
+
+ android:name=".upload.MultipleShareActivity"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name">
@@ -69,33 +72,34 @@
-
+ android:name=".contributions.ContributionsActivity"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" />
+
+ android:label="@string/title_activity_settings" />
+
+
+ android:label="@string/title_activity_signup" />
+
-
-
+
+
+ android:process=":auth">
@@ -106,27 +110,25 @@
+ android:name=".contributions.ContributionsSyncService"
+ android:exported="true">
-
+
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/contributions_sync_adapter" />
+ android:name=".modifications.ModificationsSyncService"
+ android:exported="true">
-
+
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/modifications_sync_adapter" />
+ android:resource="@xml/provider_paths" />
-
+ android:name=".contributions.ContributionsContentProvider"
+ android:authorities="fr.free.nrw.commons.contributions.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_contributions"
+ android:syncable="true" />
-
+ android:name=".modifications.ModificationsContentProvider"
+ android:authorities="fr.free.nrw.commons.modifications.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_modifications"
+ android:syncable="true" />
+
-
+ android:name=".category.CategoryContentProvider"
+ android:authorities="fr.free.nrw.commons.categories.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_categories"
+ android:syncable="false" />
diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
index 06d02aab2..0ef5fa171 100644
--- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
@@ -1,7 +1,5 @@
package fr.free.nrw.commons;
-import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
index 30cad5432..4ef4376af 100644
--- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
+++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
@@ -1,19 +1,8 @@
package fr.free.nrw.commons;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.app.Activity;
-import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
-import android.preference.PreferenceManager;
-import android.support.v4.util.LruCache;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.stetho.Stetho;
@@ -25,24 +14,23 @@ import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import java.io.File;
-import java.io.IOException;
import javax.inject.Inject;
+import javax.inject.Named;
import dagger.android.AndroidInjector;
-import dagger.android.DispatchingAndroidInjector;
-import dagger.android.HasActivityInjector;
-import fr.free.nrw.commons.auth.AccountUtil;
-import fr.free.nrw.commons.caching.CacheController;
+import dagger.android.DaggerApplication;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.data.DBOpenHelper;
-import fr.free.nrw.commons.di.DaggerAppComponent;
+import fr.free.nrw.commons.di.CommonsApplicationComponent;
+import fr.free.nrw.commons.di.CommonsApplicationModule;
+import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent;
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.utils.FileUtils;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
// TODO: Use ProGuard to rip out reporting when publishing
@@ -54,9 +42,13 @@ import timber.log.Timber;
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt,
resDialogOkToast = R.string.crash_dialog_ok_toast
)
-public class CommonsApplication extends Application implements HasActivityInjector {
+public class CommonsApplication extends DaggerApplication {
- private Account currentAccount = null; // Unlike a savings account...
+ @Inject SessionManager sessionManager;
+ @Inject DBOpenHelper dbOpenHelper;
+ @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
+ @Inject @Named("application_preferences") SharedPreferences applicationPrefs;
+ @Inject @Named("prefs") SharedPreferences otherPrefs;
public static final Object[] EVENT_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5334329L};
public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5257721L};
@@ -65,68 +57,12 @@ public class CommonsApplication extends Application implements HasActivityInject
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app";
- public static final String FEEDBACK_EMAIL = "commons-app-android-private@googlegroups.com";
+ public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
- @Inject DispatchingAndroidInjector dispatchingActivityInjector;
- @Inject MediaWikiApi mediaWikiApi;
-
- private static CommonsApplication instance = null;
- private MediaWikiApi api = null;
- private LruCache thumbnailUrlCache = new LruCache<>(1024);
- private CacheController cacheData = null;
- private DBOpenHelper dbOpenHelper = null;
- private NearbyPlaces nearbyPlaces = null;
-
+ private CommonsApplicationComponent component;
private RefWatcher refWatcher;
- /**
- * This should not be called by ANY application code (other than the magic Android glue)
- * Use CommonsApplication.getInstance() instead to get the singleton.
- */
- public CommonsApplication() {
- CommonsApplication.instance = this;
- }
-
- public static CommonsApplication getInstance() {
- if (instance == null) {
- instance = new CommonsApplication();
- }
- return instance;
- }
-
- public MediaWikiApi getMWApi() {
- if (api == null) {
- api = new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
- }
- return api;
- }
-
- public CacheController getCacheData() {
- if (cacheData == null) {
- cacheData = new CacheController();
- }
- return cacheData;
- }
-
- public LruCache getThumbnailUrlCache() {
- return thumbnailUrlCache;
- }
-
- public synchronized DBOpenHelper getDBOpenHelper() {
- if (dbOpenHelper == null) {
- dbOpenHelper = new DBOpenHelper(this);
- }
- return dbOpenHelper;
- }
-
- public synchronized NearbyPlaces getNearbyPlaces() {
- if (nearbyPlaces == null) {
- nearbyPlaces = new NearbyPlaces();
- }
- return nearbyPlaces;
- }
-
@Override
public void onCreate() {
super.onCreate();
@@ -137,12 +73,6 @@ public class CommonsApplication extends Application implements HasActivityInject
Timber.plant(new Timber.DebugTree());
- DaggerAppComponent
- .builder()
- .application(this)
- .build()
- .inject(this);
-
if (!BuildConfig.DEBUG) {
ACRA.init(this);
} else {
@@ -153,9 +83,6 @@ public class CommonsApplication extends Application implements HasActivityInject
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
Fresco.initialize(this);
-
- //For caching area -> categories
- cacheData = new CacheController();
}
protected RefWatcher setupLeakCanary() {
@@ -170,43 +97,18 @@ public class CommonsApplication extends Application implements HasActivityInject
return application.refWatcher;
}
- /**
- * @return Account|null
- */
- public Account getCurrentAccount() {
- if (currentAccount == null) {
- AccountManager accountManager = AccountManager.get(this);
- Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
- if (allAccounts.length != 0) {
- currentAccount = allAccounts[0];
- }
- }
- return currentAccount;
- }
-
- public Boolean revalidateAuthToken() {
- AccountManager accountManager = AccountManager.get(this);
- Account curAccount = getCurrentAccount();
-
- if (curAccount == null) {
- return false; // This should never happen
- }
-
- accountManager.invalidateAuthToken(AccountUtil.accountType(), mediaWikiApi.getAuthCookie());
- try {
- String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
- mediaWikiApi.setAuthCookie(authCookie);
- return true;
- } catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
- e.printStackTrace();
- return false;
- }
+ @Override
+ protected AndroidInjector extends DaggerApplication> applicationInjector() {
+ return injector();
}
- public boolean deviceHasCamera() {
- PackageManager pm = getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
- || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
+ public CommonsApplicationComponent injector() {
+ if (component == null) {
+ component = DaggerCommonsApplicationComponent.builder()
+ .appModule(new CommonsApplicationModule(this))
+ .build();
+ }
+ return component;
}
public void clearApplicationData(Context context, LogoutListener logoutListener) {
@@ -221,67 +123,25 @@ public class CommonsApplication extends Application implements HasActivityInject
}
}
- AccountManager accountManager = AccountManager.get(this);
- Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
-
- AccountManagerCallback amCallback = new AccountManagerCallback() {
-
- private int index = 0;
-
- void setIndex(int index) {
- this.index = index;
- }
-
- int getIndex() {
- return index;
- }
-
- @Override
- public void run(AccountManagerFuture accountManagerFuture) {
- setIndex(getIndex() + 1);
-
- try {
- if (accountManagerFuture != null && accountManagerFuture.getResult()) {
- Timber.d("Account removed successfully.");
- }
- } catch (OperationCanceledException | IOException | AuthenticatorException e) {
- e.printStackTrace();
- }
-
- if (getIndex() == allAccounts.length) {
+ sessionManager.clearAllAccounts()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> {
Timber.d("All accounts have been removed");
//TODO: fix preference manager
- 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();
- preferences.edit().putBoolean("firstrun", false).apply();
+ defaultPrefs.edit().clear().commit();
+ applicationPrefs.edit().clear().commit();
+ applicationPrefs.edit().putBoolean("firstrun", false).apply();otherPrefs.edit().clear().commit();
updateAllDatabases();
- currentAccount = null;
logoutListener.onLogoutComplete();
- }
- }
- };
-
- for (Account account : allAccounts) {
- accountManager.removeAccount(account, amCallback, null);
- }
- }
-
- @Override
- public AndroidInjector activityInjector() {
- return dispatchingActivityInjector;
+ });
}
/**
* Deletes all tables and re-creates them.
*/
- public void updateAllDatabases() {
- DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
+ private void updateAllDatabases() {
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
diff --git a/app/src/main/java/fr/free/nrw/commons/HandlerService.java b/app/src/main/java/fr/free/nrw/commons/HandlerService.java
index 61fa1f1c5..69a1ea4b9 100644
--- a/app/src/main/java/fr/free/nrw/commons/HandlerService.java
+++ b/app/src/main/java/fr/free/nrw/commons/HandlerService.java
@@ -1,6 +1,5 @@
package fr.free.nrw.commons;
-import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
@@ -9,7 +8,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-public abstract class HandlerService extends Service {
+import dagger.android.DaggerService;
+
+public abstract class HandlerService extends DaggerService {
private volatile Looper threadLooper;
private volatile ServiceHandler threadHandler;
private String serviceName;
diff --git a/app/src/main/java/fr/free/nrw/commons/LicenseList.java b/app/src/main/java/fr/free/nrw/commons/LicenseList.java
index 382ceee3f..ab32f8815 100644
--- a/app/src/main/java/fr/free/nrw/commons/LicenseList.java
+++ b/app/src/main/java/fr/free/nrw/commons/LicenseList.java
@@ -5,7 +5,9 @@ import android.content.res.Resources;
import android.support.annotation.Nullable;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
@@ -19,7 +21,7 @@ public class LicenseList {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
- while (Utils.xmlFastForward(parser, namespace, "license")) {
+ while (xmlFastForward(parser, namespace, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
@@ -60,4 +62,34 @@ public class LicenseList {
+ nameIdForTemplate(template), null, null);
return (nameId != 0) ? res.getString(nameId) : template;
}
+
+ /**
+ * Fast-forward an XmlPullParser to the next instance of the given element
+ * in the input stream (namespaced).
+ *
+ * @param parser
+ * @param namespace
+ * @param element
+ * @return true on match, false on failure
+ */
+ private 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)) {
+ // We found it!
+ return true;
+ }
+ }
+ return false;
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ return false;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
index 643fd68d8..0927f0338 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
@@ -11,12 +11,12 @@ import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -33,45 +33,39 @@ import timber.log.Timber;
* which are not intrinsic to the media and may change due to editing.
*/
public class MediaDataExtractor {
+ private final MediaWikiApi mediaWikiApi;
private boolean fetched;
-
- private String filename;
private ArrayList categories;
private Map descriptions;
private String license;
private @Nullable LatLng coordinates;
- private LicenseList licenseList;
- /**
- * @param filename of the target media object, should include 'File:' prefix
- */
- public MediaDataExtractor(String filename, LicenseList licenseList) {
- this.filename = filename;
- categories = new ArrayList<>();
- descriptions = new HashMap<>();
- fetched = false;
- this.licenseList = licenseList;
+ @Inject
+ public MediaDataExtractor(MediaWikiApi mwApi) {
+ this.categories = new ArrayList<>();
+ this.descriptions = new HashMap<>();
+ this.fetched = false;
+ this.mediaWikiApi = mwApi;
}
- /**
+ /*
* Actually fetch the data over the network.
* todo: use local caching?
*
* Warning: synchronous i/o, call on a background thread
*/
- public void fetch() throws IOException {
+ public void fetch(String filename, LicenseList licenseList) throws IOException {
if (fetched) {
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
}
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- MediaResult result = api.fetchMediaByFilename(filename);
+ MediaResult result = mediaWikiApi.fetchMediaByFilename(filename);
// In-page category links are extracted from source, as XML doesn't cover [[links]]
extractCategories(result.getWikiSource());
// Description template info is extracted from preprocessor XML
- processWikiParseTree(result.getParseTreeXmlSource());
+ processWikiParseTree(result.getParseTreeXmlSource(), licenseList);
fetched = true;
}
@@ -90,7 +84,7 @@ public class MediaDataExtractor {
}
}
- private void processWikiParseTree(String source) throws IOException {
+ private void processWikiParseTree(String source, LicenseList licenseList) throws IOException {
Document doc;
try {
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java b/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
index 5ccc80c06..a542cb363 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
@@ -7,16 +7,17 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
class MediaThumbnailFetchTask extends AsyncTask {
protected final Media media;
+ private MediaWikiApi mediaWikiApi;
- public MediaThumbnailFetchTask(@NonNull Media media) {
+ public MediaThumbnailFetchTask(@NonNull Media media, MediaWikiApi mwApi) {
this.media = media;
+ this.mediaWikiApi = mwApi;
}
@Override
protected String doInBackground(String... params) {
try {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- return api.findThumbnailByFilename(params[0]);
+ return mediaWikiApi.findThumbnailByFilename(params[0]);
} catch (Exception e) {
// Do something better!
}
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java b/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
index 2b017ac8b..aa7ea8a15 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.graphics.drawable.VectorDrawableCompat;
+import android.support.v4.util.LruCache;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.Toast;
@@ -11,9 +12,15 @@ import android.widget.Toast;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
public class MediaWikiImageView extends SimpleDraweeView {
+ @Inject MediaWikiApi mwApi;
+ @Inject LruCache thumbnailUrlCache;
+
private ThumbnailFetchTask currentThumbnailTask;
public MediaWikiImageView(Context context) {
@@ -39,11 +46,11 @@ public class MediaWikiImageView extends SimpleDraweeView {
return;
}
- if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) {
- setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()));
+ if (thumbnailUrlCache.get(media.getFilename()) != null) {
+ setImageUrl(thumbnailUrlCache.get(media.getFilename()));
} else {
setImageUrl(null);
- currentThumbnailTask = new ThumbnailFetchTask(media);
+ currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
currentThumbnailTask.execute(media.getFilename());
}
}
@@ -57,6 +64,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
}
private void init() {
+ ((CommonsApplication) getContext().getApplicationContext()).injector().inject(this);
setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
@@ -71,8 +79,8 @@ public class MediaWikiImageView extends SimpleDraweeView {
}
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
- ThumbnailFetchTask(@NonNull Media media) {
- super(media);
+ ThumbnailFetchTask(@NonNull Media media, @NonNull MediaWikiApi mwApi) {
+ super(media, mwApi);
}
@Override
@@ -85,7 +93,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
} else {
// only cache meaningful thumbnails received from network.
try {
- CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result);
+ thumbnailUrlCache.put(media.getFilename(), result);
} catch (NullPointerException npe) {
Timber.e("error when adding pic to cache " + npe);
diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java
index 989083138..3f5eb2105 100644
--- a/app/src/main/java/fr/free/nrw/commons/Utils.java
+++ b/app/src/main/java/fr/free/nrw/commons/Utils.java
@@ -1,102 +1,26 @@
package fr.free.nrw.commons;
import android.content.Context;
-import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.text.Html;
-import android.text.Spanned;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
-import org.w3c.dom.Node;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
import java.net.URLEncoder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMSource;
-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
- public static String getSHA1(InputStream is) {
-
- MessageDigest digest;
- try {
- digest = MessageDigest.getInstance("SHA1");
- } catch (NoSuchAlgorithmException e) {
- Timber.e(e, "Exception while getting Digest");
- return "";
- }
-
- byte[] buffer = new byte[8192];
- int read;
- try {
- while ((read = is.read(buffer)) > 0) {
- digest.update(buffer, 0, read);
- }
- byte[] md5sum = digest.digest();
- BigInteger bigInt = new BigInteger(1, md5sum);
- String output = bigInt.toString(16);
- // Fill to 40 chars
- output = String.format("%40s", output).replace(' ', '0');
- Timber.i("File SHA1: %s", output);
-
- return output;
- } catch (IOException e) {
- Timber.e(e, "IO Exception");
- return "";
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- Timber.e(e, "Exception on closing MD5 input stream");
- }
- }
- }
-
- /**
- * 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);
- } else {
- //noinspection deprecation
- return Html.fromHtml(source);
- }
- }
-
/**
* Strips localization symbols from a string.
* Removes the suffix after "@" and quotes.
@@ -113,49 +37,12 @@ public class Utils {
}
}
- public static Date parseMWDate(String mwDate) {
- SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
- try {
- return isoFormat.parse(mwDate);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static String toMWDate(Date date) {
- SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
- isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- return isoFormat.format(date);
- }
-
public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(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) {
- Transformer transformer = null;
- try {
- transformer = TransformerFactory.newInstance().newTransformer();
- } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- StringWriter outputStream = new StringWriter();
- DOMSource domSource = new DOMSource(dom);
- StreamResult strResult = new StreamResult(outputStream);
-
- try {
- transformer.transform(domSource, strResult);
- } catch (TransformerException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return outputStream.toString();
- }
-
public static String urlEncode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
@@ -164,39 +51,10 @@ public class Utils {
}
}
- public static long countBytes(InputStream stream) throws IOException {
- long count = 0;
- BufferedInputStream bis = new BufferedInputStream(stream);
- while (bis.read() != -1) {
- count++;
- }
- return count;
- }
-
public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
}
- public static String licenseTemplateFor(String license) {
- switch (license) {
- case Prefs.Licenses.CC_BY_3:
- return "{{self|cc-by-3.0}}";
- case Prefs.Licenses.CC_BY_4:
- return "{{self|cc-by-4.0}}";
- case Prefs.Licenses.CC_BY_SA_3:
- return "{{self|cc-by-sa-3.0}}";
- case Prefs.Licenses.CC_BY_SA_4:
- return "{{self|cc-by-sa-4.0}}";
- case Prefs.Licenses.CC0:
- return "{{self|cc-zero}}";
- case Prefs.Licenses.CC_BY:
- return "{{self|cc-by-3.0}}";
- case Prefs.Licenses.CC_BY_SA:
- return "{{self|cc-by-sa-3.0}}";
- }
- throw new RuntimeException("Unrecognized license value: " + license);
- }
-
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
@@ -217,51 +75,6 @@ public class Utils {
throw new RuntimeException("Unrecognized license value: " + license);
}
- public static String licenseUrlFor(String license) {
- switch (license) {
- case Prefs.Licenses.CC_BY_3:
- return "https://creativecommons.org/licenses/by/3.0/";
- case Prefs.Licenses.CC_BY_4:
- return "https://creativecommons.org/licenses/by/4.0/";
- case Prefs.Licenses.CC_BY_SA_3:
- return "https://creativecommons.org/licenses/by-sa/3.0/";
- case Prefs.Licenses.CC_BY_SA_4:
- return "https://creativecommons.org/licenses/by-sa/4.0/";
- case Prefs.Licenses.CC0:
- return "https://creativecommons.org/publicdomain/zero/1.0/";
- }
- throw new RuntimeException("Unrecognized license value: " + license);
- }
-
- /**
- * Fast-forward an XmlPullParser to the next instance of the given element
- * in the input stream (namespaced).
- *
- * @param parser
- * @param namespace
- * @param element
- * @return true on match, false on failure
- */
- 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)) {
- // We found it!
- return true;
- }
- }
- return false;
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- return false;
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
@@ -277,10 +90,6 @@ public class Utils {
return title;
}
- public static boolean isNullOrWhiteSpace(String value) {
- return value == null || value.trim().isEmpty();
- }
-
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
index 479b47444..4114b19a9 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
@@ -4,21 +4,31 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.os.Bundle;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import timber.log.Timber;
+import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION;
+import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
+import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
+
public class AccountUtil {
- public static void createAccount(@Nullable AccountAuthenticatorResponse response,
- String username, String password) {
+ public static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
+ private final Context context;
- Account account = new Account(username, accountType());
+ public AccountUtil(Context context) {
+ this.context = context;
+ }
+
+ public void createAccount(@Nullable AccountAuthenticatorResponse response,
+ String username, String password) {
+
+ Account account = new Account(username, ACCOUNT_TYPE);
boolean created = accountManager().addAccountExplicitly(account, password, null);
Timber.d("account creation " + (created ? "successful" : "failure"));
@@ -26,8 +36,8 @@ public class AccountUtil {
if (created) {
if (response != null) {
Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, username);
- bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType());
+ bundle.putString(KEY_ACCOUNT_NAME, username);
+ bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(bundle);
@@ -35,7 +45,7 @@ public class AccountUtil {
} else {
if (response != null) {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "");
+ response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
}
Timber.d("account creation failure");
}
@@ -45,18 +55,8 @@ public class AccountUtil {
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
}
- @NonNull
- public static String accountType() {
- return "fr.free.nrw.commons";
- }
-
- private static AccountManager accountManager() {
- return AccountManager.get(app());
- }
-
- @NonNull
- private static CommonsApplication app() {
- return CommonsApplication.getInstance();
+ private AccountManager accountManager() {
+ return AccountManager.get(context);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
index 5b483c328..e793d5eb9 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
@@ -5,47 +5,50 @@ import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.os.Bundle;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
+import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
+import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
- private String accountType;
- CommonsApplication app;
+ @Inject SessionManager sessionManager;
private String authCookie;
- public AuthenticatedActivity() {
- this.accountType = AccountUtil.accountType();
- }
private void getAuthCookie(Account account, AccountManager accountManager) {
Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false))
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::onAuthCookieAcquired, throwable -> onAuthFailure());
+ .subscribe(
+ this:: onAuthCookieAcquired,
+ throwable -> onAuthFailure());
}
private void addAccount(AccountManager accountManager) {
- Single.just(accountManager.addAccount(accountType, null, null, null, AuthenticatedActivity.this, null, null))
+ Single.just(accountManager.addAccount(ACCOUNT_TYPE, null, null,
+ null, AuthenticatedActivity.this, null, null))
.subscribeOn(Schedulers.io())
.map(AccountManagerFuture::getResult)
.doOnEvent((bundle, throwable) -> {
- if (!bundle.containsKey(AccountManager.KEY_ACCOUNT_NAME)) {
+ if (!bundle.containsKey(KEY_ACCOUNT_NAME)) {
throw new RuntimeException("Bundle doesn't contain account-name key: "
- + AccountManager.KEY_ACCOUNT_NAME);
+ + KEY_ACCOUNT_NAME);
}
})
- .map(bundle -> bundle.getString(AccountManager.KEY_ACCOUNT_NAME))
+ .map(bundle -> bundle.getString(KEY_ACCOUNT_NAME))
.doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
- Account[] allAccounts = accountManager.getAccountsByType(accountType);
+ Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
Account curAccount = allAccounts[0];
getAuthCookie(curAccount, accountManager);
},
@@ -58,7 +61,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
return;
}
AccountManager accountManager = AccountManager.get(this);
- Account curAccount = app.getCurrentAccount();
+ Account curAccount = sessionManager.getCurrentAccount();
if (curAccount == null) {
addAccount(accountManager);
} else {
@@ -69,7 +72,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- app = CommonsApplication.getInstance();
+
if (savedInstanceState != null) {
authCookie = savedInstanceState.getString("authCookie");
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
index f581de832..d17120106 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
@@ -21,15 +21,19 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
+import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import timber.log.Timber;
@@ -40,6 +44,12 @@ public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
+ @Inject MediaWikiApi mwApi;
+ @Inject AccountUtil accountUtil;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("application_preferences") SharedPreferences prefs;
+ @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
+
@BindView(R.id.loginButton) Button loginButton;
@BindView(R.id.signupButton) Button signupButton;
@BindView(R.id.loginUsername) EditText usernameEdit;
@@ -47,11 +57,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@BindView(R.id.loginTwoFactor) EditText twoFactorEdit;
@BindView(R.id.error_message_container) ViewGroup errorMessageContainer;
@BindView(R.id.error_message) TextView errorMessage;
-
- private CommonsApplication app;
ProgressDialog progressDialog;
private AppCompatDelegate delegate;
- private SharedPreferences prefs = null;
private LoginTextWatcher textWatcher = new LoginTextWatcher();
@Override
@@ -59,16 +66,13 @@ public class LoginActivity extends AccountAuthenticatorActivity {
setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
+ AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
- app = CommonsApplication.getInstance();
-
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
- prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
-
usernameEdit.addTextChangedListener(textWatcher);
passwordEdit.addTextChangedListener(textWatcher);
twoFactorEdit.addTextChangedListener(textWatcher);
@@ -91,7 +95,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
WelcomeActivity.startYourself(this);
prefs.edit().putBoolean("firstrun", false).apply();
}
- if (app.getCurrentAccount() != null) {
+ if (sessionManager.getCurrentAccount() != null) {
startMainActivity();
}
}
@@ -113,6 +117,25 @@ public class LoginActivity extends AccountAuthenticatorActivity {
super.onDestroy();
}
+ private LoginTask getLoginTask() {
+ return new LoginTask(
+ this,
+ canonicializeUsername(usernameEdit.getText().toString()),
+ passwordEdit.getText().toString(),
+ twoFactorEdit.getText().toString(),
+ accountUtil, mwApi, defaultPrefs
+ );
+ }
+
+ /**
+ * Because Mediawiki is upercase-first-char-then-case-sensitive :)
+ * @param username String
+ * @return String canonicial username
+ */
+ private String canonicializeUsername(String username) {
+ return new PageTitle(username).getText();
+ }
+
@Override
protected void onStart() {
super.onStart();
@@ -207,24 +230,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
};
}
- private LoginTask getLoginTask() {
- return new LoginTask(
- this,
- canonicializeUsername(usernameEdit.getText().toString()),
- passwordEdit.getText().toString(),
- twoFactorEdit.getText().toString()
- );
- }
-
- /**
- * Because Mediawiki is upercase-first-char-then-case-sensitive :)
- * @param username String
- * @return String canonicial username
- */
- private String canonicializeUsername(String username) {
- return new PageTitle(username).getText();
- }
-
private void showMessage(@StringRes int resId, @ColorRes int colorResId) {
errorMessage.setText(getString(resId));
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
index 519ea323c..fa7a671c7 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
@@ -1,8 +1,8 @@
package fr.free.nrw.commons.auth;
import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.AccountManager;
import android.app.ProgressDialog;
+import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -11,22 +11,34 @@ import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
+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 fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+
class LoginTask extends AsyncTask {
private LoginActivity loginActivity;
private String username;
private String password;
private String twoFactorCode = "";
- private CommonsApplication app;
+ private AccountUtil accountUtil;
+ private MediaWikiApi mwApi;
+ private SharedPreferences prefs;
- public LoginTask(LoginActivity loginActivity, String username, String password, String twoFactorCode) {
+ public LoginTask(LoginActivity loginActivity, String username, String password,
+ String twoFactorCode, AccountUtil accountUtil,
+ MediaWikiApi mwApi, SharedPreferences prefs) {
this.loginActivity = loginActivity;
this.username = username;
this.password = password;
this.twoFactorCode = twoFactorCode;
- app = CommonsApplication.getInstance();
+ this.accountUtil = accountUtil;
+ this.mwApi = mwApi;
+ this.prefs = prefs;
}
@Override
@@ -44,9 +56,9 @@ class LoginTask extends AsyncTask {
protected String doInBackground(String... params) {
try {
if (twoFactorCode.isEmpty()) {
- return app.getMWApi().login(username, password);
+ return mwApi.login(username, password);
} else {
- return app.getMWApi().login(username, password, twoFactorCode);
+ return mwApi.login(username, password, twoFactorCode);
}
} catch (IOException e) {
// Do something better!
@@ -59,7 +71,7 @@ class LoginTask extends AsyncTask {
super.onPostExecute(result);
Timber.d("Login done!");
- EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT)
+ EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT, mwApi, prefs)
.param("username", username)
.param("result", result)
.log();
@@ -79,16 +91,16 @@ class LoginTask extends AsyncTask {
Bundle extras = loginActivity.getIntent().getExtras();
if (extras != null) {
Timber.d("Bundle of extras: %s", extras);
- response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+ response = extras.getParcelable(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
if (response != null) {
Bundle authResult = new Bundle();
- authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
- authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
+ authResult.putString(KEY_ACCOUNT_NAME, username);
+ authResult.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(authResult);
}
}
- AccountUtil.createAccount(response, username, password);
+ accountUtil.createAccount(response, username, password);
loginActivity.startMainActivity();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
new file mode 100644
index 000000000..779b73b34
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
@@ -0,0 +1,71 @@
+package fr.free.nrw.commons.auth;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+
+import java.io.IOException;
+
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import io.reactivex.Completable;
+import io.reactivex.Observable;
+
+import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+
+/**
+ * Manage the current logged in user session.
+ */
+public class SessionManager {
+ private final Context context;
+ private final MediaWikiApi mediaWikiApi;
+ private Account currentAccount; // Unlike a savings account... ;-)
+
+ public SessionManager(Context context, MediaWikiApi mediaWikiApi) {
+ this.context = context;
+ this.mediaWikiApi = mediaWikiApi;
+ this.currentAccount = null;
+ }
+
+ /**
+ * @return Account|null
+ */
+ public Account getCurrentAccount() {
+ if (currentAccount == null) {
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ if (allAccounts.length != 0) {
+ currentAccount = allAccounts[0];
+ }
+ }
+ return currentAccount;
+ }
+
+ public Boolean revalidateAuthToken() {
+ AccountManager accountManager = AccountManager.get(context);
+ Account curAccount = getCurrentAccount();
+
+ if (curAccount == null) {
+ return false; // This should never happen
+ }
+
+ accountManager.invalidateAuthToken(ACCOUNT_TYPE, mediaWikiApi.getAuthCookie());
+ try {
+ String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
+ mediaWikiApi.setAuthCookie(authCookie);
+ return true;
+ } catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public Completable clearAllAccounts() {
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ return Completable.fromObservable(Observable.fromArray(allAccounts)
+ .map(a -> accountManager.removeAccount(a, null, null).getResult()))
+ .doOnComplete(() -> currentAccount = null);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
index 4da090531..a6b66cbf6 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
@@ -7,7 +7,6 @@ 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;
@@ -39,11 +38,8 @@ public class SignupActivity extends BaseActivity {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url);
- Toast toast = Toast.makeText(
- CommonsApplication.getInstance(),
- "Account created!",
- Toast.LENGTH_LONG
- );
+ Toast toast = Toast.makeText(SignupActivity.this,
+ "Account created!", Toast.LENGTH_LONG);
toast.show();
// terminate on task completion.
finish();
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
index ea574d432..c08e27966 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
@@ -13,7 +13,6 @@ import android.support.annotation.Nullable;
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;
@@ -25,15 +24,18 @@ 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.AccountUtil.ACCOUNT_TYPE;
import static fr.free.nrw.commons.auth.LoginActivity.PARAM_USERNAME;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
- private Context context;
+ private final Context context;
+ private MediaWikiApi mediaWikiApi;
- WikiAccountAuthenticator(Context context) {
+ WikiAccountAuthenticator(Context context, MediaWikiApi mwApi) {
super(context);
this.context = context;
+ this.mediaWikiApi = mwApi;
}
private Bundle unsupportedOperation() {
@@ -47,7 +49,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
}
private boolean supportedAccountType(@Nullable String type) {
- return AccountUtil.accountType().equals(type);
+ return ACCOUNT_TYPE.equals(type);
}
@Override
@@ -86,11 +88,10 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
}
private String getAuthCookie(String username, String password) throws IOException {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
//TODO add 2fa support here
- String result = api.login(username, password);
+ String result = mediaWikiApi.login(username, password);
if (result.equals("PASS")) {
- return api.getAuthCookie();
+ return mediaWikiApi.getAuthCookie();
} else {
return null;
}
@@ -115,7 +116,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
if (authCookie != null) {
final Bundle result = new Bundle();
result.putString(KEY_ACCOUNT_NAME, account.name);
- result.putString(KEY_ACCOUNT_TYPE, AccountUtil.accountType());
+ result.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
result.putString(KEY_AUTHTOKEN, authCookie);
return result;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
index 0a996b7d4..b0f3e6063 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
@@ -1,22 +1,28 @@
package fr.free.nrw.commons.auth;
-import android.accounts.AccountManager;
-import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
-public class WikiAccountAuthenticatorService extends Service {
+import javax.inject.Inject;
+
+import dagger.android.DaggerService;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+
+import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT;
+
+public class WikiAccountAuthenticatorService extends DaggerService {
+
+ @Inject MediaWikiApi mwApi;
+ private WikiAccountAuthenticator wikiAccountAuthenticator = null;
- private static WikiAccountAuthenticator wikiAccountAuthenticator = null;
-
@Override
public IBinder onBind(Intent intent) {
- if (!intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
+ if (!intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT)) {
return null;
}
if (wikiAccountAuthenticator == null) {
- wikiAccountAuthenticator = new WikiAccountAuthenticator(this);
+ wikiAccountAuthenticator = new WikiAccountAuthenticator(this, mwApi);
}
return wikiAccountAuthenticator.getIBinder();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
index 1a70bc57c..5c3992bf2 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
@@ -3,8 +3,6 @@ package fr.free.nrw.commons.category;
import android.content.ContentProviderClient;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -31,11 +29,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.data.Category;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
@@ -50,7 +52,7 @@ import static fr.free.nrw.commons.category.CategoryContentProvider.AUTHORITY;
/**
* Displays the category suggestion and selection screen. Category search is initiated here.
*/
-public class CategorizationFragment extends Fragment {
+public class CategorizationFragment extends DaggerFragment {
public static final int SEARCH_CATS_LIMIT = 25;
@@ -65,6 +67,9 @@ public class CategorizationFragment extends Fragment {
@BindView(R.id.categoriesExplanation)
TextView categoriesSkip;
+ @Inject MediaWikiApi mwApi;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
private RVRendererAdapter categoriesAdapter;
private OnCategoriesSaveHandler onCategoriesSaveHandler;
private HashMap> categoriesCache;
@@ -205,7 +210,9 @@ public class CategorizationFragment extends Fragment {
.sorted(sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
- s -> categoriesAdapter.add(s), Timber::e, () -> {
+ s -> categoriesAdapter.add(s),
+ Timber::e,
+ () -> {
categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE);
@@ -253,10 +260,9 @@ public class CategorizationFragment extends Fragment {
private Observable titleCategories() {
//Retrieve the title that was saved when user tapped submit icon
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String title = titleDesc.getString("Title", "");
+ String title = prefs.getString("Title", "");
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.searchTitles(title, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}
@@ -279,7 +285,7 @@ public class CategorizationFragment extends Fragment {
}
//otherwise, search API for matching categories
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.allCategories(term, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}
@@ -290,7 +296,7 @@ public class CategorizationFragment extends Fragment {
return Observable.empty();
}
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.searchCategories(term, SEARCH_CATS_LIMIT)
.map(s -> new CategoryItem(s, false));
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
index e3a2661a7..ed698ec4c 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
@@ -10,7 +10,9 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjection;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
@@ -36,17 +38,15 @@ public class CategoryContentProvider extends ContentProvider {
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
}
- private DBOpenHelper dbOpenHelper;
-
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
- @SuppressWarnings("ConstantConditions")
+ @Inject DBOpenHelper dbOpenHelper;
+
@Override
public boolean onCreate() {
- CommonsApplication app = ((CommonsApplication) getContext().getApplicationContext());
- dbOpenHelper = app.getDBOpenHelper();
+ AndroidInjection.inject(this);
return false;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
index 338743f02..e673c7d9d 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
@@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Parcel;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import java.text.SimpleDateFormat;
@@ -16,7 +17,6 @@ import java.util.Locale;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
public class Contribution extends Media {
@@ -149,7 +149,7 @@ public class Contribution extends Media {
}
buffer.append("== {{int:license-header}} ==\n")
- .append(Utils.licenseTemplateFor(getLicense())).append("\n\n")
+ .append(licenseTemplateFor(getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
.append(getTrackingTemplates());
return buffer.toString();
@@ -377,4 +377,25 @@ public class Contribution extends Media {
}
}
}
+
+ @NonNull
+ private String licenseTemplateFor(String license) {
+ switch (license) {
+ case Prefs.Licenses.CC_BY_3:
+ return "{{self|cc-by-3.0}}";
+ case Prefs.Licenses.CC_BY_4:
+ return "{{self|cc-by-4.0}}";
+ case Prefs.Licenses.CC_BY_SA_3:
+ return "{{self|cc-by-sa-3.0}}";
+ case Prefs.Licenses.CC_BY_SA_4:
+ return "{{self|cc-by-sa-4.0}}";
+ case Prefs.Licenses.CC0:
+ return "{{self|cc-zero}}";
+ case Prefs.Licenses.CC_BY:
+ return "{{self|cc-by-3.0}}";
+ case Prefs.Licenses.CC_BY_SA:
+ return "{{self|cc-by-sa-3.0}}";
+ }
+ throw new RuntimeException("Unrecognized license value: " + license);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
index 933cda6ae..6cda47d2e 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
@@ -9,7 +9,6 @@ import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
@@ -24,21 +23,20 @@ import android.widget.AdapterView;
import java.util.ArrayList;
import javax.inject.Inject;
+import javax.inject.Named;
import butterknife.ButterKnife;
-import dagger.android.AndroidInjection;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
-import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
@@ -49,10 +47,17 @@ import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUT
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, AdapterView.OnItemClickListener,
- MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener,
- ContributionsListFragment.SourceRefresher {
+public class ContributionsActivity
+ extends AuthenticatedActivity
+ implements LoaderManager.LoaderCallbacks,
+ AdapterView.OnItemClickListener,
+ MediaDetailPagerFragment.MediaDetailProvider,
+ FragmentManager.OnBackStackChangedListener,
+ ContributionsListFragment.SourceRefresher {
+
+ @Inject MediaWikiApi mediaWikiApi;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
private Cursor allContributions;
private ContributionsListFragment contributionsList;
@@ -62,9 +67,6 @@ public class ContributionsActivity extends AuthenticatedActivity
private ArrayList observersWaitingForLoad = new ArrayList<>();
private String CONTRIBUTION_SELECTION = "";
- @Inject
- MediaWikiApi mediaWikiApi;
-
/*
This sorts in the following order:
Currently Uploading
@@ -109,12 +111,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onResume() {
super.onResume();
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isSettingsChanged =
- sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- editor.apply();
+ boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
+ prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
if (isSettingsChanged) {
refreshSource();
}
@@ -122,9 +120,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
- // Do a sync every time we get here!
- CommonsApplication app = ((CommonsApplication) getApplication());
- requestSync(app.getCurrentAccount(), AUTHORITY, new Bundle());
+ // Do a sync everytime we get here!
+ requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
@@ -139,19 +136,18 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- AndroidInjection.inject(this);
setContentView(R.layout.activity_contributions);
ButterKnife.bind(this);
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
FragmentManager supportFragmentManager = getSupportFragmentManager();
- contributionsList = (ContributionsListFragment) supportFragmentManager
+ contributionsList = (ContributionsListFragment)supportFragmentManager
.findFragmentById(R.id.contributionsListFragment);
supportFragmentManager.addOnBackStackChangedListener(this);
if (savedInstanceState != null) {
- mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
+ mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
getSupportLoaderManager().initLoader(0, null, this);
@@ -241,8 +237,7 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
- SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
- int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100);
+ int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI,
ALL_FIELDS, CONTRIBUTION_SELECTION, null,
CONTRIBUTION_SORT + "LIMIT " + uploads);
@@ -289,9 +284,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
- CommonsApplication app = ((CommonsApplication) getApplication());
- Disposable uploadCountDisposable = mediaWikiApi
- .getUploadCount(app.getCurrentAccount().name)
+ compositeDisposable.add(mediaWikiApi
+ .getUploadCount(sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
@@ -299,8 +293,7 @@ public class ContributionsActivity extends AuthenticatedActivity
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed")
- );
- compositeDisposable.add(uploadCountDisposable);
+ ));
}
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
index aae9fa3bc..5ec290026 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
@@ -10,7 +10,10 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjection;
+import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
@@ -36,9 +39,12 @@ public class ContributionsContentProvider extends ContentProvider {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
+ @Inject DBOpenHelper dbOpenHelper;
+
@Override
public boolean onCreate() {
- return false;
+ AndroidInjection.inject(this);
+ return true;
}
@SuppressWarnings("ConstantConditions")
@@ -50,8 +56,7 @@ public class ContributionsContentProvider extends ContentProvider {
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase db = app.getDBOpenHelper().getReadableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch (uriType) {
@@ -87,9 +92,8 @@ public class ContributionsContentProvider extends ContentProvider {
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
- long id;
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ long id = 0;
switch (uriType) {
case CONTRIBUTIONS:
id = sqlDB.insert(TABLE_NAME, null, contentValues);
@@ -107,13 +111,12 @@ public class ContributionsContentProvider extends ContentProvider {
int rows;
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (uriType) {
case CONTRIBUTIONS_ID:
Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
- rows = sqlDB.delete(TABLE_NAME,
+ rows = db.delete(TABLE_NAME,
"_id = ?",
new String[]{uri.getLastPathSegment()}
);
@@ -130,8 +133,7 @@ public class ContributionsContentProvider extends ContentProvider {
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case CONTRIBUTIONS:
@@ -162,9 +164,8 @@ public class ContributionsContentProvider extends ContentProvider {
error out otherwise.
*/
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
- int rowsUpdated;
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ int rowsUpdated = 0;
switch (uriType) {
case CONTRIBUTIONS:
rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs);
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index 9a313854c..590d4e6ad 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -5,9 +5,7 @@ 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;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
@@ -22,9 +20,12 @@ import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.nearby.NearbyActivity;
import timber.log.Timber;
@@ -32,11 +33,10 @@ 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 class ContributionsListFragment extends DaggerFragment {
@BindView(R.id.contributionsList)
GridView contributionsList;
@@ -45,6 +45,9 @@ public class ContributionsListFragment extends Fragment {
@BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar;
+ @Inject @Named("prefs") SharedPreferences prefs;
+ @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
+
private ContributionController controller;
@Override
@@ -59,7 +62,6 @@ public class ContributionsListFragment extends Fragment {
}
//TODO: Should this be in onResume?
- SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified);
@@ -162,9 +164,7 @@ public class ContributionsListFragment extends Fragment {
return true;
case R.id.menu_from_camera:
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
+ boolean useExtStorage = defaultPrefs.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)
@@ -242,12 +242,17 @@ public class ContributionsListFragment extends Fragment {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- if (!app.deviceHasCamera()) {
+ if (!deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false);
}
}
+ public boolean deviceHasCamera() {
+ PackageManager pm = getContext().getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
+ pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
index c2363ebae..e67b164a8 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
@@ -13,9 +13,15 @@ import android.os.RemoteException;
import android.text.TextUtils;
import java.io.IOException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.inject.Inject;
+import javax.inject.Named;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
@@ -23,11 +29,11 @@ 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;
+@SuppressWarnings("WeakerAccess")
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String[] existsQuery = {COLUMN_FILENAME};
@@ -35,6 +41,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final ContentValues[] EMPTY = {};
private static int COMMIT_THRESHOLD = 10;
+ @SuppressWarnings("WeakerAccess")
+ @Inject MediaWikiApi mwApi;
+ @Inject @Named("prefs") SharedPreferences prefs;
+
public ContributionsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@@ -71,10 +81,9 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient contentProviderClient, SyncResult syncResult) {
+ ((CommonsApplication) getContext().getApplicationContext()).injector().inject(this);
// 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 = getContext().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date();
LogEventResult result;
@@ -83,7 +92,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
while (!done) {
try {
- result = api.logEvents(user, lastModified, queryContinue, getLimit());
+ result = mwApi.logEvents(user, lastModified, queryContinue, getLimit());
} catch (IOException e) {
// There isn't really much we can do, eh?
// FIXME: Perhaps add EventLogging?
@@ -137,8 +146,13 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
done = true;
}
}
- prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
+ prefs.edit().putString("lastSyncTimestamp", toMWDate(curTime)).apply();
Timber.d("Oh hai, everyone! Look, a kitty!");
}
+ private String toMWDate(Date date) {
+ SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
+ isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return isoFormat.format(date);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilder.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilder.java
deleted file mode 100644
index 4d69d46e6..000000000
--- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilder.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package fr.free.nrw.commons.di;
-
-import dagger.Module;
-import dagger.android.ContributesAndroidInjector;
-import fr.free.nrw.commons.contributions.ContributionsActivity;
-import fr.free.nrw.commons.nearby.NearbyActivity;
-
-@Module
-public abstract class ActivityBuilder {
-
- @ContributesAndroidInjector()
- abstract ContributionsActivity bindSplashScreenActivity();
-
- @ContributesAndroidInjector()
- abstract NearbyActivity bindNearbyActivity();
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
new file mode 100644
index 000000000..27e16ce23
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -0,0 +1,46 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.AboutActivity;
+import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.auth.LoginActivity;
+import fr.free.nrw.commons.auth.SignupActivity;
+import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.nearby.NearbyActivity;
+import fr.free.nrw.commons.settings.SettingsActivity;
+import fr.free.nrw.commons.upload.MultipleShareActivity;
+import fr.free.nrw.commons.upload.ShareActivity;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ActivityBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract LoginActivity bindLoginActivity();
+
+ @ContributesAndroidInjector
+ abstract WelcomeActivity bindWelcomeActivity();
+
+ @ContributesAndroidInjector
+ abstract ShareActivity bindShareActivity();
+
+ @ContributesAndroidInjector
+ abstract MultipleShareActivity bindMultipleShareActivity();
+
+ @ContributesAndroidInjector
+ abstract ContributionsActivity bindContributionsActivity();
+
+ @ContributesAndroidInjector
+ abstract SettingsActivity bindSettingsActivity();
+
+ @ContributesAndroidInjector
+ abstract AboutActivity bindAboutActivity();
+
+ @ContributesAndroidInjector
+ abstract SignupActivity bindSignupActivity();
+
+ @ContributesAndroidInjector
+ abstract NearbyActivity bindNearbyActivity();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/AppComponent.java b/app/src/main/java/fr/free/nrw/commons/di/AppComponent.java
deleted file mode 100644
index 9cc9e33dd..000000000
--- a/app/src/main/java/fr/free/nrw/commons/di/AppComponent.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package fr.free.nrw.commons.di;
-
-import android.app.Application;
-
-import javax.inject.Singleton;
-
-import dagger.BindsInstance;
-import dagger.Component;
-import dagger.android.support.AndroidSupportInjectionModule;
-import fr.free.nrw.commons.CommonsApplication;
-
-@Singleton
-@Component(modules = {
- AndroidSupportInjectionModule.class,
- AppModule.class,
- ActivityBuilder.class
-})
-public interface AppComponent {
-
- @Component.Builder
- interface Builder {
- @BindsInstance
- Builder application(Application application);
-
- AppComponent build();
- }
-
- void inject(CommonsApplication application);
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/AppModule.java b/app/src/main/java/fr/free/nrw/commons/di/AppModule.java
deleted file mode 100644
index c971a265f..000000000
--- a/app/src/main/java/fr/free/nrw/commons/di/AppModule.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package fr.free.nrw.commons.di;
-
-import android.app.Application;
-import android.content.Context;
-
-import javax.inject.Singleton;
-
-import dagger.Module;
-import dagger.Provides;
-import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.location.LocationServiceManager;
-import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
-import fr.free.nrw.commons.mwapi.MediaWikiApi;
-
-@Module
-public class AppModule {
-
- @Provides
- @Singleton
- Context provideContext(Application application) {
- return application;
- }
-
- @Provides
- @Singleton
- public MediaWikiApi getMWApi() {
- return new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
new file mode 100644
index 000000000..2881f33fd
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
@@ -0,0 +1,40 @@
+package fr.free.nrw.commons.di;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+import dagger.android.AndroidInjectionModule;
+import dagger.android.AndroidInjector;
+import dagger.android.support.AndroidSupportInjectionModule;
+import fr.free.nrw.commons.CommonsApplication;
+import fr.free.nrw.commons.MediaWikiImageView;
+import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
+import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
+
+@Singleton
+@Component(modules = {
+ CommonsApplicationModule.class,
+ AndroidInjectionModule.class,
+ AndroidSupportInjectionModule.class,
+ ActivityBuilderModule.class,
+ FragmentBuilderModule.class,
+ ServiceBuilderModule.class,
+ ContentProviderBuilderModule.class
+})
+public interface CommonsApplicationComponent extends AndroidInjector {
+ void inject(CommonsApplication application);
+
+ void inject(ContributionsSyncAdapter syncAdapter);
+
+ void inject(ModificationsSyncAdapter syncAdapter);
+
+ void inject(MediaWikiImageView mediaWikiImageView);
+
+ @Component.Builder
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ interface Builder {
+ Builder appModule(CommonsApplicationModule applicationModule);
+
+ CommonsApplicationComponent build();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
new file mode 100644
index 000000000..99d3235e7
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -0,0 +1,104 @@
+package fr.free.nrw.commons.di;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.v4.util.LruCache;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.CommonsApplication;
+import fr.free.nrw.commons.auth.AccountUtil;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.location.LocationServiceManager;
+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.upload.UploadController;
+
+import static android.content.Context.MODE_PRIVATE;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class CommonsApplicationModule {
+ private CommonsApplication application;
+
+ public CommonsApplicationModule(CommonsApplication application) {
+ this.application = application;
+ }
+
+ @Provides
+ public AccountUtil providesAccountUtil() {
+ return new AccountUtil(application);
+ }
+
+ @Provides
+ @Named("application_preferences")
+ public SharedPreferences providesApplicationSharedPreferences() {
+ return application.getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
+ }
+
+ @Provides
+ @Named("default_preferences")
+ public SharedPreferences providesDefaultSharedPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(application);
+ }
+
+ @Provides
+ @Named("prefs")
+ public SharedPreferences providesOtherSharedPreferences() {
+ return application.getSharedPreferences("prefs", MODE_PRIVATE);
+ }
+
+ @Provides
+ public UploadController providesUploadController(SessionManager sessionManager, @Named("default_preferences") SharedPreferences sharedPreferences) {
+ return new UploadController(sessionManager, application, sharedPreferences);
+ }
+
+ @Provides
+ @Singleton
+ public SessionManager providesSessionManager(MediaWikiApi mediaWikiApi) {
+ return new SessionManager(application, mediaWikiApi);
+ }
+
+ @Provides
+ @Singleton
+ public MediaWikiApi provideMediaWikiApi() {
+ return new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
+ }
+
+ @Provides
+ @Singleton
+ public LocationServiceManager provideLocationServiceManager() {
+ return new LocationServiceManager(application);
+ }
+
+ @Provides
+ @Singleton
+ public CacheController provideCacheController() {
+ return new CacheController();
+ }
+
+ @Provides
+ @Singleton
+ public DBOpenHelper provideDBOpenHelper() {
+ return new DBOpenHelper(application);
+ }
+
+ @Provides
+ @Singleton
+ public NearbyPlaces provideNearbyPlaces() {
+ return new NearbyPlaces();
+ }
+
+ @Provides
+ @Singleton
+ public LruCache provideLruCache() {
+ return new LruCache<>(1024);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java
new file mode 100644
index 000000000..f18c331c5
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java
@@ -0,0 +1,22 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.category.CategoryContentProvider;
+import fr.free.nrw.commons.contributions.ContributionsContentProvider;
+import fr.free.nrw.commons.modifications.ModificationsContentProvider;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ContentProviderBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract ContributionsContentProvider bindContributionsContentProvider();
+
+ @ContributesAndroidInjector
+ abstract ModificationsContentProvider bindModificationsContentProvider();
+
+ @ContributesAndroidInjector
+ abstract CategoryContentProvider bindCategoryContentProvider();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
new file mode 100644
index 000000000..ca7340cb1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
@@ -0,0 +1,46 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.category.CategorizationFragment;
+import fr.free.nrw.commons.contributions.ContributionsListFragment;
+import fr.free.nrw.commons.media.MediaDetailFragment;
+import fr.free.nrw.commons.media.MediaDetailPagerFragment;
+import fr.free.nrw.commons.nearby.NearbyListFragment;
+import fr.free.nrw.commons.nearby.NoPermissionsFragment;
+import fr.free.nrw.commons.settings.SettingsFragment;
+import fr.free.nrw.commons.upload.MultipleUploadListFragment;
+import fr.free.nrw.commons.upload.SingleUploadFragment;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class FragmentBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract CategorizationFragment bindCategorizationFragment();
+
+ @ContributesAndroidInjector
+ abstract ContributionsListFragment bindContributionsListFragment();
+
+ @ContributesAndroidInjector
+ abstract MediaDetailFragment bindMediaDetailFragment();
+
+ @ContributesAndroidInjector
+ abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
+
+ @ContributesAndroidInjector
+ abstract NearbyListFragment bindNearbyListFragment();
+
+ @ContributesAndroidInjector
+ abstract NoPermissionsFragment bindNoPermissionsFragment();
+
+ @ContributesAndroidInjector
+ abstract SettingsFragment bindSettingsFragment();
+
+ @ContributesAndroidInjector
+ abstract MultipleUploadListFragment bindMultipleUploadListFragment();
+
+ @ContributesAndroidInjector
+ abstract SingleUploadFragment bindSingleUploadFragment();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java
new file mode 100644
index 000000000..2d4072d15
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java
@@ -0,0 +1,18 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.auth.WikiAccountAuthenticatorService;
+import fr.free.nrw.commons.upload.UploadService;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ServiceBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract UploadService bindUploadService();
+
+ @ContributesAndroidInjector
+ abstract WikiAccountAuthenticatorService bindWikiAccountAuthenticatorService();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
index d4a19c9fc..45a1a4181 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
@@ -19,7 +19,6 @@ import javax.inject.Singleton;
import timber.log.Timber;
-@Singleton
public class LocationServiceManager implements LocationListener {
public static final int LOCATION_REQUEST = 1;
@@ -32,7 +31,6 @@ public class LocationServiceManager implements LocationListener {
private final List locationListeners = new CopyOnWriteArrayList<>();
private boolean isLocationManagerRegistered = false;
- @Inject
public LocationServiceManager(Context context) {
this.context = context;
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index fe68203ec..718ee4f58 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -6,7 +6,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -22,6 +21,10 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.License;
import fr.free.nrw.commons.LicenseList;
import fr.free.nrw.commons.Media;
@@ -30,10 +33,11 @@ import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.ui.widget.CompatTextView;
import timber.log.Timber;
-public class MediaDetailFragment extends Fragment {
+public class MediaDetailFragment extends DaggerFragment {
private boolean editable;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@@ -53,6 +57,9 @@ public class MediaDetailFragment extends Fragment {
return mf;
}
+ @Inject
+ Provider mediaDataExtractorProvider;
+
private MediaWikiImageView image;
private MediaDetailSpacer spacer;
private int initialListTop = 0;
@@ -69,7 +76,7 @@ public class MediaDetailFragment extends Fragment {
private boolean categoriesPresent = false;
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnScrollChangedListener scrollListener;
- DataSetObserver dataObserver;
+ private DataSetObserver dataObserver;
private AsyncTask detailFetchTask;
private LicenseList licenseList;
@@ -188,13 +195,13 @@ public class MediaDetailFragment extends Fragment {
@Override
protected void onPreExecute() {
- extractor = new MediaDataExtractor(media.getFilename(), licenseList);
+ extractor = mediaDataExtractorProvider.get();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
- extractor.fetch();
+ extractor.fetch(media.getFilename(), licenseList);
return Boolean.TRUE;
} catch (IOException e) {
Timber.d(e);
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
index 3008632d9..85d598b9d 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.media;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
@@ -24,12 +25,18 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
@@ -37,7 +44,11 @@ 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 class MediaDetailPagerFragment extends DaggerFragment implements ViewPager.OnPageChangeListener {
+
+ @Inject MediaWikiApi mwApi;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
private ViewPager pager;
private Boolean editable;
@@ -101,8 +112,8 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
case R.id.menu_share_current_image:
// Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252
CommonsApplication app = (CommonsApplication) getActivity().getApplication();
- EventLog.schema(EVENT_SHARE_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(EVENT_SHARE_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("filename", m.getFilename())
.log();
return true;
@@ -161,9 +172,7 @@ 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(),
- READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
+ 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(),
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
index 0f0a81316..6dc993c9e 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
@@ -10,7 +10,10 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjection;
+import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class ModificationsContentProvider extends ContentProvider {
@@ -33,10 +36,12 @@ public class ModificationsContentProvider extends ContentProvider {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
+ @Inject DBOpenHelper dbOpenHelper;
@Override
public boolean onCreate() {
- return false;
+ AndroidInjection.inject(this);
+ return true;
}
@Override
@@ -53,7 +58,7 @@ public class ModificationsContentProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI" + uri);
}
- SQLiteDatabase db = CommonsApplication.getInstance().getDBOpenHelper().getReadableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
@@ -69,7 +74,7 @@ public class ModificationsContentProvider extends ContentProvider {
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = 0;
switch (uriType) {
case MODIFICATIONS:
@@ -85,7 +90,7 @@ public class ModificationsContentProvider extends ContentProvider {
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
switch (uriType) {
case MODIFICATIONS_ID:
String id = uri.getLastPathSegment();
@@ -103,7 +108,7 @@ public class ModificationsContentProvider extends ContentProvider {
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ModificationsContentProvider)");
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case MODIFICATIONS:
@@ -131,7 +136,7 @@ public class ModificationsContentProvider extends ContentProvider {
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();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case MODIFICATIONS:
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
index f2940ec74..1e886f225 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
@@ -14,8 +14,9 @@ import android.os.RemoteException;
import java.io.IOException;
+import javax.inject.Inject;
+
import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@@ -23,6 +24,8 @@ import timber.log.Timber;
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
+ @Inject MediaWikiApi mwApi;
+
public ModificationsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@@ -30,6 +33,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
+ ((CommonsApplication)getContext().getApplicationContext()).injector().inject(this);
Cursor allModifications;
try {
@@ -54,17 +58,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
- if (Utils.isNullOrWhiteSpace(authCookie)) {
+ if (isNullOrWhiteSpace(authCookie)) {
Timber.d("Could not authenticate :(");
return;
}
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- api.setAuthCookie(authCookie);
+ mwApi.setAuthCookie(authCookie);
String editToken;
try {
- editToken = api.getEditToken();
+ editToken = mwApi.getEditToken();
} catch (IOException e) {
Timber.d("Can not retreive edit token!");
return;
@@ -95,7 +98,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
if (contrib.getState() == Contribution.STATE_COMPLETED) {
String pageContent;
try {
- pageContent = api.revisionsByFilename(contrib.getFilename());
+ pageContent = mwApi.revisionsByFilename(contrib.getFilename());
} catch (IOException e) {
Timber.d("Network fuckup on modifications sync!");
continue;
@@ -106,7 +109,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
String editResult;
try {
- editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
+ editResult = mwApi.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
} catch (IOException e) {
Timber.d("Network fuckup on modifications sync!");
continue;
@@ -129,4 +132,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
}
}
}
+
+ private boolean isNullOrWhiteSpace(String value) {
+ return value == null || value.trim().isEmpty();
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
index 18e89f072..19425a0c3 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
@@ -25,6 +25,8 @@ import org.mediawiki.api.MWApi;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -34,7 +36,6 @@ import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
-import fr.free.nrw.commons.Utils;
import in.yuvi.http.fluent.Http;
import io.reactivex.Observable;
import io.reactivex.Single;
@@ -335,7 +336,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
logEvents.add(new LogEventResult.LogEvent(
image.getString("@pageid"),
image.getString("@title"),
- Utils.parseMWDate(image.getString("@timestamp")))
+ parseMWDate(image.getString("@timestamp")))
);
}
return logEvents;
@@ -402,7 +403,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String errorCode = result.getString("/api/error/@code");
return new UploadResult(resultStatus, errorCode);
} else {
- Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
+ Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
String imageUrl = result.getString("/api/upload/imageinfo/@url");
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
@@ -428,4 +429,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return Integer.parseInt(uploadCount);
});
}
+
+ private Date parseMWDate(String mwDate) {
+ SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
+ try {
+ return isoFormat.parse(mwDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java b/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
index d3ba7c0d5..4446da738 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.mwapi;
+import android.content.SharedPreferences;
import android.os.Build;
import fr.free.nrw.commons.Utils;
@@ -15,14 +16,14 @@ public class EventLog {
}
}
- private static LogBuilder schema(String schema, long revision) {
- return new LogBuilder(schema, revision);
+ private static LogBuilder schema(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
+ return new LogBuilder(schema, revision, mwApi, prefs);
}
- public static LogBuilder schema(Object[] scid) {
+ public static LogBuilder schema(Object[] scid, MediaWikiApi mwApi, SharedPreferences prefs) {
if (scid.length != 2) {
throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second");
}
- return schema((String) scid[0], (Long) scid[1]);
+ return schema((String) scid[0], (Long) scid[1], mwApi, prefs);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java b/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
index eabbbf82e..7a4b294fa 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
@@ -3,7 +3,6 @@ package fr.free.nrw.commons.mwapi;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
-import android.preference.PreferenceManager;
import org.json.JSONException;
import org.json.JSONObject;
@@ -12,19 +11,23 @@ import java.net.MalformedURLException;
import java.net.URL;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
+@SuppressWarnings("WeakerAccess")
public class LogBuilder {
- private JSONObject data;
- private long rev;
- private String schema;
+ private final MediaWikiApi mwApi;
+ private final JSONObject data;
+ private final long rev;
+ private final String schema;
+ private final SharedPreferences prefs;
- LogBuilder(String schema, long revision) {
- data = new JSONObject();
+ LogBuilder(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
+ this.prefs = prefs;
+ this.data = new JSONObject();
this.schema = schema;
this.rev = revision;
+ this.mwApi = mwApi;
}
public LogBuilder param(String key, Object value) {
@@ -56,11 +59,10 @@ public class LogBuilder {
// Use *only* for tracking the user preference change for EventLogging
// Attempting to use anywhere else will cause kitten explosions
public void log(boolean force) {
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
- if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
+ if (!prefs.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
return; // User has disabled tracking
}
- LogTask logTask = new LogTask();
+ LogTask logTask = new LogTask(mwApi);
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java b/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
index ee947afbc..3fce8aaf1 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
@@ -2,11 +2,16 @@ package fr.free.nrw.commons.mwapi;
import android.os.AsyncTask;
-import fr.free.nrw.commons.CommonsApplication;
-
class LogTask extends AsyncTask {
+
+ private final MediaWikiApi mwApi;
+
+ public LogTask(MediaWikiApi mwApi) {
+ this.mwApi = mwApi;
+ }
+
@Override
protected Boolean doInBackground(LogBuilder... logBuilders) {
- return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders);
+ return mwApi.logEvents(logBuilders);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
index f018d1190..57d21dd95 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
@@ -28,8 +28,6 @@ import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
-import dagger.android.AndroidInjection;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
@@ -48,12 +46,17 @@ import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUE
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
+ private static final int LOCATION_REQUEST = 1;
+ private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
+
@BindView(R.id.progressBar)
ProgressBar progressBar;
- private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
@Inject
LocationServiceManager locationManager;
+ @Inject
+ NearbyController nearbyController;
+
private LatLng curLatLang;
private Bundle bundle;
private SharedPreferences sharedPreferences;
@@ -64,7 +67,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- AndroidInjection.inject(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_nearby);
ButterKnife.bind(this);
@@ -279,20 +281,14 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
}
progressBar.setVisibility(View.VISIBLE);
- setupPlaceList(this);
- }
-
- private void setupPlaceList(Context context) {
- placesDisposable = Observable.fromCallable(() -> NearbyController
- .loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance()))
+ placesDisposable = Observable.fromCallable(() -> nearbyController
+ .loadAttractionsFromLocation(curLatLang, this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe((result) -> {
- populatePlaces(context, result);
- });
+ .subscribe(this::populatePlaces);
}
- private void populatePlaces(Context context, List placeList) {
+ private void populatePlaces(List placeList) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
@@ -301,7 +297,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
if (placeList.size() == 0) {
int duration = Toast.LENGTH_SHORT;
- Toast toast = Toast.makeText(context, R.string.no_nearby, duration);
+ Toast toast = Toast.makeText(this, R.string.no_nearby, duration);
toast.show();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
index 58c8bb8a7..624a0de0f 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
@@ -3,7 +3,6 @@ package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
-import android.preference.PreferenceManager;
import android.support.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory;
@@ -15,7 +14,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UiUtils;
@@ -24,9 +25,17 @@ import timber.log.Timber;
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
-
public class NearbyController {
private static final int MAX_RESULTS = 1000;
+ private final NearbyPlaces nearbyPlaces;
+ private final SharedPreferences prefs;
+
+ @Inject
+ public NearbyController(NearbyPlaces nearbyPlaces,
+ @Named("default_preferences") SharedPreferences prefs) {
+ this.nearbyPlaces = nearbyPlaces;
+ this.prefs = prefs;
+ }
/**
* Prepares Place list to make their distance information update later.
@@ -34,13 +43,11 @@ public class NearbyController {
* @param context context
* @return Place list without distance information
*/
- public static List loadAttractionsFromLocation(LatLng curLatLng, Context context) {
+ public List loadAttractionsFromLocation(LatLng curLatLng, Context context) {
Timber.d("Loading attractions near %s", curLatLng);
if (curLatLng == null) {
return Collections.emptyList();
}
- NearbyPlaces nearbyPlaces = CommonsApplication.getInstance().getNearbyPlaces();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
List places = prefs.getBoolean("useWikidata", true)
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
: nearbyPlaces.getFromWikiNeedsPictures();
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
index 00b8a2840..817571668 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
@@ -2,7 +2,6 @@ package fr.free.nrw.commons.nearby;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -17,12 +16,13 @@ import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber;
-public class NearbyListFragment extends Fragment {
+public class NearbyListFragment extends DaggerFragment {
private static final Type LIST_TYPE = new TypeToken>() {
}.getType();
private static final Type CUR_LAT_LNG_TYPE = new TypeToken() {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
index 5065a7d93..ca0ae0a89 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
@@ -1,19 +1,19 @@
package fr.free.nrw.commons.nearby;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import butterknife.ButterKnife;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import timber.log.Timber;
/**
* Tells user that Nearby Places cannot be displayed if location permissions are denied
*/
-public class NoPermissionsFragment extends Fragment {
+public class NoPermissionsFragment extends DaggerFragment {
public NoPermissionsFragment() {
}
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
index 4b46ff401..d731c70d1 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
@@ -1,7 +1,5 @@
package fr.free.nrw.commons.settings;
-import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
index f4398d4e2..2809eb0ab 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
@@ -15,15 +15,17 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.widget.Toast;
import java.io.File;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
@@ -34,8 +36,11 @@ public class SettingsFragment extends PreferenceFragment {
private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 100;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
@Override
public void onCreate(Bundle savedInstanceState) {
+ AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
@@ -58,14 +63,12 @@ public class SettingsFragment extends PreferenceFragment {
});
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
- final SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
+ int uploads = prefs.getInt(Prefs.UPLOADS_SHOWING, 100);
uploadLimit.setText(uploads + "");
uploadLimit.setSummary(uploads + "");
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
int value = Integer.parseInt(newValue.toString());
- final SharedPreferences.Editor editor = sharedPref.edit();
+ final SharedPreferences.Editor editor = prefs.edit();
if (value > 500) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.maximum_limit)
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
index 086b97359..f08496914 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
@@ -3,18 +3,17 @@ package fr.free.nrw.commons.theme;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v7.app.AppCompatActivity;
+import dagger.android.support.DaggerAppCompatActivity;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.Utils;
-public class BaseActivity extends AppCompatActivity {
+public abstract class BaseActivity extends DaggerAppCompatActivity {
boolean currentTheme;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
- if (Utils.isDarkTheme(this)) {
+ boolean currentThemeIsDark = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
+ if (currentThemeIsDark){
currentTheme = true;
setTheme(R.style.DarkAppTheme);
} else {
@@ -27,7 +26,7 @@ public class BaseActivity extends AppCompatActivity {
@Override
protected void onResume() {
// Restart activity if theme is changed
- boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false);
+ boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
if (currentTheme != newTheme) { //is activity theme changed
Intent intent = getIntent();
finish();
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
index c27182f67..60ea325e4 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
@@ -62,10 +62,10 @@ public abstract class NavigationBaseActivity extends BaseActivity
private void setUserName() {
View navHeaderView = navigationView.getHeaderView(0);
- TextView username = (TextView) navHeaderView.findViewById(R.id.username);
+ TextView username = navHeaderView.findViewById(R.id.username);
AccountManager accountManager = AccountManager.get(this);
- Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
+ Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
if (allAccounts.length != 0) {
username.setText(allAccounts[0].name);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
index 5afa5cac3..e7f5eaeeb 100644
--- a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
+++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
@@ -1,12 +1,13 @@
package fr.free.nrw.commons.ui.widget;
import android.content.Context;
+import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
+import android.text.Html;
+import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
-import fr.free.nrw.commons.Utils;
-
/**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable.
@@ -17,10 +18,25 @@ public class HtmlTextView extends AppCompatTextView {
super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance());
- setText(Utils.fromHtml(getText().toString()));
+ setText(fromHtml(getText().toString()));
}
public void setHtmlText(String newText) {
- setText(Utils.fromHtml(newText));
+ setText(fromHtml(newText));
+ }
+
+ /**
+ * Fix Html.fromHtml is deprecated problem
+ *
+ * @param source provided Html string
+ * @return returned Spanned of appropriate method according to version check
+ */
+ private static Spanned fromHtml(String source) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
+ } else {
+ //noinspection deprecation
+ return Html.fromHtml(source);
+ }
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
index b76150643..fee0765a4 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
@@ -7,7 +7,6 @@ import android.support.v7.app.AlertDialog;
import java.io.IOException;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@@ -18,6 +17,7 @@ import timber.log.Timber;
* Displays a warning to the user if the file already exists on Commons
*/
public class ExistingFileAsync extends AsyncTask {
+
interface Callback {
void onResult(Result result);
}
@@ -28,14 +28,16 @@ public class ExistingFileAsync extends AsyncTask {
DUPLICATE_CANCELLED
}
+ private final MediaWikiApi api;
private final String fileSha1;
private final Context context;
private final Callback callback;
- public ExistingFileAsync(String fileSha1, Context context, Callback callback) {
+ public ExistingFileAsync(String fileSha1, Context context, Callback callback, MediaWikiApi mwApi) {
this.fileSha1 = fileSha1;
this.context = context;
this.callback = callback;
+ this.api = mwApi;
}
@Override
@@ -45,7 +47,6 @@ public class ExistingFileAsync extends AsyncTask {
@Override
protected Boolean doInBackground(Void... voids) {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
boolean fileExists;
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
index d9d369b65..404177032 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
@@ -8,7 +8,6 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.media.ExifInterface;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@@ -16,7 +15,6 @@ import android.support.annotation.RequiresApi;
import java.io.FileDescriptor;
import java.io.IOException;
-import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/**
@@ -26,6 +24,8 @@ import timber.log.Timber;
*/
public class GPSExtractor {
+ private final Context context;
+ private SharedPreferences prefs;
private ExifInterface exif;
private double decLatitude;
private double decLongitude;
@@ -38,9 +38,12 @@ public class GPSExtractor {
/**
* Construct from the file descriptor of the image (only for API 24 or newer).
* @param fileDescriptor the file descriptor of the image
+ * @param context the context
*/
@RequiresApi(24)
- public GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
+ public GPSExtractor(@NonNull FileDescriptor fileDescriptor, Context context, SharedPreferences prefs) {
+ this.context = context;
+ this.prefs = prefs;
try {
exif = new ExifInterface(fileDescriptor);
} catch (IOException | IllegalArgumentException e) {
@@ -51,13 +54,16 @@ public class GPSExtractor {
/**
* Construct from the file path of the image.
* @param path file path of the image
+ * @param context the context
*/
- public GPSExtractor(@NonNull String path) {
+ public GPSExtractor(@NonNull String path, Context context, SharedPreferences prefs) {
+ this.prefs = prefs;
try {
exif = new ExifInterface(path);
} catch (IOException | IllegalArgumentException e) {
Timber.w(e);
}
+ this.context = context;
}
/**
@@ -65,9 +71,7 @@ public class GPSExtractor {
* @return true if enabled, false if disabled
*/
private boolean gpsPreferenceEnabled() {
- SharedPreferences sharedPref
- = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean gpsPref = sharedPref.getBoolean("allowGps", false);
+ boolean gpsPref = prefs.getBoolean("allowGps", false);
Timber.d("Gps pref set to: %b", gpsPref);
return gpsPref;
}
@@ -76,8 +80,7 @@ public class GPSExtractor {
* Registers a LocationManager to listen for current location
*/
protected void registerLocationManager() {
- locationManager = (LocationManager) CommonsApplication.getInstance()
- .getSystemService(Context.LOCATION_SERVICE);
+ locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, true);
myLocationListener = new MyLocationListener();
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
index abeae0507..184778fcc 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
@@ -6,6 +6,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.net.Uri;
@@ -24,11 +25,15 @@ import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution;
@@ -38,24 +43,27 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
-public class MultipleShareActivity
- extends AuthenticatedActivity
- implements MediaDetailPagerFragment.MediaDetailProvider,
- AdapterView.OnItemClickListener,
- FragmentManager.OnBackStackChangedListener,
- MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
+public class MultipleShareActivity extends AuthenticatedActivity
+ implements MediaDetailPagerFragment.MediaDetailProvider,
+ AdapterView.OnItemClickListener,
+ FragmentManager.OnBackStackChangedListener,
+ MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
OnCategoriesSaveHandler {
- private CommonsApplication app;
+
+ @Inject MediaWikiApi mwApi;
+ @Inject SessionManager sessionManager;
+ @Inject UploadController uploadController;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
private ArrayList photosList = null;
private MultipleUploadListFragment uploadsList;
private MediaDetailPagerFragment mediaDetails;
private CategorizationFragment categorizationFragment;
- private UploadController uploadController;
-
@Override
public Media getMediaAtPosition(int i) {
return photosList.get(i);
@@ -132,11 +140,7 @@ public class MultipleShareActivity
dialog.setProgress(uploadCount);
if (uploadCount == photosList.size()) {
dialog.dismiss();
- Toast startingToast = Toast.makeText(
- CommonsApplication.getInstance(),
- R.string.uploading_started,
- Toast.LENGTH_LONG
- );
+ Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
}
});
@@ -163,8 +167,8 @@ public class MultipleShareActivity
@Override
public void onCategoriesSave(List categories) {
if (categories.size() > 0) {
- ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
- for (Contribution contribution: photosList) {
+ ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
+ for (Contribution contribution : photosList) {
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
@@ -176,9 +180,9 @@ public class MultipleShareActivity
}
// FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
- ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
+ EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categories.size())
.param("files-count", photosList.size())
.param("source", Contribution.SOURCE_EXTERNAL)
@@ -202,10 +206,8 @@ public class MultipleShareActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- uploadController = new UploadController();
setContentView(R.layout.activity_multiple_uploads);
- app = CommonsApplication.getInstance();
ButterKnife.bind(this);
initDrawer();
@@ -245,7 +247,7 @@ public class MultipleShareActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
- app.getMWApi().setAuthCookie(authCookie);
+ mwApi.setAuthCookie(authCookie);
Intent intent = getIntent();
if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
@@ -266,7 +268,7 @@ public class MultipleShareActivity
uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList");
if (uploadsList == null) {
- uploadsList = new MultipleUploadListFragment();
+ uploadsList = new MultipleUploadListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.uploadsFragmentContainer, uploadsList, "uploadsList")
@@ -288,16 +290,16 @@ public class MultipleShareActivity
public void onBackPressed() {
super.onBackPressed();
if (categorizationFragment != null && categorizationFragment.isVisible()) {
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
.param("files-count", photosList.size())
.param("source", Contribution.SOURCE_EXTERNAL)
.param("result", "cancelled")
.log();
} else {
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
.param("multiple", true)
.param("result", "cancelled")
@@ -307,11 +309,7 @@ public class MultipleShareActivity
@Override
public void onBackStackChanged() {
- if (mediaDetails != null && mediaDetails.isVisible()) {
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- } else {
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
+ getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ;
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
index 28f88e3d6..d20d8c1eb 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
@@ -5,7 +5,6 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.support.graphics.drawable.VectorDrawableCompat;
-import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -28,11 +27,12 @@ import android.widget.TextView;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
-public class MultipleUploadListFragment extends Fragment {
+public class MultipleUploadListFragment extends DaggerFragment {
public interface OnMultipleUploadInitiatedHandler {
void OnMultipleUploadInitiated();
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java b/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
index cf1e1c2c3..a530e79e6 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload;
+import android.content.Context;
import android.net.Uri;
import com.android.volley.Cache;
@@ -20,7 +21,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/**
@@ -33,12 +33,14 @@ public class MwVolleyApi {
private static RequestQueue REQUEST_QUEUE;
private static final Gson GSON = new GsonBuilder().create();
- protected static Set categorySet;
+ private static Set categorySet;
private static List categoryList;
private static final String MWURL = "https://commons.wikimedia.org/";
+ private final Context context;
- public MwVolleyApi() {
+ public MwVolleyApi(Context context) {
+ this.context = context;
categorySet = new HashSet<>();
}
@@ -93,7 +95,7 @@ public class MwVolleyApi {
private synchronized RequestQueue getQueue() {
if (REQUEST_QUEUE == null) {
- REQUEST_QUEUE = Volley.newRequestQueue(CommonsApplication.getInstance());
+ REQUEST_QUEUE = Volley.newRequestQueue(context);
}
return REQUEST_QUEUE;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
index fa5f0d18b..218181154 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
@@ -30,14 +30,21 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution;
@@ -46,6 +53,7 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
@@ -66,7 +74,11 @@ public class ShareActivity
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
private CategorizationFragment categorizationFragment;
- private CommonsApplication app;
+ @Inject MediaWikiApi mwApi;
+ @Inject CacheController cacheController;
+ @Inject SessionManager sessionManager;
+ @Inject UploadController uploadController;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
private String source;
private String mimeType;
@@ -75,8 +87,6 @@ public class ShareActivity
private Contribution contribution;
private SimpleDraweeView backgroundImageView;
- private UploadController uploadController;
-
private boolean cacheFound;
private GPSExtractor imageObj;
@@ -117,7 +127,7 @@ public class ShareActivity
@RequiresApi(16)
private boolean needsToRequestStoragePermission() {
// We need to ask storage permission when
- // the file is not owned by this app, (e.g. shared from the Gallery)
+ // the file is not owned by this application, (e.g. shared from the Gallery)
// and permission is not obtained.
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
@@ -127,16 +137,12 @@ public class ShareActivity
private void uploadBegins() {
getFileMetadata(locationPermitted);
- Toast startingToast = Toast.makeText(
- CommonsApplication.getInstance(),
- R.string.uploading_started,
- Toast.LENGTH_LONG
- );
+ Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
if (!cacheFound) {
//Has to be called after apiCall.request()
- app.getCacheData().cacheCategory();
+ cacheController.cacheCategory();
Timber.d("Cache the categories found");
}
@@ -168,10 +174,10 @@ public class ShareActivity
// FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
- ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
+ ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categories.size())
.param("files-count", 1)
.param("source", contribution.getSource())
@@ -192,16 +198,16 @@ public class ShareActivity
public void onBackPressed() {
super.onBackPressed();
if (categorizationFragment != null && categorizationFragment.isVisible()) {
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount())
.param("files-count", 1)
.param("source", contribution.getSource())
.param("result", "cancelled")
.log();
} else {
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
.param("multiple", true)
.param("result", "cancelled")
@@ -211,8 +217,7 @@ public class ShareActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
- app.getMWApi().setAuthCookie(authCookie);
-
+ mwApi.setAuthCookie(authCookie);
}
@Override
@@ -225,11 +230,10 @@ public class ShareActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- uploadController = new UploadController();
+
setContentView(R.layout.activity_share);
ButterKnife.bind(this);
initBack();
- app = CommonsApplication.getInstance();
backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage);
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
@@ -379,7 +383,7 @@ public class ShareActivity
try {
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
Timber.d("Input stream created from %s", mediaUri.toString());
- String fileSHA1 = Utils.getSHA1(inputStream);
+ String fileSHA1 = getSHA1(inputStream);
Timber.d("File SHA1 is: %s", fileSHA1);
ExistingFileAsync fileAsyncTask =
@@ -387,7 +391,7 @@ public class ShareActivity
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|| result == NO_DUPLICATE);
- });
+ }, mwApi);
fileAsyncTask.execute();
} catch (IOException e) {
Timber.d(e, "IO Exception: ");
@@ -424,9 +428,7 @@ public class ShareActivity
ParcelFileDescriptor descriptor
= getContentResolver().openFileDescriptor(mediaUri, "r");
if (descriptor != null) {
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
+ boolean useExtStorage = prefs.getBoolean("useExternalStorage", true);
if (useExtStorage) {
copyPath = Environment.getExternalStorageDirectory().toString()
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
@@ -467,12 +469,12 @@ public class ShareActivity
= getContentResolver().openFileDescriptor(mediaUri, "r");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) {
- imageObj = new GPSExtractor(descriptor.getFileDescriptor());
+ imageObj = new GPSExtractor(descriptor.getFileDescriptor(), this, prefs);
}
} else {
String filePath = getPathOfMediaOrCopy();
if (filePath != null) {
- imageObj = new GPSExtractor(filePath);
+ imageObj = new GPSExtractor(filePath, this, prefs);
}
}
}
@@ -499,12 +501,12 @@ public class ShareActivity
if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude();
- app.getCacheData().setQtPoint(decLongitude, decLatitude);
+ cacheController.setQtPoint(decLongitude, decLatitude);
}
- MwVolleyApi apiCall = new MwVolleyApi();
+ MwVolleyApi apiCall = new MwVolleyApi(this);
- List displayCatList = app.getCacheData().findCategory();
+ List displayCatList = cacheController.findCategory();
boolean catListEmpty = displayCatList.isEmpty();
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
@@ -550,4 +552,41 @@ public class ShareActivity
}
return super.onOptionsItemSelected(item);
}
+
+ // Get SHA1 of file from input stream
+ private String getSHA1(InputStream is) {
+
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ Timber.e(e, "Exception while getting Digest");
+ return "";
+ }
+
+ byte[] buffer = new byte[8192];
+ int read;
+ try {
+ while ((read = is.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ byte[] md5sum = digest.digest();
+ BigInteger bigInt = new BigInteger(1, md5sum);
+ String output = bigInt.toString(16);
+ // Fill to 40 chars
+ output = String.format("%40s", output).replace(' ', '0');
+ Timber.i("File SHA1: %s", output);
+
+ return output;
+ } catch (IOException e) {
+ Timber.e(e, "IO Exception");
+ return "";
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Timber.e(e, "Exception on closing MD5 input stream");
+ }
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
index 579352991..259f38c36 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
@@ -7,7 +7,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v4.app.Fragment;
+import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
@@ -28,11 +28,15 @@ import android.widget.TextView;
import java.util.ArrayList;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnItemSelected;
import butterknife.OnTouch;
+import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
@@ -41,7 +45,7 @@ import timber.log.Timber;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-public class SingleUploadFragment extends Fragment {
+public class SingleUploadFragment extends DaggerFragment {
@BindView(R.id.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit;
@@ -49,7 +53,8 @@ public class SingleUploadFragment extends Fragment {
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
- private SharedPreferences prefs;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher();
@@ -72,11 +77,10 @@ public class SingleUploadFragment extends Fragment {
String desc = descEdit.getText().toString();
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- SharedPreferences.Editor editor = titleDesc.edit();
- editor.putString("Title", title);
- editor.putString("Desc", desc);
- editor.apply();
+ prefs.edit()
+ .putString("Title", title)
+ .putString("Desc", desc)
+ .apply();
uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
return true;
@@ -91,7 +95,6 @@ public class SingleUploadFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);
-
ArrayList licenseItems = new ArrayList<>();
licenseItems.add(getString(R.string.license_name_cc0));
licenseItems.add(getString(R.string.license_name_cc_by));
@@ -99,7 +102,6 @@ public class SingleUploadFragment extends Fragment {
licenseItems.add(getString(R.string.license_name_cc_by_four));
licenseItems.add(getString(R.string.license_name_cc_by_sa_four));
- prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
// check if this is the first time we have uploaded
@@ -172,9 +174,9 @@ public class SingleUploadFragment extends Fragment {
}
setLicenseSummary(license);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(Prefs.DEFAULT_LICENSE, license);
- editor.commit();
+ prefs.edit()
+ .putString(Prefs.DEFAULT_LICENSE, license)
+ .commit();
}
@OnTouch(R.id.share_license_summary)
@@ -182,7 +184,7 @@ public class SingleUploadFragment extends Fragment {
if (motionEvent.getActionMasked() == ACTION_DOWN) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(Utils.licenseUrlFor(license)));
+ intent.setData(Uri.parse(licenseUrlFor(license)));
startActivity(intent);
return true;
} else {
@@ -193,9 +195,8 @@ public class SingleUploadFragment extends Fragment {
@OnClick(R.id.titleDescButton)
void setTitleDescButton() {
//Retrieve last title and desc entered
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String title = titleDesc.getString("Title", "");
- String desc = titleDesc.getString("Desc", "");
+ String title = prefs.getString("Title", "");
+ String desc = prefs.getString("Desc", "");
Timber.d("Title: %s, Desc: %s", title, desc);
titleEdit.setText(title);
@@ -263,6 +264,23 @@ public class SingleUploadFragment extends Fragment {
}
}
+ @NonNull
+ private String licenseUrlFor(String license) {
+ switch (license) {
+ case Prefs.Licenses.CC_BY_3:
+ return "https://creativecommons.org/licenses/by/3.0/";
+ case Prefs.Licenses.CC_BY_4:
+ return "https://creativecommons.org/licenses/by/4.0/";
+ case Prefs.Licenses.CC_BY_SA_3:
+ return "https://creativecommons.org/licenses/by-sa/3.0/";
+ case Prefs.Licenses.CC_BY_SA_4:
+ return "https://creativecommons.org/licenses/by-sa/4.0/";
+ case Prefs.Licenses.CC0:
+ return "https://creativecommons.org/publicdomain/zero/1.0/";
+ }
+ throw new RuntimeException("Unrecognized license value: " + license);
+ }
+
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
index 7951be98c..1d080e78f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
@@ -1,6 +1,7 @@
package fr.free.nrw.commons.upload;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -9,31 +10,36 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
+import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Date;
import java.util.concurrent.Executors;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
-import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class UploadController {
private UploadService uploadService;
- private final CommonsApplication app;
+ private SessionManager sessionManager;
+ private Context context;
+ private SharedPreferences prefs;
public interface ContributionUploadProgress {
void onUploadStarted(Contribution contribution);
}
- public UploadController() {
- app = CommonsApplication.getInstance();
+ public UploadController(SessionManager sessionManager, Context context, SharedPreferences sharedPreferences) {
+ this.sessionManager = sessionManager;
+ this.context = context;
+ this.prefs = sharedPreferences;
}
private boolean isUploadServiceConnected;
@@ -52,15 +58,15 @@ public class UploadController {
};
public void prepareService() {
- Intent uploadServiceIntent = new Intent(app, UploadService.class);
+ Intent uploadServiceIntent = new Intent(context, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
- app.startService(uploadServiceIntent);
- app.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
+ context.startService(uploadServiceIntent);
+ context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
}
public void cleanup() {
if (isUploadServiceConnected) {
- app.unbindService(uploadServiceConnection);
+ context.unbindService(uploadServiceConnection);
}
}
@@ -68,7 +74,9 @@ public class UploadController {
Contribution contribution;
//TODO: Modify this to include coords
- contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
+ contribution = new Contribution(mediaUri, null, title, description, -1,
+ null, null, sessionManager.getCurrentAccount().name,
+ CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
@@ -78,12 +86,9 @@ public class UploadController {
}
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
-
//Set creator, desc, and license
if (TextUtils.isEmpty(contribution.getCreator())) {
- contribution.setCreator(app.getCurrentAccount().name);
+ contribution.setCreator(sessionManager.getCurrentAccount().name);
}
if (contribution.getDescription() == null) {
@@ -102,14 +107,15 @@ public class UploadController {
@Override
protected Contribution doInBackground(Void... voids /* stare into you */) {
long length;
+ ContentResolver contentResolver = context.getContentResolver();
try {
if (contribution.getDataLength() <= 0) {
- length = app.getContentResolver()
+ length = contentResolver
.openAssetFileDescriptor(contribution.getLocalUri(), "r")
.getLength();
if (length == -1) {
// Let us find out the long way!
- length = Utils.countBytes(app.getContentResolver()
+ length = countBytes(contentResolver
.openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
@@ -126,7 +132,7 @@ public class UploadController {
Boolean imagePrefix = false;
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
- mimeType = app.getContentResolver().getType(contribution.getLocalUri());
+ mimeType = contentResolver.getType(contribution.getLocalUri());
}
if (mimeType != null) {
@@ -137,7 +143,7 @@ public class UploadController {
if (imagePrefix && contribution.getDateCreated() == null) {
Timber.d("local uri " + contribution.getLocalUri());
- Cursor cursor = app.getContentResolver().query(contribution.getLocalUri(),
+ Cursor cursor = contentResolver.query(contribution.getLocalUri(),
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
cursor.moveToFirst();
@@ -165,4 +171,14 @@ public class UploadController {
}
}.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy
}
+
+
+ private long countBytes(InputStream stream) throws IOException {
+ long count = 0;
+ BufferedInputStream bis = new BufferedInputStream(stream);
+ while (bis.read() != -1) {
+ count++;
+ }
+ return count;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
index 89b614fd6..215819d12 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
@@ -8,6 +8,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
@@ -22,10 +23,14 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
@@ -45,12 +50,13 @@ public class UploadService extends HandlerService {
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
+ @Inject MediaWikiApi mwApi;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
private NotificationManager notificationManager;
private ContentProviderClient contributionsProviderClient;
- private CommonsApplication app;
-
private NotificationCompat.Builder curProgressNotification;
-
private int toUpload;
// The file names of unfinished uploads, used to prevent overwriting
@@ -118,7 +124,6 @@ public class UploadService extends HandlerService {
super.onCreate();
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- app = CommonsApplication.getInstance();
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
}
@@ -180,8 +185,6 @@ public class UploadService extends HandlerService {
@SuppressLint("StringFormatInvalid")
private void uploadContribution(Contribution contribution) {
- MediaWikiApi api = app.getMWApi();
-
InputStream file;
String notificationTag = contribution.getLocalUri().toString();
@@ -228,9 +231,9 @@ public class UploadService extends HandlerService {
filename = findUniqueFilename(filename);
unfinishedUploads.add(filename);
}
- if (!api.validateLogin()) {
+ if (!mwApi.validateLogin()) {
// Need to revalidate!
- if (app.revalidateAuthToken()) {
+ if (sessionManager.revalidateAuthToken()) {
Timber.d("Successfully revalidated token!");
} else {
Timber.d("Unable to revalidate :(");
@@ -246,7 +249,7 @@ public class UploadService extends HandlerService {
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
contribution
);
- UploadResult uploadResult = api.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
+ UploadResult uploadResult = mwApi.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
Timber.d("Response is %s", uploadResult.toString());
@@ -255,8 +258,8 @@ public class UploadService extends HandlerService {
String resultStatus = uploadResult.getResultStatus();
if (!resultStatus.equals("Success")) {
showFailedNotification(contribution);
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("source", contribution.getSource())
.param("multiple", contribution.getMultiple())
.param("result", uploadResult.getErrorCode())
@@ -269,8 +272,8 @@ public class UploadService extends HandlerService {
contribution.setDateUploaded(uploadResult.getDateUploaded());
contribution.save();
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
+ EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
+ .param("username", sessionManager.getCurrentAccount().name)
.param("source", contribution.getSource()) //FIXME
.param("filename", contribution.getFilename())
.param("multiple", contribution.getMultiple())
@@ -287,7 +290,7 @@ public class UploadService extends HandlerService {
toUpload--;
if (toUpload == 0) {
// Sync modifications right after all uplaods are processed
- ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
+ ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
stopForeground(true);
}
}
@@ -310,7 +313,6 @@ public class UploadService extends HandlerService {
}
private String findUniqueFilename(String fileName) throws IOException {
- MediaWikiApi api = app.getMWApi();
String sequenceFileName;
for (int sequenceNumber = 1; true; sequenceNumber++) {
if (sequenceNumber == 1) {
@@ -326,7 +328,7 @@ public class UploadService extends HandlerService {
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
}
}
- if (!api.fileExistsWithName(sequenceFileName)
+ if (!mwApi.fileExistsWithName(sequenceFileName)
&& !unfinishedUploads.contains(sequenceFileName)) {
break;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
index 6c3fc9a90..4191f9d6f 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
@@ -7,7 +7,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.OutputStream;
import java.io.OutputStreamWriter;
import fr.free.nrw.commons.CommonsApplication;
diff --git a/app/src/test/java/fr/free/nrw/commons/TestCommonsApplication.java b/app/src/test/java/fr/free/nrw/commons/TestCommonsApplication.java
index f9bad405a..d6c35ab02 100644
--- a/app/src/test/java/fr/free/nrw/commons/TestCommonsApplication.java
+++ b/app/src/test/java/fr/free/nrw/commons/TestCommonsApplication.java
@@ -1,25 +1,60 @@
package fr.free.nrw.commons;
-import android.app.Activity;
+import android.content.SharedPreferences;
+import android.support.v4.util.LruCache;
import com.squareup.leakcanary.RefWatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.android.AndroidInjector;
+import fr.free.nrw.commons.auth.AccountUtil;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.di.CommonsApplicationComponent;
+import fr.free.nrw.commons.di.CommonsApplicationModule;
+import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent;
+import fr.free.nrw.commons.location.LocationServiceManager;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.nearby.NearbyPlaces;
+import fr.free.nrw.commons.upload.UploadController;
public class TestCommonsApplication extends CommonsApplication {
+
+ CommonsApplicationComponent mockApplicationComponent;
+
@Mock
- private AndroidInjector mockInjector;
+ CommonsApplicationModule commonsApplicationModule;
@Mock
- private NearbyPlaces mockNearbyPlaces;
+ AccountUtil accountUtil;
+ @Mock
+ SharedPreferences appSharedPreferences;
+ @Mock
+ SharedPreferences defaultSharedPreferences;
+ @Mock
+ SharedPreferences otherSharedPreferences;
+ @Mock
+ UploadController uploadController;
+ @Mock
+ SessionManager sessionManager;
+ @Mock
+ MediaWikiApi mediaWikiApi;
+ @Mock
+ LocationServiceManager locationServiceManager;
+ @Mock
+ CacheController cacheController;
+ @Mock
+ DBOpenHelper dbOpenHelper;
+ @Mock
+ NearbyPlaces nearbyPlaces;
+ @Mock
+ LruCache lruCache;
@Override
public void onCreate() {
- super.onCreate();
MockitoAnnotations.initMocks(this);
+ super.onCreate();
}
@Override
@@ -29,12 +64,119 @@ public class TestCommonsApplication extends CommonsApplication {
}
@Override
- public AndroidInjector activityInjector() {
- return mockInjector;
+ public CommonsApplicationComponent injector() {
+ if (mockApplicationComponent == null) {
+ mockApplicationComponent = DaggerCommonsApplicationComponent.builder()
+ .appModule(new CommonsApplicationModule(this) {
+ @Override
+ public AccountUtil providesAccountUtil() {
+ return accountUtil;
+ }
+
+ @Override
+ public SharedPreferences providesApplicationSharedPreferences() {
+ return appSharedPreferences;
+ }
+
+ @Override
+ public SharedPreferences providesDefaultSharedPreferences() {
+ return defaultSharedPreferences;
+ }
+
+ @Override
+ public SharedPreferences providesOtherSharedPreferences() {
+ return otherSharedPreferences;
+ }
+
+ @Override
+ public UploadController providesUploadController(SessionManager sessionManager, SharedPreferences sharedPreferences) {
+ return uploadController;
+ }
+
+ @Override
+ public SessionManager providesSessionManager(MediaWikiApi mediaWikiApi) {
+ return sessionManager;
+ }
+
+ @Override
+ public MediaWikiApi provideMediaWikiApi() {
+ return mediaWikiApi;
+ }
+
+ @Override
+ public LocationServiceManager provideLocationServiceManager() {
+ return locationServiceManager;
+ }
+
+ @Override
+ public CacheController provideCacheController() {
+ return cacheController;
+ }
+
+ @Override
+ public DBOpenHelper provideDBOpenHelper() {
+ return dbOpenHelper;
+ }
+
+ @Override
+ public NearbyPlaces provideNearbyPlaces() {
+ return nearbyPlaces;
+ }
+
+ @Override
+ public LruCache provideLruCache() {
+ return lruCache;
+ }
+ }).build();
+ }
+ return mockApplicationComponent;
}
- @Override
- public synchronized NearbyPlaces getNearbyPlaces() {
- return mockNearbyPlaces;
+ public AccountUtil getAccountUtil() {
+ return accountUtil;
+ }
+
+ public SharedPreferences getAppSharedPreferences() {
+ return appSharedPreferences;
+ }
+
+ public SharedPreferences getDefaultSharedPreferences() {
+ return defaultSharedPreferences;
+ }
+
+ public SharedPreferences getOtherSharedPreferences() {
+ return otherSharedPreferences;
+ }
+
+ public UploadController getUploadController() {
+ return uploadController;
+ }
+
+ public SessionManager getSessionManager() {
+ return sessionManager;
+ }
+
+ public MediaWikiApi getMediaWikiApi() {
+ return mediaWikiApi;
+ }
+
+ public LocationServiceManager getLocationServiceManager() {
+ return locationServiceManager;
+ }
+
+ public CacheController getCacheController() {
+ return cacheController;
+ }
+
+ public DBOpenHelper getDbOpenHelper() {
+ return dbOpenHelper;
+ }
+
+ public NearbyPlaces getNearbyPlaces() {
+ return nearbyPlaces;
+ }
+
+ public LruCache getLruCache() {
+ return lruCache;
}
}
diff --git a/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java b/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java
index 7a0f50119..eb1cff52b 100644
--- a/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java
+++ b/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java
@@ -1,12 +1,9 @@
package fr.free.nrw.commons.nearby;
import android.app.Activity;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
@@ -14,14 +11,11 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
-import dagger.android.AndroidInjector;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.TestCommonsApplication;
import fr.free.nrw.commons.location.LatLng;
-import fr.free.nrw.commons.location.LocationServiceManager;
import static junit.framework.Assert.assertNotNull;
-import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@@ -30,9 +24,6 @@ public class NearbyActivityTest {
private static final LatLng ST_LOUIS_MO_LAT_LNG = new LatLng(38.627003, -90.199402, 0);
- @Mock
- LocationServiceManager mockLocationManager;
-
private ActivityController activityController;
private NearbyActivity nearbyActivity;
@@ -40,13 +31,11 @@ public class NearbyActivityTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- AndroidInjector injector = ((TestCommonsApplication) RuntimeEnvironment.application).activityInjector();
- Mockito.doNothing().when(injector).inject(isA(NearbyActivity.class));
- when(mockLocationManager.getLastLocation()).thenReturn(ST_LOUIS_MO_LAT_LNG);
+ TestCommonsApplication application = (TestCommonsApplication) RuntimeEnvironment.application;
+ when(application.getLocationServiceManager().getLastLocation()).thenReturn(ST_LOUIS_MO_LAT_LNG);
activityController = Robolectric.buildActivity(NearbyActivity.class);
nearbyActivity = activityController.get();
- nearbyActivity.locationManager = mockLocationManager;
}
@Test
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/UtilsTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/UtilsTest.kt
index 5b57b6253..7efb48c24 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/UtilsTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/UtilsTest.kt
@@ -1,9 +1,8 @@
package fr.free.nrw.commons
-import org.hamcrest.CoreMatchers.`is` as _is
-
import org.junit.Assert
import org.junit.Test
+import org.hamcrest.CoreMatchers.`is` as _is
class UtilsTest {
@Test fun `strip nothing from non-localized string`() {
diff --git a/build.gradle b/build.gradle
index 1738c6dee..6b7e5dc00 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,6 @@ buildscript {
classpath "com.android.tools.build:gradle:${project.gradleVersion}"
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.1'
- classpath 'me.tatarka:gradle-retrolambda:3.6.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/dependency-injection.md b/dependency-injection.md
new file mode 100644
index 000000000..5d3599e54
--- /dev/null
+++ b/dependency-injection.md
@@ -0,0 +1,41 @@
+## Overview
+
+At its core, dependency injection is just the principle of `"tell, dont ask"` put into practice; for instance, if a class needs to use the `MediaWikiApi`, it should be handed an instance of the classs rather than reaching out to get it. This has the effect of decoupling code, making it easier to test and reuse.
+
+## Dependency Injection in the Commons app
+
+We use Dagger 2 as our dependency injection engine. Dagger is a fully static, compile-time dependency injection framework for both Java and Android. Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions that came before it, but it does come at something of a cost in complexity.
+
+For more information about Dagger, take a look at the [Dagger user guide](https://google.github.io/dagger/users-guide.html).
+
+## Dagger configuration in the Commons app
+
+The top level `CommonsApplicationComponent` pulls together configuration for injection across the app. The most important files to understand
+
+- if you need to add a new Activity, look at `ActivityBuilderModule` and copy how injection is configured. The `BaseActivity` class will take care of the rest.
+- if you are adding a new Fragment, look at `FragmentBuilderModule`
+- if you are adding a new ContentProvider, look at `ContentProviderBuilderModule`
+- if you are adding a new Service, look at `ServiceBuilderModule`
+- other dependencies are configured in `CommonsApplicationModule`
+
+## "Provider" methods
+
+Dagger will resolve the method arguments on provider methods in a module (or the constructor arguments when annotated with `@Inject`) and build the objects accordingly - either by calling another provider method or by looking for a constructor on a class that has the `@Inject` annotation. Dagger takes care of managing singletons, just annotate with the `@Singleton` annotation. For instance,
+
+```java
+@Provides
+@Singleton
+public SessionManager providesSessionManager(MediaWikiApi mediaWikiApi) {
+ return new SessionManager(application, mediaWikiApi);
+}
+```
+
+If your code injects an interface (in this case, `MediaWikiApi`) then Dagger needs to know which concrete class to use. This comes by way of a provider method:
+
+```java
+@Provides
+@Singleton
+public MediaWikiApi provideMediaWikiApi() {
+ return new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
+}
+```
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7d2af2435..7a3265ee9 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e9e3a344e..f16d26666 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Thu Nov 02 02:18:35 IST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip