aboutsummaryrefslogtreecommitdiff
path: root/src/org/fox/ttrss/util
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@madoka.volgo-balt.ru>2012-06-19 14:18:00 +0400
committerAndrew Dolgov <fox@madoka.volgo-balt.ru>2012-06-19 14:18:00 +0400
commit08397a47af403d64a012a7961e7444254ccaa9a2 (patch)
treeac3f0a2ad3a391fd60f2da3a69211d5f27c3a79b /src/org/fox/ttrss/util
parent01151df966ed0006246790645b0f3e06c2ca94b8 (diff)
categorize source files
Diffstat (limited to 'src/org/fox/ttrss/util')
-rw-r--r--src/org/fox/ttrss/util/AppRater.java100
-rw-r--r--src/org/fox/ttrss/util/DatabaseHelper.java67
-rw-r--r--src/org/fox/ttrss/util/EasySSLSocketFactory.java120
-rw-r--r--src/org/fox/ttrss/util/EasyX509TrustManager.java26
-rw-r--r--src/org/fox/ttrss/util/FragmentStatePagerAdapter.java226
-rw-r--r--src/org/fox/ttrss/util/ImageCacheService.java211
-rw-r--r--src/org/fox/ttrss/util/PrefsBackupAgent.java19
7 files changed, 769 insertions, 0 deletions
diff --git a/src/org/fox/ttrss/util/AppRater.java b/src/org/fox/ttrss/util/AppRater.java
new file mode 100644
index 00000000..a50cc9d9
--- /dev/null
+++ b/src/org/fox/ttrss/util/AppRater.java
@@ -0,0 +1,100 @@
+package org.fox.ttrss.util;
+
+// From http://androidsnippets.com/prompt-engaged-users-to-rate-your-app-in-the-android-market-appirater
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class AppRater {
+ private final static String APP_TITLE = "Tiny Tiny RSS";
+ private final static String APP_PNAME = "org.fox.ttrss";
+
+ private final static int DAYS_UNTIL_PROMPT = 3;
+ private final static int LAUNCHES_UNTIL_PROMPT = 7;
+
+ public static void appLaunched(Context mContext) {
+ SharedPreferences prefs = mContext.getSharedPreferences("apprater", 0);
+ if (prefs.getBoolean("dontshowagain", false)) { return ; }
+
+ SharedPreferences.Editor editor = prefs.edit();
+
+ // Increment launch counter
+ long launch_count = prefs.getLong("launch_count", 0) + 1;
+ editor.putLong("launch_count", launch_count);
+
+ // Get date of first launch
+ Long date_firstLaunch = prefs.getLong("date_firstlaunch", 0);
+ if (date_firstLaunch == 0) {
+ date_firstLaunch = System.currentTimeMillis();
+ editor.putLong("date_firstlaunch", date_firstLaunch);
+ }
+
+ // Wait at least n days before opening
+ if (launch_count >= LAUNCHES_UNTIL_PROMPT) {
+ if (System.currentTimeMillis() >= date_firstLaunch +
+ (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)) {
+ showRateDialog(mContext, editor);
+ }
+ }
+
+ editor.commit();
+ }
+
+ public static void showRateDialog(final Context mContext, final SharedPreferences.Editor editor) {
+ final Dialog dialog = new Dialog(mContext);
+ dialog.setTitle("Rate " + APP_TITLE);
+
+ LinearLayout ll = new LinearLayout(mContext);
+ ll.setOrientation(LinearLayout.VERTICAL);
+
+ TextView tv = new TextView(mContext);
+ tv.setText("If you enjoy using " + APP_TITLE + ", please take a moment to rate it. Thanks for your support!");
+ tv.setWidth(240);
+ tv.setPadding(4, 0, 4, 10);
+ ll.addView(tv);
+
+ Button b1 = new Button(mContext);
+ b1.setText("Rate " + APP_TITLE);
+ b1.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + APP_PNAME)));
+ dialog.dismiss();
+ }
+ });
+ ll.addView(b1);
+
+ Button b2 = new Button(mContext);
+ b2.setText("Remind me later");
+ b2.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ });
+ ll.addView(b2);
+
+ Button b3 = new Button(mContext);
+ b3.setText("No, thanks");
+ b3.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (editor != null) {
+ editor.putBoolean("dontshowagain", true);
+ editor.commit();
+ }
+ dialog.dismiss();
+ }
+ });
+ ll.addView(b3);
+
+ dialog.setContentView(ll);
+ dialog.show();
+ }
+} \ No newline at end of file
diff --git a/src/org/fox/ttrss/util/DatabaseHelper.java b/src/org/fox/ttrss/util/DatabaseHelper.java
new file mode 100644
index 00000000..b8560589
--- /dev/null
+++ b/src/org/fox/ttrss/util/DatabaseHelper.java
@@ -0,0 +1,67 @@
+package org.fox.ttrss.util;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+
+public class DatabaseHelper extends SQLiteOpenHelper {
+
+ @SuppressWarnings("unused")
+ private final String TAG = this.getClass().getSimpleName();
+ public static final String DATABASE_NAME = "OfflineStorage.db";
+ public static final int DATABASE_VERSION = 2;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS feeds;");
+ db.execSQL("DROP TABLE IF EXISTS articles;");
+ db.execSQL("DROP VIEW IF EXISTS feeds_unread;");
+ db.execSQL("DROP TRIGGER IF EXISTS articles_set_modified;");
+
+ db.execSQL("CREATE TABLE IF NOT EXISTS feeds (" +
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "feed_url TEXT, " +
+ "title TEXT, " +
+ "has_icon BOOLEAN, " +
+ "cat_id INTEGER" +
+ ");");
+
+ db.execSQL("CREATE TABLE IF NOT EXISTS articles (" +
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "unread BOOLEAN, " +
+ "marked BOOLEAN, " +
+ "published BOOLEAN, " +
+ "updated INTEGER, " +
+ "is_updated BOOLEAN, " +
+ "title TEXT, " +
+ "link TEXT, " +
+ "feed_id INTEGER, " +
+ "tags TEXT, " +
+ "content TEXT, " +
+ "selected BOOLEAN, " +
+ "modified BOOLEAN" +
+ ");");
+
+ db.execSQL("CREATE TRIGGER articles_set_modified UPDATE OF marked, published, unread ON articles " +
+ "BEGIN " +
+ " UPDATE articles SET modified = 1 WHERE " + BaseColumns._ID + " = " + "OLD." + BaseColumns._ID + "; " +
+ "END;");
+
+ db.execSQL("CREATE VIEW feeds_unread AS SELECT feeds."+BaseColumns._ID+" AS "+BaseColumns._ID+", " +
+ "feeds.title AS title, " +
+ "SUM(articles.unread) AS unread FROM feeds " +
+ "LEFT JOIN articles ON (articles.feed_id = feeds."+BaseColumns._ID+") " +
+ "GROUP BY feeds."+BaseColumns._ID+", feeds.title;");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ onCreate(db);
+ }
+
+}
diff --git a/src/org/fox/ttrss/util/EasySSLSocketFactory.java b/src/org/fox/ttrss/util/EasySSLSocketFactory.java
new file mode 100644
index 00000000..f0c2d3ad
--- /dev/null
+++ b/src/org/fox/ttrss/util/EasySSLSocketFactory.java
@@ -0,0 +1,120 @@
+package org.fox.ttrss.util;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.scheme.LayeredSocketFactory;
+import org.apache.http.conn.scheme.SocketFactory;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+
+public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory
+{
+ private SSLContext sslcontext = null;
+
+ private static SSLContext createEasySSLContext() throws IOException
+ {
+ try
+ {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, new TrustManager[] { new EasyX509TrustManager() }, null);
+ return context;
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ private SSLContext getSSLContext() throws IOException
+ {
+ if (this.sslcontext == null)
+ {
+ this.sslcontext = createEasySSLContext();
+ }
+ return this.sslcontext;
+ }
+
+ /**
+ * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int,
+ * java.net.InetAddress, int, org.apache.http.params.HttpParams)
+ */
+ public Socket connectSocket(Socket sock,
+ String host,
+ int port,
+ InetAddress localAddress,
+ int localPort,
+ HttpParams params)
+
+ throws IOException, UnknownHostException, ConnectTimeoutException
+ {
+ int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
+ int soTimeout = HttpConnectionParams.getSoTimeout(params);
+ InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
+ SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());
+
+ if ((localAddress != null) || (localPort > 0))
+ {
+ // we need to bind explicitly
+ if (localPort < 0)
+ {
+ localPort = 0; // indicates "any"
+ }
+ InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);
+ sslsock.bind(isa);
+ }
+
+ sslsock.connect(remoteAddress, connTimeout);
+ sslsock.setSoTimeout(soTimeout);
+ return sslsock;
+ }
+
+ /**
+ * @see org.apache.http.conn.scheme.SocketFactory#createSocket()
+ */
+ public Socket createSocket() throws IOException {
+ return getSSLContext().getSocketFactory().createSocket();
+ }
+
+ /**
+ * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket)
+ */
+ public boolean isSecure(Socket socket) throws IllegalArgumentException {
+ return true;
+ }
+
+ /**
+ * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int,
+ * boolean)
+ */
+ public Socket createSocket(Socket socket,
+ String host,
+ int port,
+ boolean autoClose) throws IOException,
+ UnknownHostException
+ {
+ return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
+ }
+
+ // -------------------------------------------------------------------
+ // javadoc in org.apache.http.conn.scheme.SocketFactory says :
+ // Both Object.equals() and Object.hashCode() must be overridden
+ // for the correct operation of some connection managers
+ // -------------------------------------------------------------------
+
+ public boolean equals(Object obj) {
+ return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));
+ }
+
+ public int hashCode() {
+ return EasySSLSocketFactory.class.hashCode();
+ }
+} \ No newline at end of file
diff --git a/src/org/fox/ttrss/util/EasyX509TrustManager.java b/src/org/fox/ttrss/util/EasyX509TrustManager.java
new file mode 100644
index 00000000..5ffc19bb
--- /dev/null
+++ b/src/org/fox/ttrss/util/EasyX509TrustManager.java
@@ -0,0 +1,26 @@
+
+package org.fox.ttrss.util;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+// http://stackoverflow.com/questions/6989116/httpget-not-working-due-to-not-trusted-server-certificate-but-it-works-with-ht
+
+public class EasyX509TrustManager implements X509TrustManager {
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException { }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException { }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+}
diff --git a/src/org/fox/ttrss/util/FragmentStatePagerAdapter.java b/src/org/fox/ttrss/util/FragmentStatePagerAdapter.java
new file mode 100644
index 00000000..26494fdc
--- /dev/null
+++ b/src/org/fox/ttrss/util/FragmentStatePagerAdapter.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.fox.ttrss.util;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link android.support.v4.view.PagerAdapter} that
+ * uses a {@link Fragment} to manage each page. This class also handles
+ * saving and restoring of fragment's state.
+ *
+ * <p>This version of the pager is more useful when there are a large number
+ * of pages, working more like a list view. When pages are not visible to
+ * the user, their entire fragment may be destroyed, only keeping the saved
+ * state of that fragment. This allows the pager to hold on to much less
+ * memory associated with each visited page as compared to
+ * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
+ * switching between pages.
+ *
+ * <p>When using FragmentPagerAdapter the host ViewPager must have a
+ * valid ID set.</p>
+ *
+ * <p>Subclasses only need to implement {@link #getItem(int)}
+ * and {@link #getCount()} to have a working adapter.
+ *
+ * <p>Here is an example implementation of a pager containing fragments of
+ * lists:
+ *
+ * {@sample development/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStatePagerSupport.java
+ * complete}
+ *
+ * <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
+ *
+ * {@sample development/samples/Support4Demos/res/layout/fragment_pager.xml
+ * complete}
+ *
+ * <p>The <code>R.layout.fragment_pager_list</code> resource containing each
+ * individual fragment's layout is:
+ *
+ * {@sample development/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * complete}
+ */
+public abstract class FragmentStatePagerAdapter extends PagerAdapter {
+ private static final String TAG = "FragmentStatePagerAdapter";
+ private static final boolean DEBUG = false;
+
+ private final FragmentManager mFragmentManager;
+ private FragmentTransaction mCurTransaction = null;
+
+ private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
+ private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+ private Fragment mCurrentPrimaryItem = null;
+
+ public FragmentStatePagerAdapter(FragmentManager fm) {
+ mFragmentManager = fm;
+ }
+
+ /**
+ * Return the Fragment associated with a specified position.
+ */
+ public abstract Fragment getItem(int position);
+
+ @Override
+ public void startUpdate(ViewGroup container) {
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ // If we already have this item instantiated, there is nothing
+ // to do. This can happen when we are restoring the entire pager
+ // from its saved state, where the fragment manager has already
+ // taken care of restoring the fragments we previously had instantiated.
+ if (mFragments.size() > position) {
+ Fragment f = mFragments.get(position);
+ if (f != null) {
+ return f;
+ }
+ }
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+
+ Fragment fragment = getItem(position);
+ if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ if (mSavedState.size() > position) {
+ Fragment.SavedState fss = mSavedState.get(position);
+ if (fss != null) {
+ fragment.setInitialSavedState(fss);
+ }
+ }
+ while (mFragments.size() <= position) {
+ mFragments.add(null);
+ }
+ fragment.setMenuVisibility(false);
+ mFragments.set(position, fragment);
+ mCurTransaction.add(container.getId(), fragment);
+
+ return fragment;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ Fragment fragment = (Fragment)object;
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ + " v=" + ((Fragment)object).getView());
+ while (mSavedState.size() <= position) {
+ mSavedState.add(null);
+ }
+ mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+ mFragments.set(position, null);
+
+ mCurTransaction.remove(fragment);
+ }
+
+ @Override
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ Fragment fragment = (Fragment)object;
+ if (fragment != mCurrentPrimaryItem) {
+ if (mCurrentPrimaryItem != null) {
+ fragment.setMenuVisibility(false);
+ }
+ if (fragment != null) {
+ fragment.setMenuVisibility(true);
+ }
+ mCurrentPrimaryItem = fragment;
+ }
+ }
+
+ @Override
+ public void finishUpdate(ViewGroup container) {
+ if (mCurTransaction != null) {
+ mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction = null;
+ mFragmentManager.executePendingTransactions();
+ }
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return ((Fragment)object).getView() == view;
+ }
+
+ @Override
+ public Parcelable saveState() {
+ Bundle state = null;
+ if (mSavedState.size() > 0) {
+ state = new Bundle();
+ Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
+ mSavedState.toArray(fss);
+ state.putParcelableArray("states", fss);
+ }
+ for (int i=0; i<mFragments.size(); i++) {
+ Fragment f = mFragments.get(i);
+ if (f != null) {
+ if (state == null) {
+ state = new Bundle();
+ }
+ String key = "f" + i;
+ mFragmentManager.putFragment(state, key, f);
+ }
+ }
+ return state;
+ }
+
+ @Override
+ public void restoreState(Parcelable state, ClassLoader loader) {
+ if (state != null) {
+ Bundle bundle = (Bundle)state;
+ bundle.setClassLoader(loader);
+ Parcelable[] fss = bundle.getParcelableArray("states");
+ mSavedState.clear();
+ mFragments.clear();
+ if (fss != null) {
+ for (int i=0; i<fss.length; i++) {
+ mSavedState.add((Fragment.SavedState)fss[i]);
+ }
+ }
+ Iterable<String> keys = bundle.keySet();
+ for (String key: keys) {
+ if (key.startsWith("f")) {
+ int index = Integer.parseInt(key.substring(1));
+ Fragment f = mFragmentManager.getFragment(bundle, key);
+ if (f != null) {
+ while (mFragments.size() <= index) {
+ mFragments.add(null);
+ }
+ f.setMenuVisibility(false);
+ mFragments.set(index, f);
+ } else {
+ Log.w(TAG, "Bad fragment at key " + key);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/org/fox/ttrss/util/ImageCacheService.java b/src/org/fox/ttrss/util/ImageCacheService.java
new file mode 100644
index 00000000..1541c249
--- /dev/null
+++ b/src/org/fox/ttrss/util/ImageCacheService.java
@@ -0,0 +1,211 @@
+package org.fox.ttrss.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+
+import org.fox.ttrss.MainActivity;
+import org.fox.ttrss.R;
+import org.fox.ttrss.R.drawable;
+import org.fox.ttrss.R.string;
+import org.fox.ttrss.offline.OfflineDownloadService;
+
+import android.app.ActivityManager;
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.Intent;
+import android.os.Environment;
+import android.util.Log;
+
+public class ImageCacheService extends IntentService {
+
+ private final String TAG = this.getClass().getSimpleName();
+
+ public static final int NOTIFY_DOWNLOADING = 1;
+
+ private static final String CACHE_PATH = "/data/org.fox.ttrss/image-cache/";
+
+ private int m_imagesDownloaded = 0;
+
+ private NotificationManager m_nmgr;
+
+ public ImageCacheService() {
+ super("ImageCacheService");
+ }
+
+ private boolean isDownloadServiceRunning() {
+ ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if ("org.fox.ttrss.OfflineDownloadService".equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ m_nmgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
+ }
+
+ public static boolean isUrlCached(String url) {
+ String hashedUrl = md5(url);
+
+ File storage = Environment.getExternalStorageDirectory();
+
+ File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png");
+
+ return file.exists();
+ }
+
+ public static String getCacheFileName(String url) {
+ String hashedUrl = md5(url);
+
+ File storage = Environment.getExternalStorageDirectory();
+
+ File file = new File(storage.getAbsolutePath() + CACHE_PATH + "/" + hashedUrl + ".png");
+
+ return file.getAbsolutePath();
+ }
+
+ public static void cleanupCache(boolean deleteAll) {
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ File storage = Environment.getExternalStorageDirectory();
+ File cachePath = new File(storage.getAbsolutePath() + CACHE_PATH);
+
+ long now = new Date().getTime();
+
+ if (cachePath.isDirectory()) {
+ for (File file : cachePath.listFiles()) {
+ if (deleteAll || now - file.lastModified() > 1000*60*60*24*7) {
+ file.delete();
+ }
+ }
+ }
+ }
+ }
+
+ protected static String md5(String s) {
+ try {
+ MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+ digest.update(s.getBytes());
+ byte messageDigest[] = digest.digest();
+
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0; i<messageDigest.length; i++)
+ hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+
+ return hexString.toString();
+
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private InputStream getStream(String urlString) {
+ try {
+ URL url = new URL(urlString);
+ URLConnection urlConnection = url.openConnection();
+ urlConnection.setConnectTimeout(250);
+ return urlConnection.getInputStream();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private void updateNotification(String msg) {
+ Notification notification = new Notification(R.drawable.icon,
+ getString(R.string.notify_downloading_title), System.currentTimeMillis());
+
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent(this, MainActivity.class), 0);
+
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
+
+ notification.setLatestEventInfo(this, getString(R.string.notify_downloading_title), msg, contentIntent);
+
+ m_nmgr.notify(NOTIFY_DOWNLOADING, notification);
+ }
+
+ private void updateNotification(int msgResId) {
+ updateNotification(getString(msgResId));
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String url = intent.getStringExtra("url");
+
+ //Log.d(TAG, "got request to download URL=" + url);
+
+ if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
+ return;
+
+ String hashedUrl = md5(url);
+
+ File storage = Environment.getExternalStorageDirectory();
+ File cachePath = new File(storage.getAbsolutePath() + CACHE_PATH);
+ if (!cachePath.exists()) cachePath.mkdirs();
+
+ if (cachePath.isDirectory() && hashedUrl != null) {
+ File outputFile = new File(cachePath.getAbsolutePath() + "/" + hashedUrl + ".png");
+
+ if (!outputFile.exists()) {
+
+ //Log.d(TAG, "downloading to " + outputFile.getAbsolutePath());
+
+ InputStream is = getStream(url);
+
+ if (is != null) {
+ try {
+ FileOutputStream fos = new FileOutputStream(outputFile);
+
+ byte[] buffer = new byte[1024];
+ int len = 0;
+ while ((len = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, len);
+ }
+
+ fos.close();
+ is.close();
+
+ m_imagesDownloaded++;
+
+ updateNotification(getString(R.string.notify_downloading_images, m_imagesDownloaded));
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (!isDownloadServiceRunning()) {
+ m_nmgr.cancel(NOTIFY_DOWNLOADING);
+
+ Intent success = new Intent();
+ success.setAction(OfflineDownloadService.INTENT_ACTION_SUCCESS);
+ success.addCategory(Intent.CATEGORY_DEFAULT);
+ sendBroadcast(success);
+ }
+ }
+
+}
diff --git a/src/org/fox/ttrss/util/PrefsBackupAgent.java b/src/org/fox/ttrss/util/PrefsBackupAgent.java
new file mode 100644
index 00000000..a786be56
--- /dev/null
+++ b/src/org/fox/ttrss/util/PrefsBackupAgent.java
@@ -0,0 +1,19 @@
+package org.fox.ttrss.util;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+
+public class PrefsBackupAgent extends BackupAgentHelper {
+ // The name of the SharedPreferences file
+ static final String PREFS = "org.fox.ttrss_preferences";
+
+ // A key to uniquely identify the set of backup data
+ static final String PREFS_BACKUP_KEY = "prefs";
+
+ // Allocate a helper and add it to the backup agent
+ @Override
+ public void onCreate() {
+ SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS);
+ addHelper(PREFS_BACKUP_KEY, helper);
+ }
+}