Browse Source

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
master
Anthony Restaino 8 years ago
parent
commit
e2d46bdae2
  1. 46
      app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java
  2. 21
      app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java
  3. 21
      app/src/main/java/acr/browser/lightning/activity/MainActivity.java
  4. 133
      app/src/main/java/acr/browser/lightning/activity/TabsManager.java
  5. 11
      app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java
  6. 10
      app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java
  7. 6
      app/src/main/java/acr/browser/lightning/constant/HistoryPage.java
  8. 6
      app/src/main/java/acr/browser/lightning/constant/StartPage.java
  9. 4
      app/src/main/java/acr/browser/lightning/database/BookmarkManager.java
  10. 14
      app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java
  11. 1
      app/src/main/java/acr/browser/lightning/object/SearchAdapter.java
  12. 5
      app/src/main/java/acr/browser/lightning/react/Action.java
  13. 130
      app/src/main/java/acr/browser/lightning/react/Observable.java
  14. 19
      app/src/main/java/acr/browser/lightning/react/Schedulers.java
  15. 7
      app/src/main/java/acr/browser/lightning/react/Subscriber.java
  16. 7
      app/src/main/java/acr/browser/lightning/react/Subscription.java
  17. 21
      app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java
  18. 38
      app/src/main/java/acr/browser/lightning/utils/FileUtils.java
  19. 82
      app/src/main/java/acr/browser/lightning/view/LightningView.java

46
app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java

@ -27,6 +27,7 @@ import android.os.Build; @@ -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; @@ -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; @@ -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 @@ -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<Void> 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 @@ -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<Void>() {
@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 @@ -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 @@ -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 @@ -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);
}

21
app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java

@ -9,17 +9,26 @@ import android.webkit.CookieManager; @@ -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<Void> updateCookiePreference() {
return Observable.create(new Action<Void>() {
@Override
public void onSubscribe(Subscriber<Void> 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

21
app/src/main/java/acr/browser/lightning/activity/MainActivity.java

@ -9,17 +9,26 @@ import android.webkit.CookieManager; @@ -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<Void> updateCookiePreference() {
return Observable.create(new Action<Void>() {
@Override
public void onSubscribe(Subscriber<Void> 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

133
app/src/main/java/acr/browser/lightning/activity/TabsManager.java

@ -23,7 +23,12 @@ import javax.inject.Singleton; @@ -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 { @@ -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<Void> restoreTabsAndHandleIntent(@NonNull final Activity activity,
@Nullable final Intent intent,
final boolean incognito) {
return Observable.create(new Action<Void>() {
@Override
public void onSubscribe(final Subscriber<Void> 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<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 { @@ -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<Bundle> restoreState() {
return Observable.create(new Action<Bundle>() {
@Override
public void onSubscribe(Subscriber<Bundle> 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();
}
}
});
}
/**

11
app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java

@ -25,8 +25,8 @@ import acr.browser.lightning.utils.Utils; @@ -25,8 +25,8 @@ import acr.browser.lightning.utils.Utils;
public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
private static final String TAG = ImageDownloadTask.class.getSimpleName();
private final File mCacheDir;
@NonNull private final WeakReference<ImageView> mFaviconImage;
@NonNull private final WeakReference<Context> mContextReference;
@NonNull private final HistoryItem mWeb;
private final String mUrl;
@NonNull private final Bitmap mDefaultBitmap;
@ -42,7 +42,7 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> { @@ -42,7 +42,7 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
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<Void, Void, Bitmap> { @@ -53,12 +53,17 @@ public class ImageDownloadTask extends AsyncTask<Void, Void, Bitmap> {
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()) {

10
app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java

@ -60,17 +60,17 @@ public final class BookmarkPage extends AsyncTask<Void, Void, Void> { @@ -60,17 +60,17 @@ public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
private static final String END = "</div></body></html>";
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<LightningView> 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<Void, Void, Void> { @@ -79,6 +79,8 @@ public final class BookmarkPage extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
mCacheDir = mApp.getCacheDir();
mFilesDir = mApp.getFilesDir();
cacheDefaultFolderIcon();
buildBookmarkPage(null, mManager);
return null;

6
app/src/main/java/acr/browser/lightning/constant/HistoryPage.java

@ -41,7 +41,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> { @@ -41,7 +41,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
private static final String END = "</div></body></html>";
@NonNull private final WeakReference<LightningView> 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<Void, Void, Void> { @@ -49,7 +49,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
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<Void, Void, Void> { @@ -88,7 +88,7 @@ public class HistoryPage extends AsyncTask<Void, Void, Void> {
}
historyBuilder.append(END);
File historyWebPage = new File(mFilesDir, FILENAME);
File historyWebPage = new File(mApp.getFilesDir(), FILENAME);
FileWriter historyWriter = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed

6
app/src/main/java/acr/browser/lightning/constant/StartPage.java

@ -55,7 +55,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> { @@ -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>";
@NonNull private final String mTitle;
private final File mFilesDir;
@NonNull private final Application mApp;
@NonNull private final WeakReference<LightningView> mTabReference;
@Inject PreferenceManager mPreferenceManager;
@ -65,7 +65,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> { @@ -65,7 +65,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> {
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<Void, Void, Void> { @@ -174,7 +174,7 @@ public class StartPage extends AsyncTask<Void, Void, Void> {
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

4
app/src/main/java/acr/browser/lightning/database/BookmarkManager.java

@ -55,12 +55,11 @@ public class BookmarkManager { @@ -55,12 +55,11 @@ public class BookmarkManager {
private Map<String, HistoryItem> 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 { @@ -90,6 +89,7 @@ public class BookmarkManager {
@Override
public void run() {
synchronized (BookmarkManager.this) {
mFilesDir = mContext.getFilesDir();
final Map<String, HistoryItem> bookmarks = new HashMap<>();
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS);

14
app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java

@ -18,6 +18,7 @@ import javax.inject.Inject; @@ -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 { @@ -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

1
app/src/main/java/acr/browser/lightning/object/SearchAdapter.java

@ -430,6 +430,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable { @@ -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;

5
app/src/main/java/acr/browser/lightning/react/Action.java

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

130
app/src/main/java/acr/browser/lightning/react/Observable.java

@ -0,0 +1,130 @@ @@ -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);
}
}
}

19
app/src/main/java/acr/browser/lightning/react/Schedulers.java

@ -0,0 +1,19 @@ @@ -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;
}
}

7
app/src/main/java/acr/browser/lightning/react/Subscriber.java

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

7
app/src/main/java/acr/browser/lightning/react/Subscription.java

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

21
app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java

@ -0,0 +1,21 @@ @@ -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);
}
}

38
app/src/main/java/acr/browser/lightning/utils/FileUtils.java

@ -13,6 +13,7 @@ import java.io.FileNotFoundException; @@ -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 { @@ -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);
}
}
});
}
/**

82
app/src/main/java/acr/browser/lightning/view/LightningView.java

@ -34,6 +34,7 @@ import android.webkit.WebView; @@ -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; @@ -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 { @@ -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 { @@ -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<File>() {
@Override
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -1043,6 +1108,9 @@ public class LightningView {
Message msg = mWebViewHandler.obtainMessage();
if (msg != null) {
msg.setTarget(mWebViewHandler);
if (mWebView == null) {
return;
}
mWebView.requestFocusNodeHref(msg);
}
}

Loading…
Cancel
Save