mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Merge pull request #9 from brion/recentcats
Store and show recently-used categories list
This commit is contained in:
commit
e0e7f056d2
8 changed files with 421 additions and 32 deletions
|
|
@ -130,6 +130,13 @@
|
||||||
android:authorities="org.wikimedia.commons.modifications.contentprovider"
|
android:authorities="org.wikimedia.commons.modifications.contentprovider"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name=".category.CategoryContentProvider"
|
||||||
|
android:label="@string/provider_categories"
|
||||||
|
android:syncable="false"
|
||||||
|
android:authorities="org.wikimedia.commons.categories.contentprovider"
|
||||||
|
android:exported="false">
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -5,28 +5,11 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/target/generated-sources/r" />
|
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/target/generated-sources/r" />
|
||||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/target/generated-sources/aidl" />
|
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/target/generated-sources/aidl" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/assets" />
|
|
||||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/src/main/native" />
|
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/src/main/native" />
|
||||||
<option name="USE_CUSTOM_APK_RESOURCE_FOLDER" value="false" />
|
|
||||||
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/target/generated-sources/combined-resources/res" />
|
<option name="CUSTOM_APK_RESOURCE_FOLDER" value="/target/generated-sources/combined-resources/res" />
|
||||||
<option name="USE_CUSTOM_COMPILER_MANIFEST" value="false" />
|
|
||||||
<option name="CUSTOM_COMPILER_MANIFEST" value="" />
|
|
||||||
<option name="APK_PATH" value="/target/commons.apk" />
|
<option name="APK_PATH" value="/target/commons.apk" />
|
||||||
<option name="LIBRARY_PROJECT" value="false" />
|
|
||||||
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
<option name="RUN_PROCESS_RESOURCES_MAVEN_TASK" value="false" />
|
||||||
<option name="GENERATE_UNSIGNED_APK" value="false" />
|
|
||||||
<option name="CUSTOM_DEBUG_KEYSTORE_PATH" value="" />
|
|
||||||
<option name="PACK_TEST_CODE" value="false" />
|
|
||||||
<option name="RUN_PROGUARD" value="false" />
|
|
||||||
<option name="PROGUARD_CFG_PATH" value="/proguard-project.txt" />
|
|
||||||
<resOverlayFolders>
|
|
||||||
<path>/res-overlay</path>
|
|
||||||
</resOverlayFolders>
|
|
||||||
<includeSystemProguardFile>true</includeSystemProguardFile>
|
|
||||||
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
<includeAssetsFromLibraries>true</includeAssetsFromLibraries>
|
||||||
<additionalNativeLibs />
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -80,4 +80,5 @@
|
||||||
<string name="about_privacy_policy"><a href="https://wikimediafoundation.org/wiki/Privacy_policy">Privacy policy</a></string>
|
<string name="about_privacy_policy"><a href="https://wikimediafoundation.org/wiki/Privacy_policy">Privacy policy</a></string>
|
||||||
<string name="title_activity_about">About</string>
|
<string name="title_activity_about">About</string>
|
||||||
<string name="menu_feedback">Send Feedback (via Email)</string>
|
<string name="menu_feedback">Send Feedback (via Email)</string>
|
||||||
|
<string name="provider_categories">Recently used categories</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package org.wikimedia.commons;
|
package org.wikimedia.commons;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.*;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
|
@ -18,10 +17,14 @@ import com.actionbarsherlock.view.Menu;
|
||||||
import com.actionbarsherlock.view.MenuItem;
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
import org.mediawiki.api.MWApi;
|
||||||
|
import org.wikimedia.commons.category.Category;
|
||||||
|
import org.wikimedia.commons.category.CategoryContentProvider;
|
||||||
|
import org.wikimedia.commons.contributions.Contribution;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
|
@ -45,6 +48,10 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
|
|
||||||
private HashMap<String, ArrayList<String>> categoriesCache;
|
private HashMap<String, ArrayList<String>> categoriesCache;
|
||||||
|
|
||||||
|
private ContentProviderClient client;
|
||||||
|
|
||||||
|
private final int SEARCH_CATS_LIMIT = 25;
|
||||||
|
|
||||||
public static class CategoryItem implements Parcelable {
|
public static class CategoryItem implements Parcelable {
|
||||||
public String name;
|
public String name;
|
||||||
public boolean selected;
|
public boolean selected;
|
||||||
|
|
@ -114,16 +121,38 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
categoriesAdapter.setItems(items);
|
categoriesAdapter.setItems(items);
|
||||||
categoriesAdapter.notifyDataSetInvalidated();
|
categoriesAdapter.notifyDataSetInvalidated();
|
||||||
categoriesSearchInProgress.setVisibility(View.GONE);
|
categoriesSearchInProgress.setVisibility(View.GONE);
|
||||||
if(!TextUtils.isEmpty(filter) && categories.size() == 0) {
|
if (categories.size() == 0) {
|
||||||
|
if(!TextUtils.isEmpty(filter)) {
|
||||||
categoriesNotFoundView.setText(getString(R.string.categories_not_found, filter));
|
categoriesNotFoundView.setText(getString(R.string.categories_not_found, filter));
|
||||||
categoriesNotFoundView.setVisibility(View.VISIBLE);
|
categoriesNotFoundView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If we found recent cats, hide the skip message!
|
||||||
|
categoriesSkip.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ArrayList<String> doInBackground(Void... voids) {
|
protected ArrayList<String> doInBackground(Void... voids) {
|
||||||
if(TextUtils.isEmpty(filter)) {
|
if(TextUtils.isEmpty(filter)) {
|
||||||
return new ArrayList<String>();
|
ArrayList<String> items = new ArrayList<String>();
|
||||||
|
try {
|
||||||
|
Cursor cursor = client.query(
|
||||||
|
CategoryContentProvider.BASE_URI,
|
||||||
|
Category.Table.ALL_FIELDS,
|
||||||
|
null,
|
||||||
|
new String[]{},
|
||||||
|
Category.Table.COLUMN_LAST_USED + " DESC");
|
||||||
|
// fixme add a limit on the original query instead of falling out of the loop?
|
||||||
|
while (cursor.moveToNext() && cursor.getPosition() < SEARCH_CATS_LIMIT) {
|
||||||
|
Category cat = Category.fromCursor(cursor);
|
||||||
|
items.add(cat.getName());
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// faaaail
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
if(categoriesCache.containsKey(filter)) {
|
if(categoriesCache.containsKey(filter)) {
|
||||||
return categoriesCache.get(filter);
|
return categoriesCache.get(filter);
|
||||||
|
|
@ -135,7 +164,7 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
result = api.action("query")
|
result = api.action("query")
|
||||||
.param("list", "allcategories")
|
.param("list", "allcategories")
|
||||||
.param("acprefix", filter)
|
.param("acprefix", filter)
|
||||||
.param("aclimit", 25)
|
.param("aclimit", SEARCH_CATS_LIMIT)
|
||||||
.get();
|
.get();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
@ -211,6 +240,55 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Category lookupCategory(String name) {
|
||||||
|
try {
|
||||||
|
Cursor cursor = client.query(
|
||||||
|
CategoryContentProvider.BASE_URI,
|
||||||
|
Category.Table.ALL_FIELDS,
|
||||||
|
Category.Table.COLUMN_NAME + "=?",
|
||||||
|
new String[] {name},
|
||||||
|
null);
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
Category cat = Category.fromCursor(cursor);
|
||||||
|
return cat;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// This feels lazy, but to hell with checked exceptions. :)
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newly used category...
|
||||||
|
Category cat = new Category();
|
||||||
|
cat.setName(name);
|
||||||
|
cat.setLastUsed(new Date());
|
||||||
|
cat.setTimesUsed(0);
|
||||||
|
return cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CategoryCountUpdater extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public CategoryCountUpdater(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
Category cat = lookupCategory(name);
|
||||||
|
cat.incTimesUsed();
|
||||||
|
|
||||||
|
cat.setContentProviderClient(client);
|
||||||
|
cat.save();
|
||||||
|
|
||||||
|
return null; // Make the compiler happy.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCategoryCount(String name) {
|
||||||
|
Utils.executeAsyncTask(new CategoryCountUpdater(name), executor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_categorization, null);
|
View rootView = inflater.inflate(R.layout.fragment_categorization, null);
|
||||||
|
|
@ -245,6 +323,9 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
CategoryItem item = (CategoryItem) adapterView.getAdapter().getItem(index);
|
CategoryItem item = (CategoryItem) adapterView.getAdapter().getItem(index);
|
||||||
item.selected = !item.selected;
|
item.selected = !item.selected;
|
||||||
checkedView.setChecked(item.selected);
|
checkedView.setChecked(item.selected);
|
||||||
|
if (item.selected) {
|
||||||
|
updateCategoryCount(item.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -253,11 +334,7 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||||
if(lastUpdater != null) {
|
startUpdatingCategoryList();
|
||||||
lastUpdater.cancel(true);
|
|
||||||
}
|
|
||||||
lastUpdater = new CategoriesUpdater();
|
|
||||||
Utils.executeAsyncTask(lastUpdater, executor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void afterTextChanged(Editable editable) {
|
public void afterTextChanged(Editable editable) {
|
||||||
|
|
@ -265,10 +342,19 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
startUpdatingCategoryList();
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startUpdatingCategoryList() {
|
||||||
|
if (lastUpdater != null) {
|
||||||
|
lastUpdater.cancel(true);
|
||||||
|
}
|
||||||
|
lastUpdater = new CategoriesUpdater();
|
||||||
|
Utils.executeAsyncTask(lastUpdater, executor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater inflater) {
|
||||||
menu.clear();
|
menu.clear();
|
||||||
|
|
@ -280,6 +366,13 @@ public class CategorizationFragment extends SherlockFragment{
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
getActivity().setTitle(R.string.categories_activity_title);
|
getActivity().setTitle(R.string.categories_activity_title);
|
||||||
|
client = getActivity().getContentResolver().acquireContentProviderClient(CategoryContentProvider.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package org.wikimedia.commons.category;
|
||||||
|
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import org.wikimedia.commons.contributions.ContributionsContentProvider;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Category {
|
||||||
|
private ContentProviderClient client;
|
||||||
|
private Uri contentUri;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private Date lastUsed;
|
||||||
|
private int timesUsed;
|
||||||
|
|
||||||
|
// Getters/setters
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLastUsed() {
|
||||||
|
// warning: Date objects are mutable.
|
||||||
|
return (Date)lastUsed.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastUsed(Date lastUsed) {
|
||||||
|
// warning: Date objects are mutable.
|
||||||
|
this.lastUsed = (Date)lastUsed.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch() {
|
||||||
|
lastUsed = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimesUsed() {
|
||||||
|
return timesUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimesUsed(int timesUsed) {
|
||||||
|
this.timesUsed = timesUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incTimesUsed() {
|
||||||
|
timesUsed++;
|
||||||
|
touch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database/content-provider stuff
|
||||||
|
public void setContentProviderClient(ContentProviderClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try {
|
||||||
|
if(contentUri == null) {
|
||||||
|
contentUri = client.insert(CategoryContentProvider.BASE_URI, this.toContentValues());
|
||||||
|
} else {
|
||||||
|
client.update(contentUri, toContentValues(), null, null);
|
||||||
|
}
|
||||||
|
} catch(RemoteException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentValues toContentValues() {
|
||||||
|
ContentValues cv = new ContentValues();
|
||||||
|
cv.put(Table.COLUMN_NAME, getName());
|
||||||
|
cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime());
|
||||||
|
cv.put(Table.COLUMN_TIMES_USED, getTimesUsed());
|
||||||
|
return cv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Category fromCursor(Cursor cursor) {
|
||||||
|
// Hardcoding column positions!
|
||||||
|
Category c = new Category();
|
||||||
|
c.contentUri = CategoryContentProvider.uriForId(cursor.getInt(0));
|
||||||
|
c.name = cursor.getString(1);
|
||||||
|
c.lastUsed = new Date(cursor.getLong(2));
|
||||||
|
c.timesUsed = cursor.getInt(3);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Table {
|
||||||
|
public static final String TABLE_NAME = "categories";
|
||||||
|
|
||||||
|
public static final String COLUMN_ID = "_id";
|
||||||
|
public static final String COLUMN_NAME = "name";
|
||||||
|
public static final String COLUMN_LAST_USED = "last_used";
|
||||||
|
public static final String COLUMN_TIMES_USED = "times_used";
|
||||||
|
|
||||||
|
// 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_LAST_USED,
|
||||||
|
COLUMN_TIMES_USED
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
|
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
|
||||||
|
+ COLUMN_NAME + " STRING,"
|
||||||
|
+ COLUMN_LAST_USED + " INTEGER,"
|
||||||
|
+ COLUMN_TIMES_USED + " INTEGER"
|
||||||
|
+ ");";
|
||||||
|
|
||||||
|
|
||||||
|
public static void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||||
|
if(from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(from < 4) {
|
||||||
|
// doesn't exist yet
|
||||||
|
from++;
|
||||||
|
onUpdate(db, from, to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(from == 4) {
|
||||||
|
// table added in version 5
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.wikimedia.commons.category;
|
||||||
|
|
||||||
|
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 CategoryContentProvider extends ContentProvider {
|
||||||
|
|
||||||
|
// For URI matcher
|
||||||
|
private static final int CATEGORIES = 1;
|
||||||
|
private static final int CATEGORIES_ID = 2;
|
||||||
|
|
||||||
|
public static final String AUTHORITY = "org.wikimedia.commons.categories.contentprovider";
|
||||||
|
private static final String BASE_PATH = "categories";
|
||||||
|
|
||||||
|
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
|
||||||
|
|
||||||
|
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
static {
|
||||||
|
uriMatcher.addURI(AUTHORITY, BASE_PATH, CATEGORIES);
|
||||||
|
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_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(Category.Table.TABLE_NAME);
|
||||||
|
|
||||||
|
int uriType = uriMatcher.match(uri);
|
||||||
|
|
||||||
|
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||||
|
Cursor cursor;
|
||||||
|
|
||||||
|
switch(uriType) {
|
||||||
|
case CATEGORIES:
|
||||||
|
cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
||||||
|
break;
|
||||||
|
case CATEGORIES_ID:
|
||||||
|
cursor = queryBuilder.query(db,
|
||||||
|
Category.Table.ALL_FIELDS,
|
||||||
|
"_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 CATEGORIES:
|
||||||
|
id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues);
|
||||||
|
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) {
|
||||||
|
return 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 CATEGORIES:
|
||||||
|
for(ContentValues value: values) {
|
||||||
|
Log.d("Commons", "Inserting! " + value.toString());
|
||||||
|
sqlDB.insert(Category.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 CATEGORIES_ID:
|
||||||
|
int id = Integer.valueOf(uri.getLastPathSegment());
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(selection)) {
|
||||||
|
rowsUpdated = sqlDB.update(Category.Table.TABLE_NAME,
|
||||||
|
contentValues,
|
||||||
|
Category.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -302,6 +302,11 @@ public class Contribution extends Media {
|
||||||
from++;
|
from++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(from == 4) {
|
||||||
|
// Do nothing -- added Category
|
||||||
|
from++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ package org.wikimedia.commons.data;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.database.sqlite.*;
|
import android.database.sqlite.*;
|
||||||
|
|
||||||
|
import org.wikimedia.commons.category.Category;
|
||||||
import org.wikimedia.commons.contributions.*;
|
import org.wikimedia.commons.contributions.*;
|
||||||
import org.wikimedia.commons.modifications.ModifierSequence;
|
import org.wikimedia.commons.modifications.ModifierSequence;
|
||||||
|
|
||||||
public class DBOpenHelper extends SQLiteOpenHelper{
|
public class DBOpenHelper extends SQLiteOpenHelper{
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "commons.db";
|
private static final String DATABASE_NAME = "commons.db";
|
||||||
private static final int DATABASE_VERSION = 4;
|
private static final int DATABASE_VERSION = 5;
|
||||||
|
|
||||||
public DBOpenHelper(Context context) {
|
public DBOpenHelper(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
|
@ -19,11 +20,13 @@ public class DBOpenHelper extends SQLiteOpenHelper{
|
||||||
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
||||||
Contribution.Table.onCreate(sqLiteDatabase);
|
Contribution.Table.onCreate(sqLiteDatabase);
|
||||||
ModifierSequence.Table.onCreate(sqLiteDatabase);
|
ModifierSequence.Table.onCreate(sqLiteDatabase);
|
||||||
|
Category.Table.onCreate(sqLiteDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
||||||
Contribution.Table.onUpdate(sqLiteDatabase, from, to);
|
Contribution.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
|
ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
|
Category.Table.onUpdate(sqLiteDatabase, from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue