package org.ebookdroid.ui.viewer;
import org.ebookdroid.CodecType;
import org.ebookdroid.EBookDroidApp;
import org.ebookdroid.common.settings.types.RotationType;
import org.emdev.ui.actions.ActionMenuHelper;
import org.emdev.ui.uimanager.UIManagerAppCompat;
import org.sufficientlysecure.viewer.R;
import org.ebookdroid.common.bitmaps.BitmapManager;
import org.ebookdroid.common.bitmaps.ByteBufferManager;
import org.ebookdroid.common.cache.CacheManager;
import org.ebookdroid.common.keysbinding.KeyBindingsDialog;
import org.ebookdroid.common.keysbinding.KeyBindingsManager;
import org.ebookdroid.common.settings.AppSettings;
import org.ebookdroid.common.settings.BackupSettings;
import org.ebookdroid.common.settings.SettingsManager;
import org.ebookdroid.common.settings.books.BookSettings;
import org.ebookdroid.common.settings.books.Bookmark;
import org.ebookdroid.common.settings.listeners.IAppSettingsChangeListener;
import org.ebookdroid.common.settings.listeners.IBookSettingsChangeListener;
import org.ebookdroid.common.settings.types.DocumentViewMode;
import org.ebookdroid.common.touch.TouchManager;
import org.ebookdroid.core.DecodeService;
import org.ebookdroid.core.NavigationHistory;
import org.ebookdroid.core.Page;
import org.ebookdroid.core.PageIndex;
import org.ebookdroid.core.ViewState;
import org.ebookdroid.core.codec.OutlineLink;
import org.ebookdroid.core.events.CurrentPageListener;
import org.ebookdroid.core.events.DecodingProgressListener;
import org.ebookdroid.core.models.DecodingProgressModel;
import org.ebookdroid.core.models.DocumentModel;
import org.ebookdroid.core.models.SearchModel;
import org.ebookdroid.core.models.ZoomModel;
import org.ebookdroid.droids.mupdf.codec.exceptions.MuPdfPasswordException;
import org.ebookdroid.ui.library.dialogs.FolderDlg;
import org.ebookdroid.ui.settings.SettingsUI;
import org.ebookdroid.ui.viewer.dialogs.GoToPageDialog;
import org.ebookdroid.ui.viewer.dialogs.OutlineDialog;
import org.ebookdroid.ui.viewer.stubs.ActivityControllerStub;
import org.ebookdroid.ui.viewer.stubs.ViewContollerStub;
import org.ebookdroid.ui.viewer.views.ManualCropView;
import org.ebookdroid.ui.viewer.views.SearchControls;
import org.ebookdroid.ui.viewer.views.ViewEffects;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.AnyThread;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.emdev.BaseDroidApp;
import org.emdev.common.android.AndroidVersion;
import org.emdev.common.backup.BackupManager;
import org.emdev.common.content.ContentScheme;
import org.emdev.common.filesystem.PathFromUri;
import org.emdev.common.log.LogManager;
import org.emdev.ui.AbstractActivityController;
import org.emdev.ui.actions.ActionDialogBuilder;
import org.emdev.ui.actions.ActionEx;
import org.emdev.ui.actions.ActionMethod;
import org.emdev.ui.actions.IActionController;
import org.emdev.ui.actions.params.EditableValue;
import org.emdev.ui.actions.params.EditableValue.PasswordEditable;
import org.emdev.ui.progress.IProgressIndicator;
import org.emdev.ui.tasks.AsyncTask;
import org.emdev.ui.tasks.AsyncTaskExecutor;
import org.emdev.ui.tasks.BaseAsyncTask;
import org.emdev.ui.uimanager.IUIManager;
import org.emdev.utils.FileUtils;
import org.emdev.utils.LengthUtils;
import org.emdev.utils.StringUtils;
public class ViewerActivityController extends AbstractActivityController<ViewerActivity> implements
IActivityController, DecodingProgressListener, CurrentPageListener, IAppSettingsChangeListener,
IBookSettingsChangeListener {
private final AtomicReference<IViewController> ctrl = new AtomicReference<IViewController>(ViewContollerStub.STUB);
private ZoomModel zoomModel;
private DecodingProgressModel progressModel;
private DocumentModel documentModel;
private SearchModel searchModel;
private String bookTitle;
private ContentScheme scheme;
private CodecType codecType;
private final Intent intent;
private String m_fileName;
private final NavigationHistory history;
private String currentSearchPattern;
private BookSettings bookSettings;
private final AsyncTaskExecutor executor;
/**
* Instantiates a new base viewer activity.
*/
public ViewerActivityController(final ViewerActivity activity) {
super(activity, BEFORE_CREATE, BEFORE_RECREATE, AFTER_CREATE, ON_POST_CREATE, ON_DESTROY);
intent = activity.getIntent();
history = new NavigationHistory(this);
executor = new AsyncTaskExecutor(256, 1, 5, 1, "BookExecutor-" + id);
SettingsManager.addListener(this);
}
/**
* {@inheritDoc}
*
* @see org.emdev.ui.AbstractActivityController#beforeCreate(android.app.Activity)
*/
@Override
public void beforeCreate(final ViewerActivity activity) {
final AppSettings newSettings = AppSettings.current();
activity.setRequestedOrientation(newSettings.rotation.getOrientation());
TouchManager.loadFromSettings(newSettings);
KeyBindingsManager.loadFromSettings(newSettings);
}
/**
* {@inheritDoc}
*
* @see org.emdev.ui.AbstractActivityController#beforeRecreate(android.app.Activity)
*/
@Override
public void beforeRecreate(final ViewerActivity activity) {
final AppSettings newSettings = AppSettings.current();
activity.setRequestedOrientation(newSettings.rotation.getOrientation());
TouchManager.loadFromSettings(newSettings);
KeyBindingsManager.loadFromSettings(newSettings);
}
/**
* {@inheritDoc}
*
* @see org.emdev.ui.AbstractActivityController#afterCreate(android.app.Activity, boolean)
*/
@Override
public void afterCreate(final ViewerActivity activity, final boolean recreated) {
final AppSettings appSettings = AppSettings.current();
IUIManager.instance.setFullScreenMode(activity, getManagedComponent().view.getView(), appSettings.fullScreen);
UIManagerAppCompat.setToolbarVisible(activity, appSettings.showTitle);
createAction(R.id.mainmenu_crop).putValue("view", activity.getManualCropControls()).putValue("mode",
DocumentViewMode.SINGLE_PAGE);
createAction(R.id.mainmenu_zoom).putValue("view", activity.getZoomControls());
createAction(R.id.mainmenu_search).putValue("view", activity.getSearchControls());
createAction(R.id.actions_toggleTouchManagerView).putValue("view", activity.getTouchView());
if (recreated) {
return;
}
documentModel = ActivityControllerStub.DM_STUB;
searchModel = new SearchModel(this);
if (intent == null) {
showErrorDlg(R.string.msg_bad_intent, intent);
return;
}
final Uri data = intent.getData();
if (data == null) {
showErrorDlg(R.string.msg_no_intent_data, intent);
return;
}
scheme = ContentScheme.getScheme(intent);
if (scheme == ContentScheme.UNKNOWN) {
showErrorDlg(R.string.msg_bad_intent, intent);
return;
}
bookTitle = scheme.getResourceName(activity.getContentResolver(), data);
codecType = CodecType.getByUri(bookTitle);
if (codecType == null) {
bookTitle = ContentScheme.getDefaultResourceName(data, "");
codecType = CodecType.getByUri(bookTitle);
}
bookTitle = StringUtils.cleanupTitle(bookTitle);
if (codecType == null) {
final String type = intent.getType();
LCTX.i("Book mime type: " + type);
if (LengthUtils.isNotEmpty(type)) {
codecType = CodecType.getByMimeType(type);
}
}
LCTX.i("Book codec type: " + codecType);
LCTX.i("Book title: " + bookTitle);
if (codecType == null) {
showErrorDlg(R.string.msg_unknown_intent_data_type, data);
return;
}
documentModel = new DocumentModel(codecType);
documentModel.addListener(ViewerActivityController.this);
progressModel = new DecodingProgressModel();
progressModel.addListener(ViewerActivityController.this);
final Uri uri = data;
if (scheme.temporary) {
m_fileName = scheme.key;
CacheManager.clear(scheme.key);
} else {
m_fileName = PathFromUri.retrieve(activity.getContentResolver(), uri);
}
bookSettings = SettingsManager.create(id, m_fileName, scheme.temporary, intent);
SettingsManager.applyBookSettingsChanges(null, bookSettings);
}
/**
* {@inheritDoc}
*
* @see org.emdev.ui.AbstractActivityController#onPostCreate(android.os.Bundle, boolean)
*/
@Override
public void onPostCreate(final Bundle savedInstanceState, final boolean recreated) {
setWindowTitle();
if (!recreated && documentModel != ActivityControllerStub.DM_STUB) {
startDecoding("");
}
}
public void startDecoding(final String password) {
final BookLoadTask loadTask = new BookLoadTask(password);
if (codecType != null && codecType.useCustomFonts ) {
EBookDroidApp.checkInstalledFonts(getContext());
}
loadTask.run();
}
/**
* {@inheritDoc}
*
* @see org.emdev.ui.AbstractActivityController#onDestroy(boolean)
*/
@Override
public void onDestroy(final boolean finishing) {
if (finishing) {
if (BackupSettings.current().backupOnBookClose) {
BackupManager.backup();
}
if (documentModel != null) {
documentModel.recycle();
}
if (scheme != null && scheme.temporary) {
CacheManager.clear(scheme.key);
}
SettingsManager.removeListener(this);
BitmapManager.clear("on finish");
ByteBufferManager.clear("on finish");
}
}
public void askPassword(final String fileName, final int promtId) {
final EditText input = new AppCompatEditText(getManagedComponent());
input.setSingleLine(true);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
final ActionDialogBuilder builder = new ActionDialogBuilder(getManagedComponent(), this);
builder.setTitle(fileName).setMessage(promtId).setView(input);
builder.setPositiveButton(R.id.actions_redecodingWithPassword, new EditableValue("input", input));
builder.setNegativeButton(R.id.mainmenu_close).show();
}
public void showErrorDlg(final int msgId, final Object... args) {
final ActionDialogBuilder builder = new ActionDialogBuilder(getManagedComponent(), this);
builder.setTitle(R.string.error_dlg_title);
builder.setMessage(msgId, args);
builder.setPositiveButton(R.string.error_close, R.id.mainmenu_close);
builder.show();
}
@ActionMethod(ids = R.id.actions_redecodingWithPassword)
public void redecodingWithPassword(final ActionEx action) {
final PasswordEditable value = action.getParameter("input");
final String password = value.getPassword();
startDecoding(password);
}
protected IViewController switchDocumentController(final BookSettings bs) {
if (bs != null) {
try {
final IViewController newDc = bs.viewMode.create(this);
if (newDc != null) {
final IViewController oldDc = ctrl.getAndSet(newDc);
getZoomModel().removeListener(oldDc);
getZoomModel().addListener(newDc);
return ctrl.get();
}
} catch (final Throwable e) {
LCTX.e("Unexpected error: ", e);
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.core.events.DecodingProgressListener#decodingProgressChanged(int)
*/
@Override
public void decodingProgressChanged(final int currentlyDecoding) {
final Runnable r = new Runnable() {
@Override
public void run() {
final ViewerActivity activity = getManagedComponent();
UIManagerAppCompat.setProgressSpinnerVisible(activity, currentlyDecoding > 0);
}
};
getView().post(r);
}
public String getPageNumberString() {
final int pageCount = documentModel.getPageCount();
PageIndex currentIndex = documentModel.getCurrentIndex();
String pageText = "";
if (pageCount > 0) {
final int offset = bookSettings != null ? bookSettings.firstPageOffset : 1;
if (offset == 1) {
pageText = (currentIndex.viewIndex + 1) + "/" + pageCount;
} else {
pageText = offset + "/" + (currentIndex.viewIndex + offset) + "/" + (pageCount - 1 + offset);
}
}
return pageText;
}
public String getWindowTitle() {
final AppSettings app = AppSettings.current();
if (app.pageInTitle) {
return "(" + getPageNumberString() + ") " + bookTitle;
} else {
return bookTitle;
}
}
public void setWindowTitle() {
getManagedComponent().getSupportActionBar().setTitle(getWindowTitle());
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.core.events.CurrentPageListener#currentPageChanged(org.ebookdroid.core.PageIndex,
* org.ebookdroid.core.PageIndex)
*/
@Override
public void currentPageChanged(final PageIndex oldIndex, final PageIndex newIndex) {
final Runnable r = new Runnable() {
@Override
public void run() {
setWindowTitle();
getManagedComponent().currentPageChanged(getPageNumberString());
SettingsManager.currentPageChanged(bookSettings, oldIndex, newIndex);
}
};
getView().post(r);
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#runOnUiThread(java.lang.Runnable)
*/
@Override
public void runOnUiThread(final Runnable r) {
final FutureTask<Object> task = new FutureTask<Object>(r, null);
try {
getActivity().runOnUiThread(task);
task.get();
} catch (final InterruptedException ex) {
Thread.interrupted();
} catch (final ExecutionException ex) {
ex.printStackTrace();
} catch (final Throwable th) {
th.printStackTrace();
}
}
@ActionMethod(ids = R.id.actions_openOptionsMenu)
public void openOptionsMenu(final ActionEx action) {
UIManagerAppCompat.openOptionsMenu(getManagedComponent(), getManagedComponent().view.getView());
}
@ActionMethod(ids = R.id.actions_gotoOutlineItem)
public void gotoOutlineItem(final ActionEx action) {
final OutlineLink link = action.getParameter(IActionController.ADAPTER_SELECTED_ITEM_PROPERTY);
if (link == null) {
return;
}
if (link.targetPage != -1) {
final int pageCount = documentModel.decodeService.getPageCount();
if (link.targetPage < 1 || link.targetPage > pageCount) {
getManagedComponent().showToastText(2000, R.string.error_page_out_of_rande, pageCount);
} else {
getDocumentController().goToLink(link.targetPage - 1, link.targetRect,
AppSettings.current().storeOutlineGotoHistory);
}
return;
}
if (link.targetUrl != null) {
final Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(link.targetUrl));
getManagedComponent().startActivity(i);
}
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#jumpToPage(int, float, float)
*/
@Override
public void jumpToPage(final int viewIndex, final float offsetX, final float offsetY, final boolean addToHistory) {
if (addToHistory) {
history.update();
}
getDocumentController().goToPage(viewIndex, offsetX, offsetY);
}
@ActionMethod(ids = R.id.mainmenu_outline)
public void showOutline(final ActionEx action) {
final List<OutlineLink> outline = documentModel.decodeService.getOutline();
if ((outline != null) && (outline.size() > 0)) {
final OutlineDialog dlg = new OutlineDialog(this, outline);
dlg.show();
} else {
getManagedComponent().showToastText(Toast.LENGTH_SHORT, R.string.outline_missed);
}
}
@ActionMethod(ids = { R.id.actions_doSearch, R.id.actions_doSearchBack })
public final void doSearch(final ActionEx action) {
final Editable value = action.getParameter("input");
final String newPattern = (value != null ? value.toString() : LengthUtils.toString(action.getParameter("text")));
final String oldPattern = currentSearchPattern;
currentSearchPattern = newPattern;
executor.execute(new SearchTask(), newPattern, oldPattern, (String) action.getParameter("forward"));
}
@ActionMethod(ids = R.id.mainmenu_goto_page)
public void showGotoDialog(final ActionEx action) {
final GoToPageDialog dlg = new GoToPageDialog(this);
dlg.show();
}
@ActionMethod(ids = R.id.mainmenu_booksettings)
public void showBookSettings(final ActionEx action) {
SettingsUI.showBookSettings(getManagedComponent(), bookSettings.fileName);
}
@ActionMethod(ids = R.id.mainmenu_settings)
public void showAppSettings(final ActionEx action) {
SettingsUI.showAppSettings(getManagedComponent(), bookSettings.fileName);
}
@ActionMethod(ids = R.id.mainmenu_fullscreen)
public void toggleFullScreen(final ActionEx action) {
AppSettings.toggleFullScreen();
}
@ActionMethod(ids = R.id.mainmenu_force_portrait)
public void forcePortrait(final ActionEx action) {
if (bookSettings.rotation == RotationType.PORTRAIT
|| bookSettings.rotation == RotationType.REVERSE_PORTRAIT) {
SettingsManager.setBookRotation(bookSettings, RotationType.UNSPECIFIED);
} else {
SettingsManager.setBookRotation(bookSettings, RotationType.PORTRAIT);
}
}
@ActionMethod(ids = { R.id.mainmenu_force_landscape })
public void forceLandscape(final ActionEx action) {
if (bookSettings.rotation == RotationType.LANDSCAPE
|| bookSettings.rotation == RotationType.REVERSE_LANDSCAPE) {
SettingsManager.setBookRotation(bookSettings, RotationType.UNSPECIFIED);
} else {
SettingsManager.setBookRotation(bookSettings, RotationType.LANDSCAPE);
}
}
@ActionMethod(ids = R.id.mainmenu_showtitle)
public void toggleTitleVisibility(final ActionEx action) {
AppSettings.toggleTitleVisibility();
}
private static RotationType reversedOrientation(RotationType rotation) {
if (rotation == RotationType.PORTRAIT) {
return RotationType.REVERSE_PORTRAIT;
} else if (rotation == RotationType.REVERSE_PORTRAIT) {
return RotationType.PORTRAIT;
} else if (rotation == RotationType.LANDSCAPE) {
return RotationType.REVERSE_LANDSCAPE;
} else if (rotation == RotationType.REVERSE_LANDSCAPE) {
return RotationType.LANDSCAPE;
}
return null;
}
@ActionMethod(ids = R.id.mainmenu_reverse_orientation)
public void reverseOrientation(final ActionEx action) {
RotationType newRotation = reversedOrientation(bookSettings.rotation);
if (newRotation != null) {
SettingsManager.setBookRotation(bookSettings, newRotation);
}
}
@ActionMethod(ids = R.id.mainmenu_nightmode)
public void toggleNightMode(final ActionEx action) {
SettingsManager.toggleNightMode(bookSettings);
}
@ActionMethod(ids = R.id.mainmenu_splitpages)
public void toggleSplitPages(final ActionEx action) {
SettingsManager.toggleSplitPages(bookSettings);
}
@ActionMethod(ids = R.id.mainmenu_croppages)
public void toggleCropPages(final ActionEx action) {
SettingsManager.toggleCropPages(bookSettings);
}
@ActionMethod(ids = R.id.mainmenu_thumbnail)
public void setCurrentPageAsThumbnail(final ActionEx action) {
final Page page = documentModel.getCurrentPageObject();
if (page != null) {
documentModel.createBookThumbnail(bookSettings, page, true, false);
}
}
@ActionMethod(ids = R.id.mainmenu_singlepage)
public void toggleSinglePage(final ActionEx action) {
SettingsManager.toggleSinglePage(bookSettings);
}
@ActionMethod(ids = R.id.mainmenu_bookmark)
public void showBookmarkDialog(final ActionEx action) {
final int page = documentModel.getCurrentViewPageIndex();
final String message = getManagedComponent().getString(R.string.add_bookmark_name);
final BookSettings bs = getBookSettings();
final int offset = bs != null ? bs.firstPageOffset : 1;
final EditText input = (EditText) LayoutInflater.from(getManagedComponent()).inflate(R.layout.bookmark_edit,
null);
input.setText(getManagedComponent().getString(R.string.text_page) + " " + (page + offset));
input.selectAll();
final ActionDialogBuilder builder = new ActionDialogBuilder(getManagedComponent(), this);
builder.setTitle(R.string.menu_add_bookmark).setMessage(message).setView(input);
builder.setPositiveButton(R.id.actions_addBookmark, new EditableValue("input", input));
builder.setNegativeButton().show();
}
@ActionMethod(ids = R.id.actions_addBookmark)
public void addBookmark(final ActionEx action) {
final Editable value = action.getParameter("input");
final String name = value.toString();
final Page page = documentModel.getCurrentPageObject();
if (page != null) {
final ViewState state = ViewState.get(getDocumentController());
final PointF pos = state.getPositionOnPage(page);
bookSettings.bookmarks.add(new Bookmark(name, documentModel.getCurrentIndex(), pos.x, pos.y));
Collections.sort(bookSettings.bookmarks);
SettingsManager.storeBookSettings(bookSettings);
UIManagerAppCompat.invalidateOptionsMenu(getManagedComponent());
state.release();
}
}
@ActionMethod(ids = R.id.actions_goToBookmark)
public void goToBookmark(final ActionEx action) {
final Bookmark b = action.getParameter("bookmark");
if (b == null) {
return;
}
final Page actualPage = b.page.getActualPage(getDocumentModel(), bookSettings);
if (actualPage != null) {
jumpToPage(actualPage.index.viewIndex, b.offsetX, b.offsetY, AppSettings.current().storeGotoHistory);
}
}
@ActionMethod(ids = R.id.actions_keyBindings)
public void showKeyBindingsDialog(final ActionEx action) {
final KeyBindingsDialog dlg = new KeyBindingsDialog(this);
dlg.show();
}
/**
* Gets the zoom model.
*
* @return the zoom model
*/
@Override
public ZoomModel getZoomModel() {
if (zoomModel == null) {
zoomModel = new ZoomModel();
}
return zoomModel;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getDecodeService()
*/
@Override
public DecodeService getDecodeService() {
return documentModel != null ? documentModel.decodeService : null;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getDecodingProgressModel()
*/
@Override
public DecodingProgressModel getDecodingProgressModel() {
return progressModel;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getDocumentModel()
*/
@Override
public DocumentModel getDocumentModel() {
return documentModel;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getSearchModel()
*/
@Override
public final SearchModel getSearchModel() {
return searchModel;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getDocumentController()
*/
@Override
public final IViewController getDocumentController() {
return ctrl.get();
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getView()
*/
@Override
@AnyThread
public final IView getView() {
return getManagedComponent().view;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getBookSettings()
*/
@Override
public final BookSettings getBookSettings() {
return bookSettings;
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.ui.viewer.IActivityController#getActionController()
*/
@Override
public final IActionController<?> getActionController() {
return this;
}
@ActionMethod(ids = { R.id.mainmenu_zoom, R.id.actions_toggleTouchManagerView, R.id.mainmenu_search,
R.id.mainmenu_crop })
public void toggleControls(final ActionEx action) {
final View view = action.getParameter("view");
final DocumentViewMode mode = action.getParameter("mode");
if (mode != null && bookSettings != null && bookSettings.viewMode != mode) {
final ActionDialogBuilder builder = new ActionDialogBuilder(getContext(), this);
builder.setTitle(android.R.string.dialog_alert_title);
builder.setMessage(R.string.error_invalid_view_mode, mode.getResValue());
builder.setNegativeButton();
builder.show();
return;
}
ViewEffects.toggleControls(view);
if (view instanceof ManualCropView) {
final ManualCropView mcv = (ManualCropView) view;
if (mcv.getVisibility() == View.VISIBLE) {
mcv.initControls();
}
}
UIManagerAppCompat.invalidateOptionsMenu(getManagedComponent());
}
public final boolean dispatchKeyEvent(final KeyEvent event) {
if (event.isCanceled()) {
return false;
}
final int action = event.getAction();
final int keyCode = event.getKeyCode();
if (getManagedComponent().getSearchControls().getVisibility() == View.VISIBLE) {
if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
toggleControls(getAction(R.id.mainmenu_search));
return true;
}
return false;
}
if (getDocumentController().dispatchKeyEvent(event)) {
return true;
}
if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (getManagedComponent().getTouchView().isShown()) {
ViewEffects.toggleControls(getManagedComponent().getTouchView());
} else if (getManagedComponent().getManualCropControls().isShown()) {
ViewEffects.toggleControls(getManagedComponent().getManualCropControls());
} else {
if (history.goBack()) {
return true;
}
if (AppSettings.current().confirmClose) {
final ActionDialogBuilder builder = new ActionDialogBuilder(getManagedComponent(), this);
builder.setTitle(R.string.confirmclose_title);
builder.setMessage(R.string.confirmclose_msg);
builder.setPositiveButton(R.id.mainmenu_close);
builder.setNegativeButton().show();
} else {
getOrCreateAction(R.id.mainmenu_close).run();
}
}
return true;
}
return false;
}
private String getShareMimeType() {
return codecType.mimeTypes.get(0);
}
private Uri getShareURI() {
final String mimeType = getShareMimeType();
Uri.Builder builder = new Uri.Builder();
Uri uri;
if (scheme.temporary) {
builder.scheme("content");
builder.authority("org.ebookdroid.document");
builder.path(m_fileName);
builder.query(mimeType);
uri = builder.build();
} else {
builder.scheme("file");
builder.path(m_fileName);
uri = builder.build();
}
return uri;
}
@ActionMethod(ids = R.id.mainmenu_share)
public void shareDocument(final ActionEx action) {
Intent share = new Intent(Intent.ACTION_SEND);
share.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
share.putExtra(Intent.EXTRA_STREAM, getShareURI());
share.setType(getShareMimeType());
getManagedComponent().startActivity(Intent.createChooser(share, getManagedComponent().getString(R.string.menu_share)));
}
@ActionMethod(ids = R.id.mainmenu_openwith)
public void openWith(final ActionEx action) {
Intent openwith = new Intent(Intent.ACTION_VIEW);
openwith.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
openwith.setDataAndType(getShareURI(), getShareMimeType());
getManagedComponent().startActivity(Intent.createChooser(openwith, getManagedComponent().getString(R.string.menu_openwith)));
}
@ActionMethod(ids = R.id.mainmenu_close)
public void closeActivity(final ActionEx action) {
if (scheme == null || !scheme.promptForSave) {
getOrCreateAction(R.id.actions_doClose).run();
return;
}
// TODO: These two lines appear to do nothing, "save" value is never used.
getOrCreateAction(R.id.actions_doSaveAndClose).putValue("save", Boolean.TRUE);
getOrCreateAction(R.id.actions_doClose).putValue("save", Boolean.FALSE);
final ActionDialogBuilder builder = new ActionDialogBuilder(getManagedComponent(), this);
builder.setTitle(R.string.confirmclose_title);
builder.setMessage(R.string.confirmsave_msg);
builder.setPositiveButton(R.string.confirmsave_yes_btn, R.id.actions_showSaveDlg);
builder.setNegativeButton(R.string.confirmsave_no_btn, R.id.actions_doClose);
builder.show();
}
@ActionMethod(ids = android.R.id.home)
public void navigateUp(final ActionEx action) {
// Set a flag so that R.id.actions_doClose actually performs an "up" action instead of just closing
getOrCreateAction(R.id.actions_doClose).putValue("up", Boolean.TRUE);
// Show the save prompt if needed, then runs R.id.actions_doClose
getOrCreateAction(R.id.mainmenu_close).run();
}
@ActionMethod(ids = R.id.actions_showSaveDlg)
public void showSaveDlg(final ActionEx action) {
final FolderDlg dlg = new FolderDlg(this);
dlg.show(BaseDroidApp.EXT_STORAGE, R.string.confirmclose_title, R.id.actions_doSaveAndClose, R.id.actions_doClose);
}
@ActionMethod(ids = R.id.actions_doSaveAndClose)
public void doSaveAndClose(final ActionEx action) {
final File targetFolder = action.getParameter(FolderDlg.SELECTED_FOLDER);
final File source = new File(m_fileName);
final File target = new File(targetFolder, source.getName());
try {
FileUtils.copy(source, target);
SettingsManager.copyBookSettings(target, bookSettings);
CacheManager.copy(source.getAbsolutePath(), target.getAbsolutePath(), true);
} catch (final IOException ex) {
ex.printStackTrace();
}
doClose(action);
}
@ActionMethod(ids = R.id.actions_doClose)
public void doClose(final ActionEx action) {
if (documentModel != null) {
documentModel.recycle();
}
if (scheme != null && scheme.temporary) {
CacheManager.clear(m_fileName);
}
SettingsManager.releaseBookSettings(id, bookSettings);
if (getOrCreateAction(R.id.actions_doClose).getParameter("up", Boolean.FALSE).booleanValue()) {
goUp();
} else {
getManagedComponent().finish();
}
}
private void goUp() {
// Implementation of the up button from http://developer.android.com/training/implementing-navigation/ancestral.html
// isTaskRoot() check works around a bug where pressing "up" does nothing when the viewer is launched
// by tapping on a pdf download notification in the notification list.
// see: http://stackoverflow.com/questions/19999619/navutils-navigateupto-does-not-start-any-activity
Activity activity = getActivity();
Intent upIntent = NavUtils.getParentActivityIntent(activity);
if (NavUtils.shouldUpRecreateTask(activity, upIntent) || activity.isTaskRoot()) {
// e.g., this is the case when opening a pdf from the Downloads app and pressing the up button:
// the ViewerActivity is running in the Downloads task, so the following will start a new task
// to open the document-viewer library in.
TaskStackBuilder.create(activity)
.addNextIntentWithParentStack(upIntent)
.startActivities();
getActivity().finish();
} else {
// Restart the existing instance of the RecentActivity rather than starting a new one
upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(activity, upIntent);
}
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.common.settings.listeners.IAppSettingsChangeListener#onAppSettingsChanged(org.ebookdroid.common.settings.AppSettings,
* org.ebookdroid.common.settings.AppSettings, org.ebookdroid.common.settings.AppSettings.Diff)
*/
@Override
public void onAppSettingsChanged(final AppSettings oldSettings, final AppSettings newSettings,
final AppSettings.Diff diff) {
final ViewerActivity activity = getManagedComponent();
if (diff.isRotationChanged()) {
if (bookSettings != null) {
activity.setRequestedOrientation(bookSettings.getOrientation(newSettings));
} else {
activity.setRequestedOrientation(newSettings.rotation.getOrientation());
}
}
if (diff.isFullScreenChanged()) {
IUIManager.instance.setFullScreenMode(activity, activity.view.getView(), newSettings.fullScreen);
UIManagerAppCompat.setToolbarVisible(activity, newSettings.showTitle);
setWindowTitle();
}
if (!diff.isFirstTime() && diff.isShowTitleChanged()) {
UIManagerAppCompat.setToolbarVisible(activity, newSettings.showTitle);
}
if (diff.isKeepScreenOnChanged()) {
activity.view.getView().setKeepScreenOn(newSettings.keepScreenOn);
}
if (diff.isTapConfigChanged()) {
TouchManager.loadFromSettings(newSettings);
}
if (diff.isKeyBindingChanged()) {
KeyBindingsManager.loadFromSettings(newSettings);
}
if (diff.isPagesInMemoryChanged()) {
getDocumentController().updateMemorySettings();
}
UIManagerAppCompat.invalidateOptionsMenu(getManagedComponent());
}
/**
* {@inheritDoc}
*
* @see org.ebookdroid.common.settings.listeners.IBookSettingsChangeListener#onBookSettingsChanged(org.ebookdroid.common.settings.books.BookSettings,
* org.ebookdroid.common.settings.books.BookSettings, org.ebookdroid.common.settings.books.BookSettings.Diff)
*/
@Override
public void onBookSettingsChanged(final BookSettings oldSettings, final BookSettings newSettings,
final BookSettings.Diff diff) {
if (newSettings == null) {
return;
}
boolean redrawn = false;
if (diff.isViewModeChanged() || diff.isSplitPagesChanged() || diff.isCropPagesChanged() || diff.isRTLChanged()) {
redrawn = true;
final IViewController newDc = switchDocumentController(newSettings);
if (!diff.isFirstTime() && newDc != null) {
newDc.init(null);
newDc.show();
}
}
if (diff.isRotationChanged()) {
getManagedComponent().setRequestedOrientation(newSettings.getOrientation(AppSettings.current()));
}
if (diff.isFirstTime()) {
getZoomModel().initZoom(newSettings.getZoom());
}
final IViewController dc = getDocumentController();
if (!redrawn && (diff.isEffectsChanged())) {
redrawn = true;
dc.toggleRenderingEffects();
}
if (!redrawn && diff.isPageAlignChanged()) {
dc.setAlign(newSettings.pageAlign);
}
if (diff.isAnimationTypeChanged()) {
dc.updateAnimationType();
}
currentPageChanged(PageIndex.NULL, documentModel.getCurrentIndex());
UIManagerAppCompat.invalidateOptionsMenu(getManagedComponent());
}
final class BookLoadTask extends BaseAsyncTask<String, Throwable> implements Runnable, IProgressIndicator {
private final String m_password;
public BookLoadTask(final String password) {
super(getManagedComponent(), R.string.msg_loading, false);
m_password = password;
}
@Override
public void run() {
executor.execute(this, "");
}
@Override
protected Throwable doInBackground(final String... params) {
LCTX.d("BookLoadTask.doInBackground(): start");
try {
final File cached = scheme.loadToCache(intent.getData(), codecType.getDefaultExtension(), this);
if (cached != null) {
m_fileName = cached.getAbsolutePath();
setProgressDialogMessage(startProgressStringId);
}
getView().waitForInitialization();
documentModel.open(m_fileName, m_password);
getDocumentController().init(this);
return null;
} catch (final MuPdfPasswordException pex) {
LCTX.i(pex.getMessage());
return pex;
} catch (final Exception e) {
LCTX.e(e.getMessage(), e);
return e;
} catch (final Throwable th) {
LCTX.e("BookLoadTask.doInBackground(): Unexpected error", th);
return th;
} finally {
LCTX.d("BookLoadTask.doInBackground(): finish");
}
}
@Override
protected void onPostExecute(Throwable result) {
LCTX.d("BookLoadTask.onPostExecute(): start");
try {
if (result == null) {
try {
getDocumentController().show();
final DocumentModel dm = getDocumentModel();
currentPageChanged(PageIndex.NULL, dm.getCurrentIndex());
} catch (final Throwable th) {
result = th;
}
}
super.onPostExecute(result);
if (result instanceof MuPdfPasswordException) {
final MuPdfPasswordException pex = (MuPdfPasswordException) result;
final int promptId = pex.isWrongPasswordEntered() ? R.string.msg_wrong_password
: R.string.msg_password_required;
askPassword(m_fileName, promptId);
} else if (result != null) {
final String msg = result.getMessage();
LogManager.onUnexpectedError(result);
showErrorDlg(R.string.msg_unexpected_error, msg);
}
} catch (final Throwable th) {
LCTX.e("BookLoadTask.onPostExecute(): Unexpected error", th);
LogManager.onUnexpectedError(result);
} finally {
LCTX.d("BookLoadTask.onPostExecute(): finish");
}
}
@Override
public void setProgressDialogMessage(final int resourceID, final Object... args) {
publishProgress(getManagedComponent().getString(resourceID, args));
}
}
final class SearchTask extends AsyncTask<String, String, RectF> implements SearchModel.ProgressCallback,
OnCancelListener {
private ProgressDialog progressDialog;
private final AtomicBoolean continueFlag = new AtomicBoolean(true);
private String pattern;
private Page targetPage = null;
@Override
public void onCancel(final DialogInterface dialog) {
documentModel.decodeService.stopSearch(pattern);
continueFlag.set(false);
}
@Override
public void searchStarted(final int pageIndex) {
final int offset = bookSettings != null ? bookSettings.firstPageOffset : 1;
publishProgress(getManagedComponent().getResources().getString(R.string.msg_search_text_on_page,
pageIndex + offset));
}
@Override
public void searchFinished(final int pageIndex) {
}
@Override
protected RectF doInBackground(final String... params) {
try {
final int length = LengthUtils.length(params);
pattern = length > 0 ? params[0] : null;
final boolean forward = length >= 3 ? Boolean.parseBoolean(params[2]) : true;
searchModel.setPattern(pattern);
final RectF current = forward ? searchModel.moveToNext(this) : searchModel.moveToPrev(this);
targetPage = searchModel.getCurrentPage();
if (LCTX.isDebugEnabled()) {
LCTX.d("SearchTask.doInBackground(): " + targetPage + " " + current);
}
return current;
} catch (final Throwable th) {
th.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(final RectF result) {
if (progressDialog != null) {
try {
progressDialog.dismiss();
} catch (final Throwable th) {
}
}
if (result != null) {
final RectF newRect = new RectF(result);
final SearchControls sc = getManagedComponent().getSearchControls();
final int controlsHeight = 3 + sc.getActualHeight();
final float pageHeight = targetPage.getBounds(getZoomModel().getZoom()).height();
newRect.offset(0, -(controlsHeight / pageHeight));
getDocumentController().goToLink(targetPage.index.docIndex, newRect,
AppSettings.current().storeSearchGotoHistory);
} else {
Toast.makeText(getManagedComponent(), R.string.msg_no_text_found, Toast.LENGTH_LONG).show();
}
getDocumentController().redrawView();
}
@Override
protected void onProgressUpdate(final String... values) {
final int length = LengthUtils.length(values);
if (length == 0) {
return;
}
final String last = values[length - 1];
if (progressDialog == null || !progressDialog.isShowing()) {
progressDialog = ProgressDialog.show(getManagedComponent(), "", last, true);
progressDialog.setCancelable(true);
progressDialog.setCanceledOnTouchOutside(true);
progressDialog.setOnCancelListener(this);
} else {
progressDialog.setMessage(last);
}
}
}
}