diff --git a/mobile/AndroidManifest.xml b/mobile/AndroidManifest.xml index 041ddde8..2606e95f 100644 --- a/mobile/AndroidManifest.xml +++ b/mobile/AndroidManifest.xml @@ -115,11 +115,11 @@ Configure IITCm menu Toggle visibility of IITCm menu entries + User plugin updates + Configure plugin update interval + How often IITCm should search for new plugin versions + Force plugin update + Update all enabled user plugins System Bar @@ -178,6 +183,22 @@ 2 + + Disable + 1 day + 2 days + 1 week + 2 weeks + + + + 0 + 1 + 2 + 7 + 14 + + Reload IITC Toggle fullscreen Layer Chooser diff --git a/mobile/res/xml/preferences.xml b/mobile/res/xml/preferences.xml index dbea9820..5978e5f1 100644 --- a/mobile/res/xml/preferences.xml +++ b/mobile/res/xml/preferences.xml @@ -42,7 +42,7 @@ android:summary="@string/pref_plugins_sum" android:title="@string/pref_plugins"> @@ -92,6 +92,21 @@ android:key="pref_disable_splash" android:title="@string/pref_disable_splash"/> + + + + @@ -106,7 +121,7 @@ android:key="pref_about_screen" android:persistent="false" android:title="@string/pref_about_title"> - diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java b/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java index 639f7021..ac875156 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_FileManager.java @@ -19,6 +19,8 @@ import android.webkit.WebResourceResponse; import android.widget.Toast; import com.cradle.iitc_mobile.IITC_Mobile.ResponseHandler; +import com.cradle.iitc_mobile.async.UpdateScript; +import com.cradle.iitc_mobile.prefs.PluginPreferenceActivity; import org.json.JSONObject; @@ -39,6 +41,8 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; public class IITC_FileManager { private static final WebResourceResponse EMPTY = @@ -48,7 +52,10 @@ public class IITC_FileManager { "script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');'));\n" + "(document.body || document.head || document.documentElement).appendChild(script);"; + private long mUpdateInterval = 1000*60*60*24*7; + public static final String DOMAIN = ".iitcm.localhost"; + // update interval is 2 days by default /** * copies the contents of a stream into another stream and (optionally) closes the output stream afterwards @@ -306,13 +313,44 @@ public class IITC_FileManager { if (invalidateHeaders) { try { thread.join(); - ((IITC_PluginPreferenceActivity) mActivity).invalidateHeaders(); + ((PluginPreferenceActivity) mActivity).invalidateHeaders(); } catch (final InterruptedException e) { Log.w(e); } } } + public void updatePlugins(boolean force) { + // do nothing if updates are disabled + if (mUpdateInterval == 0 && !force) return; + // check last script update + final long lastUpdated = mPrefs.getLong("pref_last_plugin_update", 0); + final long now = System.currentTimeMillis(); + + // return if no update wanted + if ((now - lastUpdated < mUpdateInterval) && !force) return; + // get the plugin preferences + final TreeMap all_prefs = new TreeMap(mPrefs.getAll()); + + // iterate through all plugins + for (final Map.Entry entry : all_prefs.entrySet()) { + final String plugin = entry.getKey(); + if (plugin.endsWith(".user.js") && entry.getValue().toString().equals("true")) { + if (plugin.startsWith(PLUGINS_PATH)) { + new UpdateScript(mActivity).execute(plugin); + } + } + } + mPrefs + .edit() + .putLong("pref_last_plugin_update", now) + .commit(); + } + + public void setUpdateInterval(int interval) { + mUpdateInterval = 1000*60*60*24 * interval; + } + private class FileRequest extends WebResourceResponse implements ResponseHandler, Runnable { private Intent mData; private final String mFunctionName; diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java index 1ee36f8b..55ab13dc 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java @@ -40,6 +40,8 @@ import android.widget.TextView; import android.widget.Toast; import com.cradle.iitc_mobile.IITC_NavigationHelper.Pane; +import com.cradle.iitc_mobile.prefs.PluginPreferenceActivity; +import com.cradle.iitc_mobile.prefs.PreferenceActivity; import com.cradle.iitc_mobile.share.ShareActivity; import org.json.JSONException; @@ -145,6 +147,7 @@ public class IITC_Mobile extends Activity mIitcWebView.updateFullscreenStatus(); mFileManager = new IITC_FileManager(this); + mFileManager.setUpdateInterval(Integer.parseInt(mSharedPrefs.getString("pref_update_plugins_interval", "7"))); mUserLocation = new IITC_UserLocation(this); mUserLocation.setLocationMode(Integer.parseInt(mSharedPrefs.getString("pref_user_location_mode", "0"))); @@ -191,6 +194,14 @@ public class IITC_Mobile extends Activity return; } else if (key.equals("pref_fake_user_agent")) { mIitcWebView.setUserAgent(); + } else if (key.equals("pref_last_plugin_update")) { + Long forceUpdate = sharedPreferences.getLong("pref_last_plugin_update", 0); + if (forceUpdate == 0) mFileManager.updatePlugins(true); + return; + } else if (key.equals("pref_update_plugins_interval")) { + final int interval = Integer.parseInt(mSharedPrefs.getString("pref_update_plugins_interval", "7")); + mFileManager.setUpdateInterval(interval); + return; } else if (key.equals("pref_press_twice_to_exit") || key.equals("pref_share_selected_tab") || key.equals("pref_messages") @@ -248,7 +259,7 @@ public class IITC_Mobile extends Activity final String type = intent.getType() == null ? "" : intent.getType(); final String path = uri.getPath() == null ? "" : uri.getPath(); if (path.endsWith(".user.js") || type.contains("javascript")) { - final Intent prefIntent = new Intent(this, IITC_PluginPreferenceActivity.class); + final Intent prefIntent = new Intent(this, PluginPreferenceActivity.class); prefIntent.setDataAndType(uri, intent.getType()); startActivity(prefIntent); } @@ -565,7 +576,7 @@ public class IITC_Mobile extends Activity } return true; case R.id.action_settings: // start settings activity - final Intent intent = new Intent(this, IITC_PreferenceActivity.class); + final Intent intent = new Intent(this, PreferenceActivity.class); try { intent.putExtra("iitc_version", mFileManager.getIITCVersion()); } catch (final IOException e) { @@ -704,6 +715,7 @@ public class IITC_Mobile extends Activity mNavigationHelper.onLoadingStateChanged(); invalidateOptionsMenu(); updateViews(); + if (!isLoading) mFileManager.updatePlugins(false); if (mSearchTerm != null && !isLoading) { new Handler().postDelayed(new Runnable() { diff --git a/mobile/src/com/cradle/iitc_mobile/async/UpdateScript.java b/mobile/src/com/cradle/iitc_mobile/async/UpdateScript.java new file mode 100644 index 00000000..aca03e42 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/async/UpdateScript.java @@ -0,0 +1,84 @@ +package com.cradle.iitc_mobile.async; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.preference.PreferenceManager; + +import com.cradle.iitc_mobile.IITC_FileManager; +import com.cradle.iitc_mobile.IITC_Mobile; +import com.cradle.iitc_mobile.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class UpdateScript extends AsyncTask { + + private final Activity mActivity; + private String mFilePath; + private String mScript; + + public UpdateScript(final Activity activity) { + mActivity = activity; + } + + @Override + protected Boolean doInBackground(final String... urls) { + try { + mFilePath = urls[0]; + // get local script meta information + mScript = IITC_FileManager.readStream(new FileInputStream(new File(mFilePath))); + final String updateURL = IITC_FileManager.getScriptInfo(mScript).get("updateURL"); + final String downloadURL = IITC_FileManager.getScriptInfo(mScript).get("downloadURL"); + + // get remote script meta information + final File file_old = new File(mFilePath); + final InputStream is = new URL(updateURL).openStream(); + final String old_version = IITC_FileManager.getScriptInfo(mScript).get("version"); + final String new_version = IITC_FileManager.getScriptInfo(IITC_FileManager.readStream(is)).get("version"); + + // update script if neccessary + if (old_version.compareTo(new_version) < 0) { + Log.d("plugin " + mFilePath + " outdated\n" + old_version + " vs " + new_version); + Log.d("updating file...."); + IITC_FileManager.copyStream(new URL(downloadURL).openStream(), new FileOutputStream(file_old), true); + Log.d("...done"); + return true; + } + } catch (final IOException e) { + return false; + } + return false; + } + + protected void onPostExecute(Boolean updated) { + if (updated) { + final String name = IITC_FileManager.getScriptInfo(mScript).get("name"); + new AlertDialog.Builder(mActivity) + .setTitle("Plugin updated") + .setMessage(name) + .setCancelable(true) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }) + .setNegativeButton("Reload", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + ((IITC_Mobile) mActivity).reloadIITC(); + } + }) + .create() + .show(); + } + } +} diff --git a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java index f12e5267..e6b1a79f 100644 --- a/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java +++ b/mobile/src/com/cradle/iitc_mobile/fragments/MainSettings.java @@ -16,9 +16,9 @@ import android.view.ViewParent; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.cradle.iitc_mobile.IITC_AboutDialogPreference; import com.cradle.iitc_mobile.Log; import com.cradle.iitc_mobile.R; +import com.cradle.iitc_mobile.prefs.AboutDialogPreference; public class MainSettings extends PreferenceFragment { @Override @@ -40,7 +40,7 @@ public class MainSettings extends PreferenceFragment { Log.w(e); } - final IITC_AboutDialogPreference pref_about = (IITC_AboutDialogPreference) findPreference("pref_about"); + final AboutDialogPreference pref_about = (AboutDialogPreference) findPreference("pref_about"); pref_about.setVersions(iitcVersion, buildVersion); final ListPreference pref_user_location_mode = (ListPreference) findPreference("pref_user_location_mode"); diff --git a/mobile/src/com/cradle/iitc_mobile/fragments/PluginsFragment.java b/mobile/src/com/cradle/iitc_mobile/fragments/PluginsFragment.java index 65fa90ab..b90248d1 100644 --- a/mobile/src/com/cradle/iitc_mobile/fragments/PluginsFragment.java +++ b/mobile/src/com/cradle/iitc_mobile/fragments/PluginsFragment.java @@ -4,9 +4,9 @@ import android.app.ActionBar; import android.os.Bundle; import android.preference.PreferenceFragment; -import com.cradle.iitc_mobile.IITC_PluginPreference; -import com.cradle.iitc_mobile.IITC_PluginPreferenceActivity; import com.cradle.iitc_mobile.R; +import com.cradle.iitc_mobile.prefs.PluginPreference; +import com.cradle.iitc_mobile.prefs.PluginPreferenceActivity; import java.util.ArrayList; @@ -26,11 +26,11 @@ public class PluginsFragment extends PreferenceFragment { // get plugins category for this fragments and plugins list String category = getArguments().getString("category"); boolean userPlugin = getArguments().getBoolean("userPlugin"); - ArrayList prefs = - IITC_PluginPreferenceActivity.getPluginPreference(category, userPlugin); + ArrayList prefs = + PluginPreferenceActivity.getPluginPreference(category, userPlugin); // add plugin checkbox preferences - for (IITC_PluginPreference pref : prefs) { + for (PluginPreference pref : prefs) { getPreferenceScreen().addPreference(pref); } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_AboutDialogPreference.java b/mobile/src/com/cradle/iitc_mobile/prefs/AboutDialogPreference.java similarity index 86% rename from mobile/src/com/cradle/iitc_mobile/IITC_AboutDialogPreference.java rename to mobile/src/com/cradle/iitc_mobile/prefs/AboutDialogPreference.java index 1bfd0fbe..ce25a780 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_AboutDialogPreference.java +++ b/mobile/src/com/cradle/iitc_mobile/prefs/AboutDialogPreference.java @@ -1,4 +1,4 @@ -package com.cradle.iitc_mobile; +package com.cradle.iitc_mobile.prefs; import android.content.Context; import android.preference.Preference; @@ -9,11 +9,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -public class IITC_AboutDialogPreference extends Preference { +import com.cradle.iitc_mobile.R; + +public class AboutDialogPreference extends Preference { private String mBuildVersion = ""; private String mIitcVersion = ""; - public IITC_AboutDialogPreference(Context context, AttributeSet attrs) { + public AboutDialogPreference(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/mobile/src/com/cradle/iitc_mobile/prefs/ForceUpdatePreference.java b/mobile/src/com/cradle/iitc_mobile/prefs/ForceUpdatePreference.java new file mode 100644 index 00000000..1cb5bcb2 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/prefs/ForceUpdatePreference.java @@ -0,0 +1,49 @@ +package com.cradle.iitc_mobile.prefs; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; + +import com.cradle.iitc_mobile.R; + +/** + * The OptionDialogPreference will display a dialog, and will persist the + * true when pressing the positive button and false + * otherwise. It will persist to the android:key specified in xml-preference. + */ +public class ForceUpdatePreference extends Preference { + + public ForceUpdatePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onClick() { + super.onClick(); + new AlertDialog.Builder(getContext()) + .setTitle(R.string.pref_force_plugin_update) + .setMessage(R.string.pref_force_plugin_update_sum) + .setCancelable(true) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit() + .putLong("pref_last_plugin_update", 0) + .commit(); + dialog.cancel(); + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }) + .create() + .show(); + } +} \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreference.java b/mobile/src/com/cradle/iitc_mobile/prefs/PluginPreference.java similarity index 84% rename from mobile/src/com/cradle/iitc_mobile/IITC_PluginPreference.java rename to mobile/src/com/cradle/iitc_mobile/prefs/PluginPreference.java index 32d6541b..8ee53343 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreference.java +++ b/mobile/src/com/cradle/iitc_mobile/prefs/PluginPreference.java @@ -1,4 +1,4 @@ -package com.cradle.iitc_mobile; +package com.cradle.iitc_mobile.prefs; import android.content.Context; import android.preference.CheckBoxPreference; @@ -7,9 +7,9 @@ import android.view.ViewGroup; import android.widget.TextView; // multiline checkbox preference -public class IITC_PluginPreference extends CheckBoxPreference { +public class PluginPreference extends CheckBoxPreference { - public IITC_PluginPreference(Context context) { + public PluginPreference(Context context) { super(context); } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java b/mobile/src/com/cradle/iitc_mobile/prefs/PluginPreferenceActivity.java similarity index 91% rename from mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java rename to mobile/src/com/cradle/iitc_mobile/prefs/PluginPreferenceActivity.java index a20ed8f0..8eb17b22 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_PluginPreferenceActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/prefs/PluginPreferenceActivity.java @@ -1,4 +1,4 @@ -package com.cradle.iitc_mobile; +package com.cradle.iitc_mobile.prefs; import android.content.ActivityNotFoundException; import android.content.Context; @@ -18,6 +18,10 @@ import android.widget.ListAdapter; import android.widget.TextView; import android.widget.Toast; +import com.cradle.iitc_mobile.IITC_FileManager; +import com.cradle.iitc_mobile.IITC_NotificationHelper; +import com.cradle.iitc_mobile.Log; +import com.cradle.iitc_mobile.R; import com.cradle.iitc_mobile.fragments.PluginsFragment; import java.io.File; @@ -31,7 +35,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -public class IITC_PluginPreferenceActivity extends PreferenceActivity { +public class PluginPreferenceActivity extends PreferenceActivity { private final static int COPY_PLUGIN_REQUEST = 1; @@ -39,10 +43,10 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { // we use a tree map to have a map with alphabetical order // don't initialize the asset plugin map, because it tells us if the settings are started the first time // and we have to parse plugins to build the preference screen - private static TreeMap> sAssetPlugins = null; + private static TreeMap> sAssetPlugins = null; // user plugins can be initialized. - private static final TreeMap> sUserPlugins = - new TreeMap>(); + private static final TreeMap> sUserPlugins = + new TreeMap>(); private static int mDeletedPlugins = 0; private IITC_FileManager mFileManager; @@ -69,7 +73,7 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { // it is enough to parse the plugin only on first start. if (sAssetPlugins == null) { Log.d("opened plugin prefs the first time since app start -> parse plugins"); - sAssetPlugins = new TreeMap>(); + sAssetPlugins = new TreeMap>(); setUpPluginPreferenceScreen(); } else { checkForNewPlugins(); @@ -170,7 +174,7 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { } // called by Plugins Fragment - public static ArrayList getPluginPreference(final String key, final boolean userPlugin) { + public static ArrayList getPluginPreference(final String key, final boolean userPlugin) { if (userPlugin) return sUserPlugins.get(key); return sAssetPlugins.get(key); @@ -203,10 +207,10 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { final File[] userPlugins = getUserPlugins(); final String[] officialPlugins = getAssetPlugins(); int numPlugins = 0; - for (final Map.Entry> entry : sUserPlugins.entrySet()) { + for (final Map.Entry> entry : sUserPlugins.entrySet()) { numPlugins += entry.getValue().size(); } - for (final Map.Entry> entry : sAssetPlugins.entrySet()) { + for (final Map.Entry> entry : sAssetPlugins.entrySet()) { numPlugins += entry.getValue().size(); } if ((userPlugins.length + officialPlugins.length) != (numPlugins + mDeletedPlugins)) { @@ -267,24 +271,24 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { // first check if we need a new category if (userPlugin) { if (!sUserPlugins.containsKey(plugin_cat)) { - sUserPlugins.put(plugin_cat, new ArrayList()); + sUserPlugins.put(plugin_cat, new ArrayList()); Log.d("create " + plugin_cat + " and add " + plugin_name); } } else { if (!sAssetPlugins.containsKey(plugin_cat)) { - sAssetPlugins.put(plugin_cat, new ArrayList()); + sAssetPlugins.put(plugin_cat, new ArrayList()); Log.d("create " + plugin_cat + " and add " + plugin_name); } } // now build a new checkable preference for the plugin - final IITC_PluginPreference plugin_pref = new IITC_PluginPreference(this); + final PluginPreference plugin_pref = new PluginPreference(this); plugin_pref.setKey(plugin_key); plugin_pref.setTitle(plugin_name); plugin_pref.setSummary(plugin_desc); plugin_pref.setDefaultValue(false); plugin_pref.setPersistent(true); - final ArrayList list = + final ArrayList list = userPlugin ? sUserPlugins.get(plugin_cat) : sAssetPlugins.get(plugin_cat); list.add(plugin_pref); } @@ -294,7 +298,7 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { final Header category = new Header(); category.title = "User Plugins"; mHeaders.add(category); - for (final Map.Entry> entry : sUserPlugins.entrySet()) { + for (final Map.Entry> entry : sUserPlugins.entrySet()) { addHeader(entry.getKey(), true); } } @@ -302,7 +306,7 @@ public class IITC_PluginPreferenceActivity extends PreferenceActivity { final Header category = new Header(); category.title = "Official Plugins"; mHeaders.add(category); - for (final Map.Entry> entry : sAssetPlugins.entrySet()) { + for (final Map.Entry> entry : sAssetPlugins.entrySet()) { addHeader(entry.getKey(), false); } } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_PreferenceActivity.java b/mobile/src/com/cradle/iitc_mobile/prefs/PreferenceActivity.java similarity index 92% rename from mobile/src/com/cradle/iitc_mobile/IITC_PreferenceActivity.java rename to mobile/src/com/cradle/iitc_mobile/prefs/PreferenceActivity.java index a5521541..eb45ff62 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_PreferenceActivity.java +++ b/mobile/src/com/cradle/iitc_mobile/prefs/PreferenceActivity.java @@ -1,4 +1,4 @@ -package com.cradle.iitc_mobile; +package com.cradle.iitc_mobile.prefs; import android.app.Activity; import android.os.Bundle; @@ -6,7 +6,7 @@ import android.view.MenuItem; import com.cradle.iitc_mobile.fragments.MainSettings; -public class IITC_PreferenceActivity extends Activity { +public class PreferenceActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) {