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
This commit is contained in:
Anthony Restaino 2016-02-01 22:17:44 -05:00
parent ba3edc00e8
commit e2d46bdae2
19 changed files with 472 additions and 110 deletions

View File

@ -27,6 +27,7 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.os.StrictMode;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.IdRes; import android.support.annotation.IdRes;
@ -89,6 +90,7 @@ import java.io.IOException;
import javax.inject.Inject; import javax.inject.Inject;
import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.browser.BrowserPresenter; 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.BookmarksFragment;
import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.fragment.TabsFragment;
import acr.browser.lightning.object.SearchAdapter; 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 acr.browser.lightning.receiver.NetworkReceiver;
import com.anthonycr.grant.PermissionsManager; import com.anthonycr.grant.PermissionsManager;
import acr.browser.lightning.react.Observable;
import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ProxyUtils;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.UrlUtils; 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); public abstract void updateHistory(@Nullable final String title, @NonNull final String url);
abstract void updateCookiePreference(); abstract Observable<Void> updateCookiePreference();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { 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); super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this); BrowserApp.getAppComponent().inject(this);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
@ -339,11 +359,21 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath());
} }
mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito()); mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito())
// At this point we always have at least a tab in the tab manager .subscribe(new Subscription<Void>() {
showTab(0); @Override
public void onComplete() {
// At this point we always have at least a tab in the tab manager
showTab(0);
mProxyUtils.checkForProxy(this); mProxyUtils.checkForProxy(BrowserActivity.this);
}
@Override
public void onNext(Void item) {
}
});
} }
private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, OnFocusChangeListener, OnTouchListener { private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, OnFocusChangeListener, OnTouchListener {
@ -624,7 +654,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
break; break;
} }
updateCookiePreference(); updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe();
mProxyUtils.updateProxySettings(this); mProxyUtils.updateProxySettings(this);
} }
@ -1080,7 +1110,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
currentTab.onPause(); currentTab.onPause();
} }
try { try {
unregisterReceiver(mNetworkReceiver); BrowserApp.get(this).unregisterReceiver(mNetworkReceiver);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -1142,7 +1172,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(NETWORK_BROADCAST_ACTION); filter.addAction(NETWORK_BROADCAST_ACTION);
registerReceiver(mNetworkReceiver, filter); BrowserApp.get(this).registerReceiver(mNetworkReceiver, filter);
mEventBus.register(mBusEventListener); mEventBus.register(mBusEventListener);
} }

View File

@ -9,17 +9,26 @@ import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
import acr.browser.lightning.R; 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") @SuppressWarnings("deprecation")
public class IncognitoActivity extends BrowserActivity { public class IncognitoActivity extends BrowserActivity {
@Override @Override
public void updateCookiePreference() { public Observable<Void> updateCookiePreference() {
CookieManager cookieManager = CookieManager.getInstance(); return Observable.create(new Action<Void>() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @Override
CookieSyncManager.createInstance(this); public void onSubscribe(Subscriber<Void> subscriber) {
} CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(IncognitoActivity.this);
}
cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled());
subscriber.onComplete();
}
});
} }
@Override @Override

View File

@ -9,17 +9,26 @@ import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
import acr.browser.lightning.R; 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") @SuppressWarnings("deprecation")
public class MainActivity extends BrowserActivity { public class MainActivity extends BrowserActivity {
@Override @Override
public void updateCookiePreference() { public Observable<Void> updateCookiePreference() {
CookieManager cookieManager = CookieManager.getInstance(); return Observable.create(new Action<Void>() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @Override
CookieSyncManager.createInstance(this); public void onSubscribe(Subscriber<Void> subscriber) {
} CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(MainActivity.this);
}
cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled());
subscriber.onComplete();
}
});
} }
@Override @Override

View File

@ -23,7 +23,12 @@ import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.preference.PreferenceManager; 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.utils.FileUtils;
import acr.browser.lightning.react.Observable;
import acr.browser.lightning.view.LightningView; import acr.browser.lightning.view.LightningView;
/** /**
@ -56,47 +61,72 @@ public class TabsManager {
* @param intent the intent that started the browser activity. * @param intent the intent that started the browser activity.
* @param incognito whether or not we are in incognito mode. * @param incognito whether or not we are in incognito mode.
*/ */
public synchronized void restoreTabsAndHandleIntent(@NonNull final Activity activity, public synchronized Observable<Void> restoreTabsAndHandleIntent(@NonNull final Activity activity,
@Nullable final Intent intent, @Nullable final Intent intent,
final boolean incognito) { final boolean incognito) {
// If incognito, only create one tab, do not handle intent return Observable.create(new Action<Void>() {
// in order to protect user privacy @Override
if (incognito && mTabList.isEmpty()) { public void onSubscribe(final Subscriber<Void> subscriber) {
newTab(activity, null, true);
return;
} // 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<Bundle>() {
@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 * It will create new tabs for each tab saved
* and will delete the saved instance file when * and will delete the saved instance file when
* restoration is complete. * restoration is complete.
*
* @param activity the Activity needed to create tabs.
*/ */
private void restoreState(@NonNull Activity activity) { private Observable<Bundle> restoreState() {
Bundle savedState = FileUtils.readBundleFromStorage(mApp, BUNDLE_STORAGE); return Observable.create(new Action<Bundle>() {
if (savedState != null) { @Override
Log.d(Constants.TAG, "Restoring previous WebView state now"); public void onSubscribe(Subscriber<Bundle> subscriber) {
for (String key : savedState.keySet()) { Bundle savedState = FileUtils.readBundleFromStorage(mApp, BUNDLE_STORAGE);
if (key.startsWith(BUNDLE_KEY)) { if (savedState != null) {
LightningView tab = newTab(activity, "", false); Log.d(Constants.TAG, "Restoring previous WebView state now");
if (tab.getWebView() != null) { for (String key : savedState.keySet()) {
tab.getWebView().restoreState(savedState.getBundle(key)); if (key.startsWith(BUNDLE_KEY)) {
subscriber.onNext(savedState.getBundle(key));
}
} }
} }
subscriber.onComplete();
} }
} });
} }
/** /**

View File

@ -25,8 +25,8 @@ import acr.browser.lightning.utils.Utils;
public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> { public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
private static final String TAG = ImageDownloadTask.class.getSimpleName(); private static final String TAG = ImageDownloadTask.class.getSimpleName();
private final File mCacheDir;
@NonNull private final WeakReference<ImageView> mFaviconImage; @NonNull private final WeakReference<ImageView> mFaviconImage;
@NonNull private final WeakReference<Context> mContextReference;
@NonNull private final HistoryItem mWeb; @NonNull private final HistoryItem mWeb;
private final String mUrl; private final String mUrl;
@NonNull private final Bitmap mDefaultBitmap; @NonNull private final Bitmap mDefaultBitmap;
@ -42,7 +42,7 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
this.mWeb = web; this.mWeb = web;
this.mUrl = web.getUrl(); this.mUrl = web.getUrl();
this.mDefaultBitmap = defaultBitmap; this.mDefaultBitmap = defaultBitmap;
this.mCacheDir = BrowserApp.get(context).getCacheDir(); this.mContextReference = new WeakReference<>(context.getApplicationContext());
} }
@Nullable @Nullable
@ -53,12 +53,17 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
if (mUrl == null) { if (mUrl == null) {
return mDefaultBitmap; return mDefaultBitmap;
} }
Context context = mContextReference.get();
if (context == null) {
return mDefaultBitmap;
}
File cache = context.getCacheDir();
final Uri uri = Uri.parse(mUrl); final Uri uri = Uri.parse(mUrl);
if (uri.getHost() == null || uri.getScheme() == null) { if (uri.getHost() == null || uri.getScheme() == null) {
return mDefaultBitmap; return mDefaultBitmap;
} }
final String hash = String.valueOf(uri.getHost().hashCode()); 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"; final String urlDisplay = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico";
// checks to see if the image exists // checks to see if the image exists
if (!image.exists()) { if (!image.exists()) {

View File

@ -60,17 +60,17 @@ public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
private final File mFilesDir; private File mFilesDir;
private final File mCacheDir; private File mCacheDir;
private Application mApp;
private final BookmarkManager mManager; private final BookmarkManager mManager;
@NonNull private final WeakReference<LightningView> mTabReference; @NonNull private final WeakReference<LightningView> mTabReference;
private final Bitmap mFolderIcon; private final Bitmap mFolderIcon;
@NonNull private final String mTitle; @NonNull private final String mTitle;
public BookmarkPage(LightningView tab, @NonNull Application app, BookmarkManager manager, Bitmap folderIcon) { public BookmarkPage(LightningView tab, @NonNull Application app, BookmarkManager manager, Bitmap folderIcon) {
mFilesDir = app.getFilesDir(); mApp = app;
mCacheDir = app.getCacheDir();
mTitle = app.getString(R.string.action_bookmarks); mTitle = app.getString(R.string.action_bookmarks);
mManager = manager; mManager = manager;
mTabReference = new WeakReference<>(tab); mTabReference = new WeakReference<>(tab);
@ -79,6 +79,8 @@ public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
mCacheDir = mApp.getCacheDir();
mFilesDir = mApp.getFilesDir();
cacheDefaultFolderIcon(); cacheDefaultFolderIcon();
buildBookmarkPage(null, mManager); buildBookmarkPage(null, mManager);
return null; return null;

View File

@ -41,7 +41,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
@NonNull private final WeakReference<LightningView> mTabReference; @NonNull private final WeakReference<LightningView> mTabReference;
private final File mFilesDir; @NonNull private final Application mApp;
@NonNull private final String mTitle; @NonNull private final String mTitle;
private final HistoryDatabase mHistoryDatabase; private final HistoryDatabase mHistoryDatabase;
@ -49,7 +49,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
public HistoryPage(LightningView tab, @NonNull Application app, HistoryDatabase database) { public HistoryPage(LightningView tab, @NonNull Application app, HistoryDatabase database) {
mTabReference = new WeakReference<>(tab); mTabReference = new WeakReference<>(tab);
mFilesDir = app.getFilesDir(); mApp = app;
mTitle = app.getString(R.string.action_history); mTitle = app.getString(R.string.action_history);
mHistoryDatabase = database; mHistoryDatabase = database;
} }
@ -88,7 +88,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
} }
historyBuilder.append(END); historyBuilder.append(END);
File historyWebPage = new File(mFilesDir, FILENAME); File historyWebPage = new File(mApp.getFilesDir(), FILENAME);
FileWriter historyWriter = null; FileWriter historyWriter = null;
try { try {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed

View File

@ -55,7 +55,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> {
private static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}</script></body></html>"; private static final String END = "\" + document.getElementById(\"search_input\").value;document.getElementById(\"search_input\").value = \"\";}return false;}</script></body></html>";
@NonNull private final String mTitle; @NonNull private final String mTitle;
private final File mFilesDir; @NonNull private final Application mApp;
@NonNull private final WeakReference<LightningView> mTabReference; @NonNull private final WeakReference<LightningView> mTabReference;
@Inject PreferenceManager mPreferenceManager; @Inject PreferenceManager mPreferenceManager;
@ -65,7 +65,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> {
public StartPage(LightningView tab, @NonNull Application app) { public StartPage(LightningView tab, @NonNull Application app) {
BrowserApp.getAppComponent().inject(this); BrowserApp.getAppComponent().inject(this);
mTitle = app.getString(R.string.home); mTitle = app.getString(R.string.home);
mFilesDir = app.getFilesDir(); mApp = app;
mTabReference = new WeakReference<>(tab); mTabReference = new WeakReference<>(tab);
} }
@ -174,7 +174,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> {
homepageBuilder.append(searchUrl); homepageBuilder.append(searchUrl);
homepageBuilder.append(END); homepageBuilder.append(END);
File homepage = new File(mFilesDir, FILENAME); File homepage = new File(mApp.getFilesDir(), FILENAME);
FileWriter hWriter = null; FileWriter hWriter = null;
try { try {
//noinspection IOResourceOpenedButNotSafelyClosed //noinspection IOResourceOpenedButNotSafelyClosed

View File

@ -55,12 +55,11 @@ public class BookmarkManager {
private Map<String, HistoryItem> mBookmarksMap; private Map<String, HistoryItem> mBookmarksMap;
@NonNull private String mCurrentFolder = ""; @NonNull private String mCurrentFolder = "";
@NonNull private final ExecutorService mExecutor; @NonNull private final ExecutorService mExecutor;
private final File mFilesDir; private File mFilesDir;
@Inject @Inject
public BookmarkManager(@NonNull Context context) { public BookmarkManager(@NonNull Context context) {
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mFilesDir = context.getFilesDir();
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled); DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled);
mExecutor.execute(new BookmarkInitializer(context)); mExecutor.execute(new BookmarkInitializer(context));
} }
@ -90,6 +89,7 @@ public class BookmarkManager {
@Override @Override
public void run() { public void run() {
synchronized (BookmarkManager.this) { synchronized (BookmarkManager.this) {
mFilesDir = mContext.getFilesDir();
final Map<String, HistoryItem> bookmarks = new HashMap<>(); final Map<String, HistoryItem> bookmarks = new HashMap<>();
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS); final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);

View File

@ -18,6 +18,7 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import acr.browser.lightning.R; import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
@Singleton @Singleton
public class HistoryDatabase extends SQLiteOpenHelper { public class HistoryDatabase extends SQLiteOpenHelper {
@ -43,7 +44,18 @@ public class HistoryDatabase extends SQLiteOpenHelper {
@Inject @Inject
public HistoryDatabase(@NonNull Context context) { public HistoryDatabase(@NonNull Context context) {
super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); 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 // Creating Tables

View File

@ -430,6 +430,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable {
private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) { private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context ConnectivityManager connectivity = (ConnectivityManager) context
.getApplicationContext()
.getSystemService(Context.CONNECTIVITY_SERVICE); .getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) { if (connectivity == null) {
return null; return null;

View File

@ -0,0 +1,5 @@
package acr.browser.lightning.react;
public interface Action<T> {
void onSubscribe(Subscriber<T> subscriber);
}

View File

@ -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 <T> the type that the Observable will emit.
*/
public class Observable<T> {
private Action<T> mAction;
@Nullable private Executor mSubscriber;
@Nullable private Executor mObserver;
private final Executor mDefault;
public Observable(Action<T> action) {
mAction = action;
Looper looper = Looper.myLooper();
Preconditions.checkNonNull(looper);
mDefault = new ThreadExecutor(looper);
}
public static <T> Observable<T> create(@NonNull Action<T> action) {
Preconditions.checkNonNull(action);
return new Observable<>(action);
}
public Observable<T> subscribeOn(@NonNull Executor subscribeExecutor) {
mSubscriber = subscribeExecutor;
return this;
}
public Observable<T> observeOn(@NonNull Executor observerExecutor) {
mObserver = observerExecutor;
return this;
}
public void subscribe() {
executeOnSubscriberThread(new Runnable() {
@Override
public void run() {
mAction.onSubscribe(new Subscriber<T>() {
@Override
public void onComplete() {
}
@Override
public void onNext(T item) {
}
});
}
});
}
public void subscribe(@NonNull final Subscription<T> subscription) {
Preconditions.checkNonNull(subscription);
executeOnSubscriberThread(new Runnable() {
@Override
public void run() {
mAction.onSubscribe(new Subscriber<T>() {
@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<T> subscription;
private final T item;
public OnNextRunnable(Subscription<T> subscription, T item) {
this.subscription = subscription;
this.item = item;
}
@Override
public void run() {
subscription.onNext(item);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,7 @@
package acr.browser.lightning.react;
public interface Subscriber<T> {
void onComplete();
void onNext(T item);
}

View File

@ -0,0 +1,7 @@
package acr.browser.lightning.react;
public interface Subscription<T> {
void onComplete();
void onNext(T item);
}

View File

@ -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);
}
}

View File

@ -13,6 +13,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.Constants;
/** /**
@ -30,22 +31,27 @@ public class FileUtils {
* @param bundle the bundle to store in persistent storage. * @param bundle the bundle to store in persistent storage.
* @param name the name of the file to store the bundle in. * @param name the name of the file to store the bundle in.
*/ */
public static void writeBundleToStorage(@NonNull Application app, Bundle bundle, @NonNull String name) { public static void writeBundleToStorage(final @NonNull Application app, final Bundle bundle, final @NonNull String name) {
File outputFile = new File(app.getFilesDir(), name); BrowserApp.getIOThread().execute(new Runnable() {
FileOutputStream outputStream = null; @Override
try { public void run() {
//noinspection IOResourceOpenedButNotSafelyClosed File outputFile = new File(app.getFilesDir(), name);
outputStream = new FileOutputStream(outputFile); FileOutputStream outputStream = null;
Parcel parcel = Parcel.obtain(); try {
parcel.writeBundle(bundle); //noinspection IOResourceOpenedButNotSafelyClosed
outputStream.write(parcel.marshall()); outputStream = new FileOutputStream(outputFile);
outputStream.flush(); Parcel parcel = Parcel.obtain();
parcel.recycle(); parcel.writeBundle(bundle);
} catch (IOException e) { outputStream.write(parcel.marshall());
Log.e(Constants.TAG, "Unable to write bundle to storage"); outputStream.flush();
} finally { parcel.recycle();
Utils.close(outputStream); } catch (IOException e) {
} Log.e(Constants.TAG, "Unable to write bundle to storage");
} finally {
Utils.close(outputStream);
}
}
});
} }
/** /**

View File

@ -34,6 +34,7 @@ import android.webkit.WebView;
import com.squareup.otto.Bus; import com.squareup.otto.Bus;
import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Map; import java.util.Map;
@ -51,6 +52,11 @@ import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.download.LightningDownloadListener;
import acr.browser.lightning.preference.PreferenceManager; 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.ProxyUtils;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.utils.UrlUtils;
@ -325,7 +331,10 @@ public class LightningView {
*/ */
@SuppressLint("NewApi") @SuppressLint("NewApi")
private void initializeSettings() { private void initializeSettings() {
WebSettings settings = mWebView.getSettings(); if (mWebView == null) {
return;
}
final WebSettings settings = mWebView.getSettings();
if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) { if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//noinspection deprecation //noinspection deprecation
settings.setAppCacheMaxSize(Long.MAX_VALUE); settings.setAppCacheMaxSize(Long.MAX_VALUE);
@ -364,12 +373,56 @@ public class LightningView {
settings.setAllowUniversalAccessFromFileURLs(false); settings.setAllowUniversalAccessFromFileURLs(false);
} }
settings.setAppCachePath(BrowserApp.get(mActivity).getDir("appcache", 0).getPath()); getPathObservable("appcache")
settings.setGeolocationDatabasePath(BrowserApp.get(mActivity).getDir("geolocation", 0).getPath()); .subscribeOn(Schedulers.worker())
if (API < Build.VERSION_CODES.KITKAT) { .subscribe(new Subscription<File>() {
//noinspection deprecation @Override
settings.setDatabasePath(BrowserApp.get(mActivity).getDir("databases", 0).getPath()); public void onComplete() {}
}
@Override
public void onNext(File item) {
settings.setAppCachePath(item.getPath());
}
});
getPathObservable("geolocation")
.subscribeOn(Schedulers.worker())
.subscribe(new Subscription<File>() {
@Override
public void onComplete() {}
@Override
public void onNext(File item) {
settings.setGeolocationDatabasePath(item.getPath());
}
});
getPathObservable("databases")
.subscribeOn(Schedulers.worker())
.subscribe(new Subscription<File>() {
@Override
public void onComplete() {}
@Override
public void onNext(File item) {
if (API < Build.VERSION_CODES.KITKAT) {
//noinspection deprecation
settings.setDatabasePath(item.getPath());
}
}
});
}
private Observable<File> getPathObservable(final String subFolder) {
return Observable.create(new Action<File>() {
@Override
public void onSubscribe(Subscriber<File> 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. * of the current LightningView.
*/ */
private void setHardwareRendering() { private void setHardwareRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint); mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
} }
@ -563,6 +619,9 @@ public class LightningView {
* the layers when necessary. * the layers when necessary.
*/ */
private void setNormalRendering() { private void setNormalRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_NONE, null); mWebView.setLayerType(View.LAYER_TYPE_NONE, null);
} }
@ -573,6 +632,9 @@ public class LightningView {
* the view. * the view.
*/ */
public void setSoftwareRendering() { public void setSoftwareRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
} }
@ -839,6 +901,9 @@ public class LightningView {
* a workaround. * a workaround.
*/ */
private void longClickPage(@Nullable final String url) { private void longClickPage(@Nullable final String url) {
if (mWebView == null) {
return;
}
final WebView.HitTestResult result = mWebView.getHitTestResult(); final WebView.HitTestResult result = mWebView.getHitTestResult();
String currentUrl = mWebView.getUrl(); String currentUrl = mWebView.getUrl();
if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) { if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) {
@ -1043,6 +1108,9 @@ public class LightningView {
Message msg = mWebViewHandler.obtainMessage(); Message msg = mWebViewHandler.obtainMessage();
if (msg != null) { if (msg != null) {
msg.setTarget(mWebViewHandler); msg.setTarget(mWebViewHandler);
if (mWebView == null) {
return;
}
mWebView.requestFocusNodeHref(msg); mWebView.requestFocusNodeHref(msg);
} }
} }