Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save the back stack of visited pages before exiting from app #448

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 Space continuations please. In multiple places in this PR.

.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();
}
}

}