mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-30 22:34:02 +01:00 
			
		
		
		
	Merge branch 'master' into removeCsv
This commit is contained in:
		
						commit
						169a4edc46
					
				
					 130 changed files with 1805 additions and 1107 deletions
				
			
		|  | @ -1,7 +1,5 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,19 +1,8 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.accounts.Account; | ||||
| import android.accounts.AccountManager; | ||||
| import android.accounts.AccountManagerCallback; | ||||
| import android.accounts.AccountManagerFuture; | ||||
| import android.accounts.AuthenticatorException; | ||||
| import android.accounts.OperationCanceledException; | ||||
| import android.app.Activity; | ||||
| import android.app.Application; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.util.LruCache; | ||||
| 
 | ||||
| import com.facebook.drawee.backends.pipeline.Fresco; | ||||
| import com.facebook.stetho.Stetho; | ||||
|  | @ -25,23 +14,23 @@ import org.acra.ReportingInteractionMode; | |||
| import org.acra.annotation.ReportsCrashes; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import dagger.android.DispatchingAndroidInjector; | ||||
| import dagger.android.HasActivityInjector; | ||||
| import fr.free.nrw.commons.auth.AccountUtil; | ||||
| import fr.free.nrw.commons.caching.CacheController; | ||||
| import dagger.android.AndroidInjector; | ||||
| import dagger.android.DaggerApplication; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.data.Category; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import fr.free.nrw.commons.di.DaggerAppComponent; | ||||
| import fr.free.nrw.commons.di.CommonsApplicationComponent; | ||||
| import fr.free.nrw.commons.di.CommonsApplicationModule; | ||||
| import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent; | ||||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.nearby.NearbyPlaces; | ||||
| import fr.free.nrw.commons.utils.FileUtils; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| // TODO: Use ProGuard to rip out reporting when publishing | ||||
|  | @ -53,9 +42,13 @@ import timber.log.Timber; | |||
|         resDialogCommentPrompt = R.string.crash_dialog_comment_prompt, | ||||
|         resDialogOkToast = R.string.crash_dialog_ok_toast | ||||
| ) | ||||
| public class CommonsApplication extends Application implements HasActivityInjector { | ||||
| public class CommonsApplication extends DaggerApplication { | ||||
| 
 | ||||
|     private Account currentAccount = null; // Unlike a savings account... | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject DBOpenHelper dbOpenHelper; | ||||
|     @Inject @Named("default_preferences") SharedPreferences defaultPrefs; | ||||
|     @Inject @Named("application_preferences") SharedPreferences applicationPrefs; | ||||
|     @Inject @Named("prefs") SharedPreferences otherPrefs; | ||||
| 
 | ||||
|     public static final Object[] EVENT_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5334329L}; | ||||
|     public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5257721L}; | ||||
|  | @ -64,84 +57,23 @@ public class CommonsApplication extends Application implements HasActivityInject | |||
| 
 | ||||
|     public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app"; | ||||
| 
 | ||||
|     public static final String FEEDBACK_EMAIL = "commons-app-android-private@googlegroups.com"; | ||||
|     public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com"; | ||||
|     public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback"; | ||||
| 
 | ||||
|     @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector; | ||||
|     @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 CommonsApplicationComponent component; | ||||
|     private RefWatcher refWatcher; | ||||
| 
 | ||||
|     /** | ||||
|      * This should not be called by ANY application code (other than the magic Android glue) | ||||
|      * Use CommonsApplication.getInstance() instead to get the singleton. | ||||
|      */ | ||||
|     public CommonsApplication() { | ||||
|         CommonsApplication.instance = this; | ||||
|     } | ||||
| 
 | ||||
|     public static CommonsApplication getInstance() { | ||||
|         if (instance == null) { | ||||
|             instance = new CommonsApplication(); | ||||
|         } | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     public MediaWikiApi getMWApi() { | ||||
|         if (api == null) { | ||||
|             api = new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST); | ||||
|         } | ||||
|         return api; | ||||
|     } | ||||
| 
 | ||||
|     public CacheController getCacheData() { | ||||
|         if (cacheData == null) { | ||||
|             cacheData = new CacheController(); | ||||
|         } | ||||
|         return cacheData; | ||||
|     } | ||||
| 
 | ||||
|     public LruCache<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 | ||||
|     public void onCreate() { | ||||
|         super.onCreate(); | ||||
| 
 | ||||
|         Fresco.initialize(this); | ||||
|         if (setupLeakCanary() == RefWatcher.DISABLED) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         Timber.plant(new Timber.DebugTree()); | ||||
| 
 | ||||
|         DaggerAppComponent | ||||
|                 .builder() | ||||
|                 .application(this) | ||||
|                 .build() | ||||
|                 .inject(this); | ||||
| 
 | ||||
|         if (!BuildConfig.DEBUG) { | ||||
|             ACRA.init(this); | ||||
|         } else { | ||||
|  | @ -150,11 +82,6 @@ public class CommonsApplication extends Application implements HasActivityInject | |||
| 
 | ||||
|         // Fire progress callbacks for every 3% of uploaded content | ||||
|         System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0"); | ||||
| 
 | ||||
|         Fresco.initialize(this); | ||||
| 
 | ||||
|         //For caching area -> categories | ||||
|         cacheData  = new CacheController(); | ||||
|     } | ||||
| 
 | ||||
|     protected RefWatcher setupLeakCanary() { | ||||
|  | @ -169,43 +96,18 @@ public class CommonsApplication extends Application implements HasActivityInject | |||
|         return application.refWatcher; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return Account|null | ||||
|      */ | ||||
|     public Account getCurrentAccount() { | ||||
|         if (currentAccount == null) { | ||||
|             AccountManager accountManager = AccountManager.get(this); | ||||
|             Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType()); | ||||
|             if (allAccounts.length != 0) { | ||||
|                 currentAccount = allAccounts[0]; | ||||
|             } | ||||
|         } | ||||
|         return currentAccount; | ||||
|     } | ||||
|      | ||||
|     public Boolean revalidateAuthToken() { | ||||
|         AccountManager accountManager = AccountManager.get(this); | ||||
|         Account curAccount = getCurrentAccount(); | ||||
|         | ||||
|         if (curAccount == null) { | ||||
|             return false; // This should never happen | ||||
|         } | ||||
| 
 | ||||
|         accountManager.invalidateAuthToken(AccountUtil.accountType(), mediaWikiApi.getAuthCookie()); | ||||
|         try { | ||||
|             String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false); | ||||
|             mediaWikiApi.setAuthCookie(authCookie); | ||||
|             return true; | ||||
|         } catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|     @Override | ||||
|      protected AndroidInjector<? extends DaggerApplication> applicationInjector() { | ||||
|         return injector(); | ||||
|     } | ||||
| 
 | ||||
|     public boolean deviceHasCamera() { | ||||
|         PackageManager pm = getPackageManager(); | ||||
|         return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) | ||||
|                 || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); | ||||
|     public CommonsApplicationComponent injector() { | ||||
|         if (component == null) { | ||||
|             component = DaggerCommonsApplicationComponent.builder() | ||||
|                     .appModule(new CommonsApplicationModule(this)) | ||||
|                     .build(); | ||||
|         } | ||||
|         return component; | ||||
|     } | ||||
| 
 | ||||
|     public void clearApplicationData(Context context, LogoutListener logoutListener) { | ||||
|  | @ -220,67 +122,25 @@ public class CommonsApplication extends Application implements HasActivityInject | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         AccountManager accountManager = AccountManager.get(this); | ||||
|         Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType()); | ||||
| 
 | ||||
|         AccountManagerCallback<Boolean> amCallback = new AccountManagerCallback<Boolean>() { | ||||
|              | ||||
|             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) { | ||||
|         sessionManager.clearAllAccounts() | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe(() -> { | ||||
|                     Timber.d("All accounts have been removed"); | ||||
|                     //TODO: fix preference manager | ||||
|                     PreferenceManager.getDefaultSharedPreferences(getInstance()) | ||||
|                             .edit().clear().commit(); | ||||
|                     SharedPreferences preferences = context | ||||
|                             .getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE); | ||||
|                     preferences.edit().clear().commit(); | ||||
|                     context.getSharedPreferences("prefs", Context.MODE_PRIVATE) | ||||
|                             .edit().clear().commit(); | ||||
|                     preferences.edit().putBoolean("firstrun", false).apply(); | ||||
|                     defaultPrefs.edit().clear().commit(); | ||||
|                     applicationPrefs.edit().clear().commit(); | ||||
|                     applicationPrefs.edit().putBoolean("firstrun", false).apply();otherPrefs.edit().clear().commit(); | ||||
|                     updateAllDatabases(); | ||||
|                     currentAccount = null; | ||||
| 
 | ||||
|                     logoutListener.onLogoutComplete(); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         for (Account account : allAccounts) { | ||||
|             accountManager.removeAccount(account, amCallback, null); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public DispatchingAndroidInjector<Activity> activityInjector() { | ||||
|         return dispatchingActivityInjector; | ||||
|                 }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Deletes all tables and re-creates them. | ||||
|      */ | ||||
|     public void updateAllDatabases() { | ||||
|         DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper(); | ||||
|     private void updateAllDatabases() { | ||||
|         dbOpenHelper.getReadableDatabase().close(); | ||||
|         SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.app.Service; | ||||
| import android.content.Intent; | ||||
| import android.os.Binder; | ||||
| import android.os.Handler; | ||||
|  | @ -9,7 +8,9 @@ import android.os.IBinder; | |||
| import android.os.Looper; | ||||
| import android.os.Message; | ||||
| 
 | ||||
| public abstract class HandlerService<T> extends Service { | ||||
| import dagger.android.DaggerService; | ||||
| 
 | ||||
| public abstract class HandlerService<T> extends DaggerService { | ||||
|     private volatile Looper threadLooper; | ||||
|     private volatile ServiceHandler threadHandler; | ||||
|     private String serviceName; | ||||
|  |  | |||
|  | @ -5,7 +5,9 @@ import android.content.res.Resources; | |||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import org.xmlpull.v1.XmlPullParser; | ||||
| import org.xmlpull.v1.XmlPullParserException; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.HashMap; | ||||
| import java.util.Locale; | ||||
|  | @ -19,7 +21,7 @@ public class LicenseList { | |||
|         res = activity.getResources(); | ||||
|         XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses); | ||||
|         String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses"; | ||||
|         while (Utils.xmlFastForward(parser, namespace, "license")) { | ||||
|         while (xmlFastForward(parser, namespace, "license")) { | ||||
|             String id = parser.getAttributeValue(null, "id"); | ||||
|             String template = parser.getAttributeValue(null, "template"); | ||||
|             String url = parser.getAttributeValue(null, "url"); | ||||
|  | @ -60,4 +62,34 @@ public class LicenseList { | |||
|                 + nameIdForTemplate(template), null, null); | ||||
|         return (nameId != 0) ? res.getString(nameId) : template; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fast-forward an XmlPullParser to the next instance of the given element | ||||
|      * in the input stream (namespaced). | ||||
|      * | ||||
|      * @param parser | ||||
|      * @param namespace | ||||
|      * @param element | ||||
|      * @return true on match, false on failure | ||||
|      */ | ||||
|     private boolean xmlFastForward(XmlPullParser parser, String namespace, String element) { | ||||
|         try { | ||||
|             while (parser.next() != XmlPullParser.END_DOCUMENT) { | ||||
|                 if (parser.getEventType() == XmlPullParser.START_TAG && | ||||
|                         parser.getNamespace().equals(namespace) && | ||||
|                         parser.getName().equals(element)) { | ||||
|                     // We found it! | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } catch (XmlPullParserException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -11,12 +11,12 @@ import org.xml.sax.SAXException; | |||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | ||||
|  | @ -33,45 +33,39 @@ import timber.log.Timber; | |||
|  * which are not intrinsic to the media and may change due to editing. | ||||
|  */ | ||||
| public class MediaDataExtractor { | ||||
|     private final MediaWikiApi mediaWikiApi; | ||||
|     private boolean fetched; | ||||
| 
 | ||||
|     private String filename; | ||||
|     private ArrayList<String> categories; | ||||
|     private Map<String, String> descriptions; | ||||
|     private String license; | ||||
|     private @Nullable LatLng coordinates; | ||||
|     private LicenseList licenseList; | ||||
| 
 | ||||
|     /** | ||||
|      * @param filename of the target media object, should include 'File:' prefix | ||||
|      */ | ||||
|     public MediaDataExtractor(String filename, LicenseList licenseList) { | ||||
|         this.filename = filename; | ||||
|         categories = new ArrayList<>(); | ||||
|         descriptions = new HashMap<>(); | ||||
|         fetched = false; | ||||
|         this.licenseList = licenseList; | ||||
|     @Inject | ||||
|     public MediaDataExtractor(MediaWikiApi mwApi) { | ||||
|         this.categories = new ArrayList<>(); | ||||
|         this.descriptions = new HashMap<>(); | ||||
|         this.fetched = false; | ||||
|         this.mediaWikiApi = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|     /* | ||||
|      * Actually fetch the data over the network. | ||||
|      * todo: use local caching? | ||||
|      * | ||||
|      * Warning: synchronous i/o, call on a background thread | ||||
|      */ | ||||
|     public void fetch() throws IOException { | ||||
|     public void fetch(String filename, LicenseList licenseList) throws IOException { | ||||
|         if (fetched) { | ||||
|             throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again."); | ||||
|         } | ||||
| 
 | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         MediaResult result = api.fetchMediaByFilename(filename); | ||||
|         MediaResult result = mediaWikiApi.fetchMediaByFilename(filename); | ||||
| 
 | ||||
|         // In-page category links are extracted from source, as XML doesn't cover [[links]] | ||||
|         extractCategories(result.getWikiSource()); | ||||
| 
 | ||||
|         // Description template info is extracted from preprocessor XML | ||||
|         processWikiParseTree(result.getParseTreeXmlSource()); | ||||
|         processWikiParseTree(result.getParseTreeXmlSource(), licenseList); | ||||
|         fetched = true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -90,7 +84,7 @@ public class MediaDataExtractor { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void processWikiParseTree(String source) throws IOException { | ||||
|     private void processWikiParseTree(String source, LicenseList licenseList) throws IOException { | ||||
|         Document doc; | ||||
|         try { | ||||
|             DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); | ||||
|  |  | |||
|  | @ -7,16 +7,17 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi; | |||
| 
 | ||||
| class MediaThumbnailFetchTask extends AsyncTask<String, String, String> { | ||||
|     protected final Media media; | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
| 
 | ||||
|     public MediaThumbnailFetchTask(@NonNull Media media) { | ||||
|     public MediaThumbnailFetchTask(@NonNull Media media, MediaWikiApi mwApi) { | ||||
|         this.media = media; | ||||
|         this.mediaWikiApi = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected String doInBackground(String... params) { | ||||
|         try { | ||||
|             MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|             return api.findThumbnailByFilename(params[0]); | ||||
|             return mediaWikiApi.findThumbnailByFilename(params[0]); | ||||
|         } catch (Exception e) { | ||||
|             // Do something better! | ||||
|         } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import android.content.Context; | |||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.graphics.drawable.VectorDrawableCompat; | ||||
| import android.support.v4.util.LruCache; | ||||
| import android.text.TextUtils; | ||||
| import android.util.AttributeSet; | ||||
| import android.widget.Toast; | ||||
|  | @ -11,9 +12,15 @@ import android.widget.Toast; | |||
| import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; | ||||
| import com.facebook.drawee.view.SimpleDraweeView; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class MediaWikiImageView extends SimpleDraweeView { | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject LruCache<String, String> thumbnailUrlCache; | ||||
| 
 | ||||
|     private ThumbnailFetchTask currentThumbnailTask; | ||||
| 
 | ||||
|     public MediaWikiImageView(Context context) { | ||||
|  | @ -39,11 +46,11 @@ public class MediaWikiImageView extends SimpleDraweeView { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) { | ||||
|             setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename())); | ||||
|         if (thumbnailUrlCache.get(media.getFilename()) != null) { | ||||
|             setImageUrl(thumbnailUrlCache.get(media.getFilename())); | ||||
|         } else { | ||||
|             setImageUrl(null); | ||||
|             currentThumbnailTask = new ThumbnailFetchTask(media); | ||||
|             currentThumbnailTask = new ThumbnailFetchTask(media, mwApi); | ||||
|             currentThumbnailTask.execute(media.getFilename()); | ||||
|         } | ||||
|     } | ||||
|  | @ -57,6 +64,7 @@ public class MediaWikiImageView extends SimpleDraweeView { | |||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         ((CommonsApplication) getContext().getApplicationContext()).injector().inject(this); | ||||
|         setHierarchy(GenericDraweeHierarchyBuilder | ||||
|                 .newInstance(getResources()) | ||||
|                 .setPlaceholderImage(VectorDrawableCompat.create(getResources(), | ||||
|  | @ -71,8 +79,8 @@ public class MediaWikiImageView extends SimpleDraweeView { | |||
|     } | ||||
| 
 | ||||
|     private class ThumbnailFetchTask extends MediaThumbnailFetchTask { | ||||
|         ThumbnailFetchTask(@NonNull Media media) { | ||||
|             super(media); | ||||
|         ThumbnailFetchTask(@NonNull Media media, @NonNull MediaWikiApi mwApi) { | ||||
|             super(media, mwApi); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|  | @ -85,7 +93,7 @@ public class MediaWikiImageView extends SimpleDraweeView { | |||
|             } else { | ||||
|                 // only cache meaningful thumbnails received from network. | ||||
|                 try { | ||||
|                     CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result); | ||||
|                     thumbnailUrlCache.put(media.getFilename(), result); | ||||
|                 } catch (NullPointerException npe) { | ||||
|                     Timber.e("error when adding pic to cache " + npe); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,102 +1,26 @@ | |||
| package fr.free.nrw.commons; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.os.Build; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.Html; | ||||
| import android.text.Spanned; | ||||
| 
 | ||||
| import org.apache.commons.codec.binary.Hex; | ||||
| import org.apache.commons.codec.digest.DigestUtils; | ||||
| import org.w3c.dom.Node; | ||||
| import org.xmlpull.v1.XmlPullParser; | ||||
| import org.xmlpull.v1.XmlPullParserException; | ||||
| 
 | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.BufferedReader; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.StringWriter; | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.math.BigInteger; | ||||
| import java.net.URLEncoder; | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
| import java.util.TimeZone; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import javax.xml.transform.Transformer; | ||||
| import javax.xml.transform.TransformerConfigurationException; | ||||
| import javax.xml.transform.TransformerException; | ||||
| import javax.xml.transform.TransformerFactory; | ||||
| import javax.xml.transform.TransformerFactoryConfigurationError; | ||||
| import javax.xml.transform.dom.DOMSource; | ||||
| import javax.xml.transform.stream.StreamResult; | ||||
| 
 | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class Utils { | ||||
| 
 | ||||
|     // Get SHA1 of file from input stream | ||||
|     public static String getSHA1(InputStream is) { | ||||
| 
 | ||||
|         MessageDigest digest; | ||||
|         try { | ||||
|             digest = MessageDigest.getInstance("SHA1"); | ||||
|         } catch (NoSuchAlgorithmException e) { | ||||
|             Timber.e(e, "Exception while getting Digest"); | ||||
|             return ""; | ||||
|         } | ||||
| 
 | ||||
|         byte[] buffer = new byte[8192]; | ||||
|         int read; | ||||
|         try { | ||||
|             while ((read = is.read(buffer)) > 0) { | ||||
|                 digest.update(buffer, 0, read); | ||||
|             } | ||||
|             byte[] md5sum = digest.digest(); | ||||
|             BigInteger bigInt = new BigInteger(1, md5sum); | ||||
|             String output = bigInt.toString(16); | ||||
|             // Fill to 40 chars | ||||
|             output = String.format("%40s", output).replace(' ', '0'); | ||||
|             Timber.i("File SHA1: %s", output); | ||||
| 
 | ||||
|             return output; | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception"); | ||||
|             return ""; | ||||
|         } finally { | ||||
|             try { | ||||
|                 is.close(); | ||||
|             } catch (IOException e) { | ||||
|                 Timber.e(e, "Exception on closing MD5 input stream"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fix Html.fromHtml is deprecated problem | ||||
|      * | ||||
|      * @param source provided Html string | ||||
|      * @return returned Spanned of appropriate method according to version check | ||||
|      */ | ||||
|     public static Spanned fromHtml(String source) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|             return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY); | ||||
|         } else { | ||||
|             //noinspection deprecation | ||||
|             return Html.fromHtml(source); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Strips localization symbols from a string. | ||||
|      * Removes the suffix after "@" and quotes. | ||||
|  | @ -113,49 +37,12 @@ public class Utils { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static Date parseMWDate(String mwDate) { | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC | ||||
|         try { | ||||
|             return isoFormat.parse(mwDate); | ||||
|         } catch (ParseException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static String toMWDate(Date date) { | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC | ||||
|         isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); | ||||
|         return isoFormat.format(date); | ||||
|     } | ||||
| 
 | ||||
|     public static String makeThumbBaseUrl(@NonNull String filename) { | ||||
|         String name = new PageTitle(filename).getPrefixedText(); | ||||
|         String sha = new String(Hex.encodeHex(DigestUtils.md5(name))); | ||||
|         return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name)); | ||||
|     } | ||||
| 
 | ||||
|     public static String getStringFromDOM(Node dom) { | ||||
|         Transformer transformer = null; | ||||
|         try { | ||||
|             transformer = TransformerFactory.newInstance().newTransformer(); | ||||
|         } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         StringWriter outputStream = new StringWriter(); | ||||
|         DOMSource domSource = new DOMSource(dom); | ||||
|         StreamResult strResult = new StreamResult(outputStream); | ||||
| 
 | ||||
|         try { | ||||
|             transformer.transform(domSource, strResult); | ||||
|         } catch (TransformerException e) { | ||||
|             // TODO Auto-generated catch block | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return outputStream.toString(); | ||||
|     } | ||||
| 
 | ||||
|     public static String urlEncode(String url) { | ||||
|         try { | ||||
|             return URLEncoder.encode(url, "utf-8"); | ||||
|  | @ -164,39 +51,10 @@ public class Utils { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static long countBytes(InputStream stream) throws IOException { | ||||
|         long count = 0; | ||||
|         BufferedInputStream bis = new BufferedInputStream(stream); | ||||
|         while (bis.read() != -1) { | ||||
|             count++; | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     public static String capitalize(String string) { | ||||
|         return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1); | ||||
|     } | ||||
| 
 | ||||
|     public static String licenseTemplateFor(String license) { | ||||
|         switch (license) { | ||||
|             case Prefs.Licenses.CC_BY_3: | ||||
|                 return "{{self|cc-by-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_4: | ||||
|                 return "{{self|cc-by-4.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA_3: | ||||
|                 return "{{self|cc-by-sa-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA_4: | ||||
|                 return "{{self|cc-by-sa-4.0}}"; | ||||
|             case Prefs.Licenses.CC0: | ||||
|                 return "{{self|cc-zero}}"; | ||||
|             case Prefs.Licenses.CC_BY: | ||||
|                 return "{{self|cc-by-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA: | ||||
|                 return "{{self|cc-by-sa-3.0}}"; | ||||
|         } | ||||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| 
 | ||||
|     public static int licenseNameFor(String license) { | ||||
|         switch (license) { | ||||
|             case Prefs.Licenses.CC_BY_3: | ||||
|  | @ -217,51 +75,6 @@ public class Utils { | |||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| 
 | ||||
|     public static String licenseUrlFor(String license) { | ||||
|         switch (license) { | ||||
|             case Prefs.Licenses.CC_BY_3: | ||||
|                 return "https://creativecommons.org/licenses/by/3.0/"; | ||||
|             case Prefs.Licenses.CC_BY_4: | ||||
|                 return "https://creativecommons.org/licenses/by/4.0/"; | ||||
|             case Prefs.Licenses.CC_BY_SA_3: | ||||
|                 return "https://creativecommons.org/licenses/by-sa/3.0/"; | ||||
|             case Prefs.Licenses.CC_BY_SA_4: | ||||
|                 return "https://creativecommons.org/licenses/by-sa/4.0/"; | ||||
|             case Prefs.Licenses.CC0: | ||||
|                 return "https://creativecommons.org/publicdomain/zero/1.0/"; | ||||
|         } | ||||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fast-forward an XmlPullParser to the next instance of the given element | ||||
|      * in the input stream (namespaced). | ||||
|      * | ||||
|      * @param parser | ||||
|      * @param namespace | ||||
|      * @param element | ||||
|      * @return true on match, false on failure | ||||
|      */ | ||||
|     public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) { | ||||
|         try { | ||||
|             while (parser.next() != XmlPullParser.END_DOCUMENT) { | ||||
|                 if (parser.getEventType() == XmlPullParser.START_TAG | ||||
|                         && parser.getNamespace().equals(namespace) | ||||
|                         && parser.getName().equals(element)) { | ||||
|                     // We found it! | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } catch (XmlPullParserException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static String fixExtension(String title, String extension) { | ||||
|         Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE); | ||||
| 
 | ||||
|  | @ -277,10 +90,6 @@ public class Utils { | |||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isNullOrWhiteSpace(String value) { | ||||
|         return value == null || value.trim().isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isDarkTheme(Context context) { | ||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false); | ||||
|     } | ||||
|  |  | |||
|  | @ -4,21 +4,31 @@ import android.accounts.Account; | |||
| import android.accounts.AccountAuthenticatorResponse; | ||||
| import android.accounts.AccountManager; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
| import fr.free.nrw.commons.modifications.ModificationsContentProvider; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION; | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; | ||||
| 
 | ||||
| public class AccountUtil { | ||||
| 
 | ||||
|     public static void createAccount(@Nullable AccountAuthenticatorResponse response, | ||||
|                                      String username, String password) { | ||||
|     public static final String ACCOUNT_TYPE = "fr.free.nrw.commons"; | ||||
|     private final Context context; | ||||
| 
 | ||||
|         Account account = new Account(username, accountType()); | ||||
|     public AccountUtil(Context context) { | ||||
|         this.context = context; | ||||
|     } | ||||
| 
 | ||||
|     public void createAccount(@Nullable AccountAuthenticatorResponse response, | ||||
|                               String username, String password) { | ||||
| 
 | ||||
|         Account account = new Account(username, ACCOUNT_TYPE); | ||||
|         boolean created = accountManager().addAccountExplicitly(account, password, null); | ||||
| 
 | ||||
|         Timber.d("account creation " + (created ? "successful" : "failure")); | ||||
|  | @ -26,8 +36,8 @@ public class AccountUtil { | |||
|         if (created) { | ||||
|             if (response != null) { | ||||
|                 Bundle bundle = new Bundle(); | ||||
|                 bundle.putString(AccountManager.KEY_ACCOUNT_NAME, username); | ||||
|                 bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType()); | ||||
|                 bundle.putString(KEY_ACCOUNT_NAME, username); | ||||
|                 bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); | ||||
| 
 | ||||
| 
 | ||||
|                 response.onResult(bundle); | ||||
|  | @ -35,7 +45,7 @@ public class AccountUtil { | |||
| 
 | ||||
|         } else { | ||||
|             if (response != null) { | ||||
|                 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, ""); | ||||
|                 response.onError(ERROR_CODE_REMOTE_EXCEPTION, ""); | ||||
|             } | ||||
|             Timber.d("account creation failure"); | ||||
|         } | ||||
|  | @ -45,18 +55,8 @@ public class AccountUtil { | |||
|         ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public static String accountType() { | ||||
|         return "fr.free.nrw.commons"; | ||||
|     } | ||||
| 
 | ||||
|     private static AccountManager accountManager() { | ||||
|         return AccountManager.get(app()); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static CommonsApplication app() { | ||||
|         return CommonsApplication.getInstance(); | ||||
|     private AccountManager accountManager() { | ||||
|         return AccountManager.get(context); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -5,47 +5,50 @@ import android.accounts.AccountManager; | |||
| import android.accounts.AccountManagerFuture; | ||||
| import android.os.Bundle; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import fr.free.nrw.commons.theme.NavigationBaseActivity; | ||||
| import io.reactivex.Single; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; | ||||
| import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE; | ||||
| 
 | ||||
| public abstract class AuthenticatedActivity extends NavigationBaseActivity { | ||||
| 
 | ||||
|     private String accountType; | ||||
|     CommonsApplication app; | ||||
|     @Inject SessionManager sessionManager; | ||||
| 
 | ||||
|     private String authCookie; | ||||
| 
 | ||||
|     public AuthenticatedActivity() { | ||||
|         this.accountType = AccountUtil.accountType(); | ||||
|     } | ||||
| 
 | ||||
|     private void getAuthCookie(Account account, AccountManager accountManager) { | ||||
|         Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false)) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .doOnError(Timber::e) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe(this::onAuthCookieAcquired, throwable -> onAuthFailure()); | ||||
|                 .subscribe( | ||||
|                         this:: onAuthCookieAcquired, | ||||
|                         throwable -> onAuthFailure()); | ||||
|     } | ||||
| 
 | ||||
|     private void addAccount(AccountManager accountManager) { | ||||
|         Single.just(accountManager.addAccount(accountType, null, null, null, AuthenticatedActivity.this, null, null)) | ||||
|         Single.just(accountManager.addAccount(ACCOUNT_TYPE, null, null, | ||||
|                 null, AuthenticatedActivity.this, null, null)) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .map(AccountManagerFuture::getResult) | ||||
|                 .doOnEvent((bundle, throwable) -> { | ||||
|                     if (!bundle.containsKey(AccountManager.KEY_ACCOUNT_NAME)) { | ||||
|                     if (!bundle.containsKey(KEY_ACCOUNT_NAME)) { | ||||
|                         throw new RuntimeException("Bundle doesn't contain account-name key: " | ||||
|                                 + AccountManager.KEY_ACCOUNT_NAME); | ||||
|                                 + KEY_ACCOUNT_NAME); | ||||
|                     } | ||||
|                 }) | ||||
|                 .map(bundle -> bundle.getString(AccountManager.KEY_ACCOUNT_NAME)) | ||||
|                 .map(bundle -> bundle.getString(KEY_ACCOUNT_NAME)) | ||||
|                 .doOnError(Timber::e) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe(s -> { | ||||
|                             Account[] allAccounts = accountManager.getAccountsByType(accountType); | ||||
|                             Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE); | ||||
|                             Account curAccount = allAccounts[0]; | ||||
|                             getAuthCookie(curAccount, accountManager); | ||||
|                         }, | ||||
|  | @ -58,7 +61,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity { | |||
|             return; | ||||
|         } | ||||
|         AccountManager accountManager = AccountManager.get(this); | ||||
|         Account curAccount = app.getCurrentAccount(); | ||||
|         Account curAccount = sessionManager.getCurrentAccount(); | ||||
|         if (curAccount == null) { | ||||
|             addAccount(accountManager); | ||||
|         } else { | ||||
|  | @ -69,7 +72,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity { | |||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         app = CommonsApplication.getInstance(); | ||||
| 
 | ||||
|         if (savedInstanceState != null) { | ||||
|             authCookie = savedInstanceState.getString("authCookie"); | ||||
|         } | ||||
|  |  | |||
|  | @ -21,15 +21,19 @@ import android.widget.Button; | |||
| import android.widget.EditText; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.WelcomeActivity; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.theme.NavigationBaseActivity; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -40,6 +44,12 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
| 
 | ||||
|     public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username"; | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject AccountUtil accountUtil; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject @Named("application_preferences") SharedPreferences prefs; | ||||
|     @Inject @Named("default_preferences") SharedPreferences defaultPrefs; | ||||
| 
 | ||||
|     @BindView(R.id.loginButton) Button loginButton; | ||||
|     @BindView(R.id.signupButton) Button signupButton; | ||||
|     @BindView(R.id.loginUsername) EditText usernameEdit; | ||||
|  | @ -47,11 +57,8 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|     @BindView(R.id.loginTwoFactor) EditText twoFactorEdit; | ||||
|     @BindView(R.id.error_message_container) ViewGroup errorMessageContainer; | ||||
|     @BindView(R.id.error_message) TextView errorMessage; | ||||
| 
 | ||||
|     private CommonsApplication app; | ||||
|     ProgressDialog progressDialog; | ||||
|     private AppCompatDelegate delegate; | ||||
|     private SharedPreferences prefs = null; | ||||
|     private LoginTextWatcher textWatcher = new LoginTextWatcher(); | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -59,16 +66,13 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|         setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme); | ||||
|         getDelegate().installViewFactory(); | ||||
|         getDelegate().onCreate(savedInstanceState); | ||||
|         AndroidInjection.inject(this); | ||||
|         super.onCreate(savedInstanceState); | ||||
| 
 | ||||
|         app = CommonsApplication.getInstance(); | ||||
| 
 | ||||
|         setContentView(R.layout.activity_login); | ||||
| 
 | ||||
|         ButterKnife.bind(this); | ||||
| 
 | ||||
|         prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE); | ||||
| 
 | ||||
|         usernameEdit.addTextChangedListener(textWatcher); | ||||
|         passwordEdit.addTextChangedListener(textWatcher); | ||||
|         twoFactorEdit.addTextChangedListener(textWatcher); | ||||
|  | @ -91,7 +95,7 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|             WelcomeActivity.startYourself(this); | ||||
|             prefs.edit().putBoolean("firstrun", false).apply(); | ||||
|         } | ||||
|         if (app.getCurrentAccount() != null) { | ||||
|         if (sessionManager.getCurrentAccount() != null) { | ||||
|             startMainActivity(); | ||||
|         } | ||||
|     } | ||||
|  | @ -113,6 +117,25 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|         super.onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     private LoginTask getLoginTask() { | ||||
|         return new LoginTask( | ||||
|                 this, | ||||
|                 canonicializeUsername(usernameEdit.getText().toString()), | ||||
|                 passwordEdit.getText().toString(), | ||||
|                 twoFactorEdit.getText().toString(), | ||||
|                 accountUtil, mwApi, defaultPrefs | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Because Mediawiki is upercase-first-char-then-case-sensitive :) | ||||
|      * @param username String | ||||
|      * @return String canonicial username | ||||
|      */ | ||||
|     private String canonicializeUsername(String username) { | ||||
|         return new PageTitle(username).getText(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStart() { | ||||
|         super.onStart(); | ||||
|  | @ -207,24 +230,6 @@ public class LoginActivity extends AccountAuthenticatorActivity { | |||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     private LoginTask getLoginTask() { | ||||
|         return new LoginTask( | ||||
|                 this, | ||||
|                 canonicializeUsername(usernameEdit.getText().toString()), | ||||
|                 passwordEdit.getText().toString(), | ||||
|                 twoFactorEdit.getText().toString() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Because Mediawiki is upercase-first-char-then-case-sensitive :) | ||||
|      * @param username String | ||||
|      * @return String canonicial username | ||||
|      */ | ||||
|     private String canonicializeUsername(String username) { | ||||
|         return new PageTitle(username).getText(); | ||||
|     } | ||||
| 
 | ||||
|     private void showMessage(@StringRes int resId, @ColorRes int colorResId) { | ||||
|         errorMessage.setText(getString(resId)); | ||||
|         errorMessage.setTextColor(ContextCompat.getColor(this, colorResId)); | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| package fr.free.nrw.commons.auth; | ||||
| 
 | ||||
| import android.accounts.AccountAuthenticatorResponse; | ||||
| import android.accounts.AccountManager; | ||||
| import android.app.ProgressDialog; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Bundle; | ||||
| 
 | ||||
|  | @ -11,22 +11,34 @@ import java.io.IOException; | |||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE; | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; | ||||
| import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; | ||||
| import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE; | ||||
| 
 | ||||
| class LoginTask extends AsyncTask<String, String, String> { | ||||
| 
 | ||||
|     private LoginActivity loginActivity; | ||||
|     private String username; | ||||
|     private String password; | ||||
|     private String twoFactorCode = ""; | ||||
|     private CommonsApplication app; | ||||
|     private AccountUtil accountUtil; | ||||
|     private MediaWikiApi mwApi; | ||||
|     private SharedPreferences prefs; | ||||
| 
 | ||||
|     public LoginTask(LoginActivity loginActivity, String username, String password, String twoFactorCode) { | ||||
|     public LoginTask(LoginActivity loginActivity, String username, String password, | ||||
|                      String twoFactorCode, AccountUtil accountUtil, | ||||
|                      MediaWikiApi mwApi, SharedPreferences prefs) { | ||||
|         this.loginActivity = loginActivity; | ||||
|         this.username = username; | ||||
|         this.password = password; | ||||
|         this.twoFactorCode = twoFactorCode; | ||||
|         app = CommonsApplication.getInstance(); | ||||
|         this.accountUtil = accountUtil; | ||||
|         this.mwApi = mwApi; | ||||
|         this.prefs = prefs; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -44,9 +56,9 @@ class LoginTask extends AsyncTask<String, String, String> { | |||
|     protected String doInBackground(String... params) { | ||||
|         try { | ||||
|             if (twoFactorCode.isEmpty()) { | ||||
|                 return app.getMWApi().login(username, password); | ||||
|                 return mwApi.login(username, password); | ||||
|             } else { | ||||
|                 return app.getMWApi().login(username, password, twoFactorCode); | ||||
|                 return mwApi.login(username, password, twoFactorCode); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             // Do something better! | ||||
|  | @ -59,7 +71,7 @@ class LoginTask extends AsyncTask<String, String, String> { | |||
|         super.onPostExecute(result); | ||||
|         Timber.d("Login done!"); | ||||
| 
 | ||||
|         EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT) | ||||
|         EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT, mwApi, prefs) | ||||
|                 .param("username", username) | ||||
|                 .param("result", result) | ||||
|                 .log(); | ||||
|  | @ -79,16 +91,16 @@ class LoginTask extends AsyncTask<String, String, String> { | |||
|         Bundle extras = loginActivity.getIntent().getExtras(); | ||||
|         if (extras != null) { | ||||
|             Timber.d("Bundle of extras: %s", extras); | ||||
|             response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); | ||||
|             response = extras.getParcelable(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); | ||||
|             if (response != null) { | ||||
|                 Bundle authResult = new Bundle(); | ||||
|                 authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username); | ||||
|                 authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType()); | ||||
|                 authResult.putString(KEY_ACCOUNT_NAME, username); | ||||
|                 authResult.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); | ||||
|                 response.onResult(authResult); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         AccountUtil.createAccount(response, username, password); | ||||
|         accountUtil.createAccount(response, username, password); | ||||
|         loginActivity.startMainActivity(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -7,7 +7,6 @@ import android.webkit.WebViewClient; | |||
| import android.widget.Toast; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.theme.BaseActivity; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -39,11 +38,8 @@ public class SignupActivity extends BaseActivity { | |||
|                 //Signup success, so clear cookies, notify user, and load LoginActivity again | ||||
|                 Timber.d("Overriding URL %s", url); | ||||
| 
 | ||||
|                 Toast toast = Toast.makeText( | ||||
|                         CommonsApplication.getInstance(), | ||||
|                         "Account created!", | ||||
|                         Toast.LENGTH_LONG | ||||
|                 ); | ||||
|                 Toast toast = Toast.makeText(SignupActivity.this, | ||||
|                         "Account created!", Toast.LENGTH_LONG); | ||||
|                 toast.show(); | ||||
|                 // terminate on task completion. | ||||
|                 finish(); | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ import android.support.annotation.Nullable; | |||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| 
 | ||||
| import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION; | ||||
|  | @ -25,15 +24,18 @@ import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT; | |||
| import static android.accounts.AccountManager.KEY_ERROR_CODE; | ||||
| import static android.accounts.AccountManager.KEY_ERROR_MESSAGE; | ||||
| import static android.accounts.AccountManager.KEY_INTENT; | ||||
| import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE; | ||||
| import static fr.free.nrw.commons.auth.LoginActivity.PARAM_USERNAME; | ||||
| 
 | ||||
| public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | ||||
| 
 | ||||
|     private Context context; | ||||
|     private final Context context; | ||||
|     private MediaWikiApi mediaWikiApi; | ||||
| 
 | ||||
|     WikiAccountAuthenticator(Context context) { | ||||
|     WikiAccountAuthenticator(Context context, MediaWikiApi mwApi) { | ||||
|         super(context); | ||||
|         this.context = context; | ||||
|         this.mediaWikiApi = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     private Bundle unsupportedOperation() { | ||||
|  | @ -47,7 +49,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | |||
|     } | ||||
| 
 | ||||
|     private boolean supportedAccountType(@Nullable String type) { | ||||
|         return AccountUtil.accountType().equals(type); | ||||
|         return ACCOUNT_TYPE.equals(type); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -86,11 +88,10 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | |||
|     } | ||||
| 
 | ||||
|     private String getAuthCookie(String username, String password) throws IOException { | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         //TODO add 2fa support here | ||||
|         String result = api.login(username, password); | ||||
|         String result = mediaWikiApi.login(username, password); | ||||
|         if (result.equals("PASS")) { | ||||
|             return api.getAuthCookie(); | ||||
|             return mediaWikiApi.getAuthCookie(); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|  | @ -115,7 +116,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { | |||
|             if (authCookie != null) { | ||||
|                 final Bundle result = new Bundle(); | ||||
|                 result.putString(KEY_ACCOUNT_NAME, account.name); | ||||
|                 result.putString(KEY_ACCOUNT_TYPE, AccountUtil.accountType()); | ||||
|                 result.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); | ||||
|                 result.putString(KEY_AUTHTOKEN, authCookie); | ||||
|                 return result; | ||||
|             } | ||||
|  |  | |||
|  | @ -1,22 +1,28 @@ | |||
| package fr.free.nrw.commons.auth; | ||||
| 
 | ||||
| import android.accounts.AccountManager; | ||||
| import android.app.Service; | ||||
| import android.content.Intent; | ||||
| import android.os.IBinder; | ||||
| 
 | ||||
| public class WikiAccountAuthenticatorService extends Service { | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import dagger.android.DaggerService; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| 
 | ||||
| import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT; | ||||
| 
 | ||||
| public class WikiAccountAuthenticatorService extends DaggerService { | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     private WikiAccountAuthenticator wikiAccountAuthenticator = null; | ||||
| 
 | ||||
|     private static WikiAccountAuthenticator wikiAccountAuthenticator = null; | ||||
|      | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         if (!intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) { | ||||
|         if (!intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT)) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         if (wikiAccountAuthenticator == null) { | ||||
|             wikiAccountAuthenticator = new WikiAccountAuthenticator(this); | ||||
|             wikiAccountAuthenticator = new WikiAccountAuthenticator(this, mwApi); | ||||
|         } | ||||
|         return wikiAccountAuthenticator.getIBinder(); | ||||
|     } | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ package fr.free.nrw.commons.category; | |||
| import android.content.ContentProviderClient; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.widget.LinearLayoutManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
|  | @ -31,11 +29,15 @@ import java.util.HashMap; | |||
| import java.util.List; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.data.Category; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.upload.MwVolleyApi; | ||||
| import fr.free.nrw.commons.utils.StringSortingUtils; | ||||
| import io.reactivex.Observable; | ||||
|  | @ -50,7 +52,7 @@ import static fr.free.nrw.commons.category.CategoryContentProvider.AUTHORITY; | |||
| /** | ||||
|  * Displays the category suggestion and selection screen. Category search is initiated here. | ||||
|  */ | ||||
| public class CategorizationFragment extends Fragment { | ||||
| public class CategorizationFragment extends DaggerFragment { | ||||
| 
 | ||||
|     public static final int SEARCH_CATS_LIMIT = 25; | ||||
| 
 | ||||
|  | @ -65,6 +67,9 @@ public class CategorizationFragment extends Fragment { | |||
|     @BindView(R.id.categoriesExplanation) | ||||
|     TextView categoriesSkip; | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private RVRendererAdapter<CategoryItem> categoriesAdapter; | ||||
|     private OnCategoriesSaveHandler onCategoriesSaveHandler; | ||||
|     private HashMap<String, ArrayList<String>> categoriesCache; | ||||
|  | @ -205,7 +210,9 @@ public class CategorizationFragment extends Fragment { | |||
|                 .sorted(sortBySimilarity(filter)) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                         s -> categoriesAdapter.add(s), Timber::e, () -> { | ||||
|                         s -> categoriesAdapter.add(s), | ||||
|                          Timber::e, | ||||
|                         () -> { | ||||
|                             categoriesAdapter.notifyDataSetChanged(); | ||||
|                             categoriesSearchInProgress.setVisibility(View.GONE); | ||||
| 
 | ||||
|  | @ -253,10 +260,9 @@ public class CategorizationFragment extends Fragment { | |||
| 
 | ||||
|     private Observable<CategoryItem> titleCategories() { | ||||
|         //Retrieve the title that was saved when user tapped submit icon | ||||
|         SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|         String title = titleDesc.getString("Title", ""); | ||||
|         String title = prefs.getString("Title", ""); | ||||
| 
 | ||||
|         return CommonsApplication.getInstance().getMWApi() | ||||
|         return mwApi | ||||
|                 .searchTitles(title, SEARCH_CATS_LIMIT) | ||||
|                 .map(name -> new CategoryItem(name, false)); | ||||
|     } | ||||
|  | @ -279,7 +285,7 @@ public class CategorizationFragment extends Fragment { | |||
|         } | ||||
| 
 | ||||
|         //otherwise, search API for matching categories | ||||
|         return CommonsApplication.getInstance().getMWApi() | ||||
|         return mwApi | ||||
|                 .allCategories(term, SEARCH_CATS_LIMIT) | ||||
|                 .map(name -> new CategoryItem(name, false)); | ||||
|     } | ||||
|  | @ -290,7 +296,7 @@ public class CategorizationFragment extends Fragment { | |||
|             return Observable.empty(); | ||||
|         } | ||||
| 
 | ||||
|         return CommonsApplication.getInstance().getMWApi() | ||||
|         return mwApi | ||||
|                 .searchCategories(term, SEARCH_CATS_LIMIT) | ||||
|                 .map(s -> new CategoryItem(s, false)); | ||||
|     } | ||||
|  |  | |||
|  | @ -10,7 +10,9 @@ import android.net.Uri; | |||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -36,17 +38,15 @@ public class CategoryContentProvider extends ContentProvider { | |||
|         uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID); | ||||
|     } | ||||
| 
 | ||||
|     private DBOpenHelper dbOpenHelper; | ||||
| 
 | ||||
|     public static Uri uriForId(int id) { | ||||
|         return Uri.parse(BASE_URI.toString() + "/" + id); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("ConstantConditions") | ||||
|     @Inject DBOpenHelper dbOpenHelper; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         CommonsApplication app = ((CommonsApplication) getContext().getApplicationContext()); | ||||
|         dbOpenHelper = app.getDBOpenHelper(); | ||||
|         AndroidInjection.inject(this); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase; | |||
| import android.net.Uri; | ||||
| import android.os.Parcel; | ||||
| import android.os.RemoteException; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import java.text.SimpleDateFormat; | ||||
|  | @ -16,7 +17,6 @@ import java.util.Locale; | |||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| 
 | ||||
| public class Contribution extends Media { | ||||
|  | @ -149,7 +149,7 @@ public class Contribution extends Media { | |||
|         } | ||||
| 
 | ||||
|         buffer.append("== {{int:license-header}} ==\n") | ||||
|                 .append(Utils.licenseTemplateFor(getLicense())).append("\n\n") | ||||
|                 .append(licenseTemplateFor(getLicense())).append("\n\n") | ||||
|                 .append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n") | ||||
|                 .append(getTrackingTemplates()); | ||||
|         return buffer.toString(); | ||||
|  | @ -377,4 +377,25 @@ public class Contribution extends Media { | |||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private String licenseTemplateFor(String license) { | ||||
|         switch (license) { | ||||
|             case Prefs.Licenses.CC_BY_3: | ||||
|                 return "{{self|cc-by-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_4: | ||||
|                 return "{{self|cc-by-4.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA_3: | ||||
|                 return "{{self|cc-by-sa-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA_4: | ||||
|                 return "{{self|cc-by-sa-4.0}}"; | ||||
|             case Prefs.Licenses.CC0: | ||||
|                 return "{{self|cc-zero}}"; | ||||
|             case Prefs.Licenses.CC_BY: | ||||
|                 return "{{self|cc-by-3.0}}"; | ||||
|             case Prefs.Licenses.CC_BY_SA: | ||||
|                 return "{{self|cc-by-sa-3.0}}"; | ||||
|         } | ||||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import android.database.Cursor; | |||
| import android.database.DataSetObserver; | ||||
| import android.os.Bundle; | ||||
| import android.os.IBinder; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.app.FragmentManager; | ||||
| import android.support.v4.app.LoaderManager; | ||||
| import android.support.v4.content.CursorLoader; | ||||
|  | @ -24,21 +23,20 @@ import android.widget.AdapterView; | |||
| import java.util.ArrayList; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.HandlerService; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.auth.AuthenticatedActivity; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.media.MediaDetailPagerFragment; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import fr.free.nrw.commons.upload.UploadService; | ||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | ||||
| import io.reactivex.disposables.CompositeDisposable; | ||||
| import io.reactivex.disposables.Disposable; | ||||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
|  | @ -49,10 +47,17 @@ import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUT | |||
| import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; | ||||
| import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING; | ||||
| 
 | ||||
| public class ContributionsActivity extends AuthenticatedActivity | ||||
|         implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener, | ||||
|         MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener, | ||||
|         ContributionsListFragment.SourceRefresher { | ||||
| public  class       ContributionsActivity | ||||
|         extends     AuthenticatedActivity | ||||
|         implements  LoaderManager.LoaderCallbacks<Cursor>, | ||||
|                     AdapterView.OnItemClickListener, | ||||
|                     MediaDetailPagerFragment.MediaDetailProvider, | ||||
|                     FragmentManager.OnBackStackChangedListener, | ||||
|                     ContributionsListFragment.SourceRefresher { | ||||
| 
 | ||||
|     @Inject MediaWikiApi mediaWikiApi; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private Cursor allContributions; | ||||
|     private ContributionsListFragment contributionsList; | ||||
|  | @ -62,9 +67,6 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
|     private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>(); | ||||
|     private String CONTRIBUTION_SELECTION = ""; | ||||
| 
 | ||||
|     @Inject | ||||
|     MediaWikiApi mediaWikiApi; | ||||
| 
 | ||||
|     /* | ||||
|         This sorts in the following order: | ||||
|         Currently Uploading | ||||
|  | @ -109,12 +111,8 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
|     @Override | ||||
|     protected void onResume() { | ||||
|         super.onResume(); | ||||
|         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); | ||||
|         boolean isSettingsChanged = | ||||
|                 sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false); | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
|         editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false); | ||||
|         editor.apply(); | ||||
|         boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false); | ||||
|         prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply(); | ||||
|         if (isSettingsChanged) { | ||||
|             refreshSource(); | ||||
|         } | ||||
|  | @ -122,9 +120,8 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
| 
 | ||||
|     @Override | ||||
|     protected void onAuthCookieAcquired(String authCookie) { | ||||
|         // Do a sync every time we get here! | ||||
|         CommonsApplication app = ((CommonsApplication) getApplication()); | ||||
|         requestSync(app.getCurrentAccount(), AUTHORITY, new Bundle()); | ||||
|         // Do a sync everytime we get here! | ||||
|         requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle()); | ||||
|         Intent uploadServiceIntent = new Intent(this, UploadService.class); | ||||
|         uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); | ||||
|         startService(uploadServiceIntent); | ||||
|  | @ -139,19 +136,18 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         AndroidInjection.inject(this); | ||||
|         setContentView(R.layout.activity_contributions); | ||||
|         ButterKnife.bind(this); | ||||
| 
 | ||||
|         // Activity can call methods in the fragment by acquiring a | ||||
|         // reference to the Fragment from FragmentManager, using findFragmentById() | ||||
|         FragmentManager supportFragmentManager = getSupportFragmentManager(); | ||||
|         contributionsList = (ContributionsListFragment) supportFragmentManager | ||||
|         contributionsList = (ContributionsListFragment)supportFragmentManager | ||||
|                 .findFragmentById(R.id.contributionsListFragment); | ||||
| 
 | ||||
|         supportFragmentManager.addOnBackStackChangedListener(this); | ||||
|         if (savedInstanceState != null) { | ||||
|             mediaDetails = (MediaDetailPagerFragment) supportFragmentManager | ||||
|             mediaDetails = (MediaDetailPagerFragment)supportFragmentManager | ||||
|                     .findFragmentById(R.id.contributionsFragmentContainer); | ||||
| 
 | ||||
|             getSupportLoaderManager().initLoader(0, null, this); | ||||
|  | @ -241,8 +237,7 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
| 
 | ||||
|     @Override | ||||
|     public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { | ||||
|         SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); | ||||
|         int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100); | ||||
|         int uploads = prefs.getInt(UPLOADS_SHOWING, 100); | ||||
|         return new CursorLoader(this, BASE_URI, | ||||
|                 ALL_FIELDS, CONTRIBUTION_SELECTION, null, | ||||
|                 CONTRIBUTION_SORT + "LIMIT " + uploads); | ||||
|  | @ -289,9 +284,8 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
| 
 | ||||
|     @SuppressWarnings("ConstantConditions") | ||||
|     private void setUploadCount() { | ||||
|         CommonsApplication app = ((CommonsApplication) getApplication()); | ||||
|         Disposable uploadCountDisposable = mediaWikiApi | ||||
|                 .getUploadCount(app.getCurrentAccount().name) | ||||
|         compositeDisposable.add(mediaWikiApi | ||||
|                 .getUploadCount(sessionManager.getCurrentAccount().name) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|  | @ -299,8 +293,7 @@ public class ContributionsActivity extends AuthenticatedActivity | |||
|                                 .getQuantityString(R.plurals.contributions_subtitle, | ||||
|                                         uploadCount, uploadCount)), | ||||
|                         t -> Timber.e(t, "Fetching upload count failed") | ||||
|                 ); | ||||
|         compositeDisposable.add(uploadCountDisposable); | ||||
|                 )); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  |  | |||
|  | @ -10,7 +10,10 @@ import android.net.Uri; | |||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.content.UriMatcher.NO_MATCH; | ||||
|  | @ -36,9 +39,12 @@ public class ContributionsContentProvider extends ContentProvider { | |||
|         return Uri.parse(BASE_URI.toString() + "/" + id); | ||||
|     } | ||||
| 
 | ||||
|     @Inject DBOpenHelper dbOpenHelper; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         return false; | ||||
|         AndroidInjection.inject(this); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("ConstantConditions") | ||||
|  | @ -50,8 +56,7 @@ public class ContributionsContentProvider extends ContentProvider { | |||
| 
 | ||||
|         int uriType = uriMatcher.match(uri); | ||||
| 
 | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         SQLiteDatabase db = app.getDBOpenHelper().getReadableDatabase(); | ||||
|         SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); | ||||
|         Cursor cursor; | ||||
| 
 | ||||
|         switch (uriType) { | ||||
|  | @ -87,9 +92,8 @@ public class ContributionsContentProvider extends ContentProvider { | |||
|     @Override | ||||
|     public Uri insert(@NonNull Uri uri, ContentValues contentValues) { | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); | ||||
|         long id; | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         long id = 0; | ||||
|         switch (uriType) { | ||||
|             case CONTRIBUTIONS: | ||||
|                 id = sqlDB.insert(TABLE_NAME, null, contentValues); | ||||
|  | @ -107,13 +111,12 @@ public class ContributionsContentProvider extends ContentProvider { | |||
|         int rows; | ||||
|         int uriType = uriMatcher.match(uri); | ||||
| 
 | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); | ||||
| 
 | ||||
|         switch (uriType) { | ||||
|             case CONTRIBUTIONS_ID: | ||||
|                 Timber.d("Deleting contribution id %s", uri.getLastPathSegment()); | ||||
|                 rows = sqlDB.delete(TABLE_NAME, | ||||
|                 rows = db.delete(TABLE_NAME, | ||||
|                         "_id = ?", | ||||
|                         new String[]{uri.getLastPathSegment()} | ||||
|                 ); | ||||
|  | @ -130,8 +133,7 @@ public class ContributionsContentProvider extends ContentProvider { | |||
|     public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { | ||||
|         Timber.d("Hello, bulk insert! (ContributionsContentProvider)"); | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         sqlDB.beginTransaction(); | ||||
|         switch (uriType) { | ||||
|             case CONTRIBUTIONS: | ||||
|  | @ -162,9 +164,8 @@ public class ContributionsContentProvider extends ContentProvider { | |||
|         error out otherwise. | ||||
|          */ | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase(); | ||||
|         int rowsUpdated; | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         int rowsUpdated = 0; | ||||
|         switch (uriType) { | ||||
|             case CONTRIBUTIONS: | ||||
|                 rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs); | ||||
|  |  | |||
|  | @ -5,9 +5,7 @@ import android.content.SharedPreferences; | |||
| import android.content.pm.PackageManager; | ||||
| import android.os.Build; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.view.LayoutInflater; | ||||
|  | @ -22,9 +20,12 @@ import android.widget.ListAdapter; | |||
| import android.widget.ProgressBar; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.nearby.NearbyActivity; | ||||
| import timber.log.Timber; | ||||
|  | @ -32,11 +33,10 @@ import timber.log.Timber; | |||
| import static android.Manifest.permission.READ_EXTERNAL_STORAGE; | ||||
| import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; | ||||
| import static android.app.Activity.RESULT_OK; | ||||
| import static android.content.Context.MODE_PRIVATE; | ||||
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; | ||||
| import static android.view.View.GONE; | ||||
| 
 | ||||
| public class ContributionsListFragment extends Fragment { | ||||
| public class ContributionsListFragment extends DaggerFragment { | ||||
| 
 | ||||
|     @BindView(R.id.contributionsList) | ||||
|     GridView contributionsList; | ||||
|  | @ -45,6 +45,9 @@ public class ContributionsListFragment extends Fragment { | |||
|     @BindView(R.id.loadingContributionsProgressBar) | ||||
|     ProgressBar progressBar; | ||||
| 
 | ||||
|     @Inject @Named("prefs") SharedPreferences prefs; | ||||
|     @Inject @Named("default_preferences") SharedPreferences defaultPrefs; | ||||
| 
 | ||||
|     private ContributionController controller; | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -59,7 +62,6 @@ public class ContributionsListFragment extends Fragment { | |||
|         } | ||||
| 
 | ||||
|         //TODO: Should this be in onResume? | ||||
|         SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE); | ||||
|         String lastModified = prefs.getString("lastSyncTimestamp", ""); | ||||
|         Timber.d("Last Sync Timestamp: %s", lastModified); | ||||
| 
 | ||||
|  | @ -162,9 +164,7 @@ public class ContributionsListFragment extends Fragment { | |||
| 
 | ||||
|                 return true; | ||||
|             case R.id.menu_from_camera: | ||||
|                 SharedPreferences sharedPref = PreferenceManager | ||||
|                         .getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|                 boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true); | ||||
|                 boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true); | ||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) { | ||||
|                     // Here, thisActivity is the current activity | ||||
|                     if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE) | ||||
|  | @ -242,12 +242,17 @@ public class ContributionsListFragment extends Fragment { | |||
|         menu.clear(); // See http://stackoverflow.com/a/8495697/17865 | ||||
|         inflater.inflate(R.menu.fragment_contributions_list, menu); | ||||
| 
 | ||||
|         CommonsApplication app = (CommonsApplication) getContext().getApplicationContext(); | ||||
|         if (!app.deviceHasCamera()) { | ||||
|         if (!deviceHasCamera()) { | ||||
|             menu.findItem(R.id.menu_from_camera).setEnabled(false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public boolean deviceHasCamera() { | ||||
|         PackageManager pm = getContext().getPackageManager(); | ||||
|         return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || | ||||
|                 pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|  |  | |||
|  | @ -13,9 +13,15 @@ import android.os.RemoteException; | |||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.TimeZone; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Utils; | ||||
|  | @ -23,11 +29,11 @@ import fr.free.nrw.commons.mwapi.LogEventResult; | |||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static android.content.Context.MODE_PRIVATE; | ||||
| import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED; | ||||
| import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME; | ||||
| import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; | ||||
| 
 | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | ||||
| 
 | ||||
|     private static final String[] existsQuery = {COLUMN_FILENAME}; | ||||
|  | @ -35,6 +41,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|     private static final ContentValues[] EMPTY = {}; | ||||
|     private static int COMMIT_THRESHOLD = 10; | ||||
| 
 | ||||
|     @SuppressWarnings("WeakerAccess") | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject @Named("prefs") SharedPreferences prefs; | ||||
| 
 | ||||
|     public ContributionsSyncAdapter(Context context, boolean autoInitialize) { | ||||
|         super(context, autoInitialize); | ||||
|     } | ||||
|  | @ -71,10 +81,9 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|     @Override | ||||
|     public void onPerformSync(Account account, Bundle bundle, String authority, | ||||
|                               ContentProviderClient contentProviderClient, SyncResult syncResult) { | ||||
|         ((CommonsApplication) getContext().getApplicationContext()).injector().inject(this); | ||||
|         // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you! | ||||
|         String user = account.name; | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         SharedPreferences prefs = getContext().getSharedPreferences("prefs", MODE_PRIVATE); | ||||
|         String lastModified = prefs.getString("lastSyncTimestamp", ""); | ||||
|         Date curTime = new Date(); | ||||
|         LogEventResult result; | ||||
|  | @ -83,7 +92,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|         while (!done) { | ||||
| 
 | ||||
|             try { | ||||
|                 result = api.logEvents(user, lastModified, queryContinue, getLimit()); | ||||
|                 result = mwApi.logEvents(user, lastModified, queryContinue, getLimit()); | ||||
|             } catch (IOException e) { | ||||
|                 // There isn't really much we can do, eh? | ||||
|                 // FIXME: Perhaps add EventLogging? | ||||
|  | @ -137,8 +146,13 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|                 done = true; | ||||
|             } | ||||
|         } | ||||
|         prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply(); | ||||
|         prefs.edit().putString("lastSyncTimestamp", toMWDate(curTime)).apply(); | ||||
|         Timber.d("Oh hai, everyone! Look, a kitty!"); | ||||
|     } | ||||
| 
 | ||||
|     private String toMWDate(Date date) { | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC | ||||
|         isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); | ||||
|         return isoFormat.format(date); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  | @ -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(); | ||||
| 
 | ||||
| } | ||||
|  | @ -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); | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
| 
 | ||||
| } | ||||
|  | @ -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(); | ||||
| 
 | ||||
| } | ||||
|  | @ -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(); | ||||
| 
 | ||||
| } | ||||
|  | @ -1,5 +1,8 @@ | |||
| package fr.free.nrw.commons.location; | ||||
| 
 | ||||
| import android.location.Location; | ||||
| import android.support.annotation.NonNull; | ||||
| 
 | ||||
| public class LatLng { | ||||
| 
 | ||||
|     private final double latitude; | ||||
|  | @ -22,6 +25,10 @@ public class LatLng { | |||
|         this.accuracy = accuracy; | ||||
|     } | ||||
| 
 | ||||
|     public static LatLng from(@NonNull Location location) { | ||||
|         return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy()); | ||||
|     } | ||||
| 
 | ||||
|     public int hashCode() { | ||||
|         boolean var1 = true; | ||||
|         byte var2 = 1; | ||||
|  |  | |||
|  | @ -1,11 +1,15 @@ | |||
| package fr.free.nrw.commons.location; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
| import android.location.Criteria; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.location.Location; | ||||
| import android.location.LocationListener; | ||||
| import android.location.LocationManager; | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.concurrent.CopyOnWriteArrayList; | ||||
|  | @ -15,61 +19,136 @@ import javax.inject.Singleton; | |||
| 
 | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| @Singleton | ||||
| public class LocationServiceManager implements LocationListener { | ||||
|     public static final int LOCATION_REQUEST = 1; | ||||
| 
 | ||||
|     private String provider; | ||||
|     private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 1000; | ||||
|     private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 10; | ||||
| 
 | ||||
|     private Context context; | ||||
|     private LocationManager locationManager; | ||||
|     private LatLng lastLocation; | ||||
|     private Float latestLocationAccuracy; | ||||
|     private Location lastLocation; | ||||
|     private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>(); | ||||
|     private boolean isLocationManagerRegistered = false; | ||||
| 
 | ||||
|     @Inject | ||||
|     public LocationServiceManager(Context context) { | ||||
|         this.context = context; | ||||
|         this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); | ||||
|         provider = locationManager.getBestProvider(new Criteria(), true); | ||||
|     } | ||||
| 
 | ||||
|     public boolean isProviderEnabled() { | ||||
|         return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); | ||||
|     } | ||||
| 
 | ||||
|     public LatLng getLastLocation() { | ||||
|         return lastLocation; | ||||
|     public boolean isLocationPermissionGranted() { | ||||
|         return ContextCompat.checkSelfPermission(context, | ||||
|                 Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the accuracy of the location. The measurement is | ||||
|      * given as a radius in meter of 68 % confidence. | ||||
|      * | ||||
|      * @return Float | ||||
|      */ | ||||
|     public Float getLatestLocationAccuracy() { | ||||
|         return latestLocationAccuracy; | ||||
|     public void requestPermissions(Activity activity) { | ||||
|         if (activity.isFinishing()) { | ||||
|             return; | ||||
|         } | ||||
|         ActivityCompat.requestPermissions(activity, | ||||
|                 new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                 LOCATION_REQUEST); | ||||
|     } | ||||
| 
 | ||||
|     public boolean isPermissionExplanationRequired(Activity activity) { | ||||
|         if (activity.isFinishing()) { | ||||
|             return false; | ||||
|         } | ||||
|         return ActivityCompat.shouldShowRequestPermissionRationale(activity, | ||||
|                 Manifest.permission.ACCESS_FINE_LOCATION); | ||||
|     } | ||||
| 
 | ||||
|     public LatLng getLastLocation() { | ||||
|         if (lastLocation == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return LatLng.from(lastLocation); | ||||
|     } | ||||
| 
 | ||||
|     /** Registers a LocationManager to listen for current location. | ||||
|      */ | ||||
|     public void registerLocationManager() { | ||||
|         if (!isLocationManagerRegistered) | ||||
|             isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) | ||||
|                     && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); | ||||
|     } | ||||
| 
 | ||||
|     private boolean requestLocationUpdatesFromProvider(String locationProvider) { | ||||
|         try { | ||||
|             locationManager.requestLocationUpdates(provider, 400, 1, this); | ||||
|             Location location = locationManager.getLastKnownLocation(provider); | ||||
|             //Location works, just need to 'send' GPS coords | ||||
|             // via emulator extended controls if testing on emulator | ||||
|             Timber.d("Checking for location..."); | ||||
|             if (location != null) { | ||||
|                 this.onLocationChanged(location); | ||||
|             } | ||||
|             locationManager.requestLocationUpdates(locationProvider, | ||||
|                     MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS, | ||||
|                     MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS, | ||||
|                     this); | ||||
|             return true; | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             Timber.e(e, "Illegal argument exception"); | ||||
|             return false; | ||||
|         } catch (SecurityException e) { | ||||
|             Timber.e(e, "Security exception"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected boolean isBetterLocation(Location location, Location currentBestLocation) { | ||||
|         if (currentBestLocation == null) { | ||||
|             // A new location is always better than no location | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // Check whether the new location fix is newer or older | ||||
|         long timeDelta = location.getTime() - currentBestLocation.getTime(); | ||||
|         boolean isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; | ||||
|         boolean isSignificantlyOlder = timeDelta < -MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; | ||||
|         boolean isNewer = timeDelta > 0; | ||||
| 
 | ||||
|         // If it's been more than two minutes since the current location, use the new location | ||||
|         // because the user has likely moved | ||||
|         if (isSignificantlyNewer) { | ||||
|             return true; | ||||
|             // If the new location is more than two minutes older, it must be worse | ||||
|         } else if (isSignificantlyOlder) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Check whether the new location fix is more or less accurate | ||||
|         int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); | ||||
|         boolean isLessAccurate = accuracyDelta > 0; | ||||
|         boolean isMoreAccurate = accuracyDelta < 0; | ||||
|         boolean isSignificantlyLessAccurate = accuracyDelta > 200; | ||||
| 
 | ||||
|         // Check if the old and new location are from the same provider | ||||
|         boolean isFromSameProvider = isSameProvider(location.getProvider(), | ||||
|                 currentBestLocation.getProvider()); | ||||
| 
 | ||||
|         // Determine location quality using a combination of timeliness and accuracy | ||||
|         if (isMoreAccurate) { | ||||
|             return true; | ||||
|         } else if (isNewer && !isLessAccurate) { | ||||
|             return true; | ||||
|         } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks whether two providers are the same | ||||
|      */ | ||||
|     private boolean isSameProvider(String provider1, String provider2) { | ||||
|         if (provider1 == null) { | ||||
|             return provider2 == null; | ||||
|         } | ||||
|         return provider1.equals(provider2); | ||||
|     } | ||||
| 
 | ||||
|     /** Unregisters location manager. | ||||
|      */ | ||||
|     public void unregisterLocationManager() { | ||||
|         isLocationManagerRegistered = false; | ||||
|         try { | ||||
|             locationManager.removeUpdates(this); | ||||
|         } catch (SecurityException e) { | ||||
|  | @ -89,15 +168,11 @@ public class LocationServiceManager implements LocationListener { | |||
| 
 | ||||
|     @Override | ||||
|     public void onLocationChanged(Location location) { | ||||
|         double currentLatitude = location.getLatitude(); | ||||
|         double currentLongitude = location.getLongitude(); | ||||
|         latestLocationAccuracy = location.getAccuracy(); | ||||
|         Timber.d("Latitude: %f Longitude: %f Accuracy %f", | ||||
|                 currentLatitude, currentLongitude, latestLocationAccuracy); | ||||
|         lastLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy); | ||||
| 
 | ||||
|         for (LocationUpdateListener listener : locationListeners) { | ||||
|             listener.onLocationChanged(lastLocation); | ||||
|         if (isBetterLocation(location, lastLocation)) { | ||||
|             lastLocation = location; | ||||
|             for (LocationUpdateListener listener : locationListeners) { | ||||
|                 listener.onLocationChanged(LatLng.from(lastLocation)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ import android.net.Uri; | |||
| import android.os.AsyncTask; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.util.TypedValue; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
|  | @ -22,6 +21,10 @@ import java.util.ArrayList; | |||
| import java.util.Date; | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Provider; | ||||
| 
 | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.License; | ||||
| import fr.free.nrw.commons.LicenseList; | ||||
| import fr.free.nrw.commons.Media; | ||||
|  | @ -30,10 +33,11 @@ import fr.free.nrw.commons.MediaWikiImageView; | |||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import fr.free.nrw.commons.ui.widget.CompatTextView; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class MediaDetailFragment extends Fragment { | ||||
| public class MediaDetailFragment extends DaggerFragment { | ||||
| 
 | ||||
|     private boolean editable; | ||||
|     private MediaDetailPagerFragment.MediaDetailProvider detailProvider; | ||||
|  | @ -53,6 +57,9 @@ public class MediaDetailFragment extends Fragment { | |||
|         return mf; | ||||
|     } | ||||
| 
 | ||||
|     @Inject | ||||
|     Provider<MediaDataExtractor> mediaDataExtractorProvider; | ||||
| 
 | ||||
|     private MediaWikiImageView image; | ||||
|     private MediaDetailSpacer spacer; | ||||
|     private int initialListTop = 0; | ||||
|  | @ -69,7 +76,7 @@ public class MediaDetailFragment extends Fragment { | |||
|     private boolean categoriesPresent = false; | ||||
|     private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once! | ||||
|     private ViewTreeObserver.OnScrollChangedListener scrollListener; | ||||
|     DataSetObserver dataObserver; | ||||
|     private DataSetObserver dataObserver; | ||||
|     private AsyncTask<Void,Void,Boolean> detailFetchTask; | ||||
|     private LicenseList licenseList; | ||||
| 
 | ||||
|  | @ -188,13 +195,13 @@ public class MediaDetailFragment extends Fragment { | |||
| 
 | ||||
|             @Override | ||||
|             protected void onPreExecute() { | ||||
|                 extractor = new MediaDataExtractor(media.getFilename(), licenseList); | ||||
|                 extractor = mediaDataExtractorProvider.get(); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             protected Boolean doInBackground(Void... voids) { | ||||
|                 try { | ||||
|                     extractor.fetch(); | ||||
|                     extractor.fetch(media.getFilename(), licenseList); | ||||
|                     return Boolean.TRUE; | ||||
|                 } catch (IOException e) { | ||||
|                     Timber.d(e); | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package fr.free.nrw.commons.media; | |||
| import android.annotation.SuppressLint; | ||||
| import android.app.DownloadManager; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.database.DataSetObserver; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
|  | @ -24,12 +25,18 @@ import android.view.MenuItem; | |||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| 
 | ||||
| import static android.Manifest.permission.READ_EXTERNAL_STORAGE; | ||||
| import static android.content.Context.DOWNLOAD_SERVICE; | ||||
|  | @ -37,7 +44,11 @@ import static android.content.Intent.ACTION_VIEW; | |||
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; | ||||
| import static fr.free.nrw.commons.CommonsApplication.EVENT_SHARE_ATTEMPT; | ||||
| 
 | ||||
| public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPageChangeListener { | ||||
| public class MediaDetailPagerFragment extends DaggerFragment implements ViewPager.OnPageChangeListener { | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private ViewPager pager; | ||||
|     private Boolean editable; | ||||
|  | @ -101,8 +112,8 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa | |||
|             case R.id.menu_share_current_image: | ||||
|                 // Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252 | ||||
|                 CommonsApplication app = (CommonsApplication) getActivity().getApplication(); | ||||
|                 EventLog.schema(EVENT_SHARE_ATTEMPT) | ||||
|                         .param("username", app.getCurrentAccount().name) | ||||
|                 EventLog.schema(EVENT_SHARE_ATTEMPT, mwApi, prefs) | ||||
|                         .param("username", sessionManager.getCurrentAccount().name) | ||||
|                         .param("filename", m.getFilename()) | ||||
|                         .log(); | ||||
|                 return true; | ||||
|  | @ -161,9 +172,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa | |||
|         req.allowScanningByMediaScanner(); | ||||
|         req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); | ||||
| 
 | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M | ||||
|                 && !(ContextCompat.checkSelfPermission(getContext(), | ||||
|                 READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) { | ||||
|             Snackbar.make(getView(), R.string.read_storage_permission_rationale, | ||||
|                     Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok, | ||||
|                     view -> ActivityCompat.requestPermissions(getActivity(), | ||||
|  |  | |||
|  | @ -10,7 +10,10 @@ import android.net.Uri; | |||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.data.DBOpenHelper; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class ModificationsContentProvider extends ContentProvider { | ||||
|  | @ -33,10 +36,12 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|         return Uri.parse(BASE_URI.toString() + "/" + id); | ||||
|     } | ||||
| 
 | ||||
|     @Inject DBOpenHelper dbOpenHelper; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         return false; | ||||
|         AndroidInjection.inject(this); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -53,7 +58,7 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|                 throw new IllegalArgumentException("Unknown URI" + uri); | ||||
|         } | ||||
| 
 | ||||
|         SQLiteDatabase db = CommonsApplication.getInstance().getDBOpenHelper().getReadableDatabase(); | ||||
|         SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); | ||||
| 
 | ||||
|         Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); | ||||
|         cursor.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|  | @ -69,7 +74,7 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|     @Override | ||||
|     public Uri insert(@NonNull Uri uri, ContentValues contentValues) { | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         long id = 0; | ||||
|         switch (uriType) { | ||||
|             case MODIFICATIONS: | ||||
|  | @ -85,7 +90,7 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|     @Override | ||||
|     public int delete(@NonNull Uri uri, String s, String[] strings) { | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         switch (uriType) { | ||||
|             case MODIFICATIONS_ID: | ||||
|                 String id = uri.getLastPathSegment(); | ||||
|  | @ -103,7 +108,7 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|     public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { | ||||
|         Timber.d("Hello, bulk insert! (ModificationsContentProvider)"); | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         sqlDB.beginTransaction(); | ||||
|         switch (uriType) { | ||||
|             case MODIFICATIONS: | ||||
|  | @ -131,7 +136,7 @@ public class ModificationsContentProvider extends ContentProvider { | |||
|         In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise. | ||||
|          */ | ||||
|         int uriType = uriMatcher.match(uri); | ||||
|         SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase(); | ||||
|         SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); | ||||
|         int rowsUpdated = 0; | ||||
|         switch (uriType) { | ||||
|             case MODIFICATIONS: | ||||
|  |  | |||
|  | @ -14,8 +14,9 @@ import android.os.RemoteException; | |||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
|  | @ -23,6 +24,8 @@ import timber.log.Timber; | |||
| 
 | ||||
| public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
| 
 | ||||
|     public ModificationsSyncAdapter(Context context, boolean autoInitialize) { | ||||
|         super(context, autoInitialize); | ||||
|     } | ||||
|  | @ -30,6 +33,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|     @Override | ||||
|     public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { | ||||
|         // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you! | ||||
|         ((CommonsApplication)getContext().getApplicationContext()).injector().inject(this); | ||||
| 
 | ||||
|         Cursor allModifications; | ||||
|         try { | ||||
|  | @ -54,17 +58,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (Utils.isNullOrWhiteSpace(authCookie)) { | ||||
|         if (isNullOrWhiteSpace(authCookie)) { | ||||
|             Timber.d("Could not authenticate :("); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
|         api.setAuthCookie(authCookie); | ||||
|         mwApi.setAuthCookie(authCookie); | ||||
|         String editToken; | ||||
| 
 | ||||
|         try { | ||||
|             editToken = api.getEditToken(); | ||||
|             editToken = mwApi.getEditToken(); | ||||
|         } catch (IOException e) { | ||||
|             Timber.d("Can not retreive edit token!"); | ||||
|             return; | ||||
|  | @ -95,7 +98,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|                 if (contrib.getState() == Contribution.STATE_COMPLETED) { | ||||
|                     String pageContent; | ||||
|                     try { | ||||
|                         pageContent = api.revisionsByFilename(contrib.getFilename()); | ||||
|                         pageContent = mwApi.revisionsByFilename(contrib.getFilename()); | ||||
|                     } catch (IOException e) { | ||||
|                         Timber.d("Network fuckup on modifications sync!"); | ||||
|                         continue; | ||||
|  | @ -106,7 +109,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
| 
 | ||||
|                     String editResult; | ||||
|                     try { | ||||
|                         editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary()); | ||||
|                         editResult = mwApi.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary()); | ||||
|                     } catch (IOException e) { | ||||
|                         Timber.d("Network fuckup on modifications sync!"); | ||||
|                         continue; | ||||
|  | @ -129,4 +132,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { | |||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private boolean isNullOrWhiteSpace(String value) { | ||||
|         return value == null || value.trim().isEmpty(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,8 @@ import org.mediawiki.api.MWApi; | |||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URL; | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
|  | @ -34,7 +36,6 @@ import java.util.concurrent.Callable; | |||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.PageTitle; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import in.yuvi.http.fluent.Http; | ||||
| import io.reactivex.Observable; | ||||
| import io.reactivex.Single; | ||||
|  | @ -335,7 +336,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|             logEvents.add(new LogEventResult.LogEvent( | ||||
|                     image.getString("@pageid"), | ||||
|                     image.getString("@title"), | ||||
|                     Utils.parseMWDate(image.getString("@timestamp"))) | ||||
|                     parseMWDate(image.getString("@timestamp"))) | ||||
|             ); | ||||
|         } | ||||
|         return logEvents; | ||||
|  | @ -402,7 +403,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|             String errorCode = result.getString("/api/error/@code"); | ||||
|             return new UploadResult(resultStatus, errorCode); | ||||
|         } else { | ||||
|             Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); | ||||
|             Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); | ||||
|             String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename | ||||
|             String imageUrl = result.getString("/api/upload/imageinfo/@url"); | ||||
|             return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl); | ||||
|  | @ -428,4 +429,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { | |||
|             return Integer.parseInt(uploadCount); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private Date parseMWDate(String mwDate) { | ||||
|         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC | ||||
|         try { | ||||
|             return isoFormat.parse(mwDate); | ||||
|         } catch (ParseException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package fr.free.nrw.commons.mwapi; | ||||
| 
 | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Utils; | ||||
|  | @ -15,14 +16,14 @@ public class EventLog { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static LogBuilder schema(String schema, long revision) { | ||||
|         return new LogBuilder(schema, revision); | ||||
|     private static LogBuilder schema(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) { | ||||
|         return new LogBuilder(schema, revision, mwApi, prefs); | ||||
|     } | ||||
| 
 | ||||
|     public static LogBuilder schema(Object[] scid) { | ||||
|     public static LogBuilder schema(Object[] scid, MediaWikiApi mwApi, SharedPreferences prefs) { | ||||
|         if (scid.length != 2) { | ||||
|             throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second"); | ||||
|         } | ||||
|         return schema((String) scid[0], (Long) scid[1]); | ||||
|         return schema((String) scid[0], (Long) scid[1], mwApi, prefs); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -3,7 +3,6 @@ package fr.free.nrw.commons.mwapi; | |||
| import android.content.SharedPreferences; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Build; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
|  | @ -12,19 +11,23 @@ import java.net.MalformedURLException; | |||
| import java.net.URL; | ||||
| 
 | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| 
 | ||||
| @SuppressWarnings("WeakerAccess") | ||||
| public class LogBuilder { | ||||
|     private JSONObject data; | ||||
|     private long rev; | ||||
|     private String schema; | ||||
|     private final MediaWikiApi mwApi; | ||||
|     private final JSONObject data; | ||||
|     private final long rev; | ||||
|     private final String schema; | ||||
|     private final SharedPreferences prefs; | ||||
| 
 | ||||
|     LogBuilder(String schema, long revision) { | ||||
|         data = new JSONObject(); | ||||
|     LogBuilder(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) { | ||||
|         this.prefs = prefs; | ||||
|         this.data = new JSONObject(); | ||||
|         this.schema = schema; | ||||
|         this.rev = revision; | ||||
|         this.mwApi = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     public LogBuilder param(String key, Object value) { | ||||
|  | @ -56,11 +59,10 @@ public class LogBuilder { | |||
|     // Use *only* for tracking the user preference change for EventLogging | ||||
|     // Attempting to use anywhere else will cause kitten explosions | ||||
|     public void log(boolean force) { | ||||
|         SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|         if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) { | ||||
|         if (!prefs.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) { | ||||
|             return; // User has disabled tracking | ||||
|         } | ||||
|         LogTask logTask = new LogTask(); | ||||
|         LogTask logTask = new LogTask(mwApi); | ||||
|         logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,11 +2,16 @@ package fr.free.nrw.commons.mwapi; | |||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| 
 | ||||
| class LogTask extends AsyncTask<LogBuilder, Void, Boolean> { | ||||
| 
 | ||||
|     private final MediaWikiApi mwApi; | ||||
| 
 | ||||
|     public LogTask(MediaWikiApi mwApi) { | ||||
|         this.mwApi = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Boolean doInBackground(LogBuilder... logBuilders) { | ||||
|         return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders); | ||||
|         return mwApi.logEvents(logBuilders); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| package fr.free.nrw.commons.nearby; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
|  | @ -10,10 +9,8 @@ import android.os.Build; | |||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v4.app.FragmentTransaction; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
|  | @ -31,8 +28,6 @@ import javax.inject.Inject; | |||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.location.LocationServiceManager; | ||||
|  | @ -46,16 +41,22 @@ import io.reactivex.disposables.Disposable; | |||
| import io.reactivex.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST; | ||||
| 
 | ||||
| 
 | ||||
| public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener { | ||||
| 
 | ||||
|     @BindView(R.id.progressBar) | ||||
|     ProgressBar progressBar; | ||||
|     private static final int LOCATION_REQUEST = 1; | ||||
|     private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed"; | ||||
| 
 | ||||
|     @BindView(R.id.progressBar) | ||||
|     ProgressBar progressBar; | ||||
| 
 | ||||
|     @Inject | ||||
|     LocationServiceManager locationManager; | ||||
|     @Inject | ||||
|     NearbyController nearbyController; | ||||
| 
 | ||||
|     private LatLng curLatLang; | ||||
|     private Bundle bundle; | ||||
|     private SharedPreferences sharedPreferences; | ||||
|  | @ -66,11 +67,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         AndroidInjection.inject(this); | ||||
|         sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); | ||||
|         setContentView(R.layout.activity_nearby); | ||||
|         ButterKnife.bind(this); | ||||
|         checkLocationPermission(); | ||||
|         bundle = new Bundle(); | ||||
|         initDrawer(); | ||||
|         initViewState(); | ||||
|  | @ -102,7 +101,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         // Handle item selection | ||||
|         switch (item.getItemId()) { | ||||
|             case R.id.action_refresh: | ||||
|                 lockNearbyView = false; | ||||
|                 lockNearbyView(false); | ||||
|                 refreshView(true); | ||||
|                 return true; | ||||
|             case R.id.action_toggle_view: | ||||
|  | @ -115,52 +114,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void checkLocationPermission() { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             if (ContextCompat.checkSelfPermission(this, | ||||
|                     Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { | ||||
|                 refreshView(false); | ||||
|             } else { | ||||
|                 if (ContextCompat.checkSelfPermission(this, | ||||
|                         Manifest.permission.ACCESS_FINE_LOCATION) | ||||
|                         != PackageManager.PERMISSION_GRANTED) { | ||||
| 
 | ||||
|                     // Should we show an explanation? | ||||
|                     if (ActivityCompat.shouldShowRequestPermissionRationale(this, | ||||
|                             Manifest.permission.ACCESS_FINE_LOCATION)) { | ||||
| 
 | ||||
|                         // Show an explanation to the user *asynchronously* -- don't block | ||||
|                         // this thread waiting for the user's response! After the user | ||||
|                         // sees the explanation, try again to request the permission. | ||||
| 
 | ||||
|                         new AlertDialog.Builder(this) | ||||
|                                 .setMessage(getString(R.string.location_permission_rationale)) | ||||
|                                 .setPositiveButton("OK", (dialog, which) -> { | ||||
|                                     ActivityCompat.requestPermissions(NearbyActivity.this, | ||||
|                                             new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                                             LOCATION_REQUEST); | ||||
|                                     dialog.dismiss(); | ||||
|                                 }) | ||||
|                                 .setNegativeButton("Cancel", null) | ||||
|                                 .create() | ||||
|                                 .show(); | ||||
| 
 | ||||
|                     } else { | ||||
| 
 | ||||
|                         // No explanation needed, we can request the permission. | ||||
| 
 | ||||
|                         ActivityCompat.requestPermissions(this, | ||||
|                                 new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, | ||||
|                                 LOCATION_REQUEST); | ||||
| 
 | ||||
|                         // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an | ||||
|                         // app-defined int constant. The callback method gets the | ||||
|                         // result of the request. | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             refreshView(false); | ||||
|     private void requestLocationPermissions() { | ||||
|         if (!isFinishing()) { | ||||
|             locationManager.requestPermissions(this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -185,7 +141,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|                 .setCancelable(false) | ||||
|                 .setPositiveButton(R.string.give_permission, (dialog, which) -> { | ||||
|                     //will ask for the location permission again | ||||
|                     checkLocationPermission(); | ||||
|                     checkGps(); | ||||
|                 }) | ||||
|                 .setNegativeButton(R.string.cancel, (dialog, which) -> { | ||||
|                     //dismiss dialog and finish activity | ||||
|  | @ -209,11 +165,48 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|                                 Timber.d("Loaded settings page"); | ||||
|                                 startActivityForResult(callGPSSettingIntent, 1); | ||||
|                             }) | ||||
|                     .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> dialog.cancel()) | ||||
|                     .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> { | ||||
|                         showLocationPermissionDeniedErrorDialog(); | ||||
|                         dialog.cancel(); | ||||
|                     }) | ||||
|                     .create() | ||||
|                     .show(); | ||||
|         } else { | ||||
|             Timber.d("GPS is enabled"); | ||||
|             checkLocationPermission(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void checkLocationPermission() { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             if (locationManager.isLocationPermissionGranted()) { | ||||
|                 refreshView(false); | ||||
|             } else { | ||||
|                 // Should we show an explanation? | ||||
|                 if (locationManager.isPermissionExplanationRequired(this)) { | ||||
|                     // Show an explanation to the user *asynchronously* -- don't block | ||||
|                     // this thread waiting for the user's response! After the user | ||||
|                     // sees the explanation, try again to request the permission. | ||||
|                     new AlertDialog.Builder(this) | ||||
|                             .setMessage(getString(R.string.location_permission_rationale_nearby)) | ||||
|                             .setPositiveButton("OK", (dialog, which) -> { | ||||
|                                 requestLocationPermissions(); | ||||
|                                 dialog.dismiss(); | ||||
|                             }) | ||||
|                             .setNegativeButton("Cancel", (dialog, id) -> { | ||||
|                                 showLocationPermissionDeniedErrorDialog(); | ||||
|                                 dialog.cancel(); | ||||
|                             }) | ||||
|                             .create() | ||||
|                             .show(); | ||||
| 
 | ||||
|                 } else { | ||||
|                     // No explanation needed, we can request the permission. | ||||
|                     requestLocationPermissions(); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             refreshView(false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -238,7 +231,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|     @Override | ||||
|     protected void onStart() { | ||||
|         super.onStart(); | ||||
|         locationManager.registerLocationManager(); | ||||
|         locationManager.addLocationListener(this); | ||||
|     } | ||||
| 
 | ||||
|  | @ -262,13 +254,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         super.onResume(); | ||||
|         lockNearbyView = false; | ||||
|         checkGps(); | ||||
|         refreshView(false); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method should be the single point to load/refresh nearby places | ||||
|      * | ||||
|      * @param isHardRefresh | ||||
|      */ | ||||
|     private void refreshView(boolean isHardRefresh) { | ||||
|         if (lockNearbyView) { | ||||
|             return; | ||||
|         } | ||||
|         locationManager.registerLocationManager(); | ||||
|         LatLng lastLocation = locationManager.getLastLocation(); | ||||
|         if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed | ||||
|             if (isHardRefresh) { | ||||
|  | @ -284,20 +281,14 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         } | ||||
| 
 | ||||
|         progressBar.setVisibility(View.VISIBLE); | ||||
|         setupPlaceList(this); | ||||
|     } | ||||
| 
 | ||||
|     private void setupPlaceList(Context context) { | ||||
|         placesDisposable = Observable.fromCallable(() -> NearbyController | ||||
|                 .loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance())) | ||||
|         placesDisposable = Observable.fromCallable(() -> nearbyController | ||||
|                 .loadAttractionsFromLocation(curLatLang, this)) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe((result) -> { | ||||
|                     populatePlaces(context, result); | ||||
|                 }); | ||||
|                 .subscribe(this::populatePlaces); | ||||
|     } | ||||
| 
 | ||||
|     private void populatePlaces(Context context, List<Place> placeList) { | ||||
|     private void populatePlaces(List<Place> placeList) { | ||||
|         Gson gson = new GsonBuilder() | ||||
|                 .registerTypeAdapter(Uri.class, new UriSerializer()) | ||||
|                 .create(); | ||||
|  | @ -306,7 +297,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
| 
 | ||||
|         if (placeList.size() == 0) { | ||||
|             int duration = Toast.LENGTH_SHORT; | ||||
|             Toast toast = Toast.makeText(context, R.string.no_nearby, duration); | ||||
|             Toast toast = Toast.makeText(this, R.string.no_nearby, duration); | ||||
|             toast.show(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -314,7 +305,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         bundle.putString("PlaceList", gsonPlaceList); | ||||
|         bundle.putString("CurLatLng", gsonCurLatLng); | ||||
| 
 | ||||
|         lockNearbyView = true; | ||||
|         lockNearbyView(true); | ||||
|         // Begin the transaction | ||||
|         if (viewMode.isMap()) { | ||||
|             setMapFragment(); | ||||
|  | @ -325,6 +316,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         hideProgressBar(); | ||||
|     } | ||||
| 
 | ||||
|     private void lockNearbyView(boolean lock) { | ||||
|         if (lock) { | ||||
|             lockNearbyView = true; | ||||
|             locationManager.unregisterLocationManager(); | ||||
|             locationManager.removeLocationListener(this); | ||||
|         } else { | ||||
|             lockNearbyView = false; | ||||
|             locationManager.registerLocationManager(); | ||||
|             locationManager.addLocationListener(this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void hideProgressBar() { | ||||
|         if (progressBar != null) { | ||||
|             progressBar.setVisibility(View.GONE); | ||||
|  | @ -338,7 +341,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); | ||||
|         Fragment fragment = new NearbyMapFragment(); | ||||
|         fragment.setArguments(bundle); | ||||
|         fragmentTransaction.replace(R.id.container, fragment); | ||||
|         fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName()); | ||||
|         fragmentTransaction.commitAllowingStateLoss(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -349,15 +352,10 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp | |||
|         FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); | ||||
|         Fragment fragment = new NearbyListFragment(); | ||||
|         fragment.setArguments(bundle); | ||||
|         fragmentTransaction.replace(R.id.container, fragment); | ||||
|         fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName()); | ||||
|         fragmentTransaction.commitAllowingStateLoss(); | ||||
|     } | ||||
| 
 | ||||
|     public static void startYourself(Context context) { | ||||
|         Intent settingsIntent = new Intent(context, NearbyActivity.class); | ||||
|         context.startActivity(settingsIntent); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onLocationChanged(LatLng latLng) { | ||||
|         refreshView(false); | ||||
|  |  | |||
|  | @ -3,7 +3,6 @@ package fr.free.nrw.commons.nearby; | |||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import android.graphics.Bitmap; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.graphics.drawable.VectorDrawableCompat; | ||||
| 
 | ||||
| import com.mapbox.mapboxsdk.annotations.IconFactory; | ||||
|  | @ -15,7 +14,9 @@ import java.util.List; | |||
| import java.util.Locale; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.utils.UiUtils; | ||||
|  | @ -24,9 +25,17 @@ import timber.log.Timber; | |||
| import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween; | ||||
| import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; | ||||
| 
 | ||||
| 
 | ||||
| public class NearbyController { | ||||
|     private static final int MAX_RESULTS = 1000; | ||||
|     private final NearbyPlaces nearbyPlaces; | ||||
|     private final SharedPreferences prefs; | ||||
| 
 | ||||
|     @Inject | ||||
|     public NearbyController(NearbyPlaces nearbyPlaces, | ||||
|                             @Named("default_preferences") SharedPreferences prefs) { | ||||
|         this.nearbyPlaces = nearbyPlaces; | ||||
|         this.prefs = prefs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prepares Place list to make their distance information update later. | ||||
|  | @ -34,13 +43,11 @@ public class NearbyController { | |||
|      * @param context context | ||||
|      * @return Place list without distance information | ||||
|      */ | ||||
|     public static List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) { | ||||
|     public List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) { | ||||
|         Timber.d("Loading attractions near %s", curLatLng); | ||||
|         if (curLatLng == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         NearbyPlaces nearbyPlaces = CommonsApplication.getInstance().getNearbyPlaces(); | ||||
|         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); | ||||
|         List<Place> places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage()); | ||||
|         if (curLatLng != null) { | ||||
|             Timber.d("Sorting places by distance..."); | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ package fr.free.nrw.commons.nearby; | |||
| 
 | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.v7.widget.LinearLayoutManager; | ||||
| import android.support.v7.widget.RecyclerView; | ||||
| import android.view.LayoutInflater; | ||||
|  | @ -17,12 +16,13 @@ import java.lang.reflect.Type; | |||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.location.LatLng; | ||||
| import fr.free.nrw.commons.utils.UriDeserializer; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class NearbyListFragment extends Fragment { | ||||
| public class NearbyListFragment extends DaggerFragment { | ||||
|     private static final Type LIST_TYPE = new TypeToken<List<Place>>() { | ||||
|     }.getType(); | ||||
|     private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() { | ||||
|  |  | |||
|  | @ -1,19 +1,19 @@ | |||
| package fr.free.nrw.commons.nearby; | ||||
| 
 | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  * Tells user that Nearby Places cannot be displayed if location permissions are denied | ||||
|  */ | ||||
| public class NoPermissionsFragment extends Fragment { | ||||
| public class NoPermissionsFragment extends DaggerFragment { | ||||
| 
 | ||||
|     public NoPermissionsFragment() { | ||||
|     } | ||||
|  |  | |||
|  | @ -1,7 +1,5 @@ | |||
| package fr.free.nrw.commons.settings; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v7.app.AppCompatDelegate; | ||||
|  |  | |||
|  | @ -15,15 +15,17 @@ import android.preference.EditTextPreference; | |||
| import android.preference.ListPreference; | ||||
| import android.preference.Preference; | ||||
| import android.preference.PreferenceFragment; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| import android.support.v4.content.ContextCompat; | ||||
| import android.support.v4.content.FileProvider; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import dagger.android.AndroidInjection; | ||||
| import fr.free.nrw.commons.BuildConfig; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.R; | ||||
|  | @ -34,8 +36,11 @@ public class SettingsFragment extends PreferenceFragment { | |||
| 
 | ||||
|     private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 100; | ||||
| 
 | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(Bundle savedInstanceState) { | ||||
|         AndroidInjection.inject(this); | ||||
|         super.onCreate(savedInstanceState); | ||||
| 
 | ||||
|         // Load the preferences from an XML resource | ||||
|  | @ -58,14 +63,12 @@ public class SettingsFragment extends PreferenceFragment { | |||
|         }); | ||||
| 
 | ||||
|         final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads"); | ||||
|         final SharedPreferences sharedPref = PreferenceManager | ||||
|                 .getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|         int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100); | ||||
|         int uploads = prefs.getInt(Prefs.UPLOADS_SHOWING, 100); | ||||
|         uploadLimit.setText(uploads + ""); | ||||
|         uploadLimit.setSummary(uploads + ""); | ||||
|         uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> { | ||||
|             int value = Integer.parseInt(newValue.toString()); | ||||
|             final SharedPreferences.Editor editor = sharedPref.edit(); | ||||
|             final SharedPreferences.Editor editor = prefs.edit(); | ||||
|             if (value > 500) { | ||||
|                 new AlertDialog.Builder(getActivity()) | ||||
|                         .setTitle(R.string.maximum_limit) | ||||
|  |  | |||
|  | @ -3,18 +3,17 @@ package fr.free.nrw.commons.theme; | |||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| 
 | ||||
| import dagger.android.support.DaggerAppCompatActivity; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| 
 | ||||
| public class BaseActivity extends AppCompatActivity { | ||||
| public abstract class BaseActivity extends DaggerAppCompatActivity { | ||||
|     boolean currentTheme; | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         if (Utils.isDarkTheme(this)) { | ||||
|         boolean currentThemeIsDark = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false); | ||||
|         if (currentThemeIsDark){ | ||||
|             currentTheme = true; | ||||
|             setTheme(R.style.DarkAppTheme); | ||||
|         } else { | ||||
|  | @ -27,7 +26,7 @@ public class BaseActivity extends AppCompatActivity { | |||
|     @Override | ||||
|     protected void onResume() { | ||||
|         // Restart activity if theme is changed | ||||
|         boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false); | ||||
|         boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false); | ||||
|         if (currentTheme != newTheme) { //is activity theme changed | ||||
|             Intent intent = getIntent(); | ||||
|             finish(); | ||||
|  |  | |||
|  | @ -62,10 +62,10 @@ public abstract class NavigationBaseActivity extends BaseActivity | |||
|     private void setUserName() { | ||||
| 
 | ||||
|         View navHeaderView = navigationView.getHeaderView(0); | ||||
|         TextView username = (TextView) navHeaderView.findViewById(R.id.username); | ||||
|         TextView username = navHeaderView.findViewById(R.id.username); | ||||
| 
 | ||||
|         AccountManager accountManager = AccountManager.get(this); | ||||
|         Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType()); | ||||
|         Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE); | ||||
|         if (allAccounts.length != 0) { | ||||
|             username.setText(allAccounts[0].name); | ||||
|         } | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| package fr.free.nrw.commons.ui.widget; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.os.Build; | ||||
| import android.support.v7.widget.AppCompatTextView; | ||||
| import android.text.Html; | ||||
| import android.text.Spanned; | ||||
| import android.text.method.LinkMovementMethod; | ||||
| import android.util.AttributeSet; | ||||
| 
 | ||||
| import fr.free.nrw.commons.Utils; | ||||
| 
 | ||||
| /** | ||||
|  * An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any | ||||
|  * links clickable. | ||||
|  | @ -17,10 +18,25 @@ public class HtmlTextView extends AppCompatTextView { | |||
|         super(context, attrs); | ||||
| 
 | ||||
|         setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|         setText(Utils.fromHtml(getText().toString())); | ||||
|         setText(fromHtml(getText().toString())); | ||||
|     } | ||||
| 
 | ||||
|     public void setHtmlText(String newText) { | ||||
|         setText(Utils.fromHtml(newText)); | ||||
|         setText(fromHtml(newText)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fix Html.fromHtml is deprecated problem | ||||
|      * | ||||
|      * @param source provided Html string | ||||
|      * @return returned Spanned of appropriate method according to version check | ||||
|      */ | ||||
|     private static Spanned fromHtml(String source) { | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|             return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY); | ||||
|         } else { | ||||
|             //noinspection deprecation | ||||
|             return Html.fromHtml(source); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import android.support.v7.app.AlertDialog; | |||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
|  | @ -18,6 +17,7 @@ import timber.log.Timber; | |||
|  * Displays a warning to the user if the file already exists on Commons | ||||
|  */ | ||||
| public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { | ||||
| 
 | ||||
|     interface Callback { | ||||
|         void onResult(Result result); | ||||
|     } | ||||
|  | @ -28,14 +28,16 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { | |||
|         DUPLICATE_CANCELLED | ||||
|     } | ||||
| 
 | ||||
|     private final MediaWikiApi api; | ||||
|     private final String fileSha1; | ||||
|     private final Context context; | ||||
|     private final Callback callback; | ||||
| 
 | ||||
|     public ExistingFileAsync(String fileSha1, Context context, Callback callback) { | ||||
|     public ExistingFileAsync(String fileSha1, Context context, Callback callback, MediaWikiApi mwApi) { | ||||
|         this.fileSha1 = fileSha1; | ||||
|         this.context = context; | ||||
|         this.callback = callback; | ||||
|         this.api = mwApi; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -45,7 +47,6 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { | |||
| 
 | ||||
|     @Override | ||||
|     protected Boolean doInBackground(Void... voids) { | ||||
|         MediaWikiApi api = CommonsApplication.getInstance().getMWApi(); | ||||
| 
 | ||||
|         // https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba | ||||
|         boolean fileExists; | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import android.location.LocationListener; | |||
| import android.location.LocationManager; | ||||
| import android.media.ExifInterface; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.RequiresApi; | ||||
|  | @ -16,7 +15,6 @@ import android.support.annotation.RequiresApi; | |||
| import java.io.FileDescriptor; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -26,6 +24,8 @@ import timber.log.Timber; | |||
|  */ | ||||
| public class GPSExtractor { | ||||
| 
 | ||||
|     private final Context context; | ||||
|     private SharedPreferences prefs; | ||||
|     private ExifInterface exif; | ||||
|     private double decLatitude; | ||||
|     private double decLongitude; | ||||
|  | @ -38,9 +38,12 @@ public class GPSExtractor { | |||
|     /** | ||||
|      * Construct from the file descriptor of the image (only for API 24 or newer). | ||||
|      * @param fileDescriptor the file descriptor of the image | ||||
|      * @param context the context | ||||
|      */ | ||||
|     @RequiresApi(24) | ||||
|     public GPSExtractor(@NonNull FileDescriptor fileDescriptor) { | ||||
|     public GPSExtractor(@NonNull FileDescriptor fileDescriptor, Context context, SharedPreferences prefs) { | ||||
|         this.context = context; | ||||
|         this.prefs = prefs; | ||||
|         try { | ||||
|             exif = new ExifInterface(fileDescriptor); | ||||
|         } catch (IOException | IllegalArgumentException e) { | ||||
|  | @ -51,13 +54,16 @@ public class GPSExtractor { | |||
|     /** | ||||
|      * Construct from the file path of the image. | ||||
|      * @param path file path of the image | ||||
|      * @param context the context | ||||
|      */ | ||||
|     public GPSExtractor(@NonNull String path) { | ||||
|     public GPSExtractor(@NonNull String path, Context context, SharedPreferences prefs) { | ||||
|         this.prefs = prefs; | ||||
|         try { | ||||
|             exif = new ExifInterface(path); | ||||
|         } catch (IOException | IllegalArgumentException e) { | ||||
|             Timber.w(e); | ||||
|         } | ||||
|         this.context = context; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -65,9 +71,7 @@ public class GPSExtractor { | |||
|      * @return true if enabled, false if disabled | ||||
|      */ | ||||
|     private boolean gpsPreferenceEnabled() { | ||||
|         SharedPreferences sharedPref | ||||
|                 = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|         boolean gpsPref = sharedPref.getBoolean("allowGps", false); | ||||
|         boolean gpsPref = prefs.getBoolean("allowGps", false); | ||||
|         Timber.d("Gps pref set to: %b", gpsPref); | ||||
|         return gpsPref; | ||||
|     } | ||||
|  | @ -76,8 +80,7 @@ public class GPSExtractor { | |||
|      * Registers a LocationManager to listen for current location | ||||
|      */ | ||||
|     protected void registerLocationManager() { | ||||
|         locationManager = (LocationManager) CommonsApplication.getInstance() | ||||
|                 .getSystemService(Context.LOCATION_SERVICE); | ||||
|         locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); | ||||
|         Criteria criteria = new Criteria(); | ||||
|         String provider = locationManager.getBestProvider(criteria, true); | ||||
|         myLocationListener = new MyLocationListener(); | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import android.content.ContentProviderClient; | |||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.database.DataSetObserver; | ||||
| import android.net.Uri; | ||||
|  | @ -24,11 +25,15 @@ import android.widget.Toast; | |||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.Media; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.auth.AuthenticatedActivity; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.category.CategorizationFragment; | ||||
| import fr.free.nrw.commons.category.OnCategoriesSaveHandler; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
|  | @ -38,24 +43,27 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider; | |||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.modifications.TemplateRemoveModifier; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public  class       MultipleShareActivity | ||||
|         extends     AuthenticatedActivity | ||||
|         implements  MediaDetailPagerFragment.MediaDetailProvider, | ||||
|                     AdapterView.OnItemClickListener, | ||||
|                     FragmentManager.OnBackStackChangedListener, | ||||
|                     MultipleUploadListFragment.OnMultipleUploadInitiatedHandler, | ||||
| public class MultipleShareActivity extends AuthenticatedActivity | ||||
|         implements MediaDetailPagerFragment.MediaDetailProvider, | ||||
|         AdapterView.OnItemClickListener, | ||||
|         FragmentManager.OnBackStackChangedListener, | ||||
|         MultipleUploadListFragment.OnMultipleUploadInitiatedHandler, | ||||
|         OnCategoriesSaveHandler { | ||||
|     private CommonsApplication app; | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject UploadController uploadController; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private ArrayList<Contribution> photosList = null; | ||||
| 
 | ||||
|     private MultipleUploadListFragment uploadsList; | ||||
|     private MediaDetailPagerFragment mediaDetails; | ||||
|     private CategorizationFragment categorizationFragment; | ||||
| 
 | ||||
|     private UploadController uploadController; | ||||
| 
 | ||||
|     @Override | ||||
|     public Media getMediaAtPosition(int i) { | ||||
|         return photosList.get(i); | ||||
|  | @ -132,11 +140,7 @@ public  class       MultipleShareActivity | |||
|                 dialog.setProgress(uploadCount); | ||||
|                 if (uploadCount == photosList.size()) { | ||||
|                     dialog.dismiss(); | ||||
|                     Toast startingToast = Toast.makeText( | ||||
|                             CommonsApplication.getInstance(), | ||||
|                             R.string.uploading_started, | ||||
|                             Toast.LENGTH_LONG | ||||
|                     ); | ||||
|                     Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG); | ||||
|                     startingToast.show(); | ||||
|                 } | ||||
|             }); | ||||
|  | @ -163,8 +167,8 @@ public  class       MultipleShareActivity | |||
|     @Override | ||||
|     public void onCategoriesSave(List<String> categories) { | ||||
|         if (categories.size() > 0) { | ||||
|         ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY); | ||||
|             for (Contribution contribution: photosList) { | ||||
|             ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY); | ||||
|             for (Contribution contribution : photosList) { | ||||
|                 ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri()); | ||||
| 
 | ||||
|                 categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{}))); | ||||
|  | @ -176,9 +180,9 @@ public  class       MultipleShareActivity | |||
|         } | ||||
|         // FIXME: Make sure that the content provider is up | ||||
|         // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin | ||||
|         ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! | ||||
|         EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) | ||||
|                 .param("username", app.getCurrentAccount().name) | ||||
|         ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! | ||||
|         EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs) | ||||
|                 .param("username", sessionManager.getCurrentAccount().name) | ||||
|                 .param("categories-count", categories.size()) | ||||
|                 .param("files-count", photosList.size()) | ||||
|                 .param("source", Contribution.SOURCE_EXTERNAL) | ||||
|  | @ -202,10 +206,8 @@ public  class       MultipleShareActivity | |||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         uploadController = new UploadController(); | ||||
| 
 | ||||
|         setContentView(R.layout.activity_multiple_uploads); | ||||
|         app = CommonsApplication.getInstance(); | ||||
|         ButterKnife.bind(this); | ||||
|         initDrawer(); | ||||
| 
 | ||||
|  | @ -245,7 +247,7 @@ public  class       MultipleShareActivity | |||
| 
 | ||||
|     @Override | ||||
|     protected void onAuthCookieAcquired(String authCookie) { | ||||
|         app.getMWApi().setAuthCookie(authCookie); | ||||
|         mwApi.setAuthCookie(authCookie); | ||||
|         Intent intent = getIntent(); | ||||
| 
 | ||||
|         if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { | ||||
|  | @ -266,7 +268,7 @@ public  class       MultipleShareActivity | |||
| 
 | ||||
|             uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList"); | ||||
|             if (uploadsList == null) { | ||||
|                 uploadsList =  new MultipleUploadListFragment(); | ||||
|                 uploadsList = new MultipleUploadListFragment(); | ||||
|                 getSupportFragmentManager() | ||||
|                         .beginTransaction() | ||||
|                         .add(R.id.uploadsFragmentContainer, uploadsList, "uploadsList") | ||||
|  | @ -288,16 +290,16 @@ public  class       MultipleShareActivity | |||
|     public void onBackPressed() { | ||||
|         super.onBackPressed(); | ||||
|         if (categorizationFragment != null && categorizationFragment.isVisible()) { | ||||
|             EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) | ||||
|                     .param("username", app.getCurrentAccount().name) | ||||
|             EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs) | ||||
|                     .param("username", sessionManager.getCurrentAccount().name) | ||||
|                     .param("categories-count", categorizationFragment.getCurrentSelectedCount()) | ||||
|                     .param("files-count", photosList.size()) | ||||
|                     .param("source", Contribution.SOURCE_EXTERNAL) | ||||
|                     .param("result", "cancelled") | ||||
|                     .log(); | ||||
|         } else { | ||||
|             EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|                     .param("username", app.getCurrentAccount().name) | ||||
|             EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs) | ||||
|                     .param("username", sessionManager.getCurrentAccount().name) | ||||
|                     .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE)) | ||||
|                     .param("multiple", true) | ||||
|                     .param("result", "cancelled") | ||||
|  | @ -307,11 +309,7 @@ public  class       MultipleShareActivity | |||
| 
 | ||||
|     @Override | ||||
|     public void onBackStackChanged() { | ||||
|         if (mediaDetails != null && mediaDetails.isVisible()) { | ||||
|             getSupportActionBar().setDisplayHomeAsUpEnabled(true); | ||||
|         } else { | ||||
|             getSupportActionBar().setDisplayHomeAsUpEnabled(false); | ||||
|         } | ||||
|         getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible()) ; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -5,7 +5,6 @@ import android.graphics.Point; | |||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.support.graphics.drawable.VectorDrawableCompat; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.text.Editable; | ||||
| import android.text.TextUtils; | ||||
| import android.text.TextWatcher; | ||||
|  | @ -28,11 +27,12 @@ import android.widget.TextView; | |||
| import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; | ||||
| import com.facebook.drawee.view.SimpleDraweeView; | ||||
| 
 | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.media.MediaDetailPagerFragment; | ||||
| 
 | ||||
| public class MultipleUploadListFragment extends Fragment { | ||||
| public class MultipleUploadListFragment extends DaggerFragment { | ||||
| 
 | ||||
|     public interface OnMultipleUploadInitiatedHandler { | ||||
|         void OnMultipleUploadInitiated(); | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| import com.android.volley.Cache; | ||||
|  | @ -20,7 +21,6 @@ import java.util.HashSet; | |||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| /** | ||||
|  | @ -33,12 +33,14 @@ public class MwVolleyApi { | |||
|     private static RequestQueue REQUEST_QUEUE; | ||||
|     private static final Gson GSON = new GsonBuilder().create(); | ||||
| 
 | ||||
|     protected static Set<String> categorySet; | ||||
|     private static Set<String> categorySet; | ||||
|     private static List<String> categoryList; | ||||
| 
 | ||||
|     private static final String MWURL = "https://commons.wikimedia.org/"; | ||||
|     private final Context context; | ||||
| 
 | ||||
|     public MwVolleyApi() { | ||||
|     public MwVolleyApi(Context context) { | ||||
|         this.context = context; | ||||
|         categorySet = new HashSet<>(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -93,7 +95,7 @@ public class MwVolleyApi { | |||
| 
 | ||||
|     private synchronized RequestQueue getQueue() { | ||||
|         if (REQUEST_QUEUE == null) { | ||||
|             REQUEST_QUEUE = Volley.newRequestQueue(CommonsApplication.getInstance()); | ||||
|             REQUEST_QUEUE = Volley.newRequestQueue(context); | ||||
|         } | ||||
|         return REQUEST_QUEUE; | ||||
|     } | ||||
|  |  | |||
|  | @ -30,14 +30,21 @@ import java.io.File; | |||
| import java.io.FileNotFoundException; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.math.BigInteger; | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.ButterKnife; | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.auth.AuthenticatedActivity; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.caching.CacheController; | ||||
| import fr.free.nrw.commons.category.CategorizationFragment; | ||||
| import fr.free.nrw.commons.category.OnCategoriesSaveHandler; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
|  | @ -46,6 +53,7 @@ import fr.free.nrw.commons.modifications.ModificationsContentProvider; | |||
| import fr.free.nrw.commons.modifications.ModifierSequence; | ||||
| import fr.free.nrw.commons.modifications.TemplateRemoveModifier; | ||||
| import fr.free.nrw.commons.mwapi.EventLog; | ||||
| import fr.free.nrw.commons.mwapi.MediaWikiApi; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED; | ||||
|  | @ -66,7 +74,11 @@ public  class      ShareActivity | |||
|     private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4; | ||||
|     private CategorizationFragment categorizationFragment; | ||||
| 
 | ||||
|     private CommonsApplication app; | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject CacheController cacheController; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject UploadController uploadController; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private String source; | ||||
|     private String mimeType; | ||||
|  | @ -75,8 +87,6 @@ public  class      ShareActivity | |||
|     private Contribution contribution; | ||||
|     private SimpleDraweeView backgroundImageView; | ||||
| 
 | ||||
|     private UploadController uploadController; | ||||
| 
 | ||||
|     private boolean cacheFound; | ||||
| 
 | ||||
|     private GPSExtractor imageObj; | ||||
|  | @ -117,7 +127,7 @@ public  class      ShareActivity | |||
|     @RequiresApi(16) | ||||
|     private boolean needsToRequestStoragePermission() { | ||||
|         // We need to ask storage permission when | ||||
|         // the file is not owned by this app, (e.g. shared from the Gallery) | ||||
|         // the file is not owned by this application, (e.g. shared from the Gallery) | ||||
|         // and permission is not obtained. | ||||
|         return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri) | ||||
|                 && (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) | ||||
|  | @ -127,16 +137,12 @@ public  class      ShareActivity | |||
|     private void uploadBegins() { | ||||
|         getFileMetadata(locationPermitted); | ||||
| 
 | ||||
|         Toast startingToast = Toast.makeText( | ||||
|                 CommonsApplication.getInstance(), | ||||
|                 R.string.uploading_started, | ||||
|                 Toast.LENGTH_LONG | ||||
|         ); | ||||
|         Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG); | ||||
|         startingToast.show(); | ||||
| 
 | ||||
|         if (!cacheFound) { | ||||
|             //Has to be called after apiCall.request() | ||||
|             app.getCacheData().cacheCategory(); | ||||
|             cacheController.cacheCategory(); | ||||
|             Timber.d("Cache the categories found"); | ||||
|         } | ||||
| 
 | ||||
|  | @ -168,10 +174,10 @@ public  class      ShareActivity | |||
| 
 | ||||
|         // FIXME: Make sure that the content provider is up | ||||
|         // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin | ||||
|         ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! | ||||
|         ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! | ||||
| 
 | ||||
|         EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) | ||||
|                 .param("username", app.getCurrentAccount().name) | ||||
|         EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs) | ||||
|                 .param("username", sessionManager.getCurrentAccount().name) | ||||
|                 .param("categories-count", categories.size()) | ||||
|                 .param("files-count", 1) | ||||
|                 .param("source", contribution.getSource()) | ||||
|  | @ -192,16 +198,16 @@ public  class      ShareActivity | |||
|     public void onBackPressed() { | ||||
|         super.onBackPressed(); | ||||
|         if (categorizationFragment != null && categorizationFragment.isVisible()) { | ||||
|             EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT) | ||||
|                     .param("username", app.getCurrentAccount().name) | ||||
|             EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT, mwApi, prefs) | ||||
|                     .param("username", sessionManager.getCurrentAccount().name) | ||||
|                     .param("categories-count", categorizationFragment.getCurrentSelectedCount()) | ||||
|                     .param("files-count", 1) | ||||
|                     .param("source", contribution.getSource()) | ||||
|                     .param("result", "cancelled") | ||||
|                     .log(); | ||||
|         } else { | ||||
|             EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|                     .param("username", app.getCurrentAccount().name) | ||||
|             EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs) | ||||
|                     .param("username", sessionManager.getCurrentAccount().name) | ||||
|                     .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE)) | ||||
|                     .param("multiple", true) | ||||
|                     .param("result", "cancelled") | ||||
|  | @ -211,8 +217,7 @@ public  class      ShareActivity | |||
| 
 | ||||
|     @Override | ||||
|     protected void onAuthCookieAcquired(String authCookie) { | ||||
|         app.getMWApi().setAuthCookie(authCookie); | ||||
| 
 | ||||
|         mwApi.setAuthCookie(authCookie); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -225,11 +230,10 @@ public  class      ShareActivity | |||
|     @Override | ||||
|     public void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         uploadController = new UploadController(); | ||||
| 
 | ||||
|         setContentView(R.layout.activity_share); | ||||
|         ButterKnife.bind(this); | ||||
|         initBack(); | ||||
|         app = CommonsApplication.getInstance(); | ||||
|         backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage); | ||||
|         backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder | ||||
|                 .newInstance(getResources()) | ||||
|  | @ -379,7 +383,7 @@ public  class      ShareActivity | |||
|                 try { | ||||
|                     InputStream inputStream = getContentResolver().openInputStream(mediaUri); | ||||
|                     Timber.d("Input stream created from %s", mediaUri.toString()); | ||||
|                     String fileSHA1 = Utils.getSHA1(inputStream); | ||||
|                     String fileSHA1 = getSHA1(inputStream); | ||||
|                     Timber.d("File SHA1 is: %s", fileSHA1); | ||||
| 
 | ||||
|                     ExistingFileAsync fileAsyncTask = | ||||
|  | @ -387,7 +391,7 @@ public  class      ShareActivity | |||
|                                 Timber.d("%s duplicate check: %s", mediaUri.toString(), result); | ||||
|                                 duplicateCheckPassed = (result == DUPLICATE_PROCEED | ||||
|                                         || result == NO_DUPLICATE); | ||||
|                             }); | ||||
|                             }, mwApi); | ||||
|                     fileAsyncTask.execute(); | ||||
|                 } catch (IOException e) { | ||||
|                     Timber.d(e, "IO Exception: "); | ||||
|  | @ -424,9 +428,7 @@ public  class      ShareActivity | |||
|                 ParcelFileDescriptor descriptor | ||||
|                         = getContentResolver().openFileDescriptor(mediaUri, "r"); | ||||
|                 if (descriptor != null) { | ||||
|                     SharedPreferences sharedPref = PreferenceManager | ||||
|                             .getDefaultSharedPreferences(CommonsApplication.getInstance()); | ||||
|                     boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true); | ||||
|                     boolean useExtStorage = prefs.getBoolean("useExternalStorage", true); | ||||
|                     if (useExtStorage) { | ||||
|                         copyPath = Environment.getExternalStorageDirectory().toString() | ||||
|                                 + "/CommonsApp/" + new Date().getTime() + ".jpg"; | ||||
|  | @ -467,12 +469,12 @@ public  class      ShareActivity | |||
|                         = getContentResolver().openFileDescriptor(mediaUri, "r"); | ||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|                     if (descriptor != null) { | ||||
|                         imageObj = new GPSExtractor(descriptor.getFileDescriptor()); | ||||
|                         imageObj = new GPSExtractor(descriptor.getFileDescriptor(), this, prefs); | ||||
|                     } | ||||
|                 } else { | ||||
|                     String filePath = getPathOfMediaOrCopy(); | ||||
|                     if (filePath != null) { | ||||
|                         imageObj = new GPSExtractor(filePath); | ||||
|                         imageObj = new GPSExtractor(filePath, this, prefs); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | @ -499,12 +501,12 @@ public  class      ShareActivity | |||
|             if (imageObj.imageCoordsExists) { | ||||
|                 double decLongitude = imageObj.getDecLongitude(); | ||||
|                 double decLatitude = imageObj.getDecLatitude(); | ||||
|                 app.getCacheData().setQtPoint(decLongitude, decLatitude); | ||||
|                 cacheController.setQtPoint(decLongitude, decLatitude); | ||||
|             } | ||||
| 
 | ||||
|             MwVolleyApi apiCall = new MwVolleyApi(); | ||||
|             MwVolleyApi apiCall = new MwVolleyApi(this); | ||||
| 
 | ||||
|             List<String> displayCatList = app.getCacheData().findCategory(); | ||||
|             List<String> displayCatList = cacheController.findCategory(); | ||||
|             boolean catListEmpty = displayCatList.isEmpty(); | ||||
| 
 | ||||
|             // If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories | ||||
|  | @ -550,4 +552,41 @@ public  class      ShareActivity | |||
|         } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| 
 | ||||
|     // Get SHA1 of file from input stream | ||||
|     private String getSHA1(InputStream is) { | ||||
| 
 | ||||
|         MessageDigest digest; | ||||
|         try { | ||||
|             digest = MessageDigest.getInstance("SHA1"); | ||||
|         } catch (NoSuchAlgorithmException e) { | ||||
|             Timber.e(e, "Exception while getting Digest"); | ||||
|             return ""; | ||||
|         } | ||||
| 
 | ||||
|         byte[] buffer = new byte[8192]; | ||||
|         int read; | ||||
|         try { | ||||
|             while ((read = is.read(buffer)) > 0) { | ||||
|                 digest.update(buffer, 0, read); | ||||
|             } | ||||
|             byte[] md5sum = digest.digest(); | ||||
|             BigInteger bigInt = new BigInteger(1, md5sum); | ||||
|             String output = bigInt.toString(16); | ||||
|             // Fill to 40 chars | ||||
|             output = String.format("%40s", output).replace(' ', '0'); | ||||
|             Timber.i("File SHA1: %s", output); | ||||
| 
 | ||||
|             return output; | ||||
|         } catch (IOException e) { | ||||
|             Timber.e(e, "IO Exception"); | ||||
|             return ""; | ||||
|         } finally { | ||||
|             try { | ||||
|                 is.close(); | ||||
|             } catch (IOException e) { | ||||
|                 Timber.e(e, "Exception on closing MD5 input stream"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import android.graphics.Color; | |||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v4.app.Fragment; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.text.Editable; | ||||
| import android.text.TextWatcher; | ||||
|  | @ -28,11 +28,15 @@ import android.widget.TextView; | |||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import butterknife.BindView; | ||||
| import butterknife.ButterKnife; | ||||
| import butterknife.OnClick; | ||||
| import butterknife.OnItemSelected; | ||||
| import butterknife.OnTouch; | ||||
| import dagger.android.support.DaggerFragment; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
|  | @ -41,7 +45,7 @@ import timber.log.Timber; | |||
| import static android.view.MotionEvent.ACTION_DOWN; | ||||
| import static android.view.MotionEvent.ACTION_UP; | ||||
| 
 | ||||
| public class SingleUploadFragment extends Fragment { | ||||
| public class SingleUploadFragment extends DaggerFragment { | ||||
| 
 | ||||
|     @BindView(R.id.titleEdit) EditText titleEdit; | ||||
|     @BindView(R.id.descEdit) EditText descEdit; | ||||
|  | @ -49,7 +53,8 @@ public class SingleUploadFragment extends Fragment { | |||
|     @BindView(R.id.share_license_summary) TextView licenseSummaryView; | ||||
|     @BindView(R.id.licenseSpinner) Spinner licenseSpinner; | ||||
| 
 | ||||
|     private SharedPreferences prefs; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private String license; | ||||
|     private OnUploadActionInitiated uploadActionInitiatedHandler; | ||||
|     private TitleTextWatcher textWatcher = new TitleTextWatcher(); | ||||
|  | @ -72,11 +77,10 @@ public class SingleUploadFragment extends Fragment { | |||
|                 String desc = descEdit.getText().toString(); | ||||
| 
 | ||||
|                 //Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these | ||||
|                 SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|                 SharedPreferences.Editor editor = titleDesc.edit(); | ||||
|                 editor.putString("Title", title); | ||||
|                 editor.putString("Desc", desc); | ||||
|                 editor.apply(); | ||||
|                 prefs.edit() | ||||
|                         .putString("Title", title) | ||||
|                         .putString("Desc", desc) | ||||
|                         .apply(); | ||||
| 
 | ||||
|                 uploadActionInitiatedHandler.uploadActionInitiated(title, desc); | ||||
|                 return true; | ||||
|  | @ -91,7 +95,6 @@ public class SingleUploadFragment extends Fragment { | |||
|         View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false); | ||||
|         ButterKnife.bind(this, rootView); | ||||
| 
 | ||||
| 
 | ||||
|         ArrayList<String> licenseItems = new ArrayList<>(); | ||||
|         licenseItems.add(getString(R.string.license_name_cc0)); | ||||
|         licenseItems.add(getString(R.string.license_name_cc_by)); | ||||
|  | @ -99,7 +102,6 @@ public class SingleUploadFragment extends Fragment { | |||
|         licenseItems.add(getString(R.string.license_name_cc_by_four)); | ||||
|         licenseItems.add(getString(R.string.license_name_cc_by_sa_four)); | ||||
| 
 | ||||
|         prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|         license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3); | ||||
| 
 | ||||
|         // check if this is the first time we have uploaded | ||||
|  | @ -172,9 +174,9 @@ public class SingleUploadFragment extends Fragment { | |||
|         } | ||||
| 
 | ||||
|         setLicenseSummary(license); | ||||
|         SharedPreferences.Editor editor = prefs.edit(); | ||||
|         editor.putString(Prefs.DEFAULT_LICENSE, license); | ||||
|         editor.commit(); | ||||
|         prefs.edit() | ||||
|                 .putString(Prefs.DEFAULT_LICENSE, license) | ||||
|                 .commit(); | ||||
|     } | ||||
| 
 | ||||
|     @OnTouch(R.id.share_license_summary) | ||||
|  | @ -182,7 +184,7 @@ public class SingleUploadFragment extends Fragment { | |||
|         if (motionEvent.getActionMasked() == ACTION_DOWN) { | ||||
|             Intent intent = new Intent(); | ||||
|             intent.setAction(Intent.ACTION_VIEW); | ||||
|             intent.setData(Uri.parse(Utils.licenseUrlFor(license))); | ||||
|             intent.setData(Uri.parse(licenseUrlFor(license))); | ||||
|             startActivity(intent); | ||||
|             return true; | ||||
|         } else { | ||||
|  | @ -193,9 +195,8 @@ public class SingleUploadFragment extends Fragment { | |||
|     @OnClick(R.id.titleDescButton) | ||||
|     void setTitleDescButton() { | ||||
|         //Retrieve last title and desc entered | ||||
|         SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); | ||||
|         String title = titleDesc.getString("Title", ""); | ||||
|         String desc = titleDesc.getString("Desc", ""); | ||||
|         String title = prefs.getString("Title", ""); | ||||
|         String desc = prefs.getString("Desc", ""); | ||||
|         Timber.d("Title: %s, Desc: %s", title, desc); | ||||
| 
 | ||||
|         titleEdit.setText(title); | ||||
|  | @ -263,6 +264,23 @@ public class SingleUploadFragment extends Fragment { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private String licenseUrlFor(String license) { | ||||
|         switch (license) { | ||||
|             case Prefs.Licenses.CC_BY_3: | ||||
|                 return "https://creativecommons.org/licenses/by/3.0/"; | ||||
|             case Prefs.Licenses.CC_BY_4: | ||||
|                 return "https://creativecommons.org/licenses/by/4.0/"; | ||||
|             case Prefs.Licenses.CC_BY_SA_3: | ||||
|                 return "https://creativecommons.org/licenses/by-sa/3.0/"; | ||||
|             case Prefs.Licenses.CC_BY_SA_4: | ||||
|                 return "https://creativecommons.org/licenses/by-sa/4.0/"; | ||||
|             case Prefs.Licenses.CC0: | ||||
|                 return "https://creativecommons.org/publicdomain/zero/1.0/"; | ||||
|         } | ||||
|         throw new RuntimeException("Unrecognized license value: " + license); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnUploadActionInitiated { | ||||
|         void uploadActionInitiated(String title, String description); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package fr.free.nrw.commons.upload; | ||||
| 
 | ||||
| import android.content.ComponentName; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
|  | @ -9,31 +10,36 @@ import android.database.Cursor; | |||
| import android.net.Uri; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.IBinder; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.provider.MediaStore; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import java.io.BufferedInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.util.Date; | ||||
| import java.util.concurrent.Executors; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.HandlerService; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.settings.Prefs; | ||||
| import timber.log.Timber; | ||||
| 
 | ||||
| public class UploadController { | ||||
|     private UploadService uploadService; | ||||
|     private final CommonsApplication app; | ||||
|     private SessionManager sessionManager; | ||||
|     private Context context; | ||||
|     private SharedPreferences prefs; | ||||
| 
 | ||||
|     public interface ContributionUploadProgress { | ||||
|         void onUploadStarted(Contribution contribution); | ||||
|     } | ||||
| 
 | ||||
|     public UploadController() { | ||||
|         app = CommonsApplication.getInstance(); | ||||
|     public UploadController(SessionManager sessionManager, Context context, SharedPreferences sharedPreferences) { | ||||
|         this.sessionManager = sessionManager; | ||||
|         this.context = context; | ||||
|         this.prefs = sharedPreferences; | ||||
|     } | ||||
| 
 | ||||
|     private boolean isUploadServiceConnected; | ||||
|  | @ -52,15 +58,15 @@ public class UploadController { | |||
|     }; | ||||
| 
 | ||||
|     public void prepareService() { | ||||
|         Intent uploadServiceIntent = new Intent(app, UploadService.class); | ||||
|         Intent uploadServiceIntent = new Intent(context, UploadService.class); | ||||
|         uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); | ||||
|         app.startService(uploadServiceIntent); | ||||
|         app.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); | ||||
|         context.startService(uploadServiceIntent); | ||||
|         context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); | ||||
|     } | ||||
| 
 | ||||
|     public void cleanup() { | ||||
|         if (isUploadServiceConnected) { | ||||
|             app.unbindService(uploadServiceConnection); | ||||
|             context.unbindService(uploadServiceConnection); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -68,7 +74,9 @@ public class UploadController { | |||
|         Contribution contribution; | ||||
| 
 | ||||
|         //TODO: Modify this to include coords | ||||
|         contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords); | ||||
|         contribution = new Contribution(mediaUri, null, title, description, -1, | ||||
|                 null, null, sessionManager.getCurrentAccount().name, | ||||
|                 CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords); | ||||
| 
 | ||||
|         contribution.setTag("mimeType", mimeType); | ||||
|         contribution.setSource(source); | ||||
|  | @ -78,12 +86,9 @@ public class UploadController { | |||
|     } | ||||
| 
 | ||||
|     public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { | ||||
| 
 | ||||
|         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app); | ||||
| 
 | ||||
|         //Set creator, desc, and license | ||||
|         if (TextUtils.isEmpty(contribution.getCreator())) { | ||||
|             contribution.setCreator(app.getCurrentAccount().name); | ||||
|             contribution.setCreator(sessionManager.getCurrentAccount().name); | ||||
|         } | ||||
| 
 | ||||
|         if (contribution.getDescription() == null) { | ||||
|  | @ -102,14 +107,15 @@ public class UploadController { | |||
|             @Override | ||||
|             protected Contribution doInBackground(Void... voids /* stare into you */) { | ||||
|                 long length; | ||||
|                 ContentResolver contentResolver = context.getContentResolver(); | ||||
|                 try { | ||||
|                     if (contribution.getDataLength() <= 0) { | ||||
|                         length = app.getContentResolver() | ||||
|                         length = contentResolver | ||||
|                                 .openAssetFileDescriptor(contribution.getLocalUri(), "r") | ||||
|                                 .getLength(); | ||||
|                         if (length == -1) { | ||||
|                             // Let us find out the long way! | ||||
|                             length = Utils.countBytes(app.getContentResolver() | ||||
|                             length = countBytes(contentResolver | ||||
|                                     .openInputStream(contribution.getLocalUri())); | ||||
|                         } | ||||
|                         contribution.setDataLength(length); | ||||
|  | @ -126,7 +132,7 @@ public class UploadController { | |||
|                 Boolean imagePrefix = false; | ||||
| 
 | ||||
|                 if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) { | ||||
|                     mimeType = app.getContentResolver().getType(contribution.getLocalUri()); | ||||
|                     mimeType = contentResolver.getType(contribution.getLocalUri()); | ||||
|                 } | ||||
| 
 | ||||
|                 if (mimeType != null) { | ||||
|  | @ -137,7 +143,7 @@ public class UploadController { | |||
| 
 | ||||
|                 if (imagePrefix && contribution.getDateCreated() == null) { | ||||
|                     Timber.d("local uri   " + contribution.getLocalUri()); | ||||
|                     Cursor cursor = app.getContentResolver().query(contribution.getLocalUri(), | ||||
|                     Cursor cursor = contentResolver.query(contribution.getLocalUri(), | ||||
|                             new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null); | ||||
|                     if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) { | ||||
|                         cursor.moveToFirst(); | ||||
|  | @ -165,4 +171,14 @@ public class UploadController { | |||
|             } | ||||
|         }.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private long countBytes(InputStream stream) throws IOException { | ||||
|         long count = 0; | ||||
|         BufferedInputStream bis = new BufferedInputStream(stream); | ||||
|         while (bis.read() != -1) { | ||||
|             count++; | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import android.content.ContentProviderClient; | |||
| import android.content.ContentResolver; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.graphics.BitmapFactory; | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.NotificationCompat; | ||||
|  | @ -22,10 +23,14 @@ import java.util.Set; | |||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
| import fr.free.nrw.commons.HandlerService; | ||||
| import fr.free.nrw.commons.R; | ||||
| import fr.free.nrw.commons.Utils; | ||||
| import fr.free.nrw.commons.auth.SessionManager; | ||||
| import fr.free.nrw.commons.contributions.Contribution; | ||||
| import fr.free.nrw.commons.contributions.ContributionsActivity; | ||||
| import fr.free.nrw.commons.contributions.ContributionsContentProvider; | ||||
|  | @ -45,12 +50,13 @@ public class UploadService extends HandlerService<Contribution> { | |||
|     public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source"; | ||||
|     public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign"; | ||||
| 
 | ||||
|     @Inject MediaWikiApi mwApi; | ||||
|     @Inject SessionManager sessionManager; | ||||
|     @Inject @Named("default_preferences") SharedPreferences prefs; | ||||
| 
 | ||||
|     private NotificationManager notificationManager; | ||||
|     private ContentProviderClient contributionsProviderClient; | ||||
|     private CommonsApplication app; | ||||
| 
 | ||||
|     private NotificationCompat.Builder curProgressNotification; | ||||
| 
 | ||||
|     private int toUpload; | ||||
| 
 | ||||
|     // The file names of unfinished uploads, used to prevent overwriting | ||||
|  | @ -118,7 +124,6 @@ public class UploadService extends HandlerService<Contribution> { | |||
|         super.onCreate(); | ||||
| 
 | ||||
|         notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); | ||||
|         app = CommonsApplication.getInstance(); | ||||
|         contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); | ||||
|     } | ||||
| 
 | ||||
|  | @ -180,8 +185,6 @@ public class UploadService extends HandlerService<Contribution> { | |||
| 
 | ||||
|     @SuppressLint("StringFormatInvalid") | ||||
|     private void uploadContribution(Contribution contribution) { | ||||
|         MediaWikiApi api = app.getMWApi(); | ||||
| 
 | ||||
|         InputStream file; | ||||
| 
 | ||||
|         String notificationTag = contribution.getLocalUri().toString(); | ||||
|  | @ -228,9 +231,9 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                 filename = findUniqueFilename(filename); | ||||
|                 unfinishedUploads.add(filename); | ||||
|             } | ||||
|             if (!api.validateLogin()) { | ||||
|             if (!mwApi.validateLogin()) { | ||||
|                 // Need to revalidate! | ||||
|                 if (app.revalidateAuthToken()) { | ||||
|                 if (sessionManager.revalidateAuthToken()) { | ||||
|                     Timber.d("Successfully revalidated token!"); | ||||
|                 } else { | ||||
|                     Timber.d("Unable to revalidate :("); | ||||
|  | @ -246,7 +249,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                     getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()), | ||||
|                     contribution | ||||
|             ); | ||||
|             UploadResult uploadResult = api.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); | ||||
|             UploadResult uploadResult = mwApi.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); | ||||
| 
 | ||||
|             Timber.d("Response is %s", uploadResult.toString()); | ||||
| 
 | ||||
|  | @ -255,8 +258,8 @@ public class UploadService extends HandlerService<Contribution> { | |||
|             String resultStatus = uploadResult.getResultStatus(); | ||||
|             if (!resultStatus.equals("Success")) { | ||||
|                 showFailedNotification(contribution); | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|                         .param("username", app.getCurrentAccount().name) | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs) | ||||
|                         .param("username", sessionManager.getCurrentAccount().name) | ||||
|                         .param("source", contribution.getSource()) | ||||
|                         .param("multiple", contribution.getMultiple()) | ||||
|                         .param("result", uploadResult.getErrorCode()) | ||||
|  | @ -269,8 +272,8 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                 contribution.setDateUploaded(uploadResult.getDateUploaded()); | ||||
|                 contribution.save(); | ||||
| 
 | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) | ||||
|                         .param("username", app.getCurrentAccount().name) | ||||
|                 EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT, mwApi, prefs) | ||||
|                         .param("username", sessionManager.getCurrentAccount().name) | ||||
|                         .param("source", contribution.getSource()) //FIXME | ||||
|                         .param("filename", contribution.getFilename()) | ||||
|                         .param("multiple", contribution.getMultiple()) | ||||
|  | @ -287,7 +290,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|             toUpload--; | ||||
|             if (toUpload == 0) { | ||||
|                 // Sync modifications right after all uplaods are processed | ||||
|                 ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle()); | ||||
|                 ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle()); | ||||
|                 stopForeground(true); | ||||
|             } | ||||
|         } | ||||
|  | @ -310,7 +313,6 @@ public class UploadService extends HandlerService<Contribution> { | |||
|     } | ||||
| 
 | ||||
|     private String findUniqueFilename(String fileName) throws IOException { | ||||
|         MediaWikiApi api = app.getMWApi(); | ||||
|         String sequenceFileName; | ||||
|         for (int sequenceNumber = 1; true; sequenceNumber++) { | ||||
|             if (sequenceNumber == 1) { | ||||
|  | @ -326,7 +328,7 @@ public class UploadService extends HandlerService<Contribution> { | |||
|                     sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2"); | ||||
|                 } | ||||
|             } | ||||
|             if (!api.fileExistsWithName(sequenceFileName) | ||||
|             if (!mwApi.fileExistsWithName(sequenceFileName) | ||||
|                     && !unfinishedUploads.contains(sequenceFileName)) { | ||||
|                 break; | ||||
|             } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import java.io.File; | |||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.OutputStream; | ||||
| import java.io.OutputStreamWriter; | ||||
| 
 | ||||
| import fr.free.nrw.commons.CommonsApplication; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vivek Maskara
						Vivek Maskara