From e2d46bdae2dfc62fdb42792fdb9b2c0cdef57f83 Mon Sep 17 00:00:00 2001 From: Anthony Restaino Date: Mon, 1 Feb 2016 22:17:44 -0500 Subject: [PATCH] Fixed StrictMode problems, created a reactive implementation class, fixed potential NPEs, fixed memory leak * Fixed places where IO was done on main thread * Created reactive class Observable so that work could easily be done on other threads * Fixed potential NPEs in LightningView * Fixed memory leak where ConnectivityManager was leaking activity --- .../lightning/activity/BrowserActivity.java | 46 ++++-- .../lightning/activity/IncognitoActivity.java | 21 ++- .../lightning/activity/MainActivity.java | 21 ++- .../lightning/activity/TabsManager.java | 133 +++++++++++------- .../lightning/async/ImageDownloadTask.java | 11 +- .../lightning/constant/BookmarkPage.java | 10 +- .../lightning/constant/HistoryPage.java | 6 +- .../browser/lightning/constant/StartPage.java | 6 +- .../lightning/database/BookmarkManager.java | 4 +- .../lightning/database/HistoryDatabase.java | 14 +- .../lightning/object/SearchAdapter.java | 1 + .../acr/browser/lightning/react/Action.java | 5 + .../browser/lightning/react/Observable.java | 130 +++++++++++++++++ .../browser/lightning/react/Schedulers.java | 19 +++ .../browser/lightning/react/Subscriber.java | 7 + .../browser/lightning/react/Subscription.java | 7 + .../lightning/react/ThreadExecutor.java | 21 +++ .../browser/lightning/utils/FileUtils.java | 38 ++--- .../browser/lightning/view/LightningView.java | 82 ++++++++++- 19 files changed, 472 insertions(+), 110 deletions(-) create mode 100644 app/src/main/java/acr/browser/lightning/react/Action.java create mode 100644 app/src/main/java/acr/browser/lightning/react/Observable.java create mode 100644 app/src/main/java/acr/browser/lightning/react/Schedulers.java create mode 100644 app/src/main/java/acr/browser/lightning/react/Subscriber.java create mode 100644 app/src/main/java/acr/browser/lightning/react/Subscription.java create mode 100644 app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index a9747ec..0670bc4 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -27,6 +27,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.StrictMode; import android.provider.MediaStore; import android.support.annotation.ColorInt; import android.support.annotation.IdRes; @@ -89,6 +90,7 @@ import java.io.IOException; import javax.inject.Inject; +import acr.browser.lightning.BuildConfig; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.browser.BrowserPresenter; @@ -108,10 +110,13 @@ import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.object.SearchAdapter; +import acr.browser.lightning.react.Schedulers; +import acr.browser.lightning.react.Subscription; import acr.browser.lightning.receiver.NetworkReceiver; import com.anthonycr.grant.PermissionsManager; +import acr.browser.lightning.react.Observable; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.UrlUtils; @@ -217,11 +222,26 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public abstract void updateHistory(@Nullable final String title, @NonNull final String url); - abstract void updateCookiePreference(); + abstract Observable updateCookiePreference(); @Override protected void onCreate(Bundle savedInstanceState) { + + if (BuildConfig.DEBUG) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectDiskReads() + .detectDiskWrites() + .detectNetwork() + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectLeakedClosableObjects() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .build()); + } + super.onCreate(savedInstanceState); BrowserApp.getAppComponent().inject(this); setContentView(R.layout.activity_main); @@ -339,11 +359,21 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); } - mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito()); - // At this point we always have at least a tab in the tab manager - showTab(0); + mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito()) + .subscribe(new Subscription() { + @Override + public void onComplete() { + // At this point we always have at least a tab in the tab manager + showTab(0); + + mProxyUtils.checkForProxy(BrowserActivity.this); + } + + @Override + public void onNext(Void item) { - mProxyUtils.checkForProxy(this); + } + }); } private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, OnFocusChangeListener, OnTouchListener { @@ -624,7 +654,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements break; } - updateCookiePreference(); + updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe(); mProxyUtils.updateProxySettings(this); } @@ -1080,7 +1110,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements currentTab.onPause(); } try { - unregisterReceiver(mNetworkReceiver); + BrowserApp.get(this).unregisterReceiver(mNetworkReceiver); } catch (IllegalArgumentException e) { e.printStackTrace(); } @@ -1142,7 +1172,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements IntentFilter filter = new IntentFilter(); filter.addAction(NETWORK_BROADCAST_ACTION); - registerReceiver(mNetworkReceiver, filter); + BrowserApp.get(this).registerReceiver(mNetworkReceiver, filter); mEventBus.register(mBusEventListener); } diff --git a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java index a2c100b..bd832f5 100644 --- a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java @@ -9,17 +9,26 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; +import acr.browser.lightning.react.Action; +import acr.browser.lightning.react.Observable; +import acr.browser.lightning.react.Subscriber; @SuppressWarnings("deprecation") public class IncognitoActivity extends BrowserActivity { @Override - public void updateCookiePreference() { - CookieManager cookieManager = CookieManager.getInstance(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieSyncManager.createInstance(this); - } - cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled()); + public Observable updateCookiePreference() { + return Observable.create(new Action() { + @Override + public void onSubscribe(Subscriber subscriber) { + CookieManager cookieManager = CookieManager.getInstance(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(IncognitoActivity.this); + } + cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled()); + subscriber.onComplete(); + } + }); } @Override diff --git a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java index f156311..bc5ddc7 100644 --- a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java @@ -9,17 +9,26 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; +import acr.browser.lightning.react.Action; +import acr.browser.lightning.react.Observable; +import acr.browser.lightning.react.Subscriber; @SuppressWarnings("deprecation") public class MainActivity extends BrowserActivity { @Override - public void updateCookiePreference() { - CookieManager cookieManager = CookieManager.getInstance(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - CookieSyncManager.createInstance(this); - } - cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled()); + public Observable updateCookiePreference() { + return Observable.create(new Action() { + @Override + public void onSubscribe(Subscriber subscriber) { + CookieManager cookieManager = CookieManager.getInstance(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(MainActivity.this); + } + cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled()); + subscriber.onComplete(); + } + }); } @Override diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java index df1c17f..3e62883 100644 --- a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -23,7 +23,12 @@ import javax.inject.Singleton; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.react.Action; +import acr.browser.lightning.react.Schedulers; +import acr.browser.lightning.react.Subscriber; +import acr.browser.lightning.react.Subscription; import acr.browser.lightning.utils.FileUtils; +import acr.browser.lightning.react.Observable; import acr.browser.lightning.view.LightningView; /** @@ -56,47 +61,72 @@ public class TabsManager { * @param intent the intent that started the browser activity. * @param incognito whether or not we are in incognito mode. */ - public synchronized void restoreTabsAndHandleIntent(@NonNull final Activity activity, - @Nullable final Intent intent, - final boolean incognito) { - // If incognito, only create one tab, do not handle intent - // in order to protect user privacy - if (incognito && mTabList.isEmpty()) { - newTab(activity, null, true); - return; - } + public synchronized Observable restoreTabsAndHandleIntent(@NonNull final Activity activity, + @Nullable final Intent intent, + final boolean incognito) { + return Observable.create(new Action() { + @Override + public void onSubscribe(final Subscriber subscriber) { + + + // If incognito, only create one tab, do not handle intent + // in order to protect user privacy + if (incognito && mTabList.isEmpty()) { + newTab(activity, null, true); + subscriber.onComplete(); + return; + } + + String dataString = null; + if (intent != null) { + dataString = intent.getDataString(); + } + final String url = dataString; + mTabList.clear(); + mCurrentTab = null; + if (mPreferenceManager.getRestoreLostTabsEnabled()) { + restoreState() + .subscribeOn(Schedulers.worker()) + .observeOn(Schedulers.main()) + .subscribe(new Subscription() { + @Override + public void onComplete() { + if (url != null) { + if (url.startsWith(Constants.FILE)) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setCancelable(true) + .setTitle(R.string.title_warning) + .setMessage(R.string.message_blocked_local) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.action_open, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + newTab(activity, url, false); + } + }).show(); + } else { + newTab(activity, url, false); + } + } + if (mTabList.size() == 0) { + newTab(activity, null, false); + } + subscriber.onComplete(); + } + + @Override + public void onNext(Bundle item) { + LightningView tab = newTab(activity, "", false); + if (tab.getWebView() != null) { + tab.getWebView().restoreState(item); + } + } + }); + } - String url = null; - if (intent != null) { - url = intent.getDataString(); - } - mTabList.clear(); - mCurrentTab = null; - if (mPreferenceManager.getRestoreLostTabsEnabled()) { - restoreState(activity); - } - if (url != null) { - if (url.startsWith(Constants.FILE)) { - final String urlToLoad = url; - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setCancelable(true) - .setTitle(R.string.title_warning) - .setMessage(R.string.message_blocked_local) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.action_open, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - newTab(activity, urlToLoad, false); - } - }) - .show(); - } else { - newTab(activity, url, false); } - } - if (mTabList.size() == 0) { - newTab(activity, null, false); - } + }); + } /** @@ -285,22 +315,23 @@ public class TabsManager { * It will create new tabs for each tab saved * and will delete the saved instance file when * restoration is complete. - * - * @param activity the Activity needed to create tabs. */ - private void restoreState(@NonNull Activity activity) { - Bundle savedState = FileUtils.readBundleFromStorage(mApp, BUNDLE_STORAGE); - if (savedState != null) { - Log.d(Constants.TAG, "Restoring previous WebView state now"); - for (String key : savedState.keySet()) { - if (key.startsWith(BUNDLE_KEY)) { - LightningView tab = newTab(activity, "", false); - if (tab.getWebView() != null) { - tab.getWebView().restoreState(savedState.getBundle(key)); + private Observable restoreState() { + return Observable.create(new Action() { + @Override + public void onSubscribe(Subscriber subscriber) { + Bundle savedState = FileUtils.readBundleFromStorage(mApp, BUNDLE_STORAGE); + if (savedState != null) { + Log.d(Constants.TAG, "Restoring previous WebView state now"); + for (String key : savedState.keySet()) { + if (key.startsWith(BUNDLE_KEY)) { + subscriber.onNext(savedState.getBundle(key)); + } } } + subscriber.onComplete(); } - } + }); } /** diff --git a/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java b/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java index 1a2fd6c..3132884 100644 --- a/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java +++ b/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java @@ -25,8 +25,8 @@ import acr.browser.lightning.utils.Utils; public class ImageDownloadTask extends AsyncTask { private static final String TAG = ImageDownloadTask.class.getSimpleName(); - private final File mCacheDir; @NonNull private final WeakReference mFaviconImage; + @NonNull private final WeakReference mContextReference; @NonNull private final HistoryItem mWeb; private final String mUrl; @NonNull private final Bitmap mDefaultBitmap; @@ -42,7 +42,7 @@ public class ImageDownloadTask extends AsyncTask { this.mWeb = web; this.mUrl = web.getUrl(); this.mDefaultBitmap = defaultBitmap; - this.mCacheDir = BrowserApp.get(context).getCacheDir(); + this.mContextReference = new WeakReference<>(context.getApplicationContext()); } @Nullable @@ -53,12 +53,17 @@ public class ImageDownloadTask extends AsyncTask { if (mUrl == null) { return mDefaultBitmap; } + Context context = mContextReference.get(); + if (context == null) { + return mDefaultBitmap; + } + File cache = context.getCacheDir(); final Uri uri = Uri.parse(mUrl); if (uri.getHost() == null || uri.getScheme() == null) { return mDefaultBitmap; } final String hash = String.valueOf(uri.getHost().hashCode()); - final File image = new File(mCacheDir, hash + ".png"); + final File image = new File(cache, hash + ".png"); final String urlDisplay = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico"; // checks to see if the image exists if (!image.exists()) { diff --git a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java index 3bdcec4..e853ee7 100644 --- a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java @@ -60,17 +60,17 @@ public final class BookmarkPage extends AsyncTask { private static final String END = ""; - private final File mFilesDir; - private final File mCacheDir; + private File mFilesDir; + private File mCacheDir; + private Application mApp; private final BookmarkManager mManager; @NonNull private final WeakReference mTabReference; private final Bitmap mFolderIcon; @NonNull private final String mTitle; public BookmarkPage(LightningView tab, @NonNull Application app, BookmarkManager manager, Bitmap folderIcon) { - mFilesDir = app.getFilesDir(); - mCacheDir = app.getCacheDir(); + mApp = app; mTitle = app.getString(R.string.action_bookmarks); mManager = manager; mTabReference = new WeakReference<>(tab); @@ -79,6 +79,8 @@ public final class BookmarkPage extends AsyncTask { @Override protected Void doInBackground(Void... params) { + mCacheDir = mApp.getCacheDir(); + mFilesDir = mApp.getFilesDir(); cacheDefaultFolderIcon(); buildBookmarkPage(null, mManager); return null; diff --git a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java index fc4b75e..ef2c58e 100644 --- a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java @@ -41,7 +41,7 @@ public class HistoryPage extends AsyncTask { private static final String END = ""; @NonNull private final WeakReference mTabReference; - private final File mFilesDir; + @NonNull private final Application mApp; @NonNull private final String mTitle; private final HistoryDatabase mHistoryDatabase; @@ -49,7 +49,7 @@ public class HistoryPage extends AsyncTask { public HistoryPage(LightningView tab, @NonNull Application app, HistoryDatabase database) { mTabReference = new WeakReference<>(tab); - mFilesDir = app.getFilesDir(); + mApp = app; mTitle = app.getString(R.string.action_history); mHistoryDatabase = database; } @@ -88,7 +88,7 @@ public class HistoryPage extends AsyncTask { } historyBuilder.append(END); - File historyWebPage = new File(mFilesDir, FILENAME); + File historyWebPage = new File(mApp.getFilesDir(), FILENAME); FileWriter historyWriter = null; try { //noinspection IOResourceOpenedButNotSafelyClosed diff --git a/app/src/main/java/acr/browser/lightning/constant/StartPage.java b/app/src/main/java/acr/browser/lightning/constant/StartPage.java index 423fc67..15c62f9 100644 --- a/app/src/main/java/acr/browser/lightning/constant/StartPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/StartPage.java @@ -55,7 +55,7 @@ public class StartPage extends AsyncTask { private static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}"; @NonNull private final String mTitle; - private final File mFilesDir; + @NonNull private final Application mApp; @NonNull private final WeakReference mTabReference; @Inject PreferenceManager mPreferenceManager; @@ -65,7 +65,7 @@ public class StartPage extends AsyncTask { public StartPage(LightningView tab, @NonNull Application app) { BrowserApp.getAppComponent().inject(this); mTitle = app.getString(R.string.home); - mFilesDir = app.getFilesDir(); + mApp = app; mTabReference = new WeakReference<>(tab); } @@ -174,7 +174,7 @@ public class StartPage extends AsyncTask { homepageBuilder.append(searchUrl); homepageBuilder.append(END); - File homepage = new File(mFilesDir, FILENAME); + File homepage = new File(mApp.getFilesDir(), FILENAME); FileWriter hWriter = null; try { //noinspection IOResourceOpenedButNotSafelyClosed diff --git a/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java b/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java index b779342..fddf2bb 100644 --- a/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java +++ b/app/src/main/java/acr/browser/lightning/database/BookmarkManager.java @@ -55,12 +55,11 @@ public class BookmarkManager { private Map mBookmarksMap; @NonNull private String mCurrentFolder = ""; @NonNull private final ExecutorService mExecutor; - private final File mFilesDir; + private File mFilesDir; @Inject public BookmarkManager(@NonNull Context context) { mExecutor = Executors.newSingleThreadExecutor(); - mFilesDir = context.getFilesDir(); DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled); mExecutor.execute(new BookmarkInitializer(context)); } @@ -90,6 +89,7 @@ public class BookmarkManager { @Override public void run() { synchronized (BookmarkManager.this) { + mFilesDir = mContext.getFilesDir(); final Map bookmarks = new HashMap<>(); final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS); diff --git a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java b/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java index 02581ee..ecbcfd2 100644 --- a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java +++ b/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java @@ -18,6 +18,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; @Singleton public class HistoryDatabase extends SQLiteOpenHelper { @@ -43,7 +44,18 @@ public class HistoryDatabase extends SQLiteOpenHelper { @Inject public HistoryDatabase(@NonNull Context context) { super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); - mDatabase = this.getWritableDatabase(); + initialize(); + } + + private void initialize() { + BrowserApp.getTaskThread().execute(new Runnable() { + @Override + public void run() { + synchronized (HistoryDatabase.this) { + mDatabase = HistoryDatabase.this.getWritableDatabase(); + } + } + }); } // Creating Tables diff --git a/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java b/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java index 88acdbe..e34e8af 100644 --- a/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java +++ b/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java @@ -430,6 +430,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable { private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) { ConnectivityManager connectivity = (ConnectivityManager) context + .getApplicationContext() .getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity == null) { return null; diff --git a/app/src/main/java/acr/browser/lightning/react/Action.java b/app/src/main/java/acr/browser/lightning/react/Action.java new file mode 100644 index 0000000..1ee22a6 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/Action.java @@ -0,0 +1,5 @@ +package acr.browser.lightning.react; + +public interface Action { + void onSubscribe(Subscriber subscriber); +} diff --git a/app/src/main/java/acr/browser/lightning/react/Observable.java b/app/src/main/java/acr/browser/lightning/react/Observable.java new file mode 100644 index 0000000..c66f6d5 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/Observable.java @@ -0,0 +1,130 @@ +package acr.browser.lightning.react; + +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.Executor; + +import acr.browser.lightning.utils.Preconditions; + +/** + * An RxJava implementation. This class allows work + * to be done on a certain thread and then allows + * items to be emitted on a different thread. It is + * a replacement for {@link android.os.AsyncTask}. + * + * @param the type that the Observable will emit. + */ +public class Observable { + + private Action mAction; + @Nullable private Executor mSubscriber; + @Nullable private Executor mObserver; + private final Executor mDefault; + + public Observable(Action action) { + mAction = action; + Looper looper = Looper.myLooper(); + Preconditions.checkNonNull(looper); + mDefault = new ThreadExecutor(looper); + } + + public static Observable create(@NonNull Action action) { + Preconditions.checkNonNull(action); + return new Observable<>(action); + } + + public Observable subscribeOn(@NonNull Executor subscribeExecutor) { + mSubscriber = subscribeExecutor; + return this; + } + + public Observable observeOn(@NonNull Executor observerExecutor) { + mObserver = observerExecutor; + return this; + } + + public void subscribe() { + executeOnSubscriberThread(new Runnable() { + @Override + public void run() { + mAction.onSubscribe(new Subscriber() { + @Override + public void onComplete() { + + } + + @Override + public void onNext(T item) { + + } + }); + } + }); + } + + public void subscribe(@NonNull final Subscription subscription) { + Preconditions.checkNonNull(subscription); + executeOnSubscriberThread(new Runnable() { + @Override + public void run() { + mAction.onSubscribe(new Subscriber() { + @Override + public void onComplete() { + executeOnObserverThread(new OnCompleteRunnable(subscription)); + } + + @Override + public void onNext(final T item) { + executeOnObserverThread(new OnNextRunnable(subscription, item)); + } + }); + + } + }); + } + + private void executeOnObserverThread(@NonNull Runnable runnable) { + if (mObserver != null) { + mObserver.execute(runnable); + } else { + mDefault.execute(runnable); + } + } + + private void executeOnSubscriberThread(@NonNull Runnable runnable) { + if (mSubscriber != null) { + mSubscriber.execute(runnable); + } else { + mDefault.execute(runnable); + } + } + + private static class OnCompleteRunnable implements Runnable { + private final Subscription subscription; + + public OnCompleteRunnable(Subscription subscription) {this.subscription = subscription;} + + @Override + public void run() { + subscription.onComplete(); + } + } + + private class OnNextRunnable implements Runnable { + private final Subscription subscription; + private final T item; + + public OnNextRunnable(Subscription subscription, T item) { + this.subscription = subscription; + this.item = item; + } + + @Override + public void run() { + subscription.onNext(item); + } + } +} + diff --git a/app/src/main/java/acr/browser/lightning/react/Schedulers.java b/app/src/main/java/acr/browser/lightning/react/Schedulers.java new file mode 100644 index 0000000..f6e6d24 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/Schedulers.java @@ -0,0 +1,19 @@ +package acr.browser.lightning.react; + +import android.os.Looper; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class Schedulers { + private static final Executor sWorker = Executors.newCachedThreadPool(); + private static final Executor sMain = new ThreadExecutor(Looper.getMainLooper()); + + public static Executor worker() { + return sWorker; + } + + public static Executor main() { + return sMain; + } +} diff --git a/app/src/main/java/acr/browser/lightning/react/Subscriber.java b/app/src/main/java/acr/browser/lightning/react/Subscriber.java new file mode 100644 index 0000000..e2a8dcf --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/Subscriber.java @@ -0,0 +1,7 @@ +package acr.browser.lightning.react; + +public interface Subscriber { + void onComplete(); + + void onNext(T item); +} diff --git a/app/src/main/java/acr/browser/lightning/react/Subscription.java b/app/src/main/java/acr/browser/lightning/react/Subscription.java new file mode 100644 index 0000000..13752b7 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/Subscription.java @@ -0,0 +1,7 @@ +package acr.browser.lightning.react; + +public interface Subscription { + void onComplete(); + + void onNext(T item); +} diff --git a/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java b/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java new file mode 100644 index 0000000..e352726 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java @@ -0,0 +1,21 @@ +package acr.browser.lightning.react; + +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; + +import java.util.concurrent.Executor; + +class ThreadExecutor implements Executor { + + private final Handler mHandler; + + public ThreadExecutor(@NonNull Looper looper) { + mHandler = new Handler(looper); + } + + @Override + public void execute(@NonNull Runnable command) { + mHandler.post(command); + } +} diff --git a/app/src/main/java/acr/browser/lightning/utils/FileUtils.java b/app/src/main/java/acr/browser/lightning/utils/FileUtils.java index d1287ec..7328f48 100644 --- a/app/src/main/java/acr/browser/lightning/utils/FileUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/FileUtils.java @@ -13,6 +13,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; /** @@ -30,22 +31,27 @@ public class FileUtils { * @param bundle the bundle to store in persistent storage. * @param name the name of the file to store the bundle in. */ - public static void writeBundleToStorage(@NonNull Application app, Bundle bundle, @NonNull String name) { - File outputFile = new File(app.getFilesDir(), name); - FileOutputStream outputStream = null; - try { - //noinspection IOResourceOpenedButNotSafelyClosed - outputStream = new FileOutputStream(outputFile); - Parcel parcel = Parcel.obtain(); - parcel.writeBundle(bundle); - outputStream.write(parcel.marshall()); - outputStream.flush(); - parcel.recycle(); - } catch (IOException e) { - Log.e(Constants.TAG, "Unable to write bundle to storage"); - } finally { - Utils.close(outputStream); - } + public static void writeBundleToStorage(final @NonNull Application app, final Bundle bundle, final @NonNull String name) { + BrowserApp.getIOThread().execute(new Runnable() { + @Override + public void run() { + File outputFile = new File(app.getFilesDir(), name); + FileOutputStream outputStream = null; + try { + //noinspection IOResourceOpenedButNotSafelyClosed + outputStream = new FileOutputStream(outputFile); + Parcel parcel = Parcel.obtain(); + parcel.writeBundle(bundle); + outputStream.write(parcel.marshall()); + outputStream.flush(); + parcel.recycle(); + } catch (IOException e) { + Log.e(Constants.TAG, "Unable to write bundle to storage"); + } finally { + Utils.close(outputStream); + } + } + }); } /** diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.java b/app/src/main/java/acr/browser/lightning/view/LightningView.java index a2ccaa9..46ee423 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.java +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.java @@ -34,6 +34,7 @@ import android.webkit.WebView; import com.squareup.otto.Bus; +import java.io.File; import java.lang.ref.WeakReference; import java.util.Map; @@ -51,6 +52,11 @@ import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.react.Action; +import acr.browser.lightning.react.Observable; +import acr.browser.lightning.react.Schedulers; +import acr.browser.lightning.react.Subscriber; +import acr.browser.lightning.react.Subscription; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.UrlUtils; @@ -325,7 +331,10 @@ public class LightningView { */ @SuppressLint("NewApi") private void initializeSettings() { - WebSettings settings = mWebView.getSettings(); + if (mWebView == null) { + return; + } + final WebSettings settings = mWebView.getSettings(); if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) { //noinspection deprecation settings.setAppCacheMaxSize(Long.MAX_VALUE); @@ -364,12 +373,56 @@ public class LightningView { settings.setAllowUniversalAccessFromFileURLs(false); } - settings.setAppCachePath(BrowserApp.get(mActivity).getDir("appcache", 0).getPath()); - settings.setGeolocationDatabasePath(BrowserApp.get(mActivity).getDir("geolocation", 0).getPath()); - if (API < Build.VERSION_CODES.KITKAT) { - //noinspection deprecation - settings.setDatabasePath(BrowserApp.get(mActivity).getDir("databases", 0).getPath()); - } + getPathObservable("appcache") + .subscribeOn(Schedulers.worker()) + .subscribe(new Subscription() { + @Override + public void onComplete() {} + + @Override + public void onNext(File item) { + settings.setAppCachePath(item.getPath()); + } + }); + + getPathObservable("geolocation") + .subscribeOn(Schedulers.worker()) + .subscribe(new Subscription() { + @Override + public void onComplete() {} + + @Override + public void onNext(File item) { + settings.setGeolocationDatabasePath(item.getPath()); + } + }); + + getPathObservable("databases") + .subscribeOn(Schedulers.worker()) + .subscribe(new Subscription() { + @Override + public void onComplete() {} + + @Override + public void onNext(File item) { + if (API < Build.VERSION_CODES.KITKAT) { + //noinspection deprecation + settings.setDatabasePath(item.getPath()); + } + } + }); + + } + + private Observable getPathObservable(final String subFolder) { + return Observable.create(new Action() { + @Override + public void onSubscribe(Subscriber subscriber) { + File file = BrowserApp.get(mActivity).getDir(subFolder, 0); + subscriber.onNext(file); + subscriber.onComplete(); + } + }); } /** @@ -554,6 +607,9 @@ public class LightningView { * of the current LightningView. */ private void setHardwareRendering() { + if (mWebView == null) { + return; + } mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint); } @@ -563,6 +619,9 @@ public class LightningView { * the layers when necessary. */ private void setNormalRendering() { + if (mWebView == null) { + return; + } mWebView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -573,6 +632,9 @@ public class LightningView { * the view. */ public void setSoftwareRendering() { + if (mWebView == null) { + return; + } mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } @@ -839,6 +901,9 @@ public class LightningView { * a workaround. */ private void longClickPage(@Nullable final String url) { + if (mWebView == null) { + return; + } final WebView.HitTestResult result = mWebView.getHitTestResult(); String currentUrl = mWebView.getUrl(); if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) { @@ -1043,6 +1108,9 @@ public class LightningView { Message msg = mWebViewHandler.obtainMessage(); if (msg != null) { msg.setTarget(mWebViewHandler); + if (mWebView == null) { + return; + } mWebView.requestFocusNodeHref(msg); } }