diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index 6c3bc6a5c..5ad1c7700 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -82,6 +82,15 @@ android:label="@string/title_activity_settings" /> + + + + + + + + + + + + + @@ -127,6 +147,13 @@ android:authorities="org.wikimedia.commons.contributions.contentprovider" android:exported="false"> + + com.actionbarsherlock actionbarsherlock - 4.2.0 + 4.4.0 apklib - - - com.google.android - support-v4 - - android diff --git a/commons/res/layout/activity_campaigns.xml b/commons/res/layout/activity_campaigns.xml new file mode 100644 index 000000000..cfac7c79b --- /dev/null +++ b/commons/res/layout/activity_campaigns.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/commons/res/layout/fragment_campaigns.xml b/commons/res/layout/fragment_campaigns.xml new file mode 100644 index 000000000..3ff614f0e --- /dev/null +++ b/commons/res/layout/fragment_campaigns.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/commons/res/layout/layout_campaign_item.xml b/commons/res/layout/layout_campaign_item.xml new file mode 100644 index 000000000..79c0355dd --- /dev/null +++ b/commons/res/layout/layout_campaign_item.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml index 5a5c1d197..8aa63f709 100644 --- a/commons/res/values/strings.xml +++ b/commons/res/values/strings.xml @@ -138,4 +138,5 @@ Categories Loading... None selected + Campaigns diff --git a/commons/res/xml/campaigns_sync_adapter.xml b/commons/res/xml/campaigns_sync_adapter.xml new file mode 100644 index 000000000..778984c07 --- /dev/null +++ b/commons/res/xml/campaigns_sync_adapter.xml @@ -0,0 +1,9 @@ + + + diff --git a/commons/src/main/java/org/wikimedia/commons/Media.java b/commons/src/main/java/org/wikimedia/commons/Media.java index 2d33c76ff..e09e00f86 100644 --- a/commons/src/main/java/org/wikimedia/commons/Media.java +++ b/commons/src/main/java/org/wikimedia/commons/Media.java @@ -59,6 +59,9 @@ public class Media implements Parcelable { } public String getImageUrl() { + if(imageUrl == null) { + imageUrl = Utils.makeThumbBaseUrl(this.getFilename()); + } return imageUrl; } @@ -103,7 +106,7 @@ public class Media implements Parcelable { } public String getThumbnailUrl(int width) { - return Utils.makeThumbUrl(imageUrl, filename, width); + return Utils.makeThumbUrl(getImageUrl(), getFilename(), width); } public int getWidth() { @@ -182,6 +185,10 @@ public class Media implements Parcelable { } } + public Media(String filename) { + this.filename = filename; + } + public Media(Uri localUri, String imageUrl, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator) { this(); this.localUri = localUri; diff --git a/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java index 57bfd4399..1cdc61aac 100644 --- a/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java +++ b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java @@ -182,7 +182,7 @@ public class MediaWikiImageView extends ImageView { if (response.getBitmap() != null) { setImageBitmap(response.getBitmap()); - if(tryOriginal && mMedia instanceof Contribution && response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight()) { + if(tryOriginal && mMedia instanceof Contribution && (response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight())) { // If there is no width information for this image, save it. This speeds up image loading massively for smaller images mMedia.setHeight(response.getBitmap().getHeight()); mMedia.setWidth(response.getBitmap().getWidth()); diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java index 54e5f3013..8f30af191 100644 --- a/commons/src/main/java/org/wikimedia/commons/Utils.java +++ b/commons/src/main/java/org/wikimedia/commons/Utils.java @@ -2,6 +2,7 @@ package org.wikimedia.commons; import android.net.Uri; import android.os.*; +import android.util.Log; import com.nostra13.universalimageloader.core.*; import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; diff --git a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java index d4730d7b0..573c0dfe9 100644 --- a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java @@ -16,6 +16,7 @@ import android.support.v4.app.NavUtils; import org.wikimedia.commons.*; import org.wikimedia.commons.EventLog; +import org.wikimedia.commons.campaigns.CampaignsContentProvider; import org.wikimedia.commons.contributions.*; import org.wikimedia.commons.modifications.ModificationsContentProvider; @@ -67,6 +68,7 @@ public class LoginActivity extends AccountAuthenticatorActivity { // FIXME: If the user turns it off, it shouldn't be auto turned back on ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default! ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(account, CampaignsContentProvider.AUTHORITY, true); // Enable sync by default! context.finish(); } else { int response; diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java new file mode 100644 index 000000000..61b796eab --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -0,0 +1,190 @@ +package org.wikimedia.commons.campaigns; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; +import java.util.ArrayList; + +// FIXME: Implement Parcelable +public class Campaign implements Serializable { + private boolean enabled; + + private String autoAddWikitext; + private ArrayList autoAddCategories; + + private String name; + private String ownWorkLicenseDefault; + + private String defaultDescription; + + private JSONObject config; + private String body; + private boolean isParsed; + private String trackingCategory; + private String description; + private String title; + + public boolean isEnabled() { + return enabled; + } + + public String getAutoAddWikitext() { + if(!this.isParsed) { + this.parseConfig(); + } + return autoAddWikitext; + } + + public ArrayList getAutoAddCategories() { + if(!this.isParsed) { + this.parseConfig(); + } + return autoAddCategories; + } + + public String getName() { + return name; + } + + public String getOwnWorkLicenseDefault() { + if(!this.isParsed) { + this.parseConfig(); + } + return ownWorkLicenseDefault; + } + + public String getDefaultDescription() { + if(!this.isParsed) { + this.parseConfig(); + } + return defaultDescription; + } + + public JSONObject getConfig() { + if(!this.isParsed) { + this.parseConfig(); + } + return config; + } + + private void parseConfig() { + try { + this.config = new JSONObject(body); + } catch (JSONException e) { + throw new RuntimeException(e); // because what else are you gonna do? + } + if(config.has("autoAdd")) { + this.autoAddWikitext = config.optJSONObject("autoAdd").optString("wikitext", null); + if(config.optJSONObject("autoAdd").has("categories")) { + this.autoAddCategories = new ArrayList(); + JSONArray catsArray = config.optJSONObject("autoAdd").optJSONArray("categories"); + for(int i=0; i < catsArray.length(); i++) { + autoAddCategories.add(catsArray.optString(i)); + } + } + } + this.title = config.optString("title", name); + this.description = config.optString("description", ""); + this.isParsed = true; + } + private Campaign(String name, String body, String trackingCategory) { + this.name = name; + this.body = body; + this.trackingCategory = trackingCategory; + } + + public ContentValues toContentValues() { + ContentValues cv = new ContentValues(); + cv.put(Table.COLUMN_NAME, this.getName()); + cv.put(Table.COLUMN_ENABLED, this.isEnabled() ? 1 : 0); + cv.put(Table.COLUMN_TITLE, this.getTitle()); + cv.put(Table.COLUMN_DESCRIPTION, this.getDescription()); + cv.put(Table.COLUMN_TRACKING_CATEGORY, this.getTrackingCategory()); + cv.put(Table.COLUMN_BODY, this.body); + return cv; + } + + public static Campaign parse(String name, String body, String trackingCategory) { + Campaign c = new Campaign(name, body, trackingCategory); + c.parseConfig(); + return c; + } + + public static Campaign fromCursor(Cursor cursor) { + String name = cursor.getString(1); + Boolean enabled = cursor.getInt(2) == 1; + String title = cursor.getString(3); + String description = cursor.getString(4); + String trackingCategory = cursor.getString(5); + String body = cursor.getString(6); + Campaign c = new Campaign(name, body, trackingCategory); + c.title = title; + c.description = description; + c.enabled = enabled; + return c; + } + + public String getTrackingCategory() { + return trackingCategory; + } + + public String getDescription() { + return description; + } + + public String getTitle() { + return title; + } + + public static class Table { + public static final String TABLE_NAME = "campaigns"; + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_NAME = "name"; + public static final String COLUMN_ENABLED = "enabled"; + public static final String COLUMN_TITLE = "title"; + public static final String COLUMN_DESCRIPTION = "description"; + public static final String COLUMN_TRACKING_CATEGORY = "tracking_category"; + public static final String COLUMN_BODY = "body"; + + // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. + public static final String[] ALL_FIELDS = { + COLUMN_ID, + COLUMN_NAME, + COLUMN_ENABLED, + COLUMN_TITLE, + COLUMN_DESCRIPTION, + COLUMN_TRACKING_CATEGORY, + COLUMN_BODY + }; + + + private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" + + "_id INTEGER PRIMARY KEY," + + "name STRING," + + "enabled INTEGER," + + "title STRING," + + "description STRING," + + "tracking_category STRING," + + "body STRING" + + ");"; + + + public static void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_STATEMENT); + } + + public static void onUpdate(SQLiteDatabase db, int from, int to) { + if(to <= 6) { + onCreate(db); + return; + } + return; + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java new file mode 100644 index 000000000..cfcf5f413 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -0,0 +1,60 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.R; +import org.wikimedia.commons.contributions.ContributionsActivity; + +public class CampaignActivity + extends SherlockFragmentActivity + implements LoaderManager.LoaderCallbacks { + + private ListView campaignsListView; + private CampaignsListAdapter campaignsListAdapter; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_campaigns); + + ContentResolver.setSyncAutomatically(((CommonsApplication)getApplicationContext()).getCurrentAccount(), CampaignsContentProvider.AUTHORITY, true); // Enable sync by default! + campaignsListView = (ListView) findViewById(R.id.campaignsList); + + campaignsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Campaign c = Campaign.fromCursor((Cursor) adapterView.getItemAtPosition(i)); + Intent intent = new Intent(CampaignActivity.this, ContributionsActivity.class); + intent.putExtra("campaign", c); + startActivity(intent); + } + }); + getSupportLoaderManager().initLoader(0, null, this); + } + + public Loader onCreateLoader(int i, Bundle bundle) { + return new CursorLoader(this, CampaignsContentProvider.BASE_URI, Campaign.Table.ALL_FIELDS, "", null, ""); + } + + public void onLoadFinished(Loader cursorLoader, Cursor cursor) { + if(campaignsListAdapter == null) { + campaignsListAdapter = new CampaignsListAdapter(this, cursor, 0); + campaignsListView.setAdapter(campaignsListAdapter); + } else { + campaignsListAdapter.swapCursor(cursor); + } + } + + public void onLoaderReset(Loader cursorLoader) { + campaignsListAdapter.swapCursor(null); + } +} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java new file mode 100644 index 000000000..a105c0e62 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java @@ -0,0 +1,49 @@ +package org.wikimedia.commons.campaigns; + +import android.net.Uri; +import org.wikimedia.commons.contributions.Contribution; + +import java.util.ArrayList; +import java.util.Date; + +public class CampaignContribution extends Contribution { + private Campaign campaign; + + private ArrayList fieldValues; + + + public CampaignContribution(Uri localUri, String remoteUri, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator, String editSummary, Campaign campaign) { + super(localUri, remoteUri, filename, description, dataLength, dateCreated, dateUploaded, creator, editSummary); + this.campaign = campaign; + } + + public Campaign getCampaign() { + return campaign; + } + + public void setCampaign(Campaign campaign) { + this.campaign = campaign; + } + + @Override + public String getTrackingTemplates() { + StringBuffer buffer = new StringBuffer(); + if(campaign.getAutoAddWikitext() != null) { + buffer.append(campaign.getAutoAddWikitext()).append("\n"); + } + if(campaign.getAutoAddCategories() != null && campaign.getAutoAddCategories().size() != 0) { + for(String cat : campaign.getAutoAddCategories()) { + buffer.append("[[Category:").append(cat).append("]]").append("\n"); + } + } else { + buffer.append("{{subst:unc}}\n"); + } + buffer.append("[[Category:").append(campaign.getTrackingCategory()).append("\n"); + return buffer.toString(); + } + + @Override + public String getDescription() { + return super.getDescription(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java new file mode 100644 index 000000000..80bd972bc --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java @@ -0,0 +1,206 @@ +package org.wikimedia.commons.campaigns; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.data.DBOpenHelper; + +public class CampaignsContentProvider extends ContentProvider{ + + private static final int CAMPAIGNS = 1; + private static final int CAMPAIGNS_ID = 2; + + public static final String AUTHORITY = "org.wikimedia.commons.campaigns.contentprovider"; + private static final String BASE_PATH = "campiagns"; + + public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); + + private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + static { + uriMatcher.addURI(AUTHORITY, BASE_PATH, CAMPAIGNS); + uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CAMPAIGNS_ID); + } + + + public static Uri uriForId(int id) { + return Uri.parse(BASE_URI.toString() + "/" + id); + } + + private DBOpenHelper dbOpenHelper; + @Override + public boolean onCreate() { + dbOpenHelper = ((CommonsApplication)this.getContext().getApplicationContext()).getDbOpenHelper(); + return false; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + queryBuilder.setTables(Campaign.Table.TABLE_NAME); + + int uriType = uriMatcher.match(uri); + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + Cursor cursor; + + switch(uriType) { + case CAMPAIGNS: + cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); + break; + case CAMPAIGNS_ID: + cursor = queryBuilder.query(db, + Campaign.Table.ALL_FIELDS, + "_id = ?", + new String[] { uri.getLastPathSegment() }, + null, + null, + sortOrder + ); + break; + default: + throw new IllegalArgumentException("Unknown URI" + uri); + } + + cursor.setNotificationUri(getContext().getContentResolver(), uri); + + return cursor; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + long id = 0; + switch (uriType) { + case CAMPAIGNS: + sqlDB.beginTransaction(); + // if the campaign already exists, rip it out and then re-insert + if(campaignExists(sqlDB, contentValues)) { + sqlDB.delete( + Campaign.Table.TABLE_NAME, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{contentValues.getAsString(Campaign.Table.COLUMN_NAME)} + ); + } + id = sqlDB.insert(Campaign.Table.TABLE_NAME, null, contentValues); + sqlDB.endTransaction(); + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return Uri.parse(BASE_URI + "/" + id); + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + int rows = 0; + int uriType = uriMatcher.match(uri); + + SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); + + switch(uriType) { + case CAMPAIGNS_ID: + rows = db.delete(Campaign.Table.TABLE_NAME, + "_id = ?", + new String[] { uri.getLastPathSegment() } + ); + break; + default: + throw new IllegalArgumentException("Unknown URI" + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return rows; + } + + private boolean campaignExists(SQLiteDatabase db, ContentValues campaign) { + Cursor cr = db.query( + Campaign.Table.TABLE_NAME, + new String[]{Campaign.Table.COLUMN_NAME}, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{campaign.getAsString(Campaign.Table.COLUMN_NAME)}, + "", "", "" + ); + return cr != null && cr.getCount() != 0; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + Log.d("Commons", "Hello, bulk insert!"); + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + sqlDB.beginTransaction(); + switch (uriType) { + case CAMPAIGNS: + for(ContentValues value: values) { + Log.d("Commons", "Inserting! " + value.toString()); + // if the campaign already exists, rip it out and then re-insert + if(campaignExists(sqlDB, value)) { + sqlDB.delete( + Campaign.Table.TABLE_NAME, + Campaign.Table.COLUMN_NAME + " = ?", + new String[]{value.getAsString(Campaign.Table.COLUMN_NAME)} + ); + } + sqlDB.insert(Campaign.Table.TABLE_NAME, null, value); + } + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + sqlDB.setTransactionSuccessful(); + sqlDB.endTransaction(); + getContext().getContentResolver().notifyChange(uri, null); + return values.length; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) { + /* + SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false") + Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues + should be fine. So only issues are those that pass in via concating. + + In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise. + */ + int uriType = uriMatcher.match(uri); + SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); + int rowsUpdated = 0; + switch (uriType) { + case CAMPAIGNS: + rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME, + contentValues, + selection, + selectionArgs); + break; + case CAMPAIGNS_ID: + int id = Integer.valueOf(uri.getLastPathSegment()); + + if (TextUtils.isEmpty(selection)) { + rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME, + contentValues, + Campaign.Table.COLUMN_ID + " = ?", + new String[] { String.valueOf(id) } ); + } else { + throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID"); + } + break; + default: + throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType); + } + getContext().getContentResolver().notifyChange(uri, null); + return rowsUpdated; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java new file mode 100644 index 000000000..b43d38354 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java @@ -0,0 +1,47 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.support.v4.widget.CursorAdapter; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.actionbarsherlock.app.SherlockFragment; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.MediaWikiImageView; +import org.wikimedia.commons.R; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.campaigns.Campaign; + +class CampaignsListAdapter extends CursorAdapter { + + private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();; + private Activity activity; + + public CampaignsListAdapter(Activity activity, Cursor c, int flags) { + super(activity, c, flags); + this.activity = activity; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { + View parent = activity.getLayoutInflater().inflate(android.R.layout.simple_list_item_1, viewGroup, false); + return parent; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView campaignName = (TextView)view.findViewById(android.R.id.text1); + + Campaign campaign = Campaign.fromCursor(cursor); + + campaignName.setText(campaign.getTitle()); + } + +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java new file mode 100644 index 000000000..9365028dd --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java @@ -0,0 +1,95 @@ +package org.wikimedia.commons.campaigns; + +import android.accounts.Account; +import android.content.*; +import android.database.Cursor; +import android.os.Bundle; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import org.mediawiki.api.ApiResult; +import org.mediawiki.api.MWApi; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.contributions.Contribution; +import org.wikimedia.commons.contributions.ContributionsContentProvider; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; + + +public class CampaignsSyncAdapter extends AbstractThreadedSyncAdapter { + private static int COMMIT_THRESHOLD = 10; + public CampaignsSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + private int getLimit() { + return 500; // FIXME: Parameterize! + } + + @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! + String user = account.name; + MWApi api = CommonsApplication.createMWApi(); + ApiResult result; + Boolean done = false; + String queryContinue = null; + while(!done) { + + try { + MWApi.RequestBuilder builder = api.action("query") + .param("list", "allcampaigns") + // Disabled, since we want to modify local state if the campaign was disabled + // FIXME: To be more effecient, delete the disabled campaigns locally + //.param("ucenabledonly", "true") + .param("uclimit", getLimit()); + if(!TextUtils.isEmpty(queryContinue)) { + builder.param("uccontinue", queryContinue); + } + result = builder.get(); + } catch (IOException e) { + // There isn't really much we can do, eh? + // FIXME: Perhaps add EventLogging? + syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs + Log.d("Commons", "Syncing failed due to " + e.toString()); + return; + } + + ArrayList campaigns = result.getNodes("/api/query/allcampaigns/campaign"); + Log.d("Commons", campaigns.size() + " results!"); + ArrayList campaignValues = new ArrayList(); + for(ApiResult campaignItem: campaigns) { + String name = campaignItem.getString("@name"); + String body = campaignItem.getString("."); + Log.d("Commons", "Campaign body is " + body); + String trackingCat = campaignItem.getString("@trackingCategory"); + Campaign campaign = Campaign.parse(name, body, trackingCat); + campaignValues.add(campaign.toContentValues()); + + if(campaignValues.size() % COMMIT_THRESHOLD == 0) { + try { + contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{})); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + campaignValues.clear(); + } + } + + if(campaignValues.size() != 0) { + try { + contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{})); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + queryContinue = result.getString("/api/query-continue/allcampaigns/@uccontinue"); + if(TextUtils.isEmpty(queryContinue)) { + done = true; + } + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java new file mode 100644 index 000000000..d1975cccb --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java @@ -0,0 +1,27 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import org.wikimedia.commons.contributions.ContributionsSyncAdapter; + +public class CampaignsSyncService extends Service { + + private static final Object sSyncAdapterLock = new Object(); + + private static CampaignsSyncAdapter sSyncAdapter = null; + + @Override + public void onCreate() { + synchronized (sSyncAdapterLock) { + if (sSyncAdapter == null) { + sSyncAdapter = new CampaignsSyncAdapter(getApplicationContext(), true); + } + } + } + + @Override + public IBinder onBind(Intent intent) { + return sSyncAdapter.getSyncAdapterBinder(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java index 7b0942372..55d9e1cd0 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java @@ -117,13 +117,17 @@ public class Contribution extends Media { this.dateUploaded = date; } + public String getTrackingTemplates() { + return "{{subst:unc}}"; // Remove when we have categorization + } + public String getPageContents() { StringBuffer buffer = new StringBuffer(); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd"); buffer .append("== {{int:filedesc}} ==\n") .append("{{Information\n") - .append("|description=").append(description).append("\n") + .append("|description=").append(getDescription()).append("\n") .append("|source=").append("{{own}}\n") .append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n"); if(dateCreated != null) { @@ -133,9 +137,9 @@ public class Contribution extends Media { buffer .append("}}").append("\n") .append("== {{int:license-header}} ==\n") - .append(Utils.licenseTemplateFor(license)).append("\n\n") + .append(Utils.licenseTemplateFor(getLicense())).append("\n\n") .append("{{Uploaded from Mobile|platform=Android|version=").append(CommonsApplication.APPLICATION_VERSION).append("}}\n") - .append("{{subst:unc}}"); // Remove when we have categorization + .append(getTrackingTemplates()); return buffer.toString(); } diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java new file mode 100644 index 000000000..f6beac475 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java @@ -0,0 +1,96 @@ +package org.wikimedia.commons.contributions; + +import android.app.*; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.util.Log; +import com.actionbarsherlock.app.SherlockFragment; +import org.wikimedia.commons.upload.ShareActivity; +import org.wikimedia.commons.upload.UploadService; + +import java.io.File; +import java.io.IOException; +import java.util.Date; + +public class ContributionController { + private SherlockFragment fragment; + private Activity activity; + + private final static int SELECT_FROM_GALLERY = 1; + private final static int SELECT_FROM_CAMERA = 2; + + public ContributionController(SherlockFragment fragment) { + this.fragment = fragment; + this.activity = fragment.getActivity(); + } + + // See http://stackoverflow.com/a/5054673/17865 for why this is done + private Uri lastGeneratedCaptureURI; + + private Uri reGenerateImageCaptureURI() { + String storageState = Environment.getExternalStorageState(); + if(storageState.equals(Environment.MEDIA_MOUNTED)) { + + String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg"; + File _photoFile = new File(path); + try { + if(_photoFile.exists() == false) { + _photoFile.getParentFile().mkdirs(); + _photoFile.createNewFile(); + } + + } catch (IOException e) { + Log.e("Commons", "Could not create file: " + path, e); + } + + return Uri.fromFile(_photoFile); + } else { + throw new RuntimeException("No external storage found!"); + } + } + + public void startCameraCapture() { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + lastGeneratedCaptureURI = reGenerateImageCaptureURI(); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureURI); + fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA); + } + + public void startGalleryPick() { + Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + pickImageIntent.setType("image/*"); + fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY); + } + + public void handleImagePicked(int requestCode, Intent data) { + Intent shareIntent = new Intent(activity, ShareActivity.class); + shareIntent.setAction(Intent.ACTION_SEND); + switch(requestCode) { + case SELECT_FROM_GALLERY: + shareIntent.setType(activity.getContentResolver().getType(data.getData())); + shareIntent.putExtra(Intent.EXTRA_STREAM, data.getData()); + shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY); + break; + case SELECT_FROM_CAMERA: + shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type + shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI); + shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA); + break; + } + activity.startActivity(shareIntent); + } + + public void saveState(Bundle outState) { + outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureURI); + } + + public void loadState(Bundle savedInstanceState) { + if(savedInstanceState != null) { + lastGeneratedCaptureURI = (Uri) savedInstanceState.getParcelable("lastGeneratedCaptureURI"); + } + } + +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java new file mode 100644 index 000000000..3bf5adf4a --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java @@ -0,0 +1,26 @@ +package org.wikimedia.commons.contributions; + +import android.view.View; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import org.wikimedia.commons.MediaWikiImageView; +import org.wikimedia.commons.R; + +class ContributionViewHolder { + final MediaWikiImageView imageView; + final TextView titleView; + final TextView stateView; + final TextView seqNumView; + final ProgressBar progressView; + + String url; + + ContributionViewHolder(View parent) { + imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage); + titleView = (TextView)parent.findViewById(R.id.contributionTitle); + stateView = (TextView)parent.findViewById(R.id.contributionState); + seqNumView = (TextView)parent.findViewById(R.id.contributionSequenceNumber); + progressView = (ProgressBar)parent.findViewById(R.id.contributionProgress); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java index 0a9574960..a1951fa9c 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java @@ -8,6 +8,7 @@ import android.support.v4.content.Loader; import android.content.*; import android.database.Cursor; import android.os.Bundle; +import android.support.v4.widget.CursorAdapter; import android.util.Log; import android.view.View; import android.widget.AdapterView; @@ -16,12 +17,15 @@ import com.actionbarsherlock.view.MenuItem; import org.wikimedia.commons.*; import org.wikimedia.commons.auth.*; +import org.wikimedia.commons.campaigns.Campaign; import org.wikimedia.commons.media.*; import org.wikimedia.commons.upload.UploadService; +import java.util.ArrayList; + public class ContributionsActivity extends AuthenticatedActivity - implements LoaderManager.LoaderCallbacks, + implements LoaderManager.LoaderCallbacks, AdapterView.OnItemClickListener, MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener { @@ -31,6 +35,8 @@ public class ContributionsActivity private ContributionsListFragment contributionsList; private MediaDetailPagerFragment mediaDetails; + private Campaign campaign; + public ContributionsActivity() { super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE); } @@ -101,6 +107,10 @@ public class ContributionsActivity setTitle(R.string.title_activity_contributions); setContentView(R.layout.activity_contributions); + if(getIntent().hasExtra("campaign")) { + this.campaign = (Campaign) getIntent().getSerializableExtra("campaign"); + } + contributionsList = (ContributionsListFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsListFragment); getSupportFragmentManager().addOnBackStackChangedListener(this); @@ -180,13 +190,7 @@ public class ContributionsActivity public void onItemClick(AdapterView adapterView, View view, int position, long item) { - Cursor cursor = (Cursor)adapterView.getItemAtPosition(position); - Contribution c = Contribution.fromCursor(cursor); - - Log.d("Commons", "Clicking for " + c.toContentValues()); showDetail(position); - - Log.d("Commons", "You clicked on:" + c.toContentValues().toString()); } @Override @@ -194,31 +198,51 @@ public class ContributionsActivity return super.onCreateOptionsMenu(menu); } - public Loader onCreateLoader(int i, Bundle bundle) { - return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT); + public Loader onCreateLoader(int i, Bundle bundle) { + if(campaign == null) { + return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT); + } else { + return new CategoryImagesLoader(this, campaign.getTrackingCategory()); + } } - public void onLoadFinished(Loader cursorLoader, Cursor cursor) { - allContributions = cursor; - contributionsList.setCursor(cursor); + public void onLoadFinished(Loader cursorLoader, Object result) { + if(campaign == null) { + Cursor cursor = (Cursor) result; + if(contributionsList.getAdapter() == null) { + contributionsList.setAdapter(new ContributionsListAdapter(this, cursor, 0)); + } else { + ((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor); + } - getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount())); + getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount())); + } else { + contributionsList.setAdapter(new MediaListAdapter(this, (ArrayList) result)); + } } - public void onLoaderReset(Loader cursorLoader) { - contributionsList.setCursor(null); + public void onLoaderReset(Loader cursorLoader) { + if(campaign == null) { + ((CursorAdapter) contributionsList.getAdapter()).swapCursor(null); + } else { + //((MediaListAdapter) contributionsList.getAdapter()). + // DO SOMETHING! + } } public Media getMediaAtPosition(int i) { - allContributions.moveToPosition(i); - return Contribution.fromCursor(allContributions); + if(campaign == null) { + return Contribution.fromCursor((Cursor) contributionsList.getAdapter().getItem(i)); + } else { + return (Media) contributionsList.getAdapter().getItem(i); + } } public int getTotalMediaCount() { - if(allContributions == null) { + if(contributionsList.getAdapter() == null) { return 0; } - return allContributions.getCount(); + return contributionsList.getAdapter().getCount(); } public void notifyDatasetChanged() { diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java new file mode 100644 index 000000000..3ce0c4c03 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java @@ -0,0 +1,107 @@ +package org.wikimedia.commons.contributions; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.support.v4.widget.CursorAdapter; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import com.actionbarsherlock.app.SherlockFragment; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.MediaWikiImageView; +import org.wikimedia.commons.R; +import org.wikimedia.commons.Utils; + +class ContributionsListAdapter extends CursorAdapter { + + private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();; + private Activity activity; + + public ContributionsListAdapter(Activity activity, Cursor c, int flags) { + super(activity, c, flags); + this.activity = activity; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { + View parent = activity.getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false); + parent.setTag(new ContributionViewHolder(parent)); + return parent; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + final ContributionViewHolder views = (ContributionViewHolder)view.getTag(); + Contribution contribution = Contribution.fromCursor(cursor); + + String actualUrl = (contribution.getLocalUri() != null && TextUtils.isEmpty(contribution.getLocalUri().toString())) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(640); + + if(views.url == null || !views.url.equals(actualUrl)) { + if(actualUrl.startsWith("http")) { + MediaWikiImageView mwImageView = (MediaWikiImageView)views.imageView; + mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader()); + // FIXME: For transparent images + } else { + com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() { + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + if(loadedImage.hasAlpha()) { + views.imageView.setBackgroundResource(android.R.color.white); + } + views.seqNumView.setVisibility(View.GONE); + } + + }); + } + views.url = actualUrl; + } + + BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable(); + if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) { + views.imageView.setBackgroundResource(android.R.color.white); + } else { + views.imageView.setBackgroundDrawable(null); + } + + views.titleView.setText(contribution.getDisplayTitle()); + + views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1)); + views.seqNumView.setVisibility(View.VISIBLE); + + switch(contribution.getState()) { + case Contribution.STATE_COMPLETED: + views.stateView.setVisibility(View.GONE); + views.progressView.setVisibility(View.GONE); + views.stateView.setText(""); + break; + case Contribution.STATE_QUEUED: + views.stateView.setVisibility(View.VISIBLE); + views.progressView.setVisibility(View.GONE); + views.stateView.setText(R.string.contribution_state_queued); + break; + case Contribution.STATE_IN_PROGRESS: + views.stateView.setVisibility(View.GONE); + views.progressView.setVisibility(View.VISIBLE); + long total = contribution.getDataLength(); + long transferred = contribution.getTransferred(); + if(transferred == 0 || transferred >= total) { + views.progressView.setIndeterminate(true); + } else { + views.progressView.setProgress((int)(((double)transferred / (double)total) * 100)); + } + break; + case Contribution.STATE_FAILED: + views.stateView.setVisibility(View.VISIBLE); + views.stateView.setText(R.string.contribution_state_failed); + views.progressView.setVisibility(View.GONE); + break; + } + + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java index e8caa90b2..0be5eaddb 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java @@ -5,14 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; import android.os.Bundle; -import android.os.Environment; -import android.provider.MediaStore; -import android.support.v4.widget.CursorAdapter; -import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -23,226 +16,55 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import java.io.*; -import java.util.*; - - -import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; import org.wikimedia.commons.*; import org.wikimedia.commons.R; -import org.wikimedia.commons.upload.ShareActivity; -import org.wikimedia.commons.upload.UploadService; public class ContributionsListFragment extends SherlockFragment { - private final static int SELECT_FROM_GALLERY = 1; - private final static int SELECT_FROM_CAMERA = 2; - private GridView contributionsList; private TextView waitingMessage; private TextView emptyMessage; - private ContributionsListAdapter contributionsAdapter; - - private DisplayImageOptions contributionDisplayOptions; - private Cursor allContributions; - - private static class ContributionViewHolder { - final ImageView imageView; - final TextView titleView; - final TextView stateView; - final TextView seqNumView; - final ProgressBar progressView; - - String url; - - ContributionViewHolder(View parent) { - imageView = (ImageView)parent.findViewById(R.id.contributionImage); - titleView = (TextView)parent.findViewById(R.id.contributionTitle); - stateView = (TextView)parent.findViewById(R.id.contributionState); - seqNumView = (TextView)parent.findViewById(R.id.contributionSequenceNumber); - progressView = (ProgressBar)parent.findViewById(R.id.contributionProgress); - } - } - - private class ContributionsListAdapter extends CursorAdapter { - - public ContributionsListAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { - View parent = getActivity().getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false); - parent.setTag(new ContributionViewHolder(parent)); - return parent; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // hack: hide the 'first sync' message once we've loaded a cell - clearSyncMessage(); - - final ContributionViewHolder views = (ContributionViewHolder)view.getTag(); - Contribution contribution = Contribution.fromCursor(cursor); - - String actualUrl = TextUtils.isEmpty(contribution.getImageUrl()) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(320); - - if(views.url == null || !views.url.equals(actualUrl)) { - if(actualUrl.startsWith("http")) { - MediaWikiImageView mwImageView = (MediaWikiImageView)views.imageView; - mwImageView.setMedia(contribution, ((CommonsApplication) getActivity().getApplicationContext()).getImageLoader()); - // FIXME: For transparent images - } else { - com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() { - - @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - if(loadedImage.hasAlpha()) { - views.imageView.setBackgroundResource(android.R.color.white); - } - views.seqNumView.setVisibility(View.GONE); - } - - }); - } - views.url = actualUrl; - } - - BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable(); - if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) { - views.imageView.setBackgroundResource(android.R.color.white); - } else { - views.imageView.setBackgroundDrawable(null); - } - - views.titleView.setText(contribution.getDisplayTitle()); - - views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1)); - views.seqNumView.setVisibility(View.VISIBLE); - - switch(contribution.getState()) { - case Contribution.STATE_COMPLETED: - views.stateView.setVisibility(View.GONE); - views.progressView.setVisibility(View.GONE); - views.stateView.setText(""); - break; - case Contribution.STATE_QUEUED: - views.stateView.setVisibility(View.VISIBLE); - views.progressView.setVisibility(View.GONE); - views.stateView.setText(R.string.contribution_state_queued); - break; - case Contribution.STATE_IN_PROGRESS: - views.stateView.setVisibility(View.GONE); - views.progressView.setVisibility(View.VISIBLE); - long total = contribution.getDataLength(); - long transferred = contribution.getTransferred(); - if(transferred == 0 || transferred >= total) { - views.progressView.setIndeterminate(true); - } else { - views.progressView.setProgress((int)(((double)transferred / (double)total) * 100)); - } - break; - case Contribution.STATE_FAILED: - views.stateView.setVisibility(View.VISIBLE); - views.stateView.setText(R.string.contribution_state_failed); - views.progressView.setVisibility(View.GONE); - break; - } - - } - } + private ContributionController controller; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_contributions, container, false); } - public void setCursor(Cursor cursor) { - if(allContributions == null) { - contributionsAdapter = new ContributionsListAdapter(this.getActivity(), cursor, 0); - contributionsList.setAdapter(contributionsAdapter); - } - allContributions = cursor; - contributionsAdapter.swapCursor(cursor); + public ListAdapter getAdapter() { + return contributionsList.getAdapter(); + } + + public void setAdapter(ListAdapter adapter) { + this.contributionsList.setAdapter(adapter); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); + controller.saveState(outState); outState.putInt("grid-position", contributionsList.getFirstVisiblePosition()); - outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureURI); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - switch(requestCode) { - case SELECT_FROM_GALLERY: - if(resultCode == Activity.RESULT_OK) { - Intent shareIntent = new Intent(getActivity(), ShareActivity.class); - shareIntent.setAction(Intent.ACTION_SEND); - - shareIntent.setType(getActivity().getContentResolver().getType(data.getData())); - shareIntent.putExtra(Intent.EXTRA_STREAM, data.getData()); - shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY); - startActivity(shareIntent); - } - break; - case SELECT_FROM_CAMERA: - if(resultCode == Activity.RESULT_OK) { - Intent shareIntent = new Intent(getActivity(), ShareActivity.class); - shareIntent.setAction(Intent.ACTION_SEND); - Log.d("Commons", "Uri is " + lastGeneratedCaptureURI); - shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type - shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI); - shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA); - startActivity(shareIntent); - } - break; + if(resultCode == Activity.RESULT_OK) { + controller.handleImagePicked(requestCode, data); } } - // See http://stackoverflow.com/a/5054673/17865 for why this is done - private Uri lastGeneratedCaptureURI; - - private void reGenerateImageCaptureURI() { - String storageState = Environment.getExternalStorageState(); - if(storageState.equals(Environment.MEDIA_MOUNTED)) { - - String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg"; - File _photoFile = new File(path); - try { - if(_photoFile.exists() == false) { - _photoFile.getParentFile().mkdirs(); - _photoFile.createNewFile(); - } - - } catch (IOException e) { - Log.e("Commons", "Could not create file: " + path, e); - } - - lastGeneratedCaptureURI = Uri.fromFile(_photoFile); - } else { - throw new RuntimeException("No external storage found!"); - } - } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.menu_from_gallery: - Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); - pickImageIntent.setType("image/*"); - startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY); + controller.startGalleryPick(); return true; case R.id.menu_from_camera: - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - reGenerateImageCaptureURI(); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureURI); - startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA); + controller.startCameraCapture(); return true; case R.id.menu_settings: Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class); @@ -282,19 +104,25 @@ public class ContributionsListFragment extends SherlockFragment { setHasOptionsMenu(true); } + @Override + public void onDestroy() { + super.onDestroy(); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + controller = new ContributionController(this); + controller.loadState(savedInstanceState); + contributionsList = (GridView)getView().findViewById(R.id.contributionsList); waitingMessage = (TextView)getView().findViewById(R.id.waitingMessage); emptyMessage = (TextView)getView().findViewById(R.id.waitingMessage); - contributionDisplayOptions = Utils.getGenericDisplayOptions().build(); contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity()); if(savedInstanceState != null) { Log.d("Commons", "Scrolling to " + savedInstanceState.getInt("grid-position")); - lastGeneratedCaptureURI = (Uri) savedInstanceState.getParcelable("lastGeneratedCaptureURI"); contributionsList.setSelection(savedInstanceState.getInt("grid-position")); } diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java b/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java new file mode 100644 index 000000000..33f846043 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java @@ -0,0 +1,47 @@ +package org.wikimedia.commons.contributions; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import com.android.volley.toolbox.ImageLoader; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.Media; +import org.wikimedia.commons.R; + +import java.util.ArrayList; + +public class MediaListAdapter extends BaseAdapter { + private ArrayList mediaList; + private Activity activity; + + public MediaListAdapter(Activity activity, ArrayList mediaList) { + this.mediaList = mediaList; + this.activity = activity; + } + + public int getCount() { + return mediaList.size(); + } + + public Object getItem(int i) { + return mediaList.get(i); + } + + public long getItemId(int i) { + return i; + } + + public View getView(int i, View view, ViewGroup viewGroup) { + if(view == null) { + view = activity.getLayoutInflater().inflate(R.layout.layout_contribution, null, false); + view.setTag(new ContributionViewHolder(view)); + } + + Media m = (Media) getItem(i); + ContributionViewHolder holder = (ContributionViewHolder) view.getTag(); + holder.imageView.setMedia(m, ((CommonsApplication)activity.getApplicationContext()).getImageLoader()); + holder.titleView.setText(m.getDisplayTitle()); + return view; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java index af680650f..a8560a9f5 100644 --- a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java +++ b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java @@ -3,6 +3,7 @@ package org.wikimedia.commons.data; import android.content.*; import android.database.sqlite.*; +import org.wikimedia.commons.campaigns.Campaign; import org.wikimedia.commons.category.Category; import org.wikimedia.commons.contributions.*; import org.wikimedia.commons.modifications.ModifierSequence; @@ -21,6 +22,7 @@ public class DBOpenHelper extends SQLiteOpenHelper{ Contribution.Table.onCreate(sqLiteDatabase); ModifierSequence.Table.onCreate(sqLiteDatabase); Category.Table.onCreate(sqLiteDatabase); + Campaign.Table.onCreate(sqLiteDatabase); } @Override @@ -28,5 +30,6 @@ public class DBOpenHelper extends SQLiteOpenHelper{ Contribution.Table.onUpdate(sqLiteDatabase, from, to); ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to); Category.Table.onUpdate(sqLiteDatabase, from, to); + Campaign.Table.onUpdate(sqLiteDatabase, from, to); } } diff --git a/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java b/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java new file mode 100644 index 000000000..02e517f35 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java @@ -0,0 +1,57 @@ +package org.wikimedia.commons.media; + +import android.content.Context; +import android.support.v4.content.AsyncTaskLoader; +import android.util.Log; +import org.mediawiki.api.ApiResult; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.Media; +import org.wikimedia.commons.Utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class CategoryImagesLoader extends AsyncTaskLoader>{ + private final CommonsApplication app; + private final String category; + + public CategoryImagesLoader(Context context, String category) { + super(context); + this.app = (CommonsApplication) context.getApplicationContext(); + this.category = category; + } + + @Override + protected void onStartLoading() { + super.onStartLoading(); + super.forceLoad(); + } + + @Override + public List loadInBackground() { + ArrayList mediaList = new ArrayList(); + ApiResult result; + try { + result = app.getApi().action("query") + .param("list", "categorymembers") + .param("cmtitle", "Category:" + category) + .param("cmprop", "title|timestamp") + .param("cmtype", "file") + .param("cmsort", "timestamp") + .param("cmdir", "descending") + .param("cmlimit", 50) + .get(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Log.d("Commons", Utils.getStringFromDOM(result.getDocument())); + + List members = result.getNodes("/api/query/categorymembers/cm"); + for(ApiResult member : members) { + mediaList.add(new Media(member.getString("@title"))); + } + return mediaList; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java index a09638bd3..4e084822b 100644 --- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java @@ -148,7 +148,7 @@ public class MediaDetailFragment extends SherlockFragment { */ - String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? media.getLocalUri().toString() : media.getThumbnailUrl(640); + String actualUrl = (media.getLocalUri() != null && TextUtils.isEmpty(media.getLocalUri().toString())) ? media.getLocalUri().toString() : media.getThumbnailUrl(640); if(actualUrl.startsWith("http")) { ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader(); MediaWikiImageView mwImage = (MediaWikiImageView)image; diff --git a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java index 62b37482d..57bdab1c8 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java @@ -38,6 +38,7 @@ public class MultipleShareActivity private MediaDetailPagerFragment mediaDetails; private CategorizationFragment categorizationFragment; + private UploadController uploadController; public MultipleShareActivity() { super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE); @@ -67,8 +68,29 @@ public class MultipleShareActivity } public void OnMultipleUploadInitiated() { - StartMultipleUploadTask startUploads = new StartMultipleUploadTask(); - Utils.executeAsyncTask(startUploads); + final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this); + dialog.setIndeterminate(false); + dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + dialog.setMax(photosList.size()); + dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size())); + dialog.show(); + + for(int i = 0; i < photosList.size(); i++) { + Contribution up = photosList.get(i); + final int uploadCount = i + 1; // Goddamn Java + + uploadController.startUpload(up, new UploadController.ContributionUploadProgress() { + public void onUploadStarted(Contribution contribution) { + dialog.setProgress(uploadCount); + if(uploadCount == photosList.size()) { + dialog.dismiss(); + Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); + startingToast.show(); + } + } + }); + } + uploadsList.setImageOnlyMode(true); categorizationFragment = (CategorizationFragment) this.getSupportFragmentManager().findFragmentByTag("categorization"); @@ -112,75 +134,6 @@ public class MultipleShareActivity finish(); } - private class StartMultipleUploadTask extends AsyncTask { - - ProgressDialog dialog; - - @Override - protected Void doInBackground(Void... voids) { - for(int i = 0; i < photosList.size(); i++) { - Contribution up = photosList.get(i); - String curMimetype = (String)up.getTag("mimeType"); - if(curMimetype == null || TextUtils.isEmpty(curMimetype) || curMimetype.endsWith("*")) { - String mimeType = getContentResolver().getType(up.getLocalUri()); - if(mimeType != null) { - up.setTag("mimeType", mimeType); - } - } - - StartUploadTask startUploadTask = new StartUploadTask(MultipleShareActivity.this, uploadService, up); - try { - Utils.executeAsyncTask(startUploadTask); - startUploadTask.get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - this.publishProgress(i); - - } - return null; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - dialog = new ProgressDialog(MultipleShareActivity.this); - dialog.setIndeterminate(false); - dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - dialog.setMax(photosList.size()); - dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size())); - dialog.show(); - } - - @Override - protected void onProgressUpdate(Integer... values) { - dialog.setProgress(values[0]); - } - - @Override - protected void onPostExecute(Void aVoid) { - dialog.dismiss(); - Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); - startingToast.show(); - } - } - - private UploadService uploadService; - private boolean isUploadServiceConnected; - private ServiceConnection uploadServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName componentName, IBinder binder) { - uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService(); - isUploadServiceConnected = true; - } - - public void onServiceDisconnected(ComponentName componentName) { - // this should never happen - throw new RuntimeException("UploadService died but the rest of the process did not!"); - } - }; - @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { @@ -196,6 +149,7 @@ public class MultipleShareActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + uploadController = new UploadController(this); setContentView(R.layout.activity_multiple_uploads); app = (CommonsApplication)this.getApplicationContext(); @@ -213,9 +167,7 @@ public class MultipleShareActivity @Override protected void onDestroy() { super.onDestroy(); - if(isUploadServiceConnected) { - unbindService(uploadServiceConnection); - } + uploadController.cleanup(); } private void showDetail(int i) { @@ -267,11 +219,7 @@ public class MultipleShareActivity .commit(); } setTitle(getResources().getQuantityString(R.plurals.multiple_uploads_title, photosList.size(), photosList.size())); - - Intent uploadServiceIntent = new Intent(getApplicationContext(), UploadService.class); - uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); - startService(uploadServiceIntent); - bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); + uploadController.prepareService(); } } diff --git a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java index 0fc955a4c..983018f20 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java @@ -44,23 +44,17 @@ public class ShareActivity private ImageView backgroundImageView; - private UploadService uploadService; - private boolean isUploadServiceConnected; - private ServiceConnection uploadServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName componentName, IBinder binder) { - uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService(); - isUploadServiceConnected = true; - } - - public void onServiceDisconnected(ComponentName componentName) { - // this should never happen - throw new RuntimeException("UploadService died but the rest of the process did not!"); - } - }; + private UploadController uploadController; public void uploadActionInitiated(String title, String description) { - StartUploadTask task = new SingleStartUploadTask(ShareActivity.this, uploadService, title, mediaUri, description, mimeType, source); - task.execute(); + Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); + startingToast.show(); + uploadController.startUpload(title, mediaUri, description, mimeType, source, new UploadController.ContributionUploadProgress() { + public void onUploadStarted(Contribution contribution) { + ShareActivity.this.contribution = contribution; + showPostUpload(); + } + }); } private void showPostUpload() { @@ -96,26 +90,6 @@ public class ShareActivity finish(); } - private class SingleStartUploadTask extends StartUploadTask { - - private SingleStartUploadTask(Activity context, UploadService uploadService, String rawTitle, Uri mediaUri, String description, String mimeType, String source) { - super(context, uploadService, rawTitle, mediaUri, description, mimeType, source); - } - - @Override - protected void onPreExecute() { - Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); - startingToast.show(); - } - - @Override - protected void onPostExecute(Contribution contribution) { - super.onPostExecute(contribution); - ShareActivity.this.contribution = contribution; - showPostUpload(); - } - } - @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -161,11 +135,7 @@ public class ShareActivity .commit(); } - - Intent uploadServiceIntent = new Intent(getApplicationContext(), UploadService.class); - uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); - startService(uploadServiceIntent); - bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); + uploadController.prepareService(); } @Override @@ -179,7 +149,7 @@ public class ShareActivity @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + uploadController = new UploadController(this); setContentView(R.layout.activity_share); app = (CommonsApplication)this.getApplicationContext(); @@ -211,9 +181,7 @@ public class ShareActivity @Override protected void onDestroy() { super.onDestroy(); - if(isUploadServiceConnected) { - unbindService(uploadServiceConnection); - } + uploadController.cleanup(); } @Override diff --git a/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java b/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java deleted file mode 100644 index 113eec975..000000000 --- a/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.wikimedia.commons.upload; - -import android.app.*; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.*; -import android.os.*; -import android.preference.PreferenceManager; -import android.provider.*; -import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import java.io.*; -import java.util.*; - -import org.wikimedia.commons.CommonsApplication; -import org.wikimedia.commons.Prefs; -import org.wikimedia.commons.Utils; -import org.wikimedia.commons.contributions.*; - -public class StartUploadTask extends AsyncTask { - - private Activity context; - private UploadService uploadService; - - private Contribution contribution; - - private CommonsApplication app; - - public StartUploadTask(Activity context, UploadService uploadService, String rawTitle, Uri mediaUri, String description, String mimeType, String source) { - - this.context = context; - this.uploadService = uploadService; - - app = (CommonsApplication)context.getApplicationContext(); - - String title = rawTitle; - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); - // People are used to ".jpg" more than ".jpeg" which the system gives us. - if (extension != null && extension.toLowerCase().equals("jpeg")) { - extension = "jpg"; - } - if(extension != null && !title.toLowerCase().endsWith(extension.toLowerCase())) { - title += "." + extension; - } - - contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY); - contribution.setTag("mimeType", mimeType); - contribution.setSource(source); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA); - contribution.setLicense(license); - } - - public StartUploadTask(Activity context, UploadService uploadService, Contribution contribution) { - this.context = context; - this.uploadService = uploadService; - this.contribution = contribution; - - // Set things that have not been set! - if(contribution.getLicense() == null) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA); - contribution.setLicense(license); - } - - app = (CommonsApplication)context.getApplicationContext(); - } - - - @Override - protected Contribution doInBackground(Void... voids) { - String title = contribution.getFilename(); - - long length; - try { - if(contribution.getDataLength() <= 0) { - length = context.getContentResolver().openAssetFileDescriptor(contribution.getLocalUri(), "r").getLength(); - if(length == -1) { - // Let us find out the long way! - length = Utils.countBytes(context.getContentResolver().openInputStream(contribution.getLocalUri())); - } - contribution.setDataLength(length); - } - } catch(IOException e) { - throw new RuntimeException(e); - } - - if(TextUtils.isEmpty(contribution.getCreator())) { - contribution.setCreator(app.getCurrentAccount().name); - } - - if(contribution.getDescription() == null) { - contribution.setDescription(""); - } - - String mimeType = (String)contribution.getTag("mimeType"); - if(mimeType.startsWith("image/") && contribution.getDateCreated() == null) { - Cursor cursor = context.getContentResolver().query(contribution.getLocalUri(), - new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null); - if(cursor != null && cursor.getCount() != 0) { - cursor.moveToFirst(); - contribution.setDateCreated(new Date(cursor.getLong(0))); - } // FIXME: Alternate way of setting dateCreated if this data is not found - } - - return contribution; - } - - @Override - protected void onPostExecute(Contribution contribution) { - uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution); - } -} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java new file mode 100644 index 000000000..3a86db3fd --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java @@ -0,0 +1,161 @@ +package org.wikimedia.commons.upload; + +import android.app.Activity; +import android.content.*; +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 android.webkit.MimeTypeMap; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.HandlerService; +import org.wikimedia.commons.Prefs; +import org.wikimedia.commons.Utils; +import org.wikimedia.commons.campaigns.Campaign; +import org.wikimedia.commons.campaigns.CampaignContribution; +import org.wikimedia.commons.contributions.Contribution; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +public class UploadController { + private UploadService uploadService; + + private final Activity activity; + private Campaign campaign; + final CommonsApplication app; + + public interface ContributionUploadProgress { + void onUploadStarted(Contribution contribution); + } + + public UploadController(Activity activity) { + this.activity = activity; + app = (CommonsApplication)activity.getApplicationContext(); + } + + public UploadController(Activity activity, Campaign campaign) { + this(activity); + this.campaign = campaign; + } + + private boolean isUploadServiceConnected; + private ServiceConnection uploadServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName componentName, IBinder binder) { + uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService(); + isUploadServiceConnected = true; + } + + public void onServiceDisconnected(ComponentName componentName) { + // this should never happen + throw new RuntimeException("UploadService died but the rest of the process did not!"); + } + }; + + public void prepareService() { + Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class); + uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); + activity.startService(uploadServiceIntent); + activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); + } + + public void cleanup() { + if(isUploadServiceConnected) { + activity.unbindService(uploadServiceConnection); + } + } + + public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, ContributionUploadProgress onComplete) { + Contribution contribution; + + String title = rawTitle; + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); + // People are used to ".jpg" more than ".jpeg" which the system gives us. + if (extension != null && extension.toLowerCase().equals("jpeg")) { + extension = "jpg"; + } + if(extension != null && !title.toLowerCase().endsWith(extension.toLowerCase())) { + title += "." + extension; + } + + if(campaign == null) { + contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY); + } else { + contribution = new CampaignContribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY, campaign); + } + contribution.setTag("mimeType", mimeType); + contribution.setSource(source); + + startUpload(contribution, onComplete); + } + + public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + + if(TextUtils.isEmpty(contribution.getCreator())) { + contribution.setCreator(app.getCurrentAccount().name); + } + + if(contribution.getDescription() == null) { + contribution.setDescription(""); + } + + String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA); + contribution.setLicense(license); + + Utils.executeAsyncTask(new AsyncTask() { + + // Fills up missing information about Contributions + // Only does things that involve some form of IO + // Runs in background thread + @Override + protected Contribution doInBackground(Void... voids /* stare into you */) { + long length; + try { + if(contribution.getDataLength() <= 0) { + length = activity.getContentResolver().openAssetFileDescriptor(contribution.getLocalUri(), "r").getLength(); + if(length == -1) { + // Let us find out the long way! + length = Utils.countBytes(activity.getContentResolver().openInputStream(contribution.getLocalUri())); + } + contribution.setDataLength(length); + } + } catch(IOException e) { + throw new RuntimeException(e); + } + + String mimeType = (String)contribution.getTag("mimeType"); + if(mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) { + mimeType = activity.getContentResolver().getType(contribution.getLocalUri()); + if(mimeType != null) { + contribution.setTag("mimeType", mimeType); + } + } + + if(mimeType.startsWith("image/") && contribution.getDateCreated() == null) { + Cursor cursor = activity.getContentResolver().query(contribution.getLocalUri(), + new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null); + if(cursor != null && cursor.getCount() != 0) { + cursor.moveToFirst(); + contribution.setDateCreated(new Date(cursor.getLong(0))); + } // FIXME: Alternate way of setting dateCreated if this data is not found + } + + return contribution; + } + + @Override + protected void onPostExecute(Contribution contribution) { + super.onPostExecute(contribution); + uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution); + onComplete.onUploadStarted(contribution); + } + }); + } + +}