Merge pull request #956 from psh/dependency-injection

Dependency injection with Dagger
This commit is contained in:
Vivek Maskara 2017-12-04 02:26:55 +05:30 committed by GitHub
commit ffc0e0779d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 1320 additions and 966 deletions

View file

@ -68,6 +68,11 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1' debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op: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' 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 { android {

View file

@ -32,22 +32,25 @@
android:finishOnTaskLaunch="true" /> android:finishOnTaskLaunch="true" />
<activity <activity
android:name=".auth.LoginActivity" android:name="org.acra.CrashReportDialog"
> android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Dialog" />
<activity android:name=".auth.LoginActivity">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".WelcomeActivity" <activity android:name=".WelcomeActivity" />
>
</activity>
<activity <activity
android:name=".upload.ShareActivity" android:name=".upload.ShareActivity"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name">
>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -55,11 +58,11 @@
<data android:mimeType="audio/ogg" /> <data android:mimeType="audio/ogg" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".upload.MultipleShareActivity" android:name=".upload.MultipleShareActivity"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name">
>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -71,27 +74,28 @@
<activity <activity
android:name=".contributions.ContributionsActivity" android:name=".contributions.ContributionsActivity"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name" />
>
</activity>
<activity <activity
android:name=".settings.SettingsActivity" android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings" />
/>
<activity <activity
android:name=".AboutActivity" android:name=".AboutActivity"
android:label="@string/title_activity_about" android:label="@string/title_activity_about"
android:parentActivityName=".contributions.ContributionsActivity" /> android:parentActivityName=".contributions.ContributionsActivity" />
<activity <activity
android:name=".auth.SignupActivity" android:name=".auth.SignupActivity"
android:label="@string/title_activity_signup" /> android:label="@string/title_activity_signup" />
<activity <activity
android:name=".nearby.NearbyActivity" android:name=".nearby.NearbyActivity"
android:label="@string/title_activity_nearby" android:label="@string/title_activity_nearby"
android:parentActivityName=".contributions.ContributionsActivity" /> android:parentActivityName=".contributions.ContributionsActivity" />
<service android:name=".upload.UploadService" > <service android:name=".upload.UploadService" />
</service>
<service <service
android:name=".auth.WikiAccountAuthenticatorService" android:name=".auth.WikiAccountAuthenticatorService"
android:exported="true" android:exported="true"
@ -109,8 +113,7 @@
android:name=".contributions.ContributionsSyncService" android:name=".contributions.ContributionsSyncService"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action <action android:name="android.content.SyncAdapter" />
android:name="android.content.SyncAdapter" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
@ -121,8 +124,7 @@
android:name=".modifications.ModificationsSyncService" android:name=".modifications.ModificationsSyncService"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action <action android:name="android.content.SyncAdapter" />
android:name="android.content.SyncAdapter" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
@ -141,26 +143,24 @@
<provider <provider
android:name=".contributions.ContributionsContentProvider" android:name=".contributions.ContributionsContentProvider"
android:label="@string/provider_contributions"
android:syncable="true"
android:authorities="fr.free.nrw.commons.contributions.contentprovider" android:authorities="fr.free.nrw.commons.contributions.contentprovider"
android:exported="false"> android:exported="false"
</provider> android:label="@string/provider_contributions"
android:syncable="true" />
<provider <provider
android:name=".modifications.ModificationsContentProvider" android:name=".modifications.ModificationsContentProvider"
android:label="@string/provider_modifications"
android:syncable="true"
android:authorities="fr.free.nrw.commons.modifications.contentprovider" android:authorities="fr.free.nrw.commons.modifications.contentprovider"
android:exported="false"> android:exported="false"
</provider> android:label="@string/provider_modifications"
android:syncable="true" />
<provider <provider
android:name=".category.CategoryContentProvider" android:name=".category.CategoryContentProvider"
android:label="@string/provider_categories"
android:syncable="false"
android:authorities="fr.free.nrw.commons.categories.contentprovider" android:authorities="fr.free.nrw.commons.categories.contentprovider"
android:exported="false"> android:exported="false"
</provider> android:label="@string/provider_categories"
android:syncable="false" />
</application> </application>

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.widget.TextView; import android.widget.TextView;

View file

@ -1,19 +1,8 @@
package fr.free.nrw.commons; 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.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase; 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.drawee.backends.pipeline.Fresco;
import com.facebook.stetho.Stetho; import com.facebook.stetho.Stetho;
@ -25,24 +14,23 @@ import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes; import org.acra.annotation.ReportsCrashes;
import java.io.File; import java.io.File;
import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import dagger.android.AndroidInjector; import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector; import dagger.android.DaggerApplication;
import dagger.android.HasActivityInjector; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.data.Category; import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.data.DBOpenHelper; 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.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 fr.free.nrw.commons.utils.FileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
// TODO: Use ProGuard to rip out reporting when publishing // TODO: Use ProGuard to rip out reporting when publishing
@ -54,9 +42,13 @@ import timber.log.Timber;
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt, resDialogCommentPrompt = R.string.crash_dialog_comment_prompt,
resDialogOkToast = R.string.crash_dialog_ok_toast 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_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5334329L};
public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5257721L}; 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 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"; public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
@Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector; private CommonsApplicationComponent component;
@Inject MediaWikiApi mediaWikiApi;
private static CommonsApplication instance = null;
private MediaWikiApi api = null;
private LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
private CacheController cacheData = null;
private DBOpenHelper dbOpenHelper = null;
private NearbyPlaces nearbyPlaces = null;
private RefWatcher refWatcher; 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<String, String> 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 @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -137,12 +73,6 @@ public class CommonsApplication extends Application implements HasActivityInject
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
ACRA.init(this); ACRA.init(this);
} else { } else {
@ -153,9 +83,6 @@ public class CommonsApplication extends Application implements HasActivityInject
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0"); System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
Fresco.initialize(this); Fresco.initialize(this);
//For caching area -> categories
cacheData = new CacheController();
} }
protected RefWatcher setupLeakCanary() { protected RefWatcher setupLeakCanary() {
@ -170,43 +97,18 @@ public class CommonsApplication extends Application implements HasActivityInject
return application.refWatcher; return application.refWatcher;
} }
/** @Override
* @return Account|null protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
*/ return injector();
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() { public CommonsApplicationComponent injector() {
AccountManager accountManager = AccountManager.get(this); if (component == null) {
Account curAccount = getCurrentAccount(); component = DaggerCommonsApplicationComponent.builder()
.appModule(new CommonsApplicationModule(this))
if (curAccount == null) { .build();
return false; // This should never happen
} }
return component;
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;
}
}
public boolean deviceHasCamera() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|| pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
} }
public void clearApplicationData(Context context, LogoutListener logoutListener) { public void clearApplicationData(Context context, LogoutListener logoutListener) {
@ -221,67 +123,25 @@ public class CommonsApplication extends Application implements HasActivityInject
} }
} }
AccountManager accountManager = AccountManager.get(this); sessionManager.clearAllAccounts()
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType()); .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
AccountManagerCallback<Boolean> amCallback = new AccountManagerCallback<Boolean>() { .subscribe(() -> {
private int index = 0;
void setIndex(int index) {
this.index = index;
}
int getIndex() {
return index;
}
@Override
public void run(AccountManagerFuture<Boolean> 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) {
Timber.d("All accounts have been removed"); Timber.d("All accounts have been removed");
//TODO: fix preference manager //TODO: fix preference manager
PreferenceManager.getDefaultSharedPreferences(getInstance()) defaultPrefs.edit().clear().commit();
.edit().clear().commit(); applicationPrefs.edit().clear().commit();
SharedPreferences preferences = context applicationPrefs.edit().putBoolean("firstrun", false).apply();otherPrefs.edit().clear().commit();
.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();
updateAllDatabases(); updateAllDatabases();
currentAccount = null;
logoutListener.onLogoutComplete(); logoutListener.onLogoutComplete();
} });
}
};
for (Account account : allAccounts) {
accountManager.removeAccount(account, amCallback, null);
}
}
@Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
} }
/** /**
* Deletes all tables and re-creates them. * Deletes all tables and re-creates them.
*/ */
public void updateAllDatabases() { private void updateAllDatabases() {
DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
dbOpenHelper.getReadableDatabase().close(); dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.Binder; import android.os.Binder;
import android.os.Handler; import android.os.Handler;
@ -9,7 +8,9 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
public abstract class HandlerService<T> extends Service { import dagger.android.DaggerService;
public abstract class HandlerService<T> extends DaggerService {
private volatile Looper threadLooper; private volatile Looper threadLooper;
private volatile ServiceHandler threadHandler; private volatile ServiceHandler threadHandler;
private String serviceName; private String serviceName;

View file

@ -5,7 +5,9 @@ import android.content.res.Resources;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -19,7 +21,7 @@ public class LicenseList {
res = activity.getResources(); res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses); XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/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 id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template"); String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url"); String url = parser.getAttributeValue(null, "url");
@ -60,4 +62,34 @@ public class LicenseList {
+ nameIdForTemplate(template), null, null); + nameIdForTemplate(template), null, null);
return (nameId != 0) ? res.getString(nameId) : template; 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;
}
}
} }

View file

@ -11,12 +11,12 @@ import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; 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. * which are not intrinsic to the media and may change due to editing.
*/ */
public class MediaDataExtractor { public class MediaDataExtractor {
private final MediaWikiApi mediaWikiApi;
private boolean fetched; private boolean fetched;
private String filename;
private ArrayList<String> categories; private ArrayList<String> categories;
private Map<String, String> descriptions; private Map<String, String> descriptions;
private String license; private String license;
private @Nullable LatLng coordinates; private @Nullable LatLng coordinates;
private LicenseList licenseList;
/** @Inject
* @param filename of the target media object, should include 'File:' prefix public MediaDataExtractor(MediaWikiApi mwApi) {
*/ this.categories = new ArrayList<>();
public MediaDataExtractor(String filename, LicenseList licenseList) { this.descriptions = new HashMap<>();
this.filename = filename; this.fetched = false;
categories = new ArrayList<>(); this.mediaWikiApi = mwApi;
descriptions = new HashMap<>();
fetched = false;
this.licenseList = licenseList;
} }
/** /*
* Actually fetch the data over the network. * Actually fetch the data over the network.
* todo: use local caching? * todo: use local caching?
* *
* Warning: synchronous i/o, call on a background thread * 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) { if (fetched) {
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again."); throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
} }
MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); MediaResult result = mediaWikiApi.fetchMediaByFilename(filename);
MediaResult result = api.fetchMediaByFilename(filename);
// In-page category links are extracted from source, as XML doesn't cover [[links]] // In-page category links are extracted from source, as XML doesn't cover [[links]]
extractCategories(result.getWikiSource()); extractCategories(result.getWikiSource());
// Description template info is extracted from preprocessor XML // Description template info is extracted from preprocessor XML
processWikiParseTree(result.getParseTreeXmlSource()); processWikiParseTree(result.getParseTreeXmlSource(), licenseList);
fetched = true; 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; Document doc;
try { try {
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

View file

@ -7,16 +7,17 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> { class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
protected final Media media; protected final Media media;
private MediaWikiApi mediaWikiApi;
public MediaThumbnailFetchTask(@NonNull Media media) { public MediaThumbnailFetchTask(@NonNull Media media, MediaWikiApi mwApi) {
this.media = media; this.media = media;
this.mediaWikiApi = mwApi;
} }
@Override @Override
protected String doInBackground(String... params) { protected String doInBackground(String... params) {
try { try {
MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); return mediaWikiApi.findThumbnailByFilename(params[0]);
return api.findThumbnailByFilename(params[0]);
} catch (Exception e) { } catch (Exception e) {
// Do something better! // Do something better!
} }

View file

@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.util.LruCache;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
@ -11,9 +12,15 @@ import android.widget.Toast;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import javax.inject.Inject;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; import timber.log.Timber;
public class MediaWikiImageView extends SimpleDraweeView { public class MediaWikiImageView extends SimpleDraweeView {
@Inject MediaWikiApi mwApi;
@Inject LruCache<String, String> thumbnailUrlCache;
private ThumbnailFetchTask currentThumbnailTask; private ThumbnailFetchTask currentThumbnailTask;
public MediaWikiImageView(Context context) { public MediaWikiImageView(Context context) {
@ -39,11 +46,11 @@ public class MediaWikiImageView extends SimpleDraweeView {
return; return;
} }
if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) { if (thumbnailUrlCache.get(media.getFilename()) != null) {
setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename())); setImageUrl(thumbnailUrlCache.get(media.getFilename()));
} else { } else {
setImageUrl(null); setImageUrl(null);
currentThumbnailTask = new ThumbnailFetchTask(media); currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
currentThumbnailTask.execute(media.getFilename()); currentThumbnailTask.execute(media.getFilename());
} }
} }
@ -57,6 +64,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
} }
private void init() { private void init() {
((CommonsApplication) getContext().getApplicationContext()).injector().inject(this);
setHierarchy(GenericDraweeHierarchyBuilder setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources()) .newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(), .setPlaceholderImage(VectorDrawableCompat.create(getResources(),
@ -71,8 +79,8 @@ public class MediaWikiImageView extends SimpleDraweeView {
} }
private class ThumbnailFetchTask extends MediaThumbnailFetchTask { private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
ThumbnailFetchTask(@NonNull Media media) { ThumbnailFetchTask(@NonNull Media media, @NonNull MediaWikiApi mwApi) {
super(media); super(media, mwApi);
} }
@Override @Override
@ -85,7 +93,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
} else { } else {
// only cache meaningful thumbnails received from network. // only cache meaningful thumbnails received from network.
try { try {
CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result); thumbnailUrlCache.put(media.getFilename(), result);
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
Timber.e("error when adding pic to cache " + npe); Timber.e("error when adding pic to cache " + npe);

View file

@ -1,102 +1,26 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull; 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.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils; 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.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder; 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.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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 fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber; import timber.log.Timber;
public class Utils { 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. * Strips localization symbols from a string.
* Removes the suffix after "@" and quotes. * 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) { public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText(); String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name))); 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)); 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) { public static String urlEncode(String url) {
try { try {
return URLEncoder.encode(url, "utf-8"); 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) { public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1); 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) { public static int licenseNameFor(String license) {
switch (license) { switch (license) {
case Prefs.Licenses.CC_BY_3: case Prefs.Licenses.CC_BY_3:
@ -217,51 +75,6 @@ public class Utils {
throw new RuntimeException("Unrecognized license value: " + license); 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) { public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE); Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
@ -277,10 +90,6 @@ public class Utils {
return title; return title;
} }
public static boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty();
}
public static boolean isDarkTheme(Context context) { public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false); return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
} }

View file

@ -4,21 +4,31 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import timber.log.Timber; 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 class AccountUtil {
public static void createAccount(@Nullable AccountAuthenticatorResponse response, public static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
private final Context context;
public AccountUtil(Context context) {
this.context = context;
}
public void createAccount(@Nullable AccountAuthenticatorResponse response,
String username, String password) { String username, String password) {
Account account = new Account(username, accountType()); Account account = new Account(username, ACCOUNT_TYPE);
boolean created = accountManager().addAccountExplicitly(account, password, null); boolean created = accountManager().addAccountExplicitly(account, password, null);
Timber.d("account creation " + (created ? "successful" : "failure")); Timber.d("account creation " + (created ? "successful" : "failure"));
@ -26,8 +36,8 @@ public class AccountUtil {
if (created) { if (created) {
if (response != null) { if (response != null) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, username); bundle.putString(KEY_ACCOUNT_NAME, username);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType()); bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(bundle); response.onResult(bundle);
@ -35,7 +45,7 @@ public class AccountUtil {
} else { } else {
if (response != null) { if (response != null) {
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, ""); response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
} }
Timber.d("account creation failure"); Timber.d("account creation failure");
} }
@ -45,18 +55,8 @@ public class AccountUtil {
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
} }
@NonNull private AccountManager accountManager() {
public static String accountType() { return AccountManager.get(context);
return "fr.free.nrw.commons";
}
private static AccountManager accountManager() {
return AccountManager.get(app());
}
@NonNull
private static CommonsApplication app() {
return CommonsApplication.getInstance();
} }
} }

View file

@ -5,47 +5,50 @@ import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture; import android.accounts.AccountManagerFuture;
import android.os.Bundle; import android.os.Bundle;
import fr.free.nrw.commons.CommonsApplication; import javax.inject.Inject;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
public abstract class AuthenticatedActivity extends NavigationBaseActivity { public abstract class AuthenticatedActivity extends NavigationBaseActivity {
private String accountType; @Inject SessionManager sessionManager;
CommonsApplication app;
private String authCookie; private String authCookie;
public AuthenticatedActivity() {
this.accountType = AccountUtil.accountType();
}
private void getAuthCookie(Account account, AccountManager accountManager) { private void getAuthCookie(Account account, AccountManager accountManager) {
Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false)) Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.doOnError(Timber::e) .doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onAuthCookieAcquired, throwable -> onAuthFailure()); .subscribe(
this:: onAuthCookieAcquired,
throwable -> onAuthFailure());
} }
private void addAccount(AccountManager accountManager) { 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()) .subscribeOn(Schedulers.io())
.map(AccountManagerFuture::getResult) .map(AccountManagerFuture::getResult)
.doOnEvent((bundle, throwable) -> { .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: " 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) .doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> { .subscribe(s -> {
Account[] allAccounts = accountManager.getAccountsByType(accountType); Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
Account curAccount = allAccounts[0]; Account curAccount = allAccounts[0];
getAuthCookie(curAccount, accountManager); getAuthCookie(curAccount, accountManager);
}, },
@ -58,7 +61,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
return; return;
} }
AccountManager accountManager = AccountManager.get(this); AccountManager accountManager = AccountManager.get(this);
Account curAccount = app.getCurrentAccount(); Account curAccount = sessionManager.getCurrentAccount();
if (curAccount == null) { if (curAccount == null) {
addAccount(accountManager); addAccount(accountManager);
} else { } else {
@ -69,7 +72,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
app = CommonsApplication.getInstance();
if (savedInstanceState != null) { if (savedInstanceState != null) {
authCookie = savedInstanceState.getString("authCookie"); authCookie = savedInstanceState.getString("authCookie");
} }

View file

@ -21,15 +21,19 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.PageTitle; import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity; import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import timber.log.Timber; 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"; 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.loginButton) Button loginButton;
@BindView(R.id.signupButton) Button signupButton; @BindView(R.id.signupButton) Button signupButton;
@BindView(R.id.loginUsername) EditText usernameEdit; @BindView(R.id.loginUsername) EditText usernameEdit;
@ -47,11 +57,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@BindView(R.id.loginTwoFactor) EditText twoFactorEdit; @BindView(R.id.loginTwoFactor) EditText twoFactorEdit;
@BindView(R.id.error_message_container) ViewGroup errorMessageContainer; @BindView(R.id.error_message_container) ViewGroup errorMessageContainer;
@BindView(R.id.error_message) TextView errorMessage; @BindView(R.id.error_message) TextView errorMessage;
private CommonsApplication app;
ProgressDialog progressDialog; ProgressDialog progressDialog;
private AppCompatDelegate delegate; private AppCompatDelegate delegate;
private SharedPreferences prefs = null;
private LoginTextWatcher textWatcher = new LoginTextWatcher(); private LoginTextWatcher textWatcher = new LoginTextWatcher();
@Override @Override
@ -59,16 +66,13 @@ public class LoginActivity extends AccountAuthenticatorActivity {
setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme); setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme);
getDelegate().installViewFactory(); getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState); getDelegate().onCreate(savedInstanceState);
AndroidInjection.inject(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
app = CommonsApplication.getInstance();
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
ButterKnife.bind(this); ButterKnife.bind(this);
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
usernameEdit.addTextChangedListener(textWatcher); usernameEdit.addTextChangedListener(textWatcher);
passwordEdit.addTextChangedListener(textWatcher); passwordEdit.addTextChangedListener(textWatcher);
twoFactorEdit.addTextChangedListener(textWatcher); twoFactorEdit.addTextChangedListener(textWatcher);
@ -91,7 +95,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
WelcomeActivity.startYourself(this); WelcomeActivity.startYourself(this);
prefs.edit().putBoolean("firstrun", false).apply(); prefs.edit().putBoolean("firstrun", false).apply();
} }
if (app.getCurrentAccount() != null) { if (sessionManager.getCurrentAccount() != null) {
startMainActivity(); startMainActivity();
} }
} }
@ -113,6 +117,25 @@ public class LoginActivity extends AccountAuthenticatorActivity {
super.onDestroy(); 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 @Override
protected void onStart() { protected void onStart() {
super.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) { private void showMessage(@StringRes int resId, @ColorRes int colorResId) {
errorMessage.setText(getString(resId)); errorMessage.setText(getString(resId));
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId)); errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));

View file

@ -1,8 +1,8 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -11,22 +11,34 @@ import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.mwapi.EventLog; import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; 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<String, String, String> { class LoginTask extends AsyncTask<String, String, String> {
private LoginActivity loginActivity; private LoginActivity loginActivity;
private String username; private String username;
private String password; private String password;
private String twoFactorCode = ""; 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.loginActivity = loginActivity;
this.username = username; this.username = username;
this.password = password; this.password = password;
this.twoFactorCode = twoFactorCode; this.twoFactorCode = twoFactorCode;
app = CommonsApplication.getInstance(); this.accountUtil = accountUtil;
this.mwApi = mwApi;
this.prefs = prefs;
} }
@Override @Override
@ -44,9 +56,9 @@ class LoginTask extends AsyncTask<String, String, String> {
protected String doInBackground(String... params) { protected String doInBackground(String... params) {
try { try {
if (twoFactorCode.isEmpty()) { if (twoFactorCode.isEmpty()) {
return app.getMWApi().login(username, password); return mwApi.login(username, password);
} else { } else {
return app.getMWApi().login(username, password, twoFactorCode); return mwApi.login(username, password, twoFactorCode);
} }
} catch (IOException e) { } catch (IOException e) {
// Do something better! // Do something better!
@ -59,7 +71,7 @@ class LoginTask extends AsyncTask<String, String, String> {
super.onPostExecute(result); super.onPostExecute(result);
Timber.d("Login done!"); Timber.d("Login done!");
EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT, mwApi, prefs)
.param("username", username) .param("username", username)
.param("result", result) .param("result", result)
.log(); .log();
@ -79,16 +91,16 @@ class LoginTask extends AsyncTask<String, String, String> {
Bundle extras = loginActivity.getIntent().getExtras(); Bundle extras = loginActivity.getIntent().getExtras();
if (extras != null) { if (extras != null) {
Timber.d("Bundle of extras: %s", extras); 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) { if (response != null) {
Bundle authResult = new Bundle(); Bundle authResult = new Bundle();
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username); authResult.putString(KEY_ACCOUNT_NAME, username);
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType()); authResult.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(authResult); response.onResult(authResult);
} }
} }
AccountUtil.createAccount(response, username, password); accountUtil.createAccount(response, username, password);
loginActivity.startMainActivity(); loginActivity.startMainActivity();
} }

View file

@ -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);
}
}

View file

@ -7,7 +7,6 @@ import android.webkit.WebViewClient;
import android.widget.Toast; import android.widget.Toast;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber; import timber.log.Timber;
@ -39,11 +38,8 @@ public class SignupActivity extends BaseActivity {
//Signup success, so clear cookies, notify user, and load LoginActivity again //Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url); Timber.d("Overriding URL %s", url);
Toast toast = Toast.makeText( Toast toast = Toast.makeText(SignupActivity.this,
CommonsApplication.getInstance(), "Account created!", Toast.LENGTH_LONG);
"Account created!",
Toast.LENGTH_LONG
);
toast.show(); toast.show();
// terminate on task completion. // terminate on task completion.
finish(); finish();

View file

@ -13,7 +13,6 @@ import android.support.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION; 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_CODE;
import static android.accounts.AccountManager.KEY_ERROR_MESSAGE; import static android.accounts.AccountManager.KEY_ERROR_MESSAGE;
import static android.accounts.AccountManager.KEY_INTENT; 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; import static fr.free.nrw.commons.auth.LoginActivity.PARAM_USERNAME;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { 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); super(context);
this.context = context; this.context = context;
this.mediaWikiApi = mwApi;
} }
private Bundle unsupportedOperation() { private Bundle unsupportedOperation() {
@ -47,7 +49,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
} }
private boolean supportedAccountType(@Nullable String type) { private boolean supportedAccountType(@Nullable String type) {
return AccountUtil.accountType().equals(type); return ACCOUNT_TYPE.equals(type);
} }
@Override @Override
@ -86,11 +88,10 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
} }
private String getAuthCookie(String username, String password) throws IOException { private String getAuthCookie(String username, String password) throws IOException {
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
//TODO add 2fa support here //TODO add 2fa support here
String result = api.login(username, password); String result = mediaWikiApi.login(username, password);
if (result.equals("PASS")) { if (result.equals("PASS")) {
return api.getAuthCookie(); return mediaWikiApi.getAuthCookie();
} else { } else {
return null; return null;
} }
@ -115,7 +116,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
if (authCookie != null) { if (authCookie != null) {
final Bundle result = new Bundle(); final Bundle result = new Bundle();
result.putString(KEY_ACCOUNT_NAME, account.name); 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); result.putString(KEY_AUTHTOKEN, authCookie);
return result; return result;
} }

View file

@ -1,22 +1,28 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.AccountManager;
import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
public class WikiAccountAuthenticatorService extends Service { import javax.inject.Inject;
private static WikiAccountAuthenticator wikiAccountAuthenticator = null; 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;
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
if (!intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) { if (!intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT)) {
return null; return null;
} }
if (wikiAccountAuthenticator == null) { if (wikiAccountAuthenticator == null) {
wikiAccountAuthenticator = new WikiAccountAuthenticator(this); wikiAccountAuthenticator = new WikiAccountAuthenticator(this, mwApi);
} }
return wikiAccountAuthenticator.getIBinder(); return wikiAccountAuthenticator.getIBinder();
} }

View file

@ -3,8 +3,6 @@ package fr.free.nrw.commons.category;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -31,11 +29,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; 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.R;
import fr.free.nrw.commons.data.Category; 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.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils; import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable; 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. * 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; public static final int SEARCH_CATS_LIMIT = 25;
@ -65,6 +67,9 @@ public class CategorizationFragment extends Fragment {
@BindView(R.id.categoriesExplanation) @BindView(R.id.categoriesExplanation)
TextView categoriesSkip; TextView categoriesSkip;
@Inject MediaWikiApi mwApi;
@Inject @Named("default_preferences") SharedPreferences prefs;
private RVRendererAdapter<CategoryItem> categoriesAdapter; private RVRendererAdapter<CategoryItem> categoriesAdapter;
private OnCategoriesSaveHandler onCategoriesSaveHandler; private OnCategoriesSaveHandler onCategoriesSaveHandler;
private HashMap<String, ArrayList<String>> categoriesCache; private HashMap<String, ArrayList<String>> categoriesCache;
@ -205,7 +210,9 @@ public class CategorizationFragment extends Fragment {
.sorted(sortBySimilarity(filter)) .sorted(sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
s -> categoriesAdapter.add(s), Timber::e, () -> { s -> categoriesAdapter.add(s),
Timber::e,
() -> {
categoriesAdapter.notifyDataSetChanged(); categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE); categoriesSearchInProgress.setVisibility(View.GONE);
@ -253,10 +260,9 @@ public class CategorizationFragment extends Fragment {
private Observable<CategoryItem> titleCategories() { private Observable<CategoryItem> titleCategories() {
//Retrieve the title that was saved when user tapped submit icon //Retrieve the title that was saved when user tapped submit icon
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); String title = prefs.getString("Title", "");
String title = titleDesc.getString("Title", "");
return CommonsApplication.getInstance().getMWApi() return mwApi
.searchTitles(title, SEARCH_CATS_LIMIT) .searchTitles(title, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false)); .map(name -> new CategoryItem(name, false));
} }
@ -279,7 +285,7 @@ public class CategorizationFragment extends Fragment {
} }
//otherwise, search API for matching categories //otherwise, search API for matching categories
return CommonsApplication.getInstance().getMWApi() return mwApi
.allCategories(term, SEARCH_CATS_LIMIT) .allCategories(term, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false)); .map(name -> new CategoryItem(name, false));
} }
@ -290,7 +296,7 @@ public class CategorizationFragment extends Fragment {
return Observable.empty(); return Observable.empty();
} }
return CommonsApplication.getInstance().getMWApi() return mwApi
.searchCategories(term, SEARCH_CATS_LIMIT) .searchCategories(term, SEARCH_CATS_LIMIT)
.map(s -> new CategoryItem(s, false)); .map(s -> new CategoryItem(s, false));
} }

View file

@ -10,7 +10,9 @@ import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; 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 fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber; import timber.log.Timber;
@ -36,17 +38,15 @@ public class CategoryContentProvider extends ContentProvider {
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID); uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
} }
private DBOpenHelper dbOpenHelper;
public static Uri uriForId(int id) { public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id); return Uri.parse(BASE_URI.toString() + "/" + id);
} }
@SuppressWarnings("ConstantConditions") @Inject DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
CommonsApplication app = ((CommonsApplication) getContext().getApplicationContext()); AndroidInjection.inject(this);
dbOpenHelper = app.getDBOpenHelper();
return false; return false;
} }

View file

@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -16,7 +17,6 @@ import java.util.Locale;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
public class Contribution extends Media { public class Contribution extends Media {
@ -149,7 +149,7 @@ public class Contribution extends Media {
} }
buffer.append("== {{int:license-header}} ==\n") 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("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
.append(getTrackingTemplates()); .append(getTrackingTemplates());
return buffer.toString(); 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);
}
} }

View file

@ -9,7 +9,6 @@ import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
@ -24,21 +23,20 @@ import android.widget.AdapterView;
import java.util.ArrayList; import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
@ -49,11 +47,18 @@ 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.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING; import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
public class ContributionsActivity extends AuthenticatedActivity public class ContributionsActivity
implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener, extends AuthenticatedActivity
MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener, implements LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher { ContributionsListFragment.SourceRefresher {
@Inject MediaWikiApi mediaWikiApi;
@Inject SessionManager sessionManager;
@Inject @Named("default_preferences") SharedPreferences prefs;
private Cursor allContributions; private Cursor allContributions;
private ContributionsListFragment contributionsList; private ContributionsListFragment contributionsList;
private MediaDetailPagerFragment mediaDetails; private MediaDetailPagerFragment mediaDetails;
@ -62,9 +67,6 @@ public class ContributionsActivity extends AuthenticatedActivity
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>(); private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
private String CONTRIBUTION_SELECTION = ""; private String CONTRIBUTION_SELECTION = "";
@Inject
MediaWikiApi mediaWikiApi;
/* /*
This sorts in the following order: This sorts in the following order:
Currently Uploading Currently Uploading
@ -109,12 +111,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
boolean isSettingsChanged = prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
editor.apply();
if (isSettingsChanged) { if (isSettingsChanged) {
refreshSource(); refreshSource();
} }
@ -123,8 +121,7 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here! // Do a sync everytime we get here!
CommonsApplication app = ((CommonsApplication) getApplication()); requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
requestSync(app.getCurrentAccount(), AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class); Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent); startService(uploadServiceIntent);
@ -139,7 +136,6 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
AndroidInjection.inject(this);
setContentView(R.layout.activity_contributions); setContentView(R.layout.activity_contributions);
ButterKnife.bind(this); ButterKnife.bind(this);
@ -241,8 +237,7 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override @Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI, return new CursorLoader(this, BASE_URI,
ALL_FIELDS, CONTRIBUTION_SELECTION, null, ALL_FIELDS, CONTRIBUTION_SELECTION, null,
CONTRIBUTION_SORT + "LIMIT " + uploads); CONTRIBUTION_SORT + "LIMIT " + uploads);
@ -289,9 +284,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
private void setUploadCount() { private void setUploadCount() {
CommonsApplication app = ((CommonsApplication) getApplication()); compositeDisposable.add(mediaWikiApi
Disposable uploadCountDisposable = mediaWikiApi .getUploadCount(sessionManager.getCurrentAccount().name)
.getUploadCount(app.getCurrentAccount().name)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
@ -299,8 +293,7 @@ public class ContributionsActivity extends AuthenticatedActivity
.getQuantityString(R.plurals.contributions_subtitle, .getQuantityString(R.plurals.contributions_subtitle,
uploadCount, uploadCount)), uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed") t -> Timber.e(t, "Fetching upload count failed")
); ));
compositeDisposable.add(uploadCountDisposable);
} }
@Override @Override

View file

@ -10,7 +10,10 @@ import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; 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 timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH; import static android.content.UriMatcher.NO_MATCH;
@ -36,9 +39,12 @@ public class ContributionsContentProvider extends ContentProvider {
return Uri.parse(BASE_URI.toString() + "/" + id); return Uri.parse(BASE_URI.toString() + "/" + id);
} }
@Inject DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
return false; AndroidInjection.inject(this);
return true;
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@ -50,8 +56,7 @@ public class ContributionsContentProvider extends ContentProvider {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
SQLiteDatabase db = app.getDBOpenHelper().getReadableDatabase();
Cursor cursor; Cursor cursor;
switch (uriType) { switch (uriType) {
@ -87,9 +92,8 @@ public class ContributionsContentProvider extends ContentProvider {
@Override @Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) { public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); long id = 0;
long id;
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
id = sqlDB.insert(TABLE_NAME, null, contentValues); id = sqlDB.insert(TABLE_NAME, null, contentValues);
@ -107,13 +111,12 @@ public class ContributionsContentProvider extends ContentProvider {
int rows; int rows;
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS_ID: case CONTRIBUTIONS_ID:
Timber.d("Deleting contribution id %s", uri.getLastPathSegment()); Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
rows = sqlDB.delete(TABLE_NAME, rows = db.delete(TABLE_NAME,
"_id = ?", "_id = ?",
new String[]{uri.getLastPathSegment()} new String[]{uri.getLastPathSegment()}
); );
@ -130,8 +133,7 @@ public class ContributionsContentProvider extends ContentProvider {
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ContributionsContentProvider)"); Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
sqlDB.beginTransaction(); sqlDB.beginTransaction();
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
@ -162,9 +164,8 @@ public class ContributionsContentProvider extends ContentProvider {
error out otherwise. error out otherwise.
*/ */
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); int rowsUpdated = 0;
int rowsUpdated;
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs); rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs);

View file

@ -5,9 +5,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -22,9 +20,12 @@ import android.widget.ListAdapter;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; 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.R;
import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.nearby.NearbyActivity;
import timber.log.Timber; 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.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK; 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.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.View.GONE; import static android.view.View.GONE;
public class ContributionsListFragment extends Fragment { public class ContributionsListFragment extends DaggerFragment {
@BindView(R.id.contributionsList) @BindView(R.id.contributionsList)
GridView contributionsList; GridView contributionsList;
@ -45,6 +45,9 @@ public class ContributionsListFragment extends Fragment {
@BindView(R.id.loadingContributionsProgressBar) @BindView(R.id.loadingContributionsProgressBar)
ProgressBar progressBar; ProgressBar progressBar;
@Inject @Named("prefs") SharedPreferences prefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
private ContributionController controller; private ContributionController controller;
@Override @Override
@ -59,7 +62,6 @@ public class ContributionsListFragment extends Fragment {
} }
//TODO: Should this be in onResume? //TODO: Should this be in onResume?
SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", ""); String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified); Timber.d("Last Sync Timestamp: %s", lastModified);
@ -162,9 +164,7 @@ public class ContributionsListFragment extends Fragment {
return true; return true;
case R.id.menu_from_camera: case R.id.menu_from_camera:
SharedPreferences sharedPref = PreferenceManager boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
.getDefaultSharedPreferences(CommonsApplication.getInstance());
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
// Here, thisActivity is the current activity // Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE) 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 menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu); inflater.inflate(R.menu.fragment_contributions_list, menu);
CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); if (!deviceHasCamera()) {
if (!app.deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false); 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View file

@ -13,9 +13,15 @@ import android.os.RemoteException;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; 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.CommonsApplication;
import fr.free.nrw.commons.Utils; 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 fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; 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.STATE_COMPLETED;
import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME; import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
@SuppressWarnings("WeakerAccess")
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String[] existsQuery = {COLUMN_FILENAME}; private static final String[] existsQuery = {COLUMN_FILENAME};
@ -35,6 +41,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final ContentValues[] EMPTY = {}; private static final ContentValues[] EMPTY = {};
private static int COMMIT_THRESHOLD = 10; private static int COMMIT_THRESHOLD = 10;
@SuppressWarnings("WeakerAccess")
@Inject MediaWikiApi mwApi;
@Inject @Named("prefs") SharedPreferences prefs;
public ContributionsSyncAdapter(Context context, boolean autoInitialize) { public ContributionsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize); super(context, autoInitialize);
} }
@ -71,10 +81,9 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override @Override
public void onPerformSync(Account account, Bundle bundle, String authority, public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient contentProviderClient, SyncResult syncResult) { 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! // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
String user = account.name; String user = account.name;
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
SharedPreferences prefs = getContext().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", ""); String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date(); Date curTime = new Date();
LogEventResult result; LogEventResult result;
@ -83,7 +92,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
while (!done) { while (!done) {
try { try {
result = api.logEvents(user, lastModified, queryContinue, getLimit()); result = mwApi.logEvents(user, lastModified, queryContinue, getLimit());
} catch (IOException e) { } catch (IOException e) {
// There isn't really much we can do, eh? // There isn't really much we can do, eh?
// FIXME: Perhaps add EventLogging? // FIXME: Perhaps add EventLogging?
@ -137,8 +146,13 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
done = true; 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!"); 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);
}
} }

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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<CommonsApplication> {
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();
}
}

View file

@ -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<String, String> provideLruCache() {
return new LruCache<>(1024);
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -19,7 +19,6 @@ import javax.inject.Singleton;
import timber.log.Timber; import timber.log.Timber;
@Singleton
public class LocationServiceManager implements LocationListener { public class LocationServiceManager implements LocationListener {
public static final int LOCATION_REQUEST = 1; public static final int LOCATION_REQUEST = 1;
@ -32,7 +31,6 @@ public class LocationServiceManager implements LocationListener {
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>(); private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
private boolean isLocationManagerRegistered = false; private boolean isLocationManagerRegistered = false;
@Inject
public LocationServiceManager(Context context) { public LocationServiceManager(Context context) {
this.context = context; this.context = context;
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

View file

@ -6,7 +6,6 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -22,6 +21,10 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Locale; 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.License;
import fr.free.nrw.commons.LicenseList; import fr.free.nrw.commons.LicenseList;
import fr.free.nrw.commons.Media; 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.PageTitle;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.ui.widget.CompatTextView; import fr.free.nrw.commons.ui.widget.CompatTextView;
import timber.log.Timber; import timber.log.Timber;
public class MediaDetailFragment extends Fragment { public class MediaDetailFragment extends DaggerFragment {
private boolean editable; private boolean editable;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@ -53,6 +57,9 @@ public class MediaDetailFragment extends Fragment {
return mf; return mf;
} }
@Inject
Provider<MediaDataExtractor> mediaDataExtractorProvider;
private MediaWikiImageView image; private MediaWikiImageView image;
private MediaDetailSpacer spacer; private MediaDetailSpacer spacer;
private int initialListTop = 0; private int initialListTop = 0;
@ -69,7 +76,7 @@ public class MediaDetailFragment extends Fragment {
private boolean categoriesPresent = false; private boolean categoriesPresent = false;
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once! private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnScrollChangedListener scrollListener; private ViewTreeObserver.OnScrollChangedListener scrollListener;
DataSetObserver dataObserver; private DataSetObserver dataObserver;
private AsyncTask<Void,Void,Boolean> detailFetchTask; private AsyncTask<Void,Void,Boolean> detailFetchTask;
private LicenseList licenseList; private LicenseList licenseList;
@ -188,13 +195,13 @@ public class MediaDetailFragment extends Fragment {
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
extractor = new MediaDataExtractor(media.getFilename(), licenseList); extractor = mediaDataExtractorProvider.get();
} }
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
try { try {
extractor.fetch(); extractor.fetch(media.getFilename(), licenseList);
return Boolean.TRUE; return Boolean.TRUE;
} catch (IOException e) { } catch (IOException e) {
Timber.d(e); Timber.d(e);

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.media;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -24,12 +25,18 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.EventLog; 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.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE; 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 android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.CommonsApplication.EVENT_SHARE_ATTEMPT; 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 ViewPager pager;
private Boolean editable; private Boolean editable;
@ -101,8 +112,8 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
case R.id.menu_share_current_image: case R.id.menu_share_current_image:
// Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252 // Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252
CommonsApplication app = (CommonsApplication) getActivity().getApplication(); CommonsApplication app = (CommonsApplication) getActivity().getApplication();
EventLog.schema(EVENT_SHARE_ATTEMPT) EventLog.schema(EVENT_SHARE_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("filename", m.getFilename()) .param("filename", m.getFilename())
.log(); .log();
return true; return true;
@ -161,9 +172,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
req.allowScanningByMediaScanner(); req.allowScanningByMediaScanner();
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
&& !(ContextCompat.checkSelfPermission(getContext(),
READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
Snackbar.make(getView(), R.string.read_storage_permission_rationale, Snackbar.make(getView(), R.string.read_storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok, Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
view -> ActivityCompat.requestPermissions(getActivity(), view -> ActivityCompat.requestPermissions(getActivity(),

View file

@ -10,7 +10,10 @@ import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils; 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 timber.log.Timber;
public class ModificationsContentProvider extends ContentProvider { public class ModificationsContentProvider extends ContentProvider {
@ -33,10 +36,12 @@ public class ModificationsContentProvider extends ContentProvider {
return Uri.parse(BASE_URI.toString() + "/" + id); return Uri.parse(BASE_URI.toString() + "/" + id);
} }
@Inject DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
return false; AndroidInjection.inject(this);
return true;
} }
@Override @Override
@ -53,7 +58,7 @@ public class ModificationsContentProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI" + uri); 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 cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri); cursor.setNotificationUri(getContext().getContentResolver(), uri);
@ -69,7 +74,7 @@ public class ModificationsContentProvider extends ContentProvider {
@Override @Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) { public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = 0; long id = 0;
switch (uriType) { switch (uriType) {
case MODIFICATIONS: case MODIFICATIONS:
@ -85,7 +90,7 @@ public class ModificationsContentProvider extends ContentProvider {
@Override @Override
public int delete(@NonNull Uri uri, String s, String[] strings) { public int delete(@NonNull Uri uri, String s, String[] strings) {
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
switch (uriType) { switch (uriType) {
case MODIFICATIONS_ID: case MODIFICATIONS_ID:
String id = uri.getLastPathSegment(); String id = uri.getLastPathSegment();
@ -103,7 +108,7 @@ public class ModificationsContentProvider extends ContentProvider {
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ModificationsContentProvider)"); Timber.d("Hello, bulk insert! (ModificationsContentProvider)");
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction(); sqlDB.beginTransaction();
switch (uriType) { switch (uriType) {
case MODIFICATIONS: 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. 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); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated = 0; int rowsUpdated = 0;
switch (uriType) { switch (uriType) {
case MODIFICATIONS: case MODIFICATIONS:

View file

@ -14,8 +14,9 @@ import android.os.RemoteException;
import java.io.IOException; import java.io.IOException;
import javax.inject.Inject;
import fr.free.nrw.commons.CommonsApplication; 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.Contribution;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.mwapi.MediaWikiApi;
@ -23,6 +24,8 @@ import timber.log.Timber;
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
@Inject MediaWikiApi mwApi;
public ModificationsSyncAdapter(Context context, boolean autoInitialize) { public ModificationsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize); super(context, autoInitialize);
} }
@ -30,6 +33,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override @Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { 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! // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
((CommonsApplication)getContext().getApplicationContext()).injector().inject(this);
Cursor allModifications; Cursor allModifications;
try { try {
@ -54,17 +58,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
return; return;
} }
if (Utils.isNullOrWhiteSpace(authCookie)) { if (isNullOrWhiteSpace(authCookie)) {
Timber.d("Could not authenticate :("); Timber.d("Could not authenticate :(");
return; return;
} }
MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); mwApi.setAuthCookie(authCookie);
api.setAuthCookie(authCookie);
String editToken; String editToken;
try { try {
editToken = api.getEditToken(); editToken = mwApi.getEditToken();
} catch (IOException e) { } catch (IOException e) {
Timber.d("Can not retreive edit token!"); Timber.d("Can not retreive edit token!");
return; return;
@ -95,7 +98,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
if (contrib.getState() == Contribution.STATE_COMPLETED) { if (contrib.getState() == Contribution.STATE_COMPLETED) {
String pageContent; String pageContent;
try { try {
pageContent = api.revisionsByFilename(contrib.getFilename()); pageContent = mwApi.revisionsByFilename(contrib.getFilename());
} catch (IOException e) { } catch (IOException e) {
Timber.d("Network fuckup on modifications sync!"); Timber.d("Network fuckup on modifications sync!");
continue; continue;
@ -106,7 +109,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
String editResult; String editResult;
try { try {
editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary()); editResult = mwApi.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
} catch (IOException e) { } catch (IOException e) {
Timber.d("Network fuckup on modifications sync!"); Timber.d("Network fuckup on modifications sync!");
continue; continue;
@ -129,4 +132,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
} }
} }
} }
private boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty();
}
} }

View file

@ -25,6 +25,8 @@ import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@ -34,7 +36,6 @@ import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle; import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.Utils;
import in.yuvi.http.fluent.Http; import in.yuvi.http.fluent.Http;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -335,7 +336,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
logEvents.add(new LogEventResult.LogEvent( logEvents.add(new LogEventResult.LogEvent(
image.getString("@pageid"), image.getString("@pageid"),
image.getString("@title"), image.getString("@title"),
Utils.parseMWDate(image.getString("@timestamp"))) parseMWDate(image.getString("@timestamp")))
); );
} }
return logEvents; return logEvents;
@ -402,7 +403,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String errorCode = result.getString("/api/error/@code"); String errorCode = result.getString("/api/error/@code");
return new UploadResult(resultStatus, errorCode); return new UploadResult(resultStatus, errorCode);
} else { } 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 canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
String imageUrl = result.getString("/api/upload/imageinfo/@url"); String imageUrl = result.getString("/api/upload/imageinfo/@url");
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl); return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
@ -428,4 +429,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return Integer.parseInt(uploadCount); 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);
}
}
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.mwapi; package fr.free.nrw.commons.mwapi;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
@ -15,14 +16,14 @@ public class EventLog {
} }
} }
private static LogBuilder schema(String schema, long revision) { private static LogBuilder schema(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
return new LogBuilder(schema, revision); 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) { if (scid.length != 2) {
throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second"); 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);
} }
} }

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.mwapi;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -12,19 +11,23 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
@SuppressWarnings("WeakerAccess")
public class LogBuilder { public class LogBuilder {
private JSONObject data; private final MediaWikiApi mwApi;
private long rev; private final JSONObject data;
private String schema; private final long rev;
private final String schema;
private final SharedPreferences prefs;
LogBuilder(String schema, long revision) { LogBuilder(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
data = new JSONObject(); this.prefs = prefs;
this.data = new JSONObject();
this.schema = schema; this.schema = schema;
this.rev = revision; this.rev = revision;
this.mwApi = mwApi;
} }
public LogBuilder param(String key, Object value) { public LogBuilder param(String key, Object value) {
@ -56,11 +59,10 @@ public class LogBuilder {
// Use *only* for tracking the user preference change for EventLogging // Use *only* for tracking the user preference change for EventLogging
// Attempting to use anywhere else will cause kitten explosions // Attempting to use anywhere else will cause kitten explosions
public void log(boolean force) { public void log(boolean force) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance()); if (!prefs.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
return; // User has disabled tracking return; // User has disabled tracking
} }
LogTask logTask = new LogTask(); LogTask logTask = new LogTask(mwApi);
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this); logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
} }

View file

@ -2,11 +2,16 @@ package fr.free.nrw.commons.mwapi;
import android.os.AsyncTask; import android.os.AsyncTask;
import fr.free.nrw.commons.CommonsApplication;
class LogTask extends AsyncTask<LogBuilder, Void, Boolean> { class LogTask extends AsyncTask<LogBuilder, Void, Boolean> {
private final MediaWikiApi mwApi;
public LogTask(MediaWikiApi mwApi) {
this.mwApi = mwApi;
}
@Override @Override
protected Boolean doInBackground(LogBuilder... logBuilders) { protected Boolean doInBackground(LogBuilder... logBuilders) {
return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders); return mwApi.logEvents(logBuilders);
} }
} }

View file

@ -28,8 +28,6 @@ import javax.inject.Inject;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.AndroidInjection;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager; 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 { 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) @BindView(R.id.progressBar)
ProgressBar progressBar; ProgressBar progressBar;
private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
@Inject @Inject
LocationServiceManager locationManager; LocationServiceManager locationManager;
@Inject
NearbyController nearbyController;
private LatLng curLatLang; private LatLng curLatLang;
private Bundle bundle; private Bundle bundle;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
@ -64,7 +67,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
AndroidInjection.inject(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_nearby); setContentView(R.layout.activity_nearby);
ButterKnife.bind(this); ButterKnife.bind(this);
@ -279,20 +281,14 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
} }
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
setupPlaceList(this); placesDisposable = Observable.fromCallable(() -> nearbyController
} .loadAttractionsFromLocation(curLatLang, this))
private void setupPlaceList(Context context) {
placesDisposable = Observable.fromCallable(() -> NearbyController
.loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((result) -> { .subscribe(this::populatePlaces);
populatePlaces(context, result);
});
} }
private void populatePlaces(Context context, List<Place> placeList) { private void populatePlaces(List<Place> placeList) {
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer()) .registerTypeAdapter(Uri.class, new UriSerializer())
.create(); .create();
@ -301,7 +297,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
if (placeList.size() == 0) { if (placeList.size() == 0) {
int duration = Toast.LENGTH_SHORT; 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(); toast.show();
} }

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.nearby;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.preference.PreferenceManager;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.IconFactory;
@ -15,7 +14,9 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; 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.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UiUtils; 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.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
public class NearbyController { public class NearbyController {
private static final int MAX_RESULTS = 1000; 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. * Prepares Place list to make their distance information update later.
@ -34,13 +43,11 @@ public class NearbyController {
* @param context context * @param context context
* @return Place list without distance information * @return Place list without distance information
*/ */
public static List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) { public List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) {
Timber.d("Loading attractions near %s", curLatLng); Timber.d("Loading attractions near %s", curLatLng);
if (curLatLng == null) { if (curLatLng == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
NearbyPlaces nearbyPlaces = CommonsApplication.getInstance().getNearbyPlaces();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
List<Place> places = prefs.getBoolean("useWikidata", true) List<Place> places = prefs.getBoolean("useWikidata", true)
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage()) ? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
: nearbyPlaces.getFromWikiNeedsPictures(); : nearbyPlaces.getFromWikiNeedsPictures();

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.nearby;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -17,12 +16,13 @@ import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer; import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber; import timber.log.Timber;
public class NearbyListFragment extends Fragment { public class NearbyListFragment extends DaggerFragment {
private static final Type LIST_TYPE = new TypeToken<List<Place>>() { private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
}.getType(); }.getType();
private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() { private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() {

View file

@ -1,19 +1,19 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import timber.log.Timber; import timber.log.Timber;
/** /**
* Tells user that Nearby Places cannot be displayed if location permissions are denied * 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() { public NoPermissionsFragment() {
} }

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons.settings; package fr.free.nrw.commons.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate; import android.support.v7.app.AppCompatDelegate;

View file

@ -15,15 +15,17 @@ import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import android.widget.Toast; import android.widget.Toast;
import java.io.File; 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.BuildConfig;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; 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; private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 100;
@Inject @Named("default_preferences") SharedPreferences prefs;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Load the preferences from an XML resource // Load the preferences from an XML resource
@ -58,14 +63,12 @@ public class SettingsFragment extends PreferenceFragment {
}); });
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads"); final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
final SharedPreferences sharedPref = PreferenceManager int uploads = prefs.getInt(Prefs.UPLOADS_SHOWING, 100);
.getDefaultSharedPreferences(CommonsApplication.getInstance());
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
uploadLimit.setText(uploads + ""); uploadLimit.setText(uploads + "");
uploadLimit.setSummary(uploads + ""); uploadLimit.setSummary(uploads + "");
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> { uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
int value = Integer.parseInt(newValue.toString()); int value = Integer.parseInt(newValue.toString());
final SharedPreferences.Editor editor = sharedPref.edit(); final SharedPreferences.Editor editor = prefs.edit();
if (value > 500) { if (value > 500) {
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
.setTitle(R.string.maximum_limit) .setTitle(R.string.maximum_limit)

View file

@ -3,18 +3,17 @@ package fr.free.nrw.commons.theme;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; 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.R;
import fr.free.nrw.commons.Utils;
public class BaseActivity extends AppCompatActivity { public abstract class BaseActivity extends DaggerAppCompatActivity {
boolean currentTheme; boolean currentTheme;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
if (Utils.isDarkTheme(this)) { boolean currentThemeIsDark = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
if (currentThemeIsDark){
currentTheme = true; currentTheme = true;
setTheme(R.style.DarkAppTheme); setTheme(R.style.DarkAppTheme);
} else { } else {

View file

@ -62,10 +62,10 @@ public abstract class NavigationBaseActivity extends BaseActivity
private void setUserName() { private void setUserName() {
View navHeaderView = navigationView.getHeaderView(0); 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); AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType()); Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
if (allAccounts.length != 0) { if (allAccounts.length != 0) {
username.setText(allAccounts[0].name); username.setText(allAccounts[0].name);
} }

View file

@ -1,12 +1,13 @@
package fr.free.nrw.commons.ui.widget; package fr.free.nrw.commons.ui.widget;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.AppCompatTextView;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.AttributeSet; import android.util.AttributeSet;
import fr.free.nrw.commons.Utils;
/** /**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any * An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable. * links clickable.
@ -17,10 +18,25 @@ public class HtmlTextView extends AppCompatTextView {
super(context, attrs); super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance()); setMovementMethod(LinkMovementMethod.getInstance());
setText(Utils.fromHtml(getText().toString())); setText(fromHtml(getText().toString()));
} }
public void setHtmlText(String newText) { 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);
}
} }
} }

View file

@ -7,7 +7,6 @@ import android.support.v7.app.AlertDialog;
import java.io.IOException; import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi; 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 * Displays a warning to the user if the file already exists on Commons
*/ */
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
interface Callback { interface Callback {
void onResult(Result result); void onResult(Result result);
} }
@ -28,14 +28,16 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
DUPLICATE_CANCELLED DUPLICATE_CANCELLED
} }
private final MediaWikiApi api;
private final String fileSha1; private final String fileSha1;
private final Context context; private final Context context;
private final Callback callback; 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.fileSha1 = fileSha1;
this.context = context; this.context = context;
this.callback = callback; this.callback = callback;
this.api = mwApi;
} }
@Override @Override
@ -45,7 +47,6 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
@Override @Override
protected Boolean doInBackground(Void... voids) { 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 // https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
boolean fileExists; boolean fileExists;

View file

@ -8,7 +8,6 @@ import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.media.ExifInterface; import android.media.ExifInterface;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
@ -16,7 +15,6 @@ import android.support.annotation.RequiresApi;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -26,6 +24,8 @@ import timber.log.Timber;
*/ */
public class GPSExtractor { public class GPSExtractor {
private final Context context;
private SharedPreferences prefs;
private ExifInterface exif; private ExifInterface exif;
private double decLatitude; private double decLatitude;
private double decLongitude; private double decLongitude;
@ -38,9 +38,12 @@ public class GPSExtractor {
/** /**
* Construct from the file descriptor of the image (only for API 24 or newer). * Construct from the file descriptor of the image (only for API 24 or newer).
* @param fileDescriptor the file descriptor of the image * @param fileDescriptor the file descriptor of the image
* @param context the context
*/ */
@RequiresApi(24) @RequiresApi(24)
public GPSExtractor(@NonNull FileDescriptor fileDescriptor) { public GPSExtractor(@NonNull FileDescriptor fileDescriptor, Context context, SharedPreferences prefs) {
this.context = context;
this.prefs = prefs;
try { try {
exif = new ExifInterface(fileDescriptor); exif = new ExifInterface(fileDescriptor);
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
@ -51,13 +54,16 @@ public class GPSExtractor {
/** /**
* Construct from the file path of the image. * Construct from the file path of the image.
* @param path 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 { try {
exif = new ExifInterface(path); exif = new ExifInterface(path);
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
Timber.w(e); Timber.w(e);
} }
this.context = context;
} }
/** /**
@ -65,9 +71,7 @@ public class GPSExtractor {
* @return true if enabled, false if disabled * @return true if enabled, false if disabled
*/ */
private boolean gpsPreferenceEnabled() { private boolean gpsPreferenceEnabled() {
SharedPreferences sharedPref boolean gpsPref = prefs.getBoolean("allowGps", false);
= PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
boolean gpsPref = sharedPref.getBoolean("allowGps", false);
Timber.d("Gps pref set to: %b", gpsPref); Timber.d("Gps pref set to: %b", gpsPref);
return gpsPref; return gpsPref;
} }
@ -76,8 +80,7 @@ public class GPSExtractor {
* Registers a LocationManager to listen for current location * Registers a LocationManager to listen for current location
*/ */
protected void registerLocationManager() { protected void registerLocationManager() {
locationManager = (LocationManager) CommonsApplication.getInstance() locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria(); Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, true); String provider = locationManager.getBestProvider(criteria, true);
myLocationListener = new MyLocationListener(); myLocationListener = new MyLocationListener();

View file

@ -6,6 +6,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.net.Uri; import android.net.Uri;
@ -24,11 +25,15 @@ import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler; import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution; 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.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.EventLog; import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; import timber.log.Timber;
public class MultipleShareActivity public class MultipleShareActivity extends AuthenticatedActivity
extends AuthenticatedActivity
implements MediaDetailPagerFragment.MediaDetailProvider, implements MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, AdapterView.OnItemClickListener,
FragmentManager.OnBackStackChangedListener, FragmentManager.OnBackStackChangedListener,
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler, MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
OnCategoriesSaveHandler { OnCategoriesSaveHandler {
private CommonsApplication app;
@Inject MediaWikiApi mwApi;
@Inject SessionManager sessionManager;
@Inject UploadController uploadController;
@Inject @Named("default_preferences") SharedPreferences prefs;
private ArrayList<Contribution> photosList = null; private ArrayList<Contribution> photosList = null;
private MultipleUploadListFragment uploadsList; private MultipleUploadListFragment uploadsList;
private MediaDetailPagerFragment mediaDetails; private MediaDetailPagerFragment mediaDetails;
private CategorizationFragment categorizationFragment; private CategorizationFragment categorizationFragment;
private UploadController uploadController;
@Override @Override
public Media getMediaAtPosition(int i) { public Media getMediaAtPosition(int i) {
return photosList.get(i); return photosList.get(i);
@ -132,11 +140,7 @@ public class MultipleShareActivity
dialog.setProgress(uploadCount); dialog.setProgress(uploadCount);
if (uploadCount == photosList.size()) { if (uploadCount == photosList.size()) {
dialog.dismiss(); dialog.dismiss();
Toast startingToast = Toast.makeText( Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
CommonsApplication.getInstance(),
R.string.uploading_started,
Toast.LENGTH_LONG
);
startingToast.show(); startingToast.show();
} }
}); });
@ -176,9 +180,9 @@ public class MultipleShareActivity
} }
// FIXME: Make sure that the content provider is up // FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
ContentResolver.setSyncAutomatically(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) EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categories.size()) .param("categories-count", categories.size())
.param("files-count", photosList.size()) .param("files-count", photosList.size())
.param("source", Contribution.SOURCE_EXTERNAL) .param("source", Contribution.SOURCE_EXTERNAL)
@ -202,10 +206,8 @@ public class MultipleShareActivity
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
uploadController = new UploadController();
setContentView(R.layout.activity_multiple_uploads); setContentView(R.layout.activity_multiple_uploads);
app = CommonsApplication.getInstance();
ButterKnife.bind(this); ButterKnife.bind(this);
initDrawer(); initDrawer();
@ -245,7 +247,7 @@ public class MultipleShareActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
app.getMWApi().setAuthCookie(authCookie); mwApi.setAuthCookie(authCookie);
Intent intent = getIntent(); Intent intent = getIntent();
if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
@ -288,16 +290,16 @@ public class MultipleShareActivity
public void onBackPressed() { public void onBackPressed() {
super.onBackPressed(); super.onBackPressed();
if (categorizationFragment != null && categorizationFragment.isVisible()) { if (categorizationFragment != null && categorizationFragment.isVisible()) {
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount()) .param("categories-count", categorizationFragment.getCurrentSelectedCount())
.param("files-count", photosList.size()) .param("files-count", photosList.size())
.param("source", Contribution.SOURCE_EXTERNAL) .param("source", Contribution.SOURCE_EXTERNAL)
.param("result", "cancelled") .param("result", "cancelled")
.log(); .log();
} else { } else {
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE)) .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
.param("multiple", true) .param("multiple", true)
.param("result", "cancelled") .param("result", "cancelled")
@ -307,11 +309,7 @@ public class MultipleShareActivity
@Override @Override
public void onBackStackChanged() { public void onBackStackChanged() {
if (mediaDetails != null && mediaDetails.isVisible()) { getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
} }
} }

View file

@ -5,7 +5,6 @@ import android.graphics.Point;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -28,11 +27,12 @@ import android.widget.TextView;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
public class MultipleUploadListFragment extends Fragment { public class MultipleUploadListFragment extends DaggerFragment {
public interface OnMultipleUploadInitiatedHandler { public interface OnMultipleUploadInitiatedHandler {
void OnMultipleUploadInitiated(); void OnMultipleUploadInitiated();

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import com.android.volley.Cache; import com.android.volley.Cache;
@ -20,7 +21,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -33,12 +33,14 @@ public class MwVolleyApi {
private static RequestQueue REQUEST_QUEUE; private static RequestQueue REQUEST_QUEUE;
private static final Gson GSON = new GsonBuilder().create(); private static final Gson GSON = new GsonBuilder().create();
protected static Set<String> categorySet; private static Set<String> categorySet;
private static List<String> categoryList; private static List<String> categoryList;
private static final String MWURL = "https://commons.wikimedia.org/"; 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<>(); categorySet = new HashSet<>();
} }
@ -93,7 +95,7 @@ public class MwVolleyApi {
private synchronized RequestQueue getQueue() { private synchronized RequestQueue getQueue() {
if (REQUEST_QUEUE == null) { if (REQUEST_QUEUE == null) {
REQUEST_QUEUE = Volley.newRequestQueue(CommonsApplication.getInstance()); REQUEST_QUEUE = Volley.newRequestQueue(context);
} }
return REQUEST_QUEUE; return REQUEST_QUEUE;
} }

View file

@ -30,14 +30,21 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; 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.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.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler; import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution; 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.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.EventLog; import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED; 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 static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
private CategorizationFragment categorizationFragment; 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 source;
private String mimeType; private String mimeType;
@ -75,8 +87,6 @@ public class ShareActivity
private Contribution contribution; private Contribution contribution;
private SimpleDraweeView backgroundImageView; private SimpleDraweeView backgroundImageView;
private UploadController uploadController;
private boolean cacheFound; private boolean cacheFound;
private GPSExtractor imageObj; private GPSExtractor imageObj;
@ -117,7 +127,7 @@ public class ShareActivity
@RequiresApi(16) @RequiresApi(16)
private boolean needsToRequestStoragePermission() { private boolean needsToRequestStoragePermission() {
// We need to ask storage permission when // 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. // and permission is not obtained.
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri) return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) && (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
@ -127,16 +137,12 @@ public class ShareActivity
private void uploadBegins() { private void uploadBegins() {
getFileMetadata(locationPermitted); getFileMetadata(locationPermitted);
Toast startingToast = Toast.makeText( Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
CommonsApplication.getInstance(),
R.string.uploading_started,
Toast.LENGTH_LONG
);
startingToast.show(); startingToast.show();
if (!cacheFound) { if (!cacheFound) {
//Has to be called after apiCall.request() //Has to be called after apiCall.request()
app.getCacheData().cacheCategory(); cacheController.cacheCategory();
Timber.d("Cache the categories found"); Timber.d("Cache the categories found");
} }
@ -168,10 +174,10 @@ public class ShareActivity
// FIXME: Make sure that the content provider is up // FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
ContentResolver.setSyncAutomatically(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) EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categories.size()) .param("categories-count", categories.size())
.param("files-count", 1) .param("files-count", 1)
.param("source", contribution.getSource()) .param("source", contribution.getSource())
@ -192,16 +198,16 @@ public class ShareActivity
public void onBackPressed() { public void onBackPressed() {
super.onBackPressed(); super.onBackPressed();
if (categorizationFragment != null && categorizationFragment.isVisible()) { if (categorizationFragment != null && categorizationFragment.isVisible()) {
EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("categories-count", categorizationFragment.getCurrentSelectedCount()) .param("categories-count", categorizationFragment.getCurrentSelectedCount())
.param("files-count", 1) .param("files-count", 1)
.param("source", contribution.getSource()) .param("source", contribution.getSource())
.param("result", "cancelled") .param("result", "cancelled")
.log(); .log();
} else { } else {
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE)) .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
.param("multiple", true) .param("multiple", true)
.param("result", "cancelled") .param("result", "cancelled")
@ -211,8 +217,7 @@ public class ShareActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
app.getMWApi().setAuthCookie(authCookie); mwApi.setAuthCookie(authCookie);
} }
@Override @Override
@ -225,11 +230,10 @@ public class ShareActivity
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
uploadController = new UploadController();
setContentView(R.layout.activity_share); setContentView(R.layout.activity_share);
ButterKnife.bind(this); ButterKnife.bind(this);
initBack(); initBack();
app = CommonsApplication.getInstance();
backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage); backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage);
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources()) .newInstance(getResources())
@ -379,7 +383,7 @@ public class ShareActivity
try { try {
InputStream inputStream = getContentResolver().openInputStream(mediaUri); InputStream inputStream = getContentResolver().openInputStream(mediaUri);
Timber.d("Input stream created from %s", mediaUri.toString()); 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); Timber.d("File SHA1 is: %s", fileSHA1);
ExistingFileAsync fileAsyncTask = ExistingFileAsync fileAsyncTask =
@ -387,7 +391,7 @@ public class ShareActivity
Timber.d("%s duplicate check: %s", mediaUri.toString(), result); Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
duplicateCheckPassed = (result == DUPLICATE_PROCEED duplicateCheckPassed = (result == DUPLICATE_PROCEED
|| result == NO_DUPLICATE); || result == NO_DUPLICATE);
}); }, mwApi);
fileAsyncTask.execute(); fileAsyncTask.execute();
} catch (IOException e) { } catch (IOException e) {
Timber.d(e, "IO Exception: "); Timber.d(e, "IO Exception: ");
@ -424,9 +428,7 @@ public class ShareActivity
ParcelFileDescriptor descriptor ParcelFileDescriptor descriptor
= getContentResolver().openFileDescriptor(mediaUri, "r"); = getContentResolver().openFileDescriptor(mediaUri, "r");
if (descriptor != null) { if (descriptor != null) {
SharedPreferences sharedPref = PreferenceManager boolean useExtStorage = prefs.getBoolean("useExternalStorage", true);
.getDefaultSharedPreferences(CommonsApplication.getInstance());
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
if (useExtStorage) { if (useExtStorage) {
copyPath = Environment.getExternalStorageDirectory().toString() copyPath = Environment.getExternalStorageDirectory().toString()
+ "/CommonsApp/" + new Date().getTime() + ".jpg"; + "/CommonsApp/" + new Date().getTime() + ".jpg";
@ -467,12 +469,12 @@ public class ShareActivity
= getContentResolver().openFileDescriptor(mediaUri, "r"); = getContentResolver().openFileDescriptor(mediaUri, "r");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) { if (descriptor != null) {
imageObj = new GPSExtractor(descriptor.getFileDescriptor()); imageObj = new GPSExtractor(descriptor.getFileDescriptor(), this, prefs);
} }
} else { } else {
String filePath = getPathOfMediaOrCopy(); String filePath = getPathOfMediaOrCopy();
if (filePath != null) { if (filePath != null) {
imageObj = new GPSExtractor(filePath); imageObj = new GPSExtractor(filePath, this, prefs);
} }
} }
} }
@ -499,12 +501,12 @@ public class ShareActivity
if (imageObj.imageCoordsExists) { if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude(); double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude(); double decLatitude = imageObj.getDecLatitude();
app.getCacheData().setQtPoint(decLongitude, decLatitude); cacheController.setQtPoint(decLongitude, decLatitude);
} }
MwVolleyApi apiCall = new MwVolleyApi(); MwVolleyApi apiCall = new MwVolleyApi(this);
List<String> displayCatList = app.getCacheData().findCategory(); List<String> displayCatList = cacheController.findCategory();
boolean catListEmpty = displayCatList.isEmpty(); boolean catListEmpty = displayCatList.isEmpty();
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories // 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); 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");
}
}
}
} }

View file

@ -7,7 +7,7 @@ import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.Fragment; import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -28,11 +28,15 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick; import butterknife.OnClick;
import butterknife.OnItemSelected; import butterknife.OnItemSelected;
import butterknife.OnTouch; import butterknife.OnTouch;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs; 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_DOWN;
import static android.view.MotionEvent.ACTION_UP; 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.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit; @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.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner; @BindView(R.id.licenseSpinner) Spinner licenseSpinner;
private SharedPreferences prefs; @Inject @Named("default_preferences") SharedPreferences prefs;
private String license; private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler; private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher(); private TitleTextWatcher textWatcher = new TitleTextWatcher();
@ -72,11 +77,10 @@ public class SingleUploadFragment extends Fragment {
String desc = descEdit.getText().toString(); String desc = descEdit.getText().toString();
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these //Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); prefs.edit()
SharedPreferences.Editor editor = titleDesc.edit(); .putString("Title", title)
editor.putString("Title", title); .putString("Desc", desc)
editor.putString("Desc", desc); .apply();
editor.apply();
uploadActionInitiatedHandler.uploadActionInitiated(title, desc); uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
return true; return true;
@ -91,7 +95,6 @@ public class SingleUploadFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false); View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView); ButterKnife.bind(this, rootView);
ArrayList<String> licenseItems = new ArrayList<>(); ArrayList<String> licenseItems = new ArrayList<>();
licenseItems.add(getString(R.string.license_name_cc0)); licenseItems.add(getString(R.string.license_name_cc0));
licenseItems.add(getString(R.string.license_name_cc_by)); 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_four));
licenseItems.add(getString(R.string.license_name_cc_by_sa_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); license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
// check if this is the first time we have uploaded // check if this is the first time we have uploaded
@ -172,9 +174,9 @@ public class SingleUploadFragment extends Fragment {
} }
setLicenseSummary(license); setLicenseSummary(license);
SharedPreferences.Editor editor = prefs.edit(); prefs.edit()
editor.putString(Prefs.DEFAULT_LICENSE, license); .putString(Prefs.DEFAULT_LICENSE, license)
editor.commit(); .commit();
} }
@OnTouch(R.id.share_license_summary) @OnTouch(R.id.share_license_summary)
@ -182,7 +184,7 @@ public class SingleUploadFragment extends Fragment {
if (motionEvent.getActionMasked() == ACTION_DOWN) { if (motionEvent.getActionMasked() == ACTION_DOWN) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(Utils.licenseUrlFor(license))); intent.setData(Uri.parse(licenseUrlFor(license)));
startActivity(intent); startActivity(intent);
return true; return true;
} else { } else {
@ -193,9 +195,8 @@ public class SingleUploadFragment extends Fragment {
@OnClick(R.id.titleDescButton) @OnClick(R.id.titleDescButton)
void setTitleDescButton() { void setTitleDescButton() {
//Retrieve last title and desc entered //Retrieve last title and desc entered
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); String title = prefs.getString("Title", "");
String title = titleDesc.getString("Title", ""); String desc = prefs.getString("Desc", "");
String desc = titleDesc.getString("Desc", "");
Timber.d("Title: %s, Desc: %s", title, desc); Timber.d("Title: %s, Desc: %s", title, desc);
titleEdit.setText(title); 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 { public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description); void uploadActionInitiated(String title, String description);
} }

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
@ -9,31 +10,36 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Date; import java.util.Date;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService; 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.contributions.Contribution;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber; import timber.log.Timber;
public class UploadController { public class UploadController {
private UploadService uploadService; private UploadService uploadService;
private final CommonsApplication app; private SessionManager sessionManager;
private Context context;
private SharedPreferences prefs;
public interface ContributionUploadProgress { public interface ContributionUploadProgress {
void onUploadStarted(Contribution contribution); void onUploadStarted(Contribution contribution);
} }
public UploadController() { public UploadController(SessionManager sessionManager, Context context, SharedPreferences sharedPreferences) {
app = CommonsApplication.getInstance(); this.sessionManager = sessionManager;
this.context = context;
this.prefs = sharedPreferences;
} }
private boolean isUploadServiceConnected; private boolean isUploadServiceConnected;
@ -52,15 +58,15 @@ public class UploadController {
}; };
public void prepareService() { public void prepareService() {
Intent uploadServiceIntent = new Intent(app, UploadService.class); Intent uploadServiceIntent = new Intent(context, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
app.startService(uploadServiceIntent); context.startService(uploadServiceIntent);
app.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
} }
public void cleanup() { public void cleanup() {
if (isUploadServiceConnected) { if (isUploadServiceConnected) {
app.unbindService(uploadServiceConnection); context.unbindService(uploadServiceConnection);
} }
} }
@ -68,7 +74,9 @@ public class UploadController {
Contribution contribution; Contribution contribution;
//TODO: Modify this to include coords //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.setTag("mimeType", mimeType);
contribution.setSource(source); contribution.setSource(source);
@ -78,12 +86,9 @@ public class UploadController {
} }
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
//Set creator, desc, and license //Set creator, desc, and license
if (TextUtils.isEmpty(contribution.getCreator())) { if (TextUtils.isEmpty(contribution.getCreator())) {
contribution.setCreator(app.getCurrentAccount().name); contribution.setCreator(sessionManager.getCurrentAccount().name);
} }
if (contribution.getDescription() == null) { if (contribution.getDescription() == null) {
@ -102,14 +107,15 @@ public class UploadController {
@Override @Override
protected Contribution doInBackground(Void... voids /* stare into you */) { protected Contribution doInBackground(Void... voids /* stare into you */) {
long length; long length;
ContentResolver contentResolver = context.getContentResolver();
try { try {
if (contribution.getDataLength() <= 0) { if (contribution.getDataLength() <= 0) {
length = app.getContentResolver() length = contentResolver
.openAssetFileDescriptor(contribution.getLocalUri(), "r") .openAssetFileDescriptor(contribution.getLocalUri(), "r")
.getLength(); .getLength();
if (length == -1) { if (length == -1) {
// Let us find out the long way! // Let us find out the long way!
length = Utils.countBytes(app.getContentResolver() length = countBytes(contentResolver
.openInputStream(contribution.getLocalUri())); .openInputStream(contribution.getLocalUri()));
} }
contribution.setDataLength(length); contribution.setDataLength(length);
@ -126,7 +132,7 @@ public class UploadController {
Boolean imagePrefix = false; Boolean imagePrefix = false;
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) { if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
mimeType = app.getContentResolver().getType(contribution.getLocalUri()); mimeType = contentResolver.getType(contribution.getLocalUri());
} }
if (mimeType != null) { if (mimeType != null) {
@ -137,7 +143,7 @@ public class UploadController {
if (imagePrefix && contribution.getDateCreated() == null) { if (imagePrefix && contribution.getDateCreated() == null) {
Timber.d("local uri " + contribution.getLocalUri()); 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); new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) { if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
cursor.moveToFirst(); cursor.moveToFirst();
@ -165,4 +171,14 @@ public class UploadController {
} }
}.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy }.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;
}
} }

View file

@ -8,6 +8,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -22,10 +23,14 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
@ -45,12 +50,13 @@ public class UploadService extends HandlerService<Contribution> {
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source"; public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign"; 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 NotificationManager notificationManager;
private ContentProviderClient contributionsProviderClient; private ContentProviderClient contributionsProviderClient;
private CommonsApplication app;
private NotificationCompat.Builder curProgressNotification; private NotificationCompat.Builder curProgressNotification;
private int toUpload; private int toUpload;
// The file names of unfinished uploads, used to prevent overwriting // The file names of unfinished uploads, used to prevent overwriting
@ -118,7 +124,6 @@ public class UploadService extends HandlerService<Contribution> {
super.onCreate(); super.onCreate();
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
app = CommonsApplication.getInstance();
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
} }
@ -180,8 +185,6 @@ public class UploadService extends HandlerService<Contribution> {
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
private void uploadContribution(Contribution contribution) { private void uploadContribution(Contribution contribution) {
MediaWikiApi api = app.getMWApi();
InputStream file; InputStream file;
String notificationTag = contribution.getLocalUri().toString(); String notificationTag = contribution.getLocalUri().toString();
@ -228,9 +231,9 @@ public class UploadService extends HandlerService<Contribution> {
filename = findUniqueFilename(filename); filename = findUniqueFilename(filename);
unfinishedUploads.add(filename); unfinishedUploads.add(filename);
} }
if (!api.validateLogin()) { if (!mwApi.validateLogin()) {
// Need to revalidate! // Need to revalidate!
if (app.revalidateAuthToken()) { if (sessionManager.revalidateAuthToken()) {
Timber.d("Successfully revalidated token!"); Timber.d("Successfully revalidated token!");
} else { } else {
Timber.d("Unable to revalidate :("); Timber.d("Unable to revalidate :(");
@ -246,7 +249,7 @@ public class UploadService extends HandlerService<Contribution> {
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()), getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
contribution contribution
); );
UploadResult uploadResult = 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()); Timber.d("Response is %s", uploadResult.toString());
@ -255,8 +258,8 @@ public class UploadService extends HandlerService<Contribution> {
String resultStatus = uploadResult.getResultStatus(); String resultStatus = uploadResult.getResultStatus();
if (!resultStatus.equals("Success")) { if (!resultStatus.equals("Success")) {
showFailedNotification(contribution); showFailedNotification(contribution);
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("source", contribution.getSource()) .param("source", contribution.getSource())
.param("multiple", contribution.getMultiple()) .param("multiple", contribution.getMultiple())
.param("result", uploadResult.getErrorCode()) .param("result", uploadResult.getErrorCode())
@ -269,8 +272,8 @@ public class UploadService extends HandlerService<Contribution> {
contribution.setDateUploaded(uploadResult.getDateUploaded()); contribution.setDateUploaded(uploadResult.getDateUploaded());
contribution.save(); contribution.save();
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs)
.param("username", app.getCurrentAccount().name) .param("username", sessionManager.getCurrentAccount().name)
.param("source", contribution.getSource()) //FIXME .param("source", contribution.getSource()) //FIXME
.param("filename", contribution.getFilename()) .param("filename", contribution.getFilename())
.param("multiple", contribution.getMultiple()) .param("multiple", contribution.getMultiple())
@ -287,7 +290,7 @@ public class UploadService extends HandlerService<Contribution> {
toUpload--; toUpload--;
if (toUpload == 0) { if (toUpload == 0) {
// Sync modifications right after all uplaods are processed // Sync modifications right after all uplaods are processed
ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle()); ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
stopForeground(true); stopForeground(true);
} }
} }
@ -310,7 +313,6 @@ public class UploadService extends HandlerService<Contribution> {
} }
private String findUniqueFilename(String fileName) throws IOException { private String findUniqueFilename(String fileName) throws IOException {
MediaWikiApi api = app.getMWApi();
String sequenceFileName; String sequenceFileName;
for (int sequenceNumber = 1; true; sequenceNumber++) { for (int sequenceNumber = 1; true; sequenceNumber++) {
if (sequenceNumber == 1) { if (sequenceNumber == 1) {
@ -326,7 +328,7 @@ public class UploadService extends HandlerService<Contribution> {
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2"); sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
} }
} }
if (!api.fileExistsWithName(sequenceFileName) if (!mwApi.fileExistsWithName(sequenceFileName)
&& !unfinishedUploads.contains(sequenceFileName)) { && !unfinishedUploads.contains(sequenceFileName)) {
break; break;
} }

View file

@ -7,7 +7,6 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;

View file

@ -1,25 +1,60 @@
package fr.free.nrw.commons; 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 com.squareup.leakcanary.RefWatcher;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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.nearby.NearbyPlaces;
import fr.free.nrw.commons.upload.UploadController;
public class TestCommonsApplication extends CommonsApplication { public class TestCommonsApplication extends CommonsApplication {
CommonsApplicationComponent mockApplicationComponent;
@Mock @Mock
private AndroidInjector<Activity> mockInjector; CommonsApplicationModule commonsApplicationModule;
@Mock @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<String, String> lruCache;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate();
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
super.onCreate();
} }
@Override @Override
@ -29,12 +64,119 @@ public class TestCommonsApplication extends CommonsApplication {
} }
@Override @Override
public AndroidInjector<Activity> activityInjector() { public CommonsApplicationComponent injector() {
return mockInjector; if (mockApplicationComponent == null) {
mockApplicationComponent = DaggerCommonsApplicationComponent.builder()
.appModule(new CommonsApplicationModule(this) {
@Override
public AccountUtil providesAccountUtil() {
return accountUtil;
} }
@Override @Override
public synchronized NearbyPlaces getNearbyPlaces() { public SharedPreferences providesApplicationSharedPreferences() {
return mockNearbyPlaces; 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<String, String> provideLruCache() {
return lruCache;
}
}).build();
}
return mockApplicationComponent;
}
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<String, String> getLruCache() {
return lruCache;
} }
} }

View file

@ -1,12 +1,9 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.app.Activity; import android.app.Activity;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@ -14,14 +11,11 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController; import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import dagger.android.AndroidInjector;
import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.TestCommonsApplication; import fr.free.nrw.commons.TestCommonsApplication;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class) @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); private static final LatLng ST_LOUIS_MO_LAT_LNG = new LatLng(38.627003, -90.199402, 0);
@Mock
LocationServiceManager mockLocationManager;
private ActivityController<NearbyActivity> activityController; private ActivityController<NearbyActivity> activityController;
private NearbyActivity nearbyActivity; private NearbyActivity nearbyActivity;
@ -40,13 +31,11 @@ public class NearbyActivityTest {
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
AndroidInjector<Activity> injector = ((TestCommonsApplication) RuntimeEnvironment.application).activityInjector(); TestCommonsApplication application = (TestCommonsApplication) RuntimeEnvironment.application;
Mockito.doNothing().when(injector).inject(isA(NearbyActivity.class)); when(application.getLocationServiceManager().getLastLocation()).thenReturn(ST_LOUIS_MO_LAT_LNG);
when(mockLocationManager.getLastLocation()).thenReturn(ST_LOUIS_MO_LAT_LNG);
activityController = Robolectric.buildActivity(NearbyActivity.class); activityController = Robolectric.buildActivity(NearbyActivity.class);
nearbyActivity = activityController.get(); nearbyActivity = activityController.get();
nearbyActivity.locationManager = mockLocationManager;
} }
@Test @Test

View file

@ -1,9 +1,8 @@
package fr.free.nrw.commons package fr.free.nrw.commons
import org.hamcrest.CoreMatchers.`is` as _is
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.hamcrest.CoreMatchers.`is` as _is
class UtilsTest { class UtilsTest {
@Test fun `strip nothing from non-localized string`() { @Test fun `strip nothing from non-localized string`() {

View file

@ -10,7 +10,6 @@ buildscript {
classpath "com.android.tools.build:gradle:${project.gradleVersion}" classpath "com.android.tools.build:gradle:${project.gradleVersion}"
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

41
dependency-injection.md Normal file
View file

@ -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);
}
```

Binary file not shown.

View file

@ -1,6 +1,5 @@
#Thu Nov 02 02:18:35 IST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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