Commit 268396d3 authored by Alan McNatty's avatar Alan McNatty
Browse files

A large commit that moves the artefactdatasqlhelper 'stuff' into a content...

A large commit that moves the artefactdatasqlhelper 'stuff' into a content provider so we can set upa content observer and listen for content changes. This is significant in the cleanliness of the artefacts .. an uninstall and reinstall will be required.

Also split the Utils class into Sync and Account Util classes so it's a bit more focused (easier to know where to look etc).
parent 0c1048db
......@@ -94,8 +94,14 @@
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
<provider android:name="nz.net.catalyst.MaharaDroid.provider.MaharaProvider"
android:authorities="nz.net.catalyst.MaharaDroid"
<provider android:name=".provider.SyncContentProvider"
android:authorities="nz.net.catalyst.MaharaDroid.Sync"
android:label="MaharaDroid"
android:syncable="true"
android:enabled="true">
</provider>
<provider android:name=".provider.ArtefactContentProvider"
android:authorities="nz.net.catalyst.MaharaDroid.Artefact"
android:label="MaharaDroid"
android:syncable="true"
android:enabled="true">
......
......@@ -50,12 +50,6 @@
style="@android:style/TextAppearance.Small">
</TextView>
</LinearLayout>
<!--
<ImageView android:id="@+id/UploadArtefact"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ImageView>
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ArtefactTitleLayout"
......
......@@ -43,7 +43,7 @@
android:layout_below="@+id/artefacts_banner_text"
/>
<TextView android:text="@string/artefacts_help"
<TextView android:id="@+id/artefacts_help"
android:layout_margin="5dp"
android:layout_width="fill_parent"
android:singleLine="false"
......
......@@ -4,8 +4,16 @@
<string name="artefacts_banner_text">Open source ePortfolios</string>
<string name="artefacts_banner_text2">[mah-har-rah;verb]: to think, thinking, thought</string>
<string name="artefacts_help">
<string name="artefacts_help">&lt;p&gt;Thanks for using MaharaDroid!&lt;/p&gt;
&lt;p&gt;To get started touch Menu -&gt; Preferences.&lt;/p&gt;
&lt;p&gt;
Enter your Mahara username on set the initial token. At the same time visit Mahara (Settings) and set the same token value and click update.
Back in MaharaDroid touch your back button, and an authentication attempt will be made.
&lt;/p&gt;
&lt;p&gt;
Once authenticated check the Menu for options on writing Journal posts and uploading files.
Alternatively when taking photos, videos or recording audio look for options to \'share\' these files with MaharaDroid.
&lt;/p&gt;
</string>
<string name="artefacts_no_saved_artefacts">Nothing saved at present.</string>
......
<!-- API Version 7 requires the accountType attribute to be hard-coded as a string -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="nz.net.catalyst.MaharaDroid"
android:contentAuthority="nz.net.catalyst.MaharaDroid.Sync"
android:accountType="nz.net.catalyst.MaharaDroid.account"
android:supportsUploading="true"
android:userVisible="true"
......
......@@ -52,12 +52,16 @@ public class GlobalResources {
public static final String ACCOUNT_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String AUTHTOKEN_TYPE = "nz.net.catalyst.MaharaDroid.account";
public static final String CONTENT_URL = "nz.net.catalyst.MaharaDroid";
public static final String SYNC_CONTENT_URL = "nz.net.catalyst.MaharaDroid.Sync";
public static final String ARTEFACT_CONTENT_URL = "nz.net.catalyst.MaharaDroid.Artefact";
public static final String SYNC_AUTHORITY = "nz.net.catalyst.MaharaDroid";
public static final String EXTRAS_SYNC_IS_PERIODIC = "nz.net.catalyst.MaharaDroid.periodic";
public static final String BROADCAST_ACTION = "nz.net.catalyst.MaharaDroid.UPLOAD_COMPLETED";
public static final String[] CONTENT_TABLES = new String[] { "tag", "blog", "folder" };
public static final String[] CONTENT_FIELDS = new String[] { "ID", "VALUE" };
public static final String[] SYNC_CONTENT_TABLES = new String[] { "tag", "blog", "folder" };
public static final String[] SYNC_CONTENT_FIELDS = new String[] { "ID", "VALUE" };
public static final Map<Integer, String> NOTIFICATIONS;
static {
......@@ -76,5 +80,4 @@ public class GlobalResources {
public static final int REQ_RECORD_AUDIO_RETURN = 2;
public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
}
\ No newline at end of file
......@@ -21,40 +21,26 @@
package nz.net.catalyst.MaharaDroid;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import nz.net.catalyst.MaharaDroid.R;
import nz.net.catalyst.MaharaDroid.ui.ArtefactExpandableListAdapterActivity;
import nz.net.catalyst.MaharaDroid.ui.about.AboutActivity;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.PeriodicSync;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.provider.SyncStateContract.Constants;
import android.util.Log;
public class Utils {
......@@ -133,47 +119,6 @@ public class Utils {
return upload_url;
}
public static String getSyncURLPref(Context context) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
String sync_url = mPrefs.getString(context.getResources().getString(R.string.pref_sync_url_key),
context.getResources().getString(R.string.pref_sync_url_default)).trim();
// If the part overrides the whole - just go with the part.
if ( sync_url.startsWith("http://") ) {
if ( DEBUG ) Log.d(TAG, "setting sync url to '" + sync_url + "'");
return sync_url;
}
String base_url = mPrefs.getString(context.getResources().getString(R.string.pref_base_url_key),
context.getResources().getString(R.string.pref_base_url_default)).trim().toLowerCase();
if ( ! base_url.startsWith("http") )
base_url = "http://" + base_url;
if ( ! base_url.endsWith("/") && ! sync_url.startsWith("/") )
base_url = base_url + "/";
// multiple joining '//' are fine
sync_url = base_url + sync_url;
if ( DEBUG ) Log.d(TAG, "setting sync url to '" + sync_url + "'");
return sync_url;
}
public static String getSyncNotificationsPref(Context context) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
String notificationString = "";
Iterator<Entry<Integer, String>> it = GlobalResources.NOTIFICATIONS.entrySet().iterator();
while (it.hasNext()) {
Entry<Integer, String> not = it.next();
if ( mPrefs.getBoolean(context.getResources().getString(not.getKey()), true) ) {
notificationString += ( notificationString.length() > 0 ) ? "," : "";
notificationString += not.getValue();
}
}
if ( DEBUG ) Log.d(TAG, "setting notifications string to '" + notificationString + "'");
return ( notificationString == "" ) ? null : notificationString;
}
public static String updateTokenFromResult(JSONObject json, Context context) {
String newToken = null;
if (json == null || json.has("fail")) {
......@@ -241,94 +186,7 @@ public class Utils {
mNM.cancel(id);
}
public static long processSyncResults(JSONObject result, ContentProviderClient myProvider, Context context, String sync_key) {
if ( myProvider == null ) {
Uri uri = Uri.parse("content://" + GlobalResources.CONTENT_URL);
myProvider = context.getContentResolver().acquireContentProviderClient(uri);
}
// TODO Auto-generated method stub
long numUpdates = 0;
try {
JSONObject syncObj = result.getJSONObject("sync");
//Log.i(TAG, syncObj.toString());
if ( syncObj.has("activity") && syncObj.optJSONArray("activity") != null ) {
JSONArray notArr = syncObj.getJSONArray("activity");
for (int i=0; i<notArr.length(); i++) {
Utils.showNotification(Integer.parseInt(notArr.getJSONObject(i).getString("id")),
notArr.getJSONObject(i).getString("subject"), notArr.getJSONObject(i).getString("message"),
null, context);
numUpdates++;
}
}
if ( syncObj.has("tags") && syncObj.optJSONArray("tags") != null ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("tags"), "tag");
numUpdates += newItems;
}
if ( syncObj.has("blogs") && syncObj.optJSONArray("blogs") != null ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("blogs"), "blog");
numUpdates += newItems;
}
if ( syncObj.has("folders") && syncObj.optJSONArray("folders") != null ) {
long newItems = updateListPreferenceFromJSON(myProvider, syncObj.getJSONArray("folders"), "folder");
numUpdates += newItems;
}
if ( syncObj.has("time") ) {
// Save last sync time
String last_sync = syncObj.getString("time");
Log.v(TAG, "saving sync time as: " + last_sync);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// We save current time in seconds since 1970 in UTC!!
// TODO fix this - get the sync api to respond with the current server time which we can save here
// i.e. a syncObj.has("time") piece containing the epoch to store.
mPrefs.edit()
.putString(sync_key, last_sync)
.commit()
;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return numUpdates;
}
private static long updateListPreferenceFromJSON(ContentProviderClient myProvider, JSONArray jsonArray, String fieldName) throws JSONException, RemoteException {
int items = jsonArray.length();
ContentValues[] cv = new ContentValues[items];
Uri uri = Uri.parse("content://" + GlobalResources.CONTENT_URL + "/" + fieldName);
Log.i(TAG, jsonArray.toString());
for (int i=0; i<items; i++) {
String value = jsonArray.getJSONObject(i).getString(fieldName);
String id = jsonArray.getJSONObject(i).getString("id");
Log.v(TAG, "saving " + fieldName + " [ id: " + id + ", value: " + value + "]");
// test provider query
myProvider.query(uri, null, null, null, null);
if ( cv[i] == null )
cv[i] = new ContentValues();
cv[i].put("ID", id);
cv[i].put("VALUE", value);
//}
}
// TODO add a 'last_seen' column and delete any last_seen < this_sync
myProvider.delete(uri, null, null); // delete them all
myProvider.bulkInsert(uri, cv);
return items;
}
public static Intent makeCameraIntent(Context context) {
//define the file-name to save photo taken by Camera activity
......@@ -354,108 +212,6 @@ public class Utils {
return i;
}
public static void setPeriodicSync(Account account, Context context) {
if ( account == null )
return;
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
Long periodic_sync = Long.valueOf(mPrefs.getString(context.getResources().getString(R.string.pref_sync_periodic_key), "0"));
if ( periodic_sync == null || periodic_sync <= 0 ) {
// Note - should only ever have 1
List<PeriodicSync> ps = ContentResolver.getPeriodicSyncs(account, GlobalResources.ACCOUNT_TYPE);
while ( ps != null && ! ps.isEmpty() ) {
if ( periodic_sync == 0 || ps.get(0).period != periodic_sync ) {
ContentResolver.removePeriodicSync(account, GlobalResources.ACCOUNT_TYPE, ps.get(0).extras);
if ( VERBOSE ) Log.v(TAG, "setPeriodicSync removing periodic sync '" + ps.get(0).period + "'");
}
ps.remove(0);
}
return;
}
periodic_sync = periodic_sync * 60; // convert to seconds
if ( DEBUG ) Log.v(TAG, "setPeriodicSync of '" + periodic_sync + "' seconds");
final Bundle bundle = new Bundle();
bundle.putBoolean( ContentResolver.SYNC_EXTRAS_UPLOAD, true );
ContentResolver.addPeriodicSync(account, GlobalResources.SYNC_AUTHORITY, bundle, periodic_sync);
}
public static Account getAccount(Context context) {
AccountManager mAccountManager = AccountManager.get(context);
Account account = null;
// if ( periodic_sync != null && periodic_sync > 0 ) {
//
// TODO replicated from AuthenticatorActivity
Account[] mAccounts = mAccountManager.getAccountsByType(GlobalResources.ACCOUNT_TYPE);
if ( mAccounts.length > 0 ) {
// Just pick the first one .. support multiple accounts can come later.
account = mAccounts[0];
}
return account;
}
public static void deleteAccount(Context context) {
AccountManager mAccountManager = AccountManager.get(context);
Account[] mAccounts = mAccountManager.getAccountsByType(GlobalResources.ACCOUNT_TYPE);
for ( int i = 0; i < mAccounts.length; i++ ) {
mAccountManager.removeAccount(mAccounts[i], null, null);
}
}
public static String[][] getJournals(String nullitem, Context context) {
return getValues("blog", nullitem, context);
}
public static String[][] getTags(String nullitem, Context context) {
return getValues("tag", nullitem, context);
}
public static String[][] getFolders(String nullitem, Context context) {
return getValues("folder", nullitem, context);
}
private static String[][] getValues(String type, String nullitem, Context context) {
Uri uri = Uri.parse("content://" + GlobalResources.CONTENT_URL + "/" + type);
ContentProviderClient myProvider = context.getContentResolver().acquireContentProviderClient(uri);
Cursor cursor = null;
try {
cursor = myProvider.query(uri, new String[] { "ID", "VALUE" }, null, null, null);
} catch (RemoteException e) {
// TODO Auto-generated catch block
Log.e(TAG, "Failed to aquire content provider for query - is there an active sync running?");
e.printStackTrace();
}
if ( cursor == null ) {
return null;
}
if ( VERBOSE ) Log.v(TAG, "getValues: have acquired content provider for " + type +
" (" + cursor.getCount() + " items returned for " + uri.toString() + ")");
cursor.moveToFirst();
String[] k = new String[cursor.getCount() + 1];
String[] v = new String[cursor.getCount() + 1];
if ( VERBOSE ) Log.v(TAG, "getValues: size " + k.length + " for " + type);
k[0] = null;
v[0] = nullitem;
while (! cursor.isAfterLast() ) {
k[cursor.getPosition() + 1] = cursor.getString(0);
v[cursor.getPosition() + 1] = cursor.getString(1);
if ( VERBOSE ) Log.v(TAG, "getValues: adding " + cursor.getString(0) + " at position " + cursor.getPosition() + " to " + type);
cursor.moveToNext();
}
cursor.close();
return new String[][] { k, v };
}
public static Bitmap getFileThumbData(Context context, String filename) {
if ( filename == null )
return null;
......@@ -490,5 +246,4 @@ public class Utils {
return bm;
}
}
......@@ -17,6 +17,7 @@
package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.authenticator.AuthenticatorActivity;
import nz.net.catalyst.MaharaDroid.data.SyncUtils;
import nz.net.catalyst.MaharaDroid.GlobalResources;
import nz.net.catalyst.MaharaDroid.LogConfig;
import nz.net.catalyst.MaharaDroid.Utils;
......@@ -103,7 +104,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity {
//TODO confirm but shouldn't have to force this - setSyncAutomatically should kick one off I believe
// ContentResolver.requestSync(account, GlobalResources.SYNC_AUTHORITY, null);
Utils.setPeriodicSync(account, getApplicationContext());
SyncUtils.setPeriodicSync(account, getApplicationContext());
}
final Intent intent = new Intent();
......
......@@ -3,7 +3,8 @@ package nz.net.catalyst.MaharaDroid.authenticator;
import nz.net.catalyst.MaharaDroid.LogConfig;
import nz.net.catalyst.MaharaDroid.R;
import nz.net.catalyst.MaharaDroid.Utils;
import nz.net.catalyst.MaharaDroid.provider.MaharaProvider;
import nz.net.catalyst.MaharaDroid.data.SyncUtils;
import nz.net.catalyst.MaharaDroid.provider.SyncContentProvider;
import nz.net.catalyst.MaharaDroid.upload.http.RestClient;
import org.json.JSONObject;
......@@ -61,7 +62,7 @@ public class MaharaAuthHandler {
// application preferences
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String authSyncURI = Utils.getSyncURLPref(mContext);
String authSyncURI = SyncUtils.getSyncURLPref(mContext);
String token = mPrefs.getString(mContext.getResources().getString(R.string.pref_auth_token_key), "");
......@@ -74,12 +75,12 @@ public class MaharaAuthHandler {
Utils.showNotification(NOTIFICATION, mContext.getResources().getText(R.string.login_authenticating),
null, null, mContext);
String syncNotifications = Utils.getSyncNotificationsPref(mContext);
String syncNotifications = SyncUtils.getSyncNotificationsPref(mContext);
JSONObject result = RestClient.AuthSync(authSyncURI, token, username, lastsync, syncNotifications, mContext);
token = Utils.updateTokenFromResult(result, mContext);
if ( result.has("sync") ) {
Utils.processSyncResults(result, null, mContext, sync_key);
SyncUtils.processSyncResults(result, null, mContext, sync_key);
}
if ( token != null ) {
......
......@@ -241,9 +241,7 @@ public class Artefact extends Object implements Parcelable {
mContext.startService(i);
}
public void delete(Context mContext) {
ArtefactDataSQLHelper artefactData = new ArtefactDataSQLHelper(mContext);
artefactData.deleteSavedArtefact(id);
artefactData.close();
ArtefactUtils.deleteSavedArtefact(mContext, id);
}
public void view(Context mContext) {
try {
......@@ -269,21 +267,29 @@ public class Artefact extends Object implements Parcelable {
mContext.startActivity(i);
}
public void save(Context mContext) {
ArtefactDataSQLHelper artefactData = new ArtefactDataSQLHelper(mContext);
if ( id != 0 ) { // update
Log.d("Artefact", "save: is_draft: " + is_draft);
Log.d("Artefact", "save: allow comments: " + allow_comments);
artefactData.update(id, filename, title, description, tags, saved_id, journal_id, is_draft, allow_comments);
ArtefactUtils.update(mContext, id, filename, title, description, tags, saved_id, journal_id, is_draft, allow_comments);
} else { // add
artefactData.add(filename, title, description, tags, journal_id, is_draft, allow_comments);
ArtefactUtils.add(mContext, filename, title, description, tags, journal_id, is_draft, allow_comments);
}
artefactData.close();
}
public void load(Context mContext, Long id) {
ArtefactDataSQLHelper artefactData = new ArtefactDataSQLHelper(mContext);
artefactData.loadSavedArtefact(id);
artefactData.close();
Artefact ta = ArtefactUtils.loadSavedArtefact(mContext, id);
this.id = ta.getId();
this.time = ta.getTime();
filename = ta.getFilename();
title = ta.getTitle();
description = ta.getDescription();
tags = ta.getTags();
saved_id = ta.saved_id;
journal_id = ta.getJournalId();
is_draft = ta.getIsDraft();
allow_comments = ta.getAllowComments();
}
public String getFilePath(Context context) {
if ( filename == null )
......
/*
* MaharaDroid - Artefact uploader
*
* This file is part of MaharaDroid.
*
* Copyright [2010] [Catalyst IT Limited]
*
* This file is free software: you may copy, redistribute and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nz.net.catalyst.MaharaDroid.data;
import nz.net.catalyst.MaharaDroid.LogConfig;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.util.Log;
import android.widget.Toast;
/** Helper to the database, manages versions and creation */
public class ArtefactDataSQLHelper extends SQLiteOpenHelper {
static final String TAG = LogConfig.getLogTag(ArtefactDataSQLHelper.class);
// whether DEBUG level logging is enabled (whether globally, or explicitly for this log tag)
static final boolean DEBUG = LogConfig.isDebug(TAG);
// whether VERBOSE level logging is enabled
static final boolean VERBOSE = LogConfig.VERBOSE;
private static final String DATABASE_NAME = "maharadroid_upload_log.db";
private static final int DATABASE_VERSION = 2;
private static Context mContext;
// Table name
public static final String TABLE = "upload_log";
// Columns
public static final String TIME = "time";
public static final String FILENAME = "filename";
public static final String URI = "uri";
public static final String TITLE = "title";
public static final String DESCRIPTION = "description";
public static final String TAGS = "tags";
public static final String SAVED_ID = "id";
public static final String JOURNAL_ID = "journal_id";
public static final String IS_DRAFT = "is_draft";
public static final String ALLOW_COMMENTS = "allow_comments";
public ArtefactDataSQLHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
SQLiteDatabase db = this.getReadableDatabase();
// db.execSQL("DROP TABLE " + TABLE + "; ");
// this.onCreate(db);
// String sql = "alter table " + TABLE + " ADD COLUMN " + IS_DRAFT + " boolean; ";
// String sql = "alter table " + TABLE + " ADD COLUMN " + ALLOW_COMMENTS + " boolean; ";
// Log.d("LogData", "onCreate: " + sql);
// db.execSQL(sql);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table " + TABLE + "( " + BaseColumns._ID
+ " integer primary key autoincrement, " + TIME + " integer, "
+ FILENAME + " text, "
+ TITLE + " text not null, "
+ DESCRIPTION + " text, "
+ TAGS + " text, "
+ SAVED_ID + " integer, "
+ JOURNAL_ID + " text, "
+ IS_DRAFT + " boolean, "
+ ALLOW_COMMENTS + " boolean "
+ ");";
if ( DEBUG ) Log.d("LogData", "onCreate: " + sql);