Skip to content

Commit

Permalink
Save back forward history and restore it on app startup
Browse files Browse the repository at this point in the history
  • Loading branch information
RohanBh committed Mar 10, 2018
1 parent 6d6dbf8 commit d23b893
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 48 deletions.
116 changes: 72 additions & 44 deletions app/src/main/java/org/kiwix/kiwixmobile/KiwixMobileActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
import android.widget.RelativeLayout;
import android.widget.Toast;

import org.json.JSONArray;
import org.kiwix.kiwixmobile.base.BaseActivity;
import org.kiwix.kiwixmobile.bookmarks_view.BookmarksActivity;
import org.kiwix.kiwixmobile.database.BookmarksDao;
Expand Down Expand Up @@ -112,9 +111,16 @@

import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES;
import static org.kiwix.kiwixmobile.TableDrawerAdapter.DocumentSection;
import static org.kiwix.kiwixmobile.TableDrawerAdapter.TableClickListener;
import static org.kiwix.kiwixmobile.utils.Constants.BOOKMARK_CHOSEN_REQUEST;
Expand All @@ -132,22 +138,19 @@
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_SEARCH;
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_ZIM_FILE;
import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_ZIM_FILE_2;
import static org.kiwix.kiwixmobile.utils.Constants.KEY_SAVED_STATE_BUNDLE;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_KIWIX_MOBILE;
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_FILE_SEARCH;
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_FILE_SELECT;
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_PREFERENCES;
import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION;
import static org.kiwix.kiwixmobile.utils.Constants.RESULT_HISTORY_CLEARED;
import static org.kiwix.kiwixmobile.utils.Constants.RESULT_RESTART;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_CURRENT_ARTICLES;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_CURRENT_FILE;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_CURRENT_POSITIONS;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_CURRENT_TAB;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_FILE_SEARCHED;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES;

public class KiwixMobileActivity extends BaseActivity implements WebViewCallback {

Expand Down Expand Up @@ -450,7 +453,6 @@ public void sectionsLoaded(String title, List<DocumentSection> sections) {

manageExternalLaunchAndRestoringViewState();
setUpExitFullscreenButton();
loadPrefs();
updateTitle(ZimContentProvider.getZimFileTitle());

Intent i = getIntent();
Expand Down Expand Up @@ -738,6 +740,37 @@ private KiwixWebView newTab() {
return newTab(mainPage);
}

private KiwixWebView newTab(Bundle state) {
AttributeSet attrs = StyleUtils.getAttributes(this, R.xml.webview);
KiwixWebView webView;
if (!isHideToolbar) {
webView = new ToolbarScrollingKiwixWebView(KiwixMobileActivity.this, this, toolbarContainer, pageBottomTabLayout, attrs);
((ToolbarScrollingKiwixWebView) webView).setOnToolbarVisibilityChangeListener(
new ToolbarScrollingKiwixWebView.OnToolbarVisibilityChangeListener() {
@Override
public void onToolbarDisplayed() {
shrinkDrawers();
}

@Override
public void onToolbarHidden() {
expandDrawers();
}
}
);
} else {
webView = new ToolbarStaticKiwixWebView(KiwixMobileActivity.this, this, toolbarContainer, attrs);
}
webView.restoreState(state);
webView.loadPrefs();
mWebViews.add(webView);
selectTab(mWebViews.size() - 1);
tabDrawerAdapter.notifyDataSetChanged();
setUpWebView();
documentParser.initInterface(webView);
return webView;
}

private KiwixWebView newTab(String url) {
KiwixWebView webView = getWebView(url);
mWebViews.add(webView);
Expand Down Expand Up @@ -991,7 +1024,7 @@ private void externalLinkPopup(Intent intent) {
.show();
}

public boolean openZimFile(File file, boolean clearHistory) {
public boolean openZimFile(File file, boolean clearHistory, boolean openMainPage) {
if (file.canRead() || Build.VERSION.SDK_INT < 19 || (BuildConfig.IS_CUSTOM_APP
&& Build.VERSION.SDK_INT != 23)) {
if (file.exists()) {
Expand All @@ -1013,7 +1046,9 @@ public boolean openZimFile(File file, boolean clearHistory) {
bookmarksDao = new BookmarksDao(KiwixDatabase.getInstance(this));
bookmarks = bookmarksDao.getBookmarks(ZimContentProvider.getId(), ZimContentProvider.getName());

openMainPage();
if (openMainPage) {
openMainPage();
}
refreshBookmarks();
return true;
} else {
Expand Down Expand Up @@ -1685,48 +1720,41 @@ public void selectSettings() {
public void saveTabStates() {
SharedPreferences settings = getSharedPreferences(PREF_KIWIX_MOBILE, 0);
SharedPreferences.Editor editor = settings.edit();

JSONArray urls = new JSONArray();
JSONArray positions = new JSONArray();
for (KiwixWebView view : mWebViews) {
if (view.getUrl() == null) continue;
urls.put(view.getUrl());
positions.put(view.getScrollY());
}

editor.putString(TAG_CURRENT_FILE, ZimContentProvider.getZimFile());
editor.putString(TAG_CURRENT_ARTICLES, urls.toString());
editor.putString(TAG_CURRENT_POSITIONS, positions.toString());
editor.putInt(TAG_CURRENT_TAB, currentWebViewIndex);

editor.apply();

Bundle outState = new Bundle(ClassLoader.getSystemClassLoader());
for (int i = 0; i < mWebViews.size(); i++) {
KiwixWebView webView = mWebViews.get(i);
Bundle webViewState = new Bundle(ClassLoader.getSystemClassLoader());
webView.saveState(webViewState);
outState.putBundle(KEY_SAVED_STATE_BUNDLE + i, webViewState);
}
FileUtils.writeBundleToStorage(getApplication(), outState);
}

public void restoreTabStates() {
SharedPreferences settings = getSharedPreferences(PREF_KIWIX_MOBILE, 0);
String zimFile = settings.getString(TAG_CURRENT_FILE, null);
String zimArticles = settings.getString(TAG_CURRENT_ARTICLES, null);
String zimPositions = settings.getString(TAG_CURRENT_POSITIONS, null);

int currentTab = settings.getInt(TAG_CURRENT_TAB, 0);

openZimFile(new File(zimFile), false);
try {
JSONArray urls = new JSONArray(zimArticles);
JSONArray positions = new JSONArray(zimPositions);
int i = 0;
getCurrentWebView().loadUrl(urls.getString(i));
getCurrentWebView().setScrollY(positions.getInt(i));
i++;
for (; i < urls.length(); i++) {
newTab(urls.getString(i));
getCurrentWebView().setScrollY(positions.getInt(i));
}
selectTab(currentTab);
} catch (Exception e) {
Log.w(TAG_KIWIX, "Kiwix shared preferences corrupted", e);
//TODO: Show to user
}
openZimFile(new File(zimFile), false, false);
Single.create((SingleOnSubscribe<Bundle>) e -> {
e.onSuccess(FileUtils.readBundleFromStorage(getApplication()));
FileUtils.deleteBundleInStorage(getApplication());
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(savedState -> {
if (!savedState.isEmpty()) {
Observable.fromIterable(savedState.keySet())
.filter(s -> s.startsWith(KEY_SAVED_STATE_BUNDLE))
.map(savedState::getBundle)
.subscribe(this::newTab, t -> {}, () -> selectTab(currentTab));
} else {
newTab();
}
loadPrefs();
});
}

private void manageExternalLaunchAndRestoringViewState() {
Expand All @@ -1742,16 +1770,15 @@ private void manageExternalLaunchAndRestoringViewState() {
Log.d(TAG_KIWIX, "Kiwix started from a filemanager. Intent filePath: "
+ filePath
+ " -> open this zimfile and load menu_main page");
openZimFile(new File(filePath), false);
openZimFile(new File(filePath), false, true);
loadPrefs();
} else {
SharedPreferences settings = getSharedPreferences(PREF_KIWIX_MOBILE, 0);
String zimFile = settings.getString(TAG_CURRENT_FILE, null);
if (zimFile != null && new File(zimFile).exists()) {
Log.d(TAG_KIWIX,
"Kiwix normal start, zimFile loaded last time -> Open last used zimFile " + zimFile);
restoreTabStates();
// Alternative would be to restore webView state. But more effort to implement, and actually
// fits better normal android behavior if after closing app ("back" button) state is not maintained.
} else {

if (BuildConfig.IS_CUSTOM_APP) {
Expand Down Expand Up @@ -1809,12 +1836,13 @@ private void manageExternalLaunchAndRestoringViewState() {
AlertDialog zimFileMissingDialog = zimFileMissingBuilder.create();
zimFileMissingDialog.show();
} else {
openZimFile(new File(filePath), true);
openZimFile(new File(filePath), true, true);
}
} else {
Log.d(TAG_KIWIX, "Kiwix normal start, no zimFile loaded last time -> display help page");
showHelpPage();
}
loadPrefs();
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions app/src/main/java/org/kiwix/kiwixmobile/utils/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ public final class Constants {

public static final String TAG_CURRENT_FILE = "currentzimfile";

public static final String TAG_CURRENT_ARTICLES = "currentarticles";

public static final String TAG_CURRENT_POSITIONS = "currentpositions";

public static final String TAG_CURRENT_TAB = "currenttab";

// Extras
Expand Down Expand Up @@ -121,4 +117,7 @@ public final class Constants {
public static final String EXTRA_WEBVIEWS_LIST = "webviewsList";

public static final String EXTRA_BOOKMARK_CONTENTS = "bookmark_contents";

// Keys
public static final String KEY_SAVED_STATE_BUNDLE = "bookmark_contents";
}
108 changes: 108 additions & 0 deletions app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,42 @@
*/
package org.kiwix.kiwixmobile.utils.files;

import android.app.Application;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.provider.DocumentsContract;
import android.support.annotation.NonNull;
import android.util.Log;

import org.kiwix.kiwixmobile.BuildConfig;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;

import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;

public class FileUtils {

// the name of the file to store the bundle in.
private static final String BUNDLE_STORAGE = "SAVED_WEB_VIEW.parcel";

public static File getFileCacheDir(Context context) {
boolean external = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());

Expand Down Expand Up @@ -286,4 +299,99 @@ public static long getCurrentSize(Book book) {
return size;
}

/**
* Reads a bundle from the file with the specified
* name in the persistent storage files directory.
* This method is a blocking operation and should be used
* in a io thread.
*
* @param app the application needed to obtain the files directory.
* @return a valid Bundle loaded using the system class loader
* or null if the method was unable to read the Bundle from storage.
*/
@NonNull
public static Bundle readBundleFromStorage(@NonNull Application app) {
File inputFile = new File(app.getFilesDir(), BUNDLE_STORAGE);
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(inputFile);
Parcel parcel = Parcel.obtain();
byte[] data = new byte[(int) inputStream.getChannel().size()];

//noinspection ResultOfMethodCallIgnored
inputStream.read(data, 0, data.length);
parcel.unmarshall(data, 0, data.length);
parcel.setDataPosition(0);
Bundle out = parcel.readBundle(ClassLoader.getSystemClassLoader());
out.putAll(out);
parcel.recycle();
return out;
} catch (FileNotFoundException e) {
Log.e(TAG_KIWIX, "Unable to read bundle from storage");
} catch (IOException e) {
Log.e(TAG_KIWIX, "Unable to read bundle from storage", e);
} finally {
if (inputStream != null) {
//noinspection ResultOfMethodCallIgnored
inputFile.delete();
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new Bundle();
}

/**
* Writes a bundle to persistent storage in the files directory
* using the specified file name. This method is runs blocking operations
* on io thread.
*
* @param app the application needed to obtain the file directory.
* @param writeBundle the bundle to store in persistent storage.
*/
public static void writeBundleToStorage(final @NonNull Application app, Bundle writeBundle) {
Observable.just(writeBundle)
.observeOn(Schedulers.io())
.subscribe(bundle -> {
File outputFile = new File(app.getFilesDir(), BUNDLE_STORAGE);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outputFile);
Parcel parcel = Parcel.obtain();
parcel.writeBundle(bundle);
outputStream.write(parcel.marshall());
outputStream.flush();
parcel.recycle();
} catch (IOException e) {
Log.e(TAG_KIWIX, "Unable to write bundle to storage");
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}

/**
* Use this method to delete the bundle with the specified name.
* This is a blocking call and should be used within a io
* thread.
*
* @param app the application object needed to get the file.
*/
public static void deleteBundleInStorage(final @NonNull Application app) {
File outputFile = new File(app.getFilesDir(), BUNDLE_STORAGE);
if (outputFile.exists()) {
//noinspection ResultOfMethodCallIgnored
outputFile.delete();
}
}

}

0 comments on commit d23b893

Please sign in to comment.