1 package org.coolreader.crengine;
2 
3 import android.annotation.SuppressLint;
4 import android.annotation.TargetApi;
5 import android.app.Activity;
6 import android.app.AlertDialog;
7 import android.content.Context;
8 import android.content.Intent;
9 import android.content.SharedPreferences;
10 import android.content.pm.ActivityInfo;
11 import android.content.pm.PackageInfo;
12 import android.content.pm.PackageManager;
13 import android.content.pm.PackageManager.NameNotFoundException;
14 import android.content.res.Configuration;
15 import android.content.res.Resources;
16 import android.content.res.TypedArray;
17 import android.os.Build;
18 import android.os.Bundle;
19 import android.os.PowerManager;
20 import android.text.ClipboardManager;
21 import android.util.DisplayMetrics;
22 import android.view.Display;
23 import android.view.Gravity;
24 import android.view.KeyEvent;
25 import android.view.MenuItem;
26 import android.view.View;
27 import android.view.ViewConfiguration;
28 import android.view.Window;
29 import android.view.WindowManager;
30 import android.view.WindowManager.LayoutParams;
31 import android.widget.Toast;
32 
33 import org.coolreader.Dictionaries;
34 import org.coolreader.Dictionaries.DictInfo;
35 import org.coolreader.R;
36 import org.coolreader.db.CRDBService;
37 import org.coolreader.db.CRDBServiceAccessor;
38 import org.coolreader.genrescollection.GenresCollection;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileOutputStream;
43 import java.lang.reflect.Field;
44 import java.lang.reflect.InvocationTargetException;
45 import java.lang.reflect.Method;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.Iterator;
50 import java.util.List;
51 import java.util.Locale;
52 import java.util.Map.Entry;
53 import java.util.Set;
54 
55 @SuppressLint("Registered")
56 public class BaseActivity extends Activity implements Settings {
57 
58 	private static final Logger log = L.create("ba");
59 	private View mDecorView;
60 
61 	private CRDBServiceAccessor mCRDBService;
62 	protected Dictionaries mDictionaries;
63 
unbindCRDBService()64 	protected void unbindCRDBService() {
65 		if (mCRDBService != null) {
66 			mCRDBService.unbind();
67 			mCRDBService = null;
68 		}
69 	}
70 
bindCRDBService()71 	protected void bindCRDBService() {
72 		if (mCRDBService == null) {
73 			mCRDBService = new CRDBServiceAccessor(this, Engine.getInstance(this).getPathCorrector());
74 		}
75 		mCRDBService.bind(null);
76 	}
77 
78 	/**
79 	 * Wait until database is bound.
80 	 *
81 	 * @param readyCallback to be called after DB is ready
82 	 */
waitForCRDBService(Runnable readyCallback)83 	public synchronized void waitForCRDBService(Runnable readyCallback) {
84 		if (mCRDBService == null) {
85 			mCRDBService = new CRDBServiceAccessor(this, Engine.getInstance(this).getPathCorrector());
86 		}
87 		mCRDBService.bind(readyCallback);
88 	}
89 
getDBService()90 	public CRDBServiceAccessor getDBService() {
91 		return mCRDBService;
92 	}
93 
getDB()94 	public CRDBService.LocalBinder getDB() {
95 		return mCRDBService != null ? mCRDBService.get() : null;
96 	}
97 
settings()98 	public Properties settings() {
99 		return mSettingsManager.mSettings;
100 	}
101 
102 	private SettingsManager mSettingsManager;
103 
104 	private EinkScreen mEinkScreen;
105 
getEinkScreen()106 	public EinkScreen getEinkScreen() {
107 		return mEinkScreen;
108 	}
109 
startServices()110 	protected void startServices() {
111 		if (DeviceInfo.EINK_NOOK)
112 			mEinkScreen = new EinkScreenNook();
113 		else if (DeviceInfo.EINK_TOLINO)
114 			mEinkScreen = new EinkScreenTolino();
115 		else if (DeviceInfo.EINK_ONYX)
116 			mEinkScreen = new EinkScreenOnyx();
117 		else
118 			mEinkScreen = new EinkScreenDummy();
119 
120 		DisplayMetrics dm = new DisplayMetrics();
121 		getWindowManager().getDefaultDisplay().getMetrics(dm);
122 
123 		try {
124 			Field fld = dm.getClass().getField("densityDpi");
125 			if (fld != null) {
126 				Object v = fld.get(dm);
127 				if (v != null && v instanceof Integer) {
128 					densityDpi = ((Integer) v).intValue();
129 					log.i("Screen density detected: " + densityDpi + "DPI");
130 				}
131 			}
132 		} catch (Exception e) {
133 			log.e("Cannot find field densityDpi, using default value");
134 		}
135 		float widthInches = (float)dm.widthPixels / (float)densityDpi;
136 		float heightInches = (float)dm.heightPixels / (float)densityDpi;
137 		diagonalInches = (float) Math.sqrt(widthInches * widthInches + heightInches * heightInches);
138 		log.i("diagonal=" + diagonalInches + "  isSmartphone=" + isSmartphone());
139 
140 		// create settings
141 		mSettingsManager = new SettingsManager(this);
142 		// create rest of settings
143 		Services.startServices(this);
144 	}
145 
146 	@SuppressLint("NewApi")
147 	@Override
onWindowFocusChanged(boolean hasFocus)148 	public void onWindowFocusChanged(boolean hasFocus) {
149 		super.onWindowFocusChanged(hasFocus);
150 		if (hasFocus)
151 			setSystemUiVisibility();
152 	}
153 
154 	/**
155 	 * Called when the activity is first created.
156 	 */
157 	@Override
onCreate(Bundle savedInstanceState)158 	protected void onCreate(Bundle savedInstanceState) {
159 		log.i("BaseActivity.onCreate() entered");
160 		requestWindowFeature(Window.FEATURE_NO_TITLE);
161 		mDecorView = getWindow().getDecorView();
162 
163 		super.onCreate(savedInstanceState);
164 
165 		mDictionaries = new Dictionaries(this);
166 
167 		try {
168 			PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
169 			mVersion = pi.versionName;
170 		} catch (NameNotFoundException e) {
171 			// ignore
172 		}
173 		log.i("CoolReader version : " + getVersion());
174 		//log.i("CoolReader.window=" + getWindow());
175 		if (!DeviceInfo.EINK_SCREEN) {
176 			WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
177 			lp.alpha = 1.0f;
178 			lp.dimAmount = 0.0f;
179 			lp.format = DeviceInfo.PIXEL_FORMAT;
180 			lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
181 			lp.horizontalMargin = 0;
182 			lp.verticalMargin = 0;
183 			lp.windowAnimations = 0;
184 			lp.layoutAnimationParameters = null;
185 			lp.memoryType = WindowManager.LayoutParams.MEMORY_TYPE_NORMAL;
186 			getWindow().setAttributes(lp);
187 		}
188 
189 		// load settings
190 		Properties props = settings();
191 		String theme = props.getProperty(ReaderView.PROP_APP_THEME, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST1" : "LIGHT");
192 		String lang = props.getProperty(ReaderView.PROP_APP_LOCALE, Lang.DEFAULT.code);
193 		setLanguage(lang);
194 		setCurrentTheme(theme);
195 
196 		setScreenBacklightDuration(props.getInt(ReaderView.PROP_APP_SCREEN_BACKLIGHT_LOCK, 3));
197 
198 		setFullscreen(props.getBool(ReaderView.PROP_APP_FULLSCREEN, DeviceInfo.EINK_SCREEN));
199 		int orientation = props.getInt(ReaderView.PROP_APP_SCREEN_ORIENTATION, 0); //(DeviceInfo.EINK_SCREEN?0:4)
200 		if (orientation < 0 || orientation > 5)
201 			orientation = 5;
202 		setScreenOrientation(orientation);
203 		int backlight = props.getInt(ReaderView.PROP_APP_SCREEN_BACKLIGHT, -1);
204 		if (backlight < -1 || backlight > DeviceInfo.MAX_SCREEN_BRIGHTNESS_VALUE)
205 			backlight = -1;
206 		if (!DeviceInfo.EINK_SCREEN)
207 			setScreenBacklightLevel(backlight);
208 
209 		bindCRDBService();
210 	}
211 
212 	protected BaseDialog currentDialog;
onDialogCreated(BaseDialog dlg)213 	public void onDialogCreated(BaseDialog dlg) {
214 		currentDialog = dlg;
215 	}
onDialogClosed(BaseDialog dlg)216 	public void onDialogClosed(BaseDialog dlg) {
217     	if (currentDialog == dlg) {
218     		currentDialog = null;
219 		}
220 	}
getCurrentDialog()221 	public BaseDialog getCurrentDialog() {
222     	return currentDialog;
223 	}
isDialogActive()224 	public boolean isDialogActive() {
225     	return currentDialog != null;
226 	}
227 
228 	@Override
onDestroy()229 	protected void onDestroy() {
230 		super.onDestroy();
231 		unbindCRDBService();
232 	}
233 
234 	@Override
onStart()235 	protected void onStart() {
236 		super.onStart();
237 
238 //		Properties props = settings().get();
239 //		String theme = props.getProperty(ReaderView.PROP_APP_THEME, DeviceInfo.FORCE_HC_THEME ? "WHITE" : "LIGHT");
240 //		setCurrentTheme(theme);
241 
242 		mIsStarted = true;
243 		mPaused = false;
244 		onUserActivity();
245 	}
246 
247 	@Override
onStop()248 	protected void onStop() {
249 		mIsStarted = false;
250 		super.onStop();
251 	}
252 
253 	@Override
onPause()254 	protected void onPause() {
255 		log.i("CoolReader.onPause() : saving reader state");
256 		mIsStarted = false;
257 		mPaused = true;
258 		releaseBacklightControl();
259 		super.onPause();
260 	}
261 
262 	protected static String PREF_FILE = "CR3LastBook";
263 	protected static String PREF_LAST_BOOK = "LastBook";
264 	protected static String PREF_LAST_LOCATION = "LastLocation";
265 	protected static String PREF_LAST_NOTIFICATION_MASK = "LastNoticeMask";
266 	protected static String PREF_EXT_DATADIR_CREATETIME = "ExtDataDirCreateTime";
267 	protected static String PREF_LAST_LOGCAT = "LastLogcat";
268 
269 	@Override
onResume()270 	protected void onResume() {
271 		log.i("CoolReader.onResume()");
272 		mPaused = false;
273 		mIsStarted = true;
274 		backlightControl.onUserActivity();
275 		super.onResume();
276 	}
277 
278 	private boolean mIsStarted = false;
279 	private boolean mPaused = false;
280 
isStarted()281 	public boolean isStarted() {
282 		return mIsStarted;
283 	}
284 
285 	private String mVersion = "3.1";
286 
getVersion()287 	public String getVersion() {
288 		return mVersion;
289 	}
290 
rebaseSettings()291 	public void rebaseSettings() {
292 		// if rootFs changed (for example, when external storage permission firstly granted) open config from new root
293 		Properties oldProps = mSettingsManager.mSettings;
294 		mSettingsManager.rebaseSettings();
295 		onSettingsChanged(settings(), oldProps);
296 	}
297 
loadSettings(int profile)298 	public Properties loadSettings(int profile) {
299 		return mSettingsManager.loadSettings(profile);
300 	}
301 
saveSettings(int profile, Properties settings)302 	public void saveSettings(int profile, Properties settings) {
303 		mSettingsManager.saveSettings(profile, settings);
304 	}
305 
saveSettings(File f, Properties settings)306 	public void saveSettings(File f, Properties settings) {
307 		mSettingsManager.saveSettings(f, settings);
308 	}
309 
getPalmTipPixels()310 	public int getPalmTipPixels() {
311 		return densityDpi / 3; // 1/3"
312 	}
313 
getDensityDpi()314 	public int getDensityDpi() {
315 		return densityDpi;
316 	}
317 
getDensityFactor()318 	public float getDensityFactor() {
319 		return ((float) densityDpi) / 160f;
320 	}
321 
getDiagonalInches()322 	public float getDiagonalInches() {
323 		return diagonalInches;
324 	}
325 
getSettingsFile(int profile)326 	public String getSettingsFile(int profile) {
327 		File file = mSettingsManager.getSettingsFile(profile);
328 		if (file.exists())
329 			return file.getAbsolutePath();
330 		return null;
331 	}
332 
isSmartphone()333 	public boolean isSmartphone() {
334 		return diagonalInches <= 6.8; //5.8;
335 	}
336 
337 	private int densityDpi = 160;
338 	private float diagonalInches = 5;
339 
340 
341 	private InterfaceTheme currentTheme = null;
342 
getCurrentTheme()343 	public InterfaceTheme getCurrentTheme() {
344 		return currentTheme;
345 	}
346 
setCurrentTheme(String themeCode)347 	public void setCurrentTheme(String themeCode) {
348 		InterfaceTheme theme = InterfaceTheme.findByCode(themeCode);
349 		if (null == theme)
350 			theme = DeviceInfo.FORCE_HC_THEME ? InterfaceTheme.HICONTRAST1 : InterfaceTheme.LIGHT;
351 		if (currentTheme != theme) {
352 			setCurrentTheme(theme);
353 		}
354 	}
355 
356 	private int preferredItemHeight = 36;
357 
getPreferredItemHeight()358 	public int getPreferredItemHeight() {
359 		return preferredItemHeight;
360 	}
361 
362 	private int minFontSize = 9;
363 
getMinFontSize()364 	public int getMinFontSize() {
365 		return minFontSize;
366 	}
367 
368 	private int maxFontSize = 90;
369 
getMaxFontSize()370 	public int getMaxFontSize() {
371 		return maxFontSize;
372 	}
373 
updateBackground()374 	public void updateBackground() {
375 		TypedArray a = getTheme().obtainStyledAttributes(new int[]{android.R.attr.windowBackground, android.R.attr.background, android.R.attr.textColor, android.R.attr.colorBackground, android.R.attr.colorForeground, android.R.attr.listPreferredItemHeight});
376 		int bgRes = a.getResourceId(0, 0);
377 		//int clText = a.getColor(1, 0);
378 		int clBackground = a.getColor(2, 0);
379 		//int clForeground = a.getColor(3, 0);
380 		preferredItemHeight = densityDpi / 3; //a.getDimensionPixelSize(5, 36);
381 		//View contentView = getContentView();
382 		if (contentView != null) {
383 			if (bgRes != 0) {
384 				//Drawable d = getResources().getDrawable(bgRes);
385 				//log.v("Setting background resource " + d.getIntrinsicWidth() + "x" + d.getIntrinsicHeight());
386 				//contentView.setBackgroundResource(null);
387 				contentView.setBackgroundResource(bgRes);
388 				//getWindow().setBackgroundDrawableResource(bgRes);//Drawable(d);
389 				//getWindow().setBackgroundDrawable(d);
390 			} else if (clBackground != 0) {
391 				contentView.setBackgroundColor(clBackground);
392 				//getWindow().setBackgroundDrawable(Utils.solidColorDrawable(clBackground));
393 			}
394 		} else {
395 //			if (bgRes != 0)
396 //				getWindow().setBackgroundDrawableResource(bgRes);
397 //			else if (clBackground != 0)
398 //				getWindow().setBackgroundDrawable(Utils.solidColorDrawable(clBackground));
399 		}
400 		a.recycle();
401 		Display display = getWindowManager().getDefaultDisplay();
402 		int sz = display.getWidth();
403 		if (sz > display.getHeight())
404 			sz = display.getHeight();
405 		minFontSize = sz / 45;
406 		maxFontSize = sz / 8;
407 		if (maxFontSize > 340)
408 			maxFontSize = 340;
409 		if (minFontSize < 9)
410 			minFontSize = 9;
411 	}
412 
413 	@SuppressLint("ResourceType")
updateActionsIcons()414 	public void updateActionsIcons() {
415 		int[] attrs = {R.attr.cr3_button_prev_drawable, R.attr.cr3_button_next_drawable, R.attr.cr3_viewer_toc_drawable,
416 				R.attr.cr3_viewer_find_drawable, R.attr.cr3_viewer_settings_drawable, R.attr.cr3_button_bookmarks_drawable,
417 				R.attr.cr3_browser_folder_root_drawable, R.attr.cr3_option_night_drawable, R.attr.cr3_option_touch_drawable,
418 				R.attr.cr3_button_go_page_drawable, R.attr.cr3_button_go_percent_drawable, R.attr.cr3_browser_folder_drawable,
419 				R.attr.cr3_button_tts_drawable, R.attr.cr3_browser_folder_recent_drawable, R.attr.cr3_button_scroll_go_drawable,
420 				R.attr.cr3_btn_books_swap_drawable, R.attr.cr3_logo_button_drawable, R.attr.cr3_viewer_exit_drawable,
421 				R.attr.cr3_button_book_open_drawable, R.attr.cr3_browser_folder_current_book_drawable, R.attr.cr3_browser_folder_opds_drawable,
422 				R.attr.google_drive_drawable, R.attr.cr3_button_log_drawable, R.attr.cr3_button_light_drawable };
423 		TypedArray a = getTheme().obtainStyledAttributes(attrs);
424 		int btnPrevDrawableRes = a.getResourceId(0, 0);
425 		int btnNextDrawableRes = a.getResourceId(1, 0);
426 		int viewerTocDrawableRes = a.getResourceId(2, 0);
427 		int viewerFindDrawableRes = a.getResourceId(3, 0);
428 		int viewerSettingDrawableRes = a.getResourceId(4, 0);
429 		int btnBookmarksDrawableRes = a.getResourceId(5, 0);
430 		int brFolderRootDrawableRes = a.getResourceId(6, 0);
431 		int optionNightDrawableRes = a.getResourceId(7, 0);
432 		int optionTouchDrawableRes = a.getResourceId(8, 0);
433 		int btnGoPageDrawableRes = a.getResourceId(9, 0);
434 		int btnGoPercentDrawableRes = a.getResourceId(10, 0);
435 		int brFolderDrawableRes = a.getResourceId(11, 0);
436 		int btnTtsDrawableRes = a.getResourceId(12, 0);
437 		int brFolderRecentDrawableRes = a.getResourceId(13, 0);
438 		int btnScrollGoDrawableRes = a.getResourceId(14, 0);
439 		int btnBooksSwapDrawableRes = a.getResourceId(15, 0);
440 		int logoBtnDrawableRes = a.getResourceId(16, 0);
441 		int viewerExitDrawableRes = a.getResourceId(17, 0);
442 		int btnBookOpenDrawableRes = a.getResourceId(18, 0);
443 		int brFolderCurrBookDrawableRes = a.getResourceId(19, 0);
444 		int brFolderOpdsDrawableRes = a.getResourceId(20, 0);
445 		int googleDriveDrawableRes = a.getResourceId(21, 0);
446 		int btnLogDrawableRes = a.getResourceId(22, 0);
447 		int btnLightDrawableRes = a.getResourceId(23, 0);
448 		a.recycle();
449 		if (btnPrevDrawableRes != 0) {
450 			ReaderAction.GO_BACK.setIconId(btnPrevDrawableRes);
451 			ReaderAction.FILE_BROWSER_UP.setIconId(btnPrevDrawableRes);
452 		}
453 		if (btnNextDrawableRes != 0)
454 			ReaderAction.GO_FORWARD.setIconId(btnNextDrawableRes);
455 		if (viewerTocDrawableRes != 0)
456 			ReaderAction.TOC.setIconId(viewerTocDrawableRes);
457 		if (viewerFindDrawableRes != 0)
458 			ReaderAction.SEARCH.setIconId(viewerFindDrawableRes);
459 		if (viewerSettingDrawableRes != 0)
460 			ReaderAction.OPTIONS.setIconId(viewerSettingDrawableRes);
461 		if (btnBookmarksDrawableRes != 0)
462 			ReaderAction.BOOKMARKS.setIconId(btnBookmarksDrawableRes);
463 		if (brFolderRootDrawableRes != 0)
464 			ReaderAction.FILE_BROWSER_ROOT.setIconId(brFolderRootDrawableRes);
465 		if (optionNightDrawableRes != 0)
466 			ReaderAction.TOGGLE_DAY_NIGHT.setIconId(optionNightDrawableRes);
467 		if (optionTouchDrawableRes != 0)
468 			ReaderAction.TOGGLE_SELECTION_MODE.setIconId(optionTouchDrawableRes);
469 		if (btnGoPageDrawableRes != 0)
470 			ReaderAction.GO_PAGE.setIconId(btnGoPageDrawableRes);
471 		if (btnGoPercentDrawableRes != 0)
472 			ReaderAction.GO_PERCENT.setIconId(btnGoPercentDrawableRes);
473 		if (brFolderDrawableRes != 0)
474 			ReaderAction.FILE_BROWSER.setIconId(brFolderDrawableRes);
475 		if (btnTtsDrawableRes != 0)
476 			ReaderAction.TTS_PLAY.setIconId(btnTtsDrawableRes);
477 		if (brFolderRecentDrawableRes != 0)
478 			ReaderAction.RECENT_BOOKS.setIconId(brFolderRecentDrawableRes);
479 		if (btnScrollGoDrawableRes != 0)
480 			ReaderAction.TOGGLE_AUTOSCROLL.setIconId(btnScrollGoDrawableRes);
481 		if (btnBooksSwapDrawableRes != 0)
482 			ReaderAction.OPEN_PREVIOUS_BOOK.setIconId(btnBooksSwapDrawableRes);
483 		if (logoBtnDrawableRes != 0)
484 			ReaderAction.ABOUT.setIconId(logoBtnDrawableRes);
485 		if (viewerExitDrawableRes != 0)
486 			ReaderAction.EXIT.setIconId(viewerExitDrawableRes);
487 		if (btnBookOpenDrawableRes != 0)
488 			ReaderAction.CURRENT_BOOK.setIconId(btnBookOpenDrawableRes);
489 		if (brFolderCurrBookDrawableRes != 0)
490 			ReaderAction.CURRENT_BOOK_DIRECTORY.setIconId(brFolderCurrBookDrawableRes);
491 		if (brFolderOpdsDrawableRes != 0)
492 			ReaderAction.OPDS_CATALOGS.setIconId(brFolderOpdsDrawableRes);
493 		if (googleDriveDrawableRes != 0) {
494 			ReaderAction.GDRIVE_SYNCTO.setIconId(googleDriveDrawableRes);
495 			ReaderAction.GDRIVE_SYNCFROM.setIconId(googleDriveDrawableRes);
496 		}
497 		if (btnLogDrawableRes != 0)
498 			ReaderAction.SAVE_LOGCAT.setIconId(btnLogDrawableRes);
499 		if (btnLightDrawableRes != 0)
500 			ReaderAction.SHOW_SYSTEM_BACKLIGHT_DIALOG.setIconId(btnLightDrawableRes);
501 	}
502 
setCurrentTheme(InterfaceTheme theme)503 	public void setCurrentTheme(InterfaceTheme theme) {
504 		log.i("setCurrentTheme(" + theme + ")");
505 		currentTheme = theme;
506 		getApplication().setTheme(theme.getThemeId());
507 		setTheme(theme.getThemeId());
508 		updateBackground();
509 		updateActionsIcons();
510 	}
511 
512 	int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
513 
applyScreenOrientation(Window wnd)514 	public void applyScreenOrientation(Window wnd) {
515 		if (wnd != null) {
516 			WindowManager.LayoutParams attrs = wnd.getAttributes();
517 			attrs.screenOrientation = screenOrientation;
518 			wnd.setAttributes(attrs);
519 			if (DeviceInfo.EINK_SCREEN) {
520 				//TODO:
521 				//EinkScreen.ResetController(mReaderView);
522 			}
523 		}
524 	}
525 
getScreenOrientation()526 	public int getScreenOrientation() {
527 		switch (screenOrientation) {
528 			case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
529 				return 0;
530 			case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
531 				return 1;
532 			case ActivityInfo_SCREEN_ORIENTATION_REVERSE_PORTRAIT:
533 				return 2;
534 			case ActivityInfo_SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
535 				return 3;
536 			case ActivityInfo.SCREEN_ORIENTATION_USER:
537 				return 5;
538 			default:
539 				return orientationFromSensor;
540 		}
541 	}
542 
543 	@TargetApi(Build.VERSION_CODES.GINGERBREAD)
isReverseLandscape()544 	private boolean isReverseLandscape() {
545 		return screenOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
546 	}
547 
isLandscape()548 	public boolean isLandscape() {
549 		if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
550 			return true;
551 		if (DeviceInfo.getSDKLevel() >= 9 && isReverseLandscape())
552 			return true;
553 		return false;
554 	}
555 
556 	// support pre API LEVEL 9
557 	final static public int ActivityInfo_SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
558 	final static public int ActivityInfo_SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
559 	final static public int ActivityInfo_SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
560 	final static public int ActivityInfo_SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
561 	final static public int ActivityInfo_SCREEN_ORIENTATION_FULL_SENSOR = 10;
562 
setScreenOrientation(int angle)563 	public void setScreenOrientation(int angle) {
564 		int newOrientation = screenOrientation;
565 		boolean level9 = DeviceInfo.getSDKLevel() >= 9;
566 		switch (angle) {
567 			case 0:
568 				newOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; // level9 ? ActivityInfo_SCREEN_ORIENTATION_SENSOR_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
569 				break;
570 			case 1:
571 				newOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; // level9 ? ActivityInfo_SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
572 				break;
573 			case 2:
574 				newOrientation = level9 ? ActivityInfo_SCREEN_ORIENTATION_REVERSE_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
575 				break;
576 			case 3:
577 				newOrientation = level9 ? ActivityInfo_SCREEN_ORIENTATION_REVERSE_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
578 				break;
579 			case 4:
580 				newOrientation = level9 ? ActivityInfo_SCREEN_ORIENTATION_FULL_SENSOR : ActivityInfo.SCREEN_ORIENTATION_SENSOR;
581 				break;
582 			case 5:
583 				newOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
584 				break;
585 		}
586 		if (newOrientation != screenOrientation) {
587 			log.d("setScreenOrientation(" + angle + ")");
588 			screenOrientation = newOrientation;
589 			setRequestedOrientation(screenOrientation);
590 			applyScreenOrientation(getWindow());
591 		}
592 	}
593 
594 
595 	private int orientationFromSensor = 0;
596 
getOrientationFromSensor()597 	public int getOrientationFromSensor() {
598 		return orientationFromSensor;
599 	}
600 
601 	@Override
onConfigurationChanged(Configuration newConfig)602 	public void onConfigurationChanged(Configuration newConfig) {
603 		// pass
604 		orientationFromSensor = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? 1 : 0;
605 		//final int orientation = newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
606 //		if ( orientation!=screenOrientation ) {
607 //			log.d("Screen orientation has been changed: ask for change");
608 //			AlertDialog.Builder dlg = new AlertDialog.Builder(this);
609 //			dlg.setTitle(R.string.win_title_screen_orientation_change_apply);//R.string.win_title_options_apply);
610 //			dlg.setPositiveButton(R.string.dlg_button_ok, new OnClickListener() {
611 //				public void onClick(DialogInterface arg0, int arg1) {
612 //					//onPositiveButtonClick();
613 //					Properties oldSettings = mReaderView.getSettings();
614 //					Properties newSettings = new Properties(oldSettings);
615 //					newSettings.setInt(ReaderView.PROP_APP_SCREEN_ORIENTATION, orientation==ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ? 1 : 0);
616 //					mReaderView.setSettings(newSettings, oldSettings);
617 //				}
618 //			});
619 //			dlg.setNegativeButton(R.string.dlg_button_cancel, new OnClickListener() {
620 //				public void onClick(DialogInterface arg0, int arg1) {
621 //					//onNegativeButtonClick();
622 //				}
623 //			});
624 //		}
625 		super.onConfigurationChanged(newConfig);
626 	}
627 
628 	private boolean mFullscreen = false;
629 
isFullscreen()630 	public boolean isFullscreen() {
631 		return mFullscreen;
632 	}
633 
applyFullscreen(Window wnd)634 	public void applyFullscreen(Window wnd) {
635 		if (mFullscreen) {
636 			//mActivity.getWindow().requestFeature(Window.)
637 			wnd.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
638 					WindowManager.LayoutParams.FLAG_FULLSCREEN);
639 		} else {
640 			wnd.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
641 		}
642 		// enforce new window ui visibility flags
643 		lastSystemUiVisibility = -1;
644 		setSystemUiVisibility();
645 	}
646 
setFullscreen(boolean fullscreen)647 	public void setFullscreen(boolean fullscreen) {
648 		if (mFullscreen != fullscreen) {
649 			mFullscreen = fullscreen;
650 			applyFullscreen(getWindow());
651 		}
652 	}
653 
654 
655 //	public void simulateTouch() {
656 //		// Obtain MotionEvent object
657 //		long downTime = SystemClock.uptimeMillis();
658 //		long eventTime = SystemClock.uptimeMillis() + 10;
659 //		float x = 0.0f;
660 //		float y = 0.0f;
661 //		// List of meta states found here: developer.android.com/reference/android/view/KeyEvent.html#getMetaState()
662 //		int metaState = 0;
663 //		MotionEvent motionEvent = MotionEvent.obtain(
664 //		    downTime,
665 //		    downTime,
666 //		    MotionEvent.ACTION_DOWN,
667 //		    x,
668 //		    y,
669 //		    metaState
670 //		);
671 //		MotionEvent motionEvent2 = MotionEvent.obtain(
672 //			    downTime,
673 //			    eventTime,
674 //			    MotionEvent.ACTION_UP,
675 //			    x,
676 //			    y,
677 //			    metaState
678 //			);
679 //		//motionEvent.setEdgeFlags(flags)
680 //		// Dispatch touch event to view
681 //		//new Handler().dispatchMessage(motionEvent);
682 //		if (getContentView() != null) {
683 //			getContentView().dispatchTouchEvent(motionEvent);
684 //			getContentView().dispatchTouchEvent(motionEvent2);
685 //		}
686 //
687 //	}
688 
wantHideNavbarInFullscreen()689 	protected boolean wantHideNavbarInFullscreen() {
690 		return false;
691 	}
692 
693 	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
694 	@SuppressLint("NewApi")
setSystemUiVisibility()695 	public boolean setSystemUiVisibility() {
696 		if (DeviceInfo.getSDKLevel() >= DeviceInfo.HONEYCOMB) {
697 			int flags = 0;
698 			if (getKeyBacklight() == 0) {
699 				if (DeviceInfo.getSDKLevel() < 19)
700 					// backlight of hardware buttons enabled/disabled
701 					// in updateButtonsBrightness(), turnOffKeyBacklight(), turnOnKeyBacklight()
702 					// entry point onUserActivity().
703 					// This flag just shade software navigation bar and system UI
704 					flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
705 			}
706 			if (isFullscreen() /*&& wantHideNavbarInFullscreen() && isSmartphone()*/) {
707 				if (DeviceInfo.getSDKLevel() >= 19)
708 					// Flag View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY added in API 19
709 					// without this flag  SYSTEM_UI_FLAG_HIDE_NAVIGATION will be force cleared by the system on any user interaction,
710 					// and SYSTEM_UI_FLAG_FULLSCREEN will be force-cleared by the system if the user swipes from the top of the screen.
711 					// So use this flags only on API >= 19
712 					flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
713 							View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
714 							View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
715 							View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
716 							View.SYSTEM_UI_FLAG_FULLSCREEN |
717 							View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
718 				else
719 					flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
720 			}
721 			setSystemUiVisibility(flags);
722 //			if (isFullscreen() && DeviceInfo.getSDKLevel() >= DeviceInfo.ICE_CREAM_SANDWICH)
723 //				simulateTouch();
724 			return true;
725 		}
726 		return false;
727 	}
728 
729 
730 	private int lastSystemUiVisibility = -1;
731 	private boolean systemUiVisibilityListenerIsSet = false;
732 
733 	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
734 	@SuppressLint("NewApi")
setSystemUiVisibility(int value)735 	private boolean setSystemUiVisibility(int value) {
736 		if (DeviceInfo.getSDKLevel() >= DeviceInfo.HONEYCOMB) {
737 			if (!systemUiVisibilityListenerIsSet && null != mDecorView) {
738 				mDecorView.setOnSystemUiVisibilityChangeListener(visibility -> lastSystemUiVisibility = visibility);
739 				systemUiVisibilityListenerIsSet = true;
740 			}
741 			boolean a4 = DeviceInfo.getSDKLevel() >= DeviceInfo.ICE_CREAM_SANDWICH;
742 			if (!a4)
743 				value &= View.SYSTEM_UI_FLAG_LOW_PROFILE;
744 			if (value == lastSystemUiVisibility)// && a4)
745 				return false;
746 			//lastSystemUiVisibility = value;
747 
748 			if (null == mDecorView)
749 				return false;
750 			try {
751 				Method m = mDecorView.getClass().getMethod("setSystemUiVisibility", int.class);
752 				m.invoke(mDecorView, value);
753 				return true;
754 			} catch (SecurityException e) {
755 				// ignore
756 			} catch (NoSuchMethodException e) {
757 				// ignore
758 			} catch (IllegalArgumentException e) {
759 				// ignore
760 			} catch (IllegalAccessException e) {
761 				// ignore
762 			} catch (InvocationTargetException e) {
763 				// ignore
764 			}
765 		}
766 		return false;
767 	}
768 
769 	private int currentKeyBacklightLevel = 1;
770 
getKeyBacklight()771 	public int getKeyBacklight() {
772 		return currentKeyBacklightLevel;
773 	}
774 
setKeyBacklight(int value)775 	public boolean setKeyBacklight(int value) {
776 		currentKeyBacklightLevel = value;
777 		// Try ICS way
778 		if (DeviceInfo.getSDKLevel() >= DeviceInfo.HONEYCOMB) {
779 			setSystemUiVisibility();
780 		}
781 		// thread safe
782 		return Engine.getInstance(this).setKeyBacklight(value);
783 	}
784 
785 
786 	private boolean keyBacklightOff = true;
787 
isKeyBacklightDisabled()788 	public boolean isKeyBacklightDisabled() {
789 		return keyBacklightOff;
790 	}
791 
setKeyBacklightDisabled(boolean disabled)792 	public void setKeyBacklightDisabled(boolean disabled) {
793 		keyBacklightOff = disabled;
794 		onUserActivity();
795 	}
796 
getScreenBacklightLevel()797 	public int getScreenBacklightLevel() {
798 		if (!DeviceInfo.EINK_SCREEN)
799 			return screenBacklightBrightness;
800 		else if (DeviceInfo.EINK_HAVE_FRONTLIGHT) {
801 			// on E-INK devices fetch the system backlight level
802 			return mEinkScreen.getFrontLightValue(this);
803 		}
804 		return 0;
805 	}
806 
getWarmBacklightLevel()807 	public int getWarmBacklightLevel() {
808 		if (!DeviceInfo.EINK_SCREEN)
809 			return screenWarmBacklightBrightness;
810 		else if (DeviceInfo.EINK_HAVE_NATURAL_BACKLIGHT)
811 			return mEinkScreen.getWarmLightValue(this);
812 		return 0;
813 	}
814 
setScreenBacklightLevel(int value)815 	public void setScreenBacklightLevel(int value) {
816 		if (value < -1)
817 			value = -1;
818 		else if (value > DeviceInfo.MAX_SCREEN_BRIGHTNESS_VALUE)
819 			value = -1;
820 		screenBacklightBrightness = value;
821 		if (!DeviceInfo.EINK_SCREEN)
822 			onUserActivity();
823 		else if (null != mEinkScreen)
824 			mEinkScreen.setFrontLightValue(this, value);
825 	}
826 
setScreenWarmBacklightLevel(int value)827 	public void setScreenWarmBacklightLevel(int value) {
828 		if (value < -1)
829 			value = -1;
830 		else if (value > DeviceInfo.MAX_SCREEN_BRIGHTNESS_WARM_VALUE)
831 			value = -1;
832 		if (null != mEinkScreen) {
833 			if (mEinkScreen.setWarmLightValue(this, value))
834 				screenWarmBacklightBrightness = value;
835 		}
836 	}
837 
838 	private int screenBacklightBrightness = -1; // use default
839 	private int screenWarmBacklightBrightness = -1; // use default
840 	//private boolean brightnessHackError = false;
841 	private boolean brightnessHackError = DeviceInfo.SAMSUNG_BUTTONS_HIGHLIGHT_PATCH;
842 
turnOffKeyBacklight()843 	private void turnOffKeyBacklight() {
844 		if (!isStarted())
845 			return;
846 		if (DeviceInfo.getSDKLevel() >= DeviceInfo.HONEYCOMB) {
847 			setKeyBacklight(0);
848 		}
849 		// repeat again in short interval
850 		if (!Engine.getInstance(this).setKeyBacklight(0)) {
851 			//log.w("Cannot control key backlight directly");
852 			return;
853 		}
854 		// repeat again in short interval
855 		Runnable task = () -> {
856 			if (!isStarted())
857 				return;
858 			if (!Engine.getInstance(BaseActivity.this).setKeyBacklight(0)) {
859 				//log.w("Cannot control key backlight directly (delayed)");
860 			}
861 		};
862 		BackgroundThread.instance().postGUI(task, 1);
863 		//BackgroundThread.instance().postGUI(task, 10);
864 	}
865 
turnOnKeyBacklight()866 	private void turnOnKeyBacklight() {
867 		if (!isStarted())
868 			return;
869 		setKeyBacklight(1);
870 		// repeat again in short interval
871 		if (!Engine.getInstance(this).setKeyBacklight(1)) {
872 			//log.w("Cannot control key backlight directly");
873 			return;
874 		}
875 		// repeat again in short interval
876 		Runnable task = () -> {
877 			if (!isStarted())
878 				return;
879 			if (!Engine.getInstance(BaseActivity.this).setKeyBacklight(1)) {
880 				//log.w("Cannot control key backlight directly (delayed)");
881 			}
882 		};
883 		BackgroundThread.instance().postGUI(task, 1);
884 		//BackgroundThread.instance().postGUI(task, 10);
885 	}
886 
updateBacklightBrightness(float b)887 	private void updateBacklightBrightness(float b) {
888 		Window wnd = getWindow();
889 		if (wnd != null) {
890 			LayoutParams attrs = wnd.getAttributes();
891 			boolean changed = false;
892 			if (b < 0 && b > -0.99999f) {
893 				//log.d("dimming screen by " + (int)((1 + b)*100) + "%");
894 				b = -b * attrs.screenBrightness;
895 				if (b < 0.15)
896 					return;
897 			}
898 			float delta = attrs.screenBrightness - b;
899 			if (delta < 0)
900 				delta = -delta;
901 			if (delta > 0.01) {
902 				attrs.screenBrightness = b;
903 				changed = true;
904 			}
905 			if (changed) {
906 				log.d("Window attribute changed: " + attrs);
907 				wnd.setAttributes(attrs);
908 			}
909 		}
910 	}
911 
updateButtonsBrightness(float buttonBrightness)912 	private void updateButtonsBrightness(float buttonBrightness) {
913 		Window wnd = getWindow();
914 		if (wnd != null) {
915 			LayoutParams attrs = wnd.getAttributes();
916 			boolean changed = false;
917 			// hack to set buttonBrightness field
918 			//float buttonBrightness = keyBacklightOff ? 0.0f : -1.0f;
919 			if (!brightnessHackError)
920 				try {
921 					Field bb = attrs.getClass().getField("buttonBrightness");
922 					if (bb != null) {
923 						Float oldValue = (Float) bb.get(attrs);
924 						if (oldValue == null || oldValue.floatValue() != buttonBrightness) {
925 							bb.set(attrs, buttonBrightness);
926 							changed = true;
927 						}
928 					}
929 				} catch (Exception e) {
930 					log.e("WindowManager.LayoutParams.buttonBrightness field is not found, cannot turn buttons backlight off");
931 					brightnessHackError = true;
932 				}
933 			//attrs.buttonBrightness = 0;
934 			if (changed) {
935 				log.d("Window attribute changed: " + attrs);
936 				wnd.setAttributes(attrs);
937 			}
938 			if (keyBacklightOff)
939 				turnOffKeyBacklight();
940 			else
941 				turnOnKeyBacklight();
942 		}
943 	}
944 
945 	private final static int MIN_BACKLIGHT_LEVEL_PERCENT = DeviceInfo.MIN_SCREEN_BRIGHTNESS_VALUE;
946 
setDimmingAlpha(int alpha)947 	protected void setDimmingAlpha(int alpha) {
948 		// override it
949 	}
950 
allowLowBrightness()951 	protected boolean allowLowBrightness() {
952 		// override to force higher brightness in non-reading mode (to avoid black screen on some devices when brightness level set to small value)
953 		return true;
954 	}
955 
956 	private final static int MIN_BRIGHTNESS_IN_BROWSER = 12;
957 
onUserActivity()958 	public void onUserActivity() {
959 		if (backlightControl != null)
960 			backlightControl.onUserActivity();
961 		// Hack
962 		//if ( backlightControl.isHeld() )
963 		BackgroundThread.instance().executeGUI(() -> {
964 			try {
965 				float b;
966 				int dimmingAlpha = 255;
967 				// screenBacklightBrightness is 0..100
968 				if (screenBacklightBrightness >= 0) {
969 					int percent = screenBacklightBrightness;
970 					if (!allowLowBrightness() && percent < MIN_BRIGHTNESS_IN_BROWSER)
971 						percent = MIN_BRIGHTNESS_IN_BROWSER;
972 					float minb = MIN_BACKLIGHT_LEVEL_PERCENT / 100.0f;
973 					if (percent >= 10) {
974 						// real brightness control, no colors dimming
975 						b = (percent - 10) / (100.0f - 10.0f); // 0..1
976 						b = minb + b * (1 - minb); // minb..1
977 						if (b < minb) // BRIGHTNESS_OVERRIDE_OFF
978 							b = minb;
979 						else if (b > 1.0f)
980 							b = 1.0f; //BRIGHTNESS_OVERRIDE_FULL
981 					} else {
982 						// minimal brightness with colors dimming
983 						b = minb;
984 						dimmingAlpha = 255 - (11 - percent) * 180 / 10;
985 					}
986 				} else {
987 					// system
988 					b = -1.0f; //BRIGHTNESS_OVERRIDE_NONE
989 				}
990 				setDimmingAlpha(dimmingAlpha);
991 				//log.v("Brightness: " + b + ", dim: " + dimmingAlpha);
992 				updateBacklightBrightness(b);
993 				updateButtonsBrightness(keyBacklightOff ? 0.0f : -1.0f);
994 			} catch (Exception e) {
995 				// ignore
996 			}
997 		});
998 	}
999 
1000 
isWakeLockEnabled()1001 	public boolean isWakeLockEnabled() {
1002 		return screenBacklightDuration > 0;
1003 	}
1004 
1005 	/**
1006 	 * @param backlightDurationMinutes 0 = system default, 1 == 3 minutes, 2..5 == 2..5 minutes
1007 	 */
setScreenBacklightDuration(int backlightDurationMinutes)1008 	public void setScreenBacklightDuration(int backlightDurationMinutes) {
1009 		if (backlightDurationMinutes == 1)
1010 			backlightDurationMinutes = 3;
1011 		if (screenBacklightDuration != backlightDurationMinutes * 60 * 1000) {
1012 			screenBacklightDuration = backlightDurationMinutes * 60 * 1000;
1013 			if (screenBacklightDuration == 0)
1014 				backlightControl.release();
1015 			else
1016 				backlightControl.onUserActivity();
1017 		}
1018 	}
1019 
1020 	private Runnable backlightTimerTask = null;
1021 	private static long lastUserActivityTime;
1022 	public static final int DEF_SCREEN_BACKLIGHT_TIMER_INTERVAL = 3 * 60 * 1000;
1023 	private int screenBacklightDuration = DEF_SCREEN_BACKLIGHT_TIMER_INTERVAL;
1024 
1025 	private class ScreenBacklightControl {
1026 		PowerManager.WakeLock wl = null;
1027 
ScreenBacklightControl()1028 		public ScreenBacklightControl() {
1029 		}
1030 
1031 		long lastUpdateTimeStamp;
1032 
onUserActivity()1033 		public void onUserActivity() {
1034 			lastUserActivityTime = Utils.timeStamp();
1035 			if (Utils.timeInterval(lastUpdateTimeStamp) < 5000)
1036 				return;
1037 			lastUpdateTimeStamp = android.os.SystemClock.uptimeMillis();
1038 			if (!isWakeLockEnabled())
1039 				return;
1040 			if (wl == null) {
1041 				PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
1042 				wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
1043 						/* | PowerManager.ON_AFTER_RELEASE */, "cr3:wakelock");
1044 				log.d("ScreenBacklightControl: WakeLock created");
1045 			}
1046 			if (!isStarted()) {
1047 				log.d("ScreenBacklightControl: user activity while not started");
1048 				release();
1049 				return;
1050 			}
1051 
1052 			if (!isHeld()) {
1053 				log.d("ScreenBacklightControl: acquiring WakeLock");
1054 				wl.acquire();
1055 			}
1056 
1057 			if (backlightTimerTask == null) {
1058 				log.v("ScreenBacklightControl: timer task started");
1059 				backlightTimerTask = new BacklightTimerTask();
1060 				BackgroundThread.instance().postGUI(backlightTimerTask,
1061 						screenBacklightDuration / 10);
1062 			}
1063 		}
1064 
isHeld()1065 		public boolean isHeld() {
1066 			return wl != null && wl.isHeld();
1067 		}
1068 
release()1069 		public void release() {
1070 			if (wl != null && wl.isHeld()) {
1071 				log.d("ScreenBacklightControl: wl.release()");
1072 				wl.release();
1073 			}
1074 			backlightTimerTask = null;
1075 			lastUpdateTimeStamp = 0;
1076 		}
1077 
1078 		private class BacklightTimerTask implements Runnable {
1079 
1080 			@Override
run()1081 			public void run() {
1082 				if (backlightTimerTask == null)
1083 					return;
1084 				long interval = Utils.timeInterval(lastUserActivityTime);
1085 //				log.v("ScreenBacklightControl: timer task, lastActivityMillis = "
1086 //						+ interval);
1087 				int nextTimerInterval = screenBacklightDuration / 20;
1088 				boolean dim = false;
1089 				if (interval > screenBacklightDuration * 8 / 10) {
1090 					nextTimerInterval = nextTimerInterval / 8;
1091 					dim = true;
1092 				}
1093 				if (interval > screenBacklightDuration) {
1094 					log.v("ScreenBacklightControl: interval is expired");
1095 					release();
1096 				} else {
1097 					BackgroundThread.instance().postGUI(backlightTimerTask, nextTimerInterval);
1098 					if (dim) {
1099 						updateBacklightBrightness(-0.9f); // reduce by 9%
1100 					}
1101 				}
1102 			}
1103 
1104 		}
1105 
1106 		;
1107 
1108 	}
1109 
releaseBacklightControl()1110 	public void releaseBacklightControl() {
1111 		backlightControl.release();
1112 	}
1113 
1114 	ScreenBacklightControl backlightControl = new ScreenBacklightControl();
1115 
1116 
1117 	private EinkScreen.EinkUpdateMode mScreenUpdateMode = EinkScreen.EinkUpdateMode.Clear;
1118 
getScreenUpdateMode()1119 	public EinkScreen.EinkUpdateMode getScreenUpdateMode() {
1120 		return mScreenUpdateMode;
1121 	}
1122 
setScreenUpdateMode(EinkScreen.EinkUpdateMode screenUpdateMode, View view)1123 	public void setScreenUpdateMode(EinkScreen.EinkUpdateMode screenUpdateMode, View view) {
1124 		//if (mReaderView != null) {
1125 		if (null != mEinkScreen) {
1126 			mScreenUpdateMode = screenUpdateMode;
1127 			if (mEinkScreen.getUpdateMode() != screenUpdateMode || mEinkScreen.getUpdateMode() == EinkScreen.EinkUpdateMode.Active) {
1128 				mEinkScreen.setupController(mScreenUpdateMode, mScreenUpdateInterval, view);
1129 			}
1130 		}
1131 		//}
1132 	}
1133 
1134 	private int mScreenUpdateInterval = 0;
1135 
getScreenUpdateInterval()1136 	public int getScreenUpdateInterval() {
1137 		return mScreenUpdateInterval;
1138 	}
1139 
setScreenUpdateInterval(int screenUpdateInterval, View view)1140 	public void setScreenUpdateInterval(int screenUpdateInterval, View view) {
1141 		if (null != mEinkScreen) {
1142 			mScreenUpdateInterval = screenUpdateInterval;
1143 			if (mEinkScreen.getUpdateInterval() != screenUpdateInterval) {
1144 				mEinkScreen.setupController(mScreenUpdateMode, screenUpdateInterval, view);
1145 			}
1146 		}
1147 	}
1148 
1149 
showToast(int stringResourceId)1150 	public void showToast(int stringResourceId) {
1151 		showToast(stringResourceId, Toast.LENGTH_LONG);
1152 	}
1153 
showToast(int stringResourceId, Object... formatArgs)1154 	public void showToast(int stringResourceId, Object... formatArgs) {
1155 		String s = getString(stringResourceId, formatArgs);
1156 		if (s != null)
1157 			showToast(s, Toast.LENGTH_LONG);
1158 	}
1159 
showToast(int stringResourceId, int duration)1160 	public void showToast(int stringResourceId, int duration) {
1161 		String s = getString(stringResourceId);
1162 		if (s != null)
1163 			showToast(s, duration);
1164 	}
1165 
showToast(String msg)1166 	public void showToast(String msg) {
1167 		showToast(msg, Toast.LENGTH_LONG);
1168 	}
1169 
showToast(String msg, int duration)1170 	public void showToast(String msg, int duration) {
1171 		log.v("showing toast: " + msg);
1172 		if (DeviceInfo.USE_CUSTOM_TOAST) {
1173 			ToastView.showToast(getContentView(), msg, Toast.LENGTH_LONG, settings().getInt(ReaderView.PROP_FONT_SIZE, 20));
1174 		} else {
1175 			// classic Toast
1176 			Toast toast = Toast.makeText(this, msg, duration);
1177 			toast.show();
1178 		}
1179 	}
1180 
1181 
1182 	protected View contentView;
1183 
getContentView()1184 	public View getContentView() {
1185 		return contentView;
1186 	}
1187 
setContentView(View view)1188 	public void setContentView(View view) {
1189 		this.contentView = view;
1190 		super.setContentView(view);
1191 		//systemUiVisibilityListenerIsSet = false;
1192 		//updateBackground();
1193 		setCurrentTheme(currentTheme);
1194 	}
1195 
1196 
1197 	private boolean mNightMode = false;
1198 
isNightMode()1199 	public boolean isNightMode() {
1200 		return mNightMode;
1201 	}
1202 
setNightMode(boolean nightMode)1203 	public void setNightMode(boolean nightMode) {
1204 		mNightMode = nightMode;
1205 	}
1206 
1207 
getClipboardmanager()1208 	public ClipboardManager getClipboardmanager() {
1209 		return (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1210 	}
1211 
1212 
showHomeScreen()1213 	public void showHomeScreen() {
1214 		Intent intent = new Intent(Intent.ACTION_MAIN);
1215 		intent.addCategory(Intent.CATEGORY_HOME);
1216 		startActivity(intent);
1217 	}
1218 
1219 
1220 	private static String PREF_HELP_FILE = "HelpFile";
1221 
getLastGeneratedHelpFileSignature()1222 	public String getLastGeneratedHelpFileSignature() {
1223 		SharedPreferences pref = getSharedPreferences(PREF_FILE, 0);
1224 		String res = pref.getString(PREF_HELP_FILE, null);
1225 		return res;
1226 	}
1227 
setLastGeneratedHelpFileSignature(String v)1228 	public void setLastGeneratedHelpFileSignature(String v) {
1229 		SharedPreferences pref = getSharedPreferences(PREF_FILE, 0);
1230 		pref.edit().putString(PREF_HELP_FILE, v).commit();
1231 	}
1232 
1233 
1234 	private String currentLanguage;
1235 
getCurrentLanguage()1236 	public String getCurrentLanguage() {
1237 		return currentLanguage;
1238 	}
1239 
setLanguage(String lang)1240 	public void setLanguage(String lang) {
1241 		setLanguage(Lang.byCode(lang));
1242 		// reload Genres Collection
1243 		GenresCollection.reloadGenresFromResource(this);
1244 	}
1245 
setLanguage(Lang lang)1246 	public void setLanguage(Lang lang) {
1247 		try {
1248 			Resources res = getResources();
1249 			// Change locale settings in the app.
1250 			DisplayMetrics dm = res.getDisplayMetrics();
1251 			android.content.res.Configuration conf = res.getConfiguration();
1252 			conf.locale = (lang == Lang.DEFAULT) ? defaultLocale : lang.getLocale();
1253 			currentLanguage = (lang == Lang.DEFAULT) ? Lang.getCode(defaultLocale) : lang.code;
1254 			res.updateConfiguration(conf, dm);
1255 		} catch (Exception e) {
1256 			log.e("error while setting locale " + lang, e);
1257 		}
1258 	}
1259 
1260 	// Store system locale here, on class creation
1261 	private static final Locale defaultLocale = Locale.getDefault();
1262 
1263 
applyAppSetting(String key, String value)1264 	public void applyAppSetting(String key, String value) {
1265 		boolean flg = "1".equals(value);
1266 		if (key.equals(PROP_APP_FULLSCREEN)) {
1267 			setFullscreen("1".equals(value));
1268 		} else if (key.equals(PROP_APP_LOCALE)) {
1269 			setLanguage(value);
1270 		} else if (key.equals(PROP_APP_KEY_BACKLIGHT_OFF)) {
1271 			setKeyBacklightDisabled(flg);
1272 		} else if (key.equals(PROP_APP_SCREEN_BACKLIGHT_LOCK)) {
1273 			setScreenBacklightDuration(Utils.parseInt(value, 0));
1274 		} else if (key.equals(PROP_NIGHT_MODE)) {
1275 			setNightMode(flg);
1276 		} else if (key.equals(PROP_APP_SCREEN_UPDATE_MODE)) {
1277 			setScreenUpdateMode(EinkScreen.EinkUpdateMode.byCode(Utils.parseInt(value, 0)), getContentView());
1278 		} else if (key.equals(PROP_APP_SCREEN_UPDATE_INTERVAL)) {
1279 			setScreenUpdateInterval(Utils.parseInt(value, 10), getContentView());
1280 		} else if (key.equals(PROP_APP_THEME)) {
1281 			setCurrentTheme(value);
1282 		} else if (key.equals(PROP_APP_SCREEN_ORIENTATION)) {
1283 			setScreenOrientation(Utils.parseInt(value, 0));
1284 		} else if (PROP_APP_SCREEN_BACKLIGHT.equals(key) && !DeviceInfo.EINK_SCREEN) {
1285 			final int n = Utils.parseInt(value, -1, -1, 100);
1286 			// delay before setting brightness
1287 			BackgroundThread.instance().postGUI(() -> BackgroundThread.instance()
1288 					.postBackground(() -> BackgroundThread.instance()
1289 							.postGUI(() -> setScreenBacklightLevel(n))), 100);
1290 		} else if (key.equals(PROP_APP_FILE_BROWSER_HIDE_EMPTY_FOLDERS)) {
1291 			Services.getScanner().setHideEmptyDirs(flg);
1292 		}
1293 		// Don't apply screen brightness on e-ink devices on program startup and at any other events
1294 		// On e-ink in ReaderView gesture handlers setScreenBacklightLevel() & setScreenWarmBacklightLevel() called directly
1295 	}
1296 
1297 
showNotice(int questionResourceId, final Runnable action, final Runnable cancelAction)1298 	public void showNotice(int questionResourceId, final Runnable action, final Runnable cancelAction) {
1299 		NoticeDialog dlg = new NoticeDialog(this, action, cancelAction);
1300 		dlg.setMessage(questionResourceId);
1301 		dlg.show();
1302 	}
1303 
showNotice(int questionResourceId, final Runnable action)1304 	public void showNotice(int questionResourceId, final Runnable action) {
1305 		NoticeDialog dlg = new NoticeDialog(this, action, null);
1306 		dlg.setMessage(questionResourceId);
1307 		dlg.show();
1308 	}
1309 
showMessage(String title, String message)1310 	public void showMessage(String title, String message) {
1311 		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
1312 		if (null != title)
1313 			dlg.setTitle(title);
1314 		dlg.setMessage(message);
1315 		dlg.setPositiveButton(R.string.dlg_button_ok, (arg0, arg1) -> {});
1316 		dlg.show();
1317 	}
1318 
askConfirmation(int questionResourceId, final Runnable action)1319 	public void askConfirmation(int questionResourceId, final Runnable action) {
1320 		askConfirmation(questionResourceId, action, null);
1321 	}
1322 
askConfirmation(int questionResourceId, final Runnable action, final Runnable cancelAction)1323 	public void askConfirmation(int questionResourceId, final Runnable action, final Runnable cancelAction) {
1324 		String question = getString(questionResourceId);
1325 		askConfirmation(null, question, action, cancelAction);
1326 	}
1327 
askConfirmation(int titleResourceId, int questionResourceId, final Runnable action, final Runnable cancelAction)1328 	public void askConfirmation(int titleResourceId, int questionResourceId, final Runnable action, final Runnable cancelAction) {
1329 		String title = getString(titleResourceId);
1330 		String question = getString(questionResourceId);
1331 		askConfirmation(title, question, action, cancelAction);
1332 	}
1333 
askQuestion(int titleResourceId, int questionResourceId, final Runnable yesAction, final Runnable noAction)1334 	public void askQuestion(int titleResourceId, int questionResourceId, final Runnable yesAction, final Runnable noAction) {
1335 		String title = getString(titleResourceId);
1336 		String question = getString(questionResourceId);
1337 		askQuestion(title, question, yesAction, noAction);
1338 	}
1339 
askConfirmation(String title, String question, final Runnable action, final Runnable cancelAction)1340 	public void askConfirmation(String title, String question, final Runnable action, final Runnable cancelAction) {
1341 		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
1342 		if (null != title)
1343 			dlg.setTitle(title);
1344 		dlg.setMessage(question);
1345 		dlg.setPositiveButton(R.string.dlg_button_ok, (arg0, arg1) -> action.run());
1346 		dlg.setNegativeButton(R.string.dlg_button_cancel, (arg0, arg1) -> {
1347 			if (cancelAction != null)
1348 				cancelAction.run();
1349 		});
1350 		dlg.show();
1351 	}
1352 
askQuestion(String title, String question, final Runnable yesAction, final Runnable noAction)1353 	public void askQuestion(String title, String question, final Runnable yesAction, final Runnable noAction) {
1354 		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
1355 		if (null != title)
1356 			dlg.setTitle(title);
1357 		dlg.setMessage(question);
1358 		dlg.setPositiveButton(R.string.dlg_button_yes, (arg0, arg1) -> yesAction.run());
1359 		dlg.setNegativeButton(R.string.dlg_button_no, (arg0, arg1) -> {
1360 			if (noAction != null)
1361 				noAction.run();
1362 		});
1363 		dlg.show();
1364 	}
1365 
askConfirmation(String question, final Runnable action)1366 	public void askConfirmation(String question, final Runnable action) {
1367 		AlertDialog.Builder dlg = new AlertDialog.Builder(this);
1368 		dlg.setTitle(question);
1369 		dlg.setPositiveButton(R.string.dlg_button_ok, (arg0, arg1) -> action.run());
1370 		dlg.setNegativeButton(R.string.dlg_button_cancel, (arg0, arg1) -> {
1371 			// do nothing
1372 		});
1373 		dlg.show();
1374 	}
1375 
directoryUpdated(FileInfo dir)1376 	public void directoryUpdated(FileInfo dir) {
1377 		// override it to use
1378 	}
1379 
onSettingsChanged(Properties props, Properties oldProps)1380 	public void onSettingsChanged(Properties props, Properties oldProps) {
1381 		// override for specific actions
1382 	}
1383 
showActionsPopupMenu(final ReaderAction[] actions, final CRToolBar.OnActionHandler onActionHandler)1384 	public void showActionsPopupMenu(final ReaderAction[] actions, final CRToolBar.OnActionHandler onActionHandler) {
1385 		ArrayList<ReaderAction> list = new ArrayList<>(actions.length);
1386 		Collections.addAll(list, actions);
1387 		showActionsPopupMenu(list, onActionHandler);
1388 	}
1389 
showActionsPopupMenu(final ArrayList<ReaderAction> actions, final CRToolBar.OnActionHandler onActionHandler)1390 	public void showActionsPopupMenu(final ArrayList<ReaderAction> actions, final CRToolBar.OnActionHandler onActionHandler) {
1391 		registerForContextMenu(contentView);
1392 		contentView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
1393 			//populate only it is not populated by children
1394 			if (menu.size() == 0) {
1395 				int order = 0;
1396 				for (final ReaderAction action : actions) {
1397 					MenuItem item = menu.add(0, action.menuItemId, order++, action.nameId);
1398 					item.setOnMenuItemClickListener(item1 -> onActionHandler.onActionSelected(action));
1399 				}
1400 			}
1401 		});
1402 		contentView.showContextMenu();
1403 	}
1404 
showBrowserOptionsDialog()1405 	public void showBrowserOptionsDialog() {
1406 		OptionsDialog dlg = new OptionsDialog(BaseActivity.this, OptionsDialog.Mode.BROWSER, null, null, null);
1407 		dlg.show();
1408 	}
1409 
1410 	private int currentProfile = 0;
1411 
getCurrentProfile()1412 	public int getCurrentProfile() {
1413 		if (currentProfile == 0) {
1414 			currentProfile = mSettingsManager.getInt(PROP_PROFILE_NUMBER, 1);
1415 			if (currentProfile < 1 || currentProfile > MAX_PROFILES)
1416 				currentProfile = 1;
1417 		}
1418 		return currentProfile;
1419 	}
1420 
setCurrentProfile(int profile)1421 	public void setCurrentProfile(int profile) {
1422 		if (profile == 0 || profile == getCurrentProfile())
1423 			return;
1424 		log.i("Switching from profile " + currentProfile + " to " + profile);
1425 		mSettingsManager.saveSettings(currentProfile, null);
1426 		final Properties loadedSettings = mSettingsManager.loadSettings(profile);
1427 		mSettingsManager.setSettings(loadedSettings, 0, true);
1428 		currentProfile = profile;
1429 	}
1430 
setSetting(String name, String value, boolean notify)1431 	public void setSetting(String name, String value, boolean notify) {
1432 		mSettingsManager.setSetting(name, value, notify);
1433 	}
1434 
setSettings(Properties settings, int delayMillis, boolean notify)1435 	public void setSettings(Properties settings, int delayMillis, boolean notify) {
1436 		mSettingsManager.setSettings(settings, delayMillis, notify);
1437 	}
1438 
mergeSettings(Properties settings, boolean notify)1439 	public void mergeSettings(Properties settings, boolean notify) {
1440 		mSettingsManager.mergeSettings(settings, notify);
1441 	}
1442 
notifySettingsChanged()1443 	public void notifySettingsChanged() {
1444 		setSettings(mSettingsManager.get(), -1, true);
1445 	}
1446 
1447 	private static class SettingsManager {
1448 
1449 		public static final Logger log = L.create("cr");
1450 
1451 		private BaseActivity mActivity;
1452 		private Properties mSettings;
1453 
1454 		private final DisplayMetrics displayMetrics = new DisplayMetrics();
1455 		private final File defaultSettingsDir;
1456 
SettingsManager(BaseActivity activity)1457 		public SettingsManager(BaseActivity activity) {
1458 			this.mActivity = activity;
1459 			activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
1460 			defaultSettingsDir = activity.getDir("settings", Context.MODE_PRIVATE);
1461 
1462 			mSettings = loadSettings();
1463 		}
1464 
rebaseSettings()1465 		public void rebaseSettings() {
1466 			mSettings = loadSettings();
1467 		}
1468 
1469 		//int lastSaveId = 0;
setSettings(Properties settings, int delayMillis, boolean notify)1470 		public void setSettings(Properties settings, int delayMillis, boolean notify) {
1471 			Properties oldSettings = mSettings;
1472 			mSettings = new Properties(settings);
1473 			saveSettings(mSettings);
1474 //			if (delayMillis >= 0) {
1475 //				saveSettingsTask.postDelayed(new Runnable() {
1476 //		    		public void run() {
1477 //		    			BackgroundThread.instance().postGUI(new Runnable() {
1478 //		    				@Override
1479 //		    				public void run() {
1480 //		   						saveSettings(mSettings);
1481 //		    				}
1482 //		    			});
1483 //		    		}
1484 //		    	}, delayMillis);
1485 //			}
1486 			if (notify)
1487 				mActivity.onSettingsChanged(mSettings, oldSettings);
1488 		}
1489 
mergeSettings(Properties settings, boolean notify)1490 		public void mergeSettings(Properties settings, boolean notify) {
1491 			Properties oldSettings = mSettings;
1492 			mSettings = new Properties(oldSettings);
1493 			Set<Entry<Object, Object>> entries = settings.entrySet();
1494 			for (Entry<Object, Object> entry : entries) {
1495 				mSettings.put(entry.getKey(), entry.getValue());
1496 			}
1497 			saveSettings(mSettings);
1498 			if (notify)
1499 				mActivity.onSettingsChanged(mSettings, oldSettings);
1500 		}
1501 
setSetting(String name, String value, boolean notify)1502 		public void setSetting(String name, String value, boolean notify) {
1503 			Properties props = new Properties(mSettings);
1504 			if (value.equals(mSettings.getProperty(name)))
1505 				return;
1506 			props.setProperty(name, value);
1507 			setSettings(props, 1000, notify);
1508 		}
1509 
1510 		private static class DefKeyAction {
1511 			public int keyCode;
1512 			public int type;
1513 			public ReaderAction action;
1514 
DefKeyAction(int keyCode, int type, ReaderAction action)1515 			public DefKeyAction(int keyCode, int type, ReaderAction action) {
1516 				this.keyCode = keyCode;
1517 				this.type = type;
1518 				this.action = action;
1519 			}
1520 
getProp()1521 			public String getProp() {
1522 				return ReaderView.PROP_APP_KEY_ACTIONS_PRESS + ReaderAction.getTypeString(type) + keyCode;
1523 			}
1524 		}
1525 
1526 
1527 		private static class DefTapAction {
1528 			public int zone;
1529 			public boolean longPress;
1530 			public ReaderAction action;
1531 
DefTapAction(int zone, boolean longPress, ReaderAction action)1532 			public DefTapAction(int zone, boolean longPress, ReaderAction action) {
1533 				this.zone = zone;
1534 				this.longPress = longPress;
1535 				this.action = action;
1536 			}
1537 		}
1538 
1539 		private static DefKeyAction[] DEF_KEY_ACTIONS = {
1540 				new DefKeyAction(KeyEvent.KEYCODE_BACK, ReaderAction.NORMAL, ReaderAction.GO_BACK),
1541 				new DefKeyAction(KeyEvent.KEYCODE_BACK, ReaderAction.LONG, ReaderAction.EXIT),
1542 				new DefKeyAction(KeyEvent.KEYCODE_BACK, ReaderAction.DOUBLE, ReaderAction.EXIT),
1543 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_CENTER, ReaderAction.NORMAL, ReaderAction.RECENT_BOOKS),
1544 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_CENTER, ReaderAction.LONG, ReaderAction.BOOKMARKS),
1545 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_UP, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1546 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_DOWN, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1547 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_UP, ReaderAction.LONG, (DeviceInfo.EINK_SONY ? ReaderAction.PAGE_UP_10 : ReaderAction.REPEAT)),
1548 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_DOWN, ReaderAction.LONG, (DeviceInfo.EINK_SONY ? ReaderAction.PAGE_DOWN_10 : ReaderAction.REPEAT)),
1549 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_LEFT, ReaderAction.NORMAL, (DeviceInfo.NAVIGATE_LEFTRIGHT ? ReaderAction.PAGE_UP : ReaderAction.PAGE_UP_10)),
1550 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_RIGHT, ReaderAction.NORMAL, (DeviceInfo.NAVIGATE_LEFTRIGHT ? ReaderAction.PAGE_DOWN : ReaderAction.PAGE_DOWN_10)),
1551 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_LEFT, ReaderAction.LONG, ReaderAction.REPEAT),
1552 				new DefKeyAction(KeyEvent.KEYCODE_DPAD_RIGHT, ReaderAction.LONG, ReaderAction.REPEAT),
1553 				new DefKeyAction(KeyEvent.KEYCODE_VOLUME_UP, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1554 				new DefKeyAction(KeyEvent.KEYCODE_VOLUME_DOWN, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1555 				new DefKeyAction(KeyEvent.KEYCODE_VOLUME_UP, ReaderAction.LONG, ReaderAction.REPEAT),
1556 				new DefKeyAction(KeyEvent.KEYCODE_VOLUME_DOWN, ReaderAction.LONG, ReaderAction.REPEAT),
1557 				new DefKeyAction(KeyEvent.KEYCODE_MENU, ReaderAction.NORMAL, ReaderAction.READER_MENU),
1558 				new DefKeyAction(KeyEvent.KEYCODE_MENU, ReaderAction.LONG, ReaderAction.OPTIONS),
1559 				new DefKeyAction(KeyEvent.KEYCODE_CAMERA, ReaderAction.NORMAL, ReaderAction.NONE),
1560 				new DefKeyAction(KeyEvent.KEYCODE_CAMERA, ReaderAction.LONG, ReaderAction.NONE),
1561 				new DefKeyAction(KeyEvent.KEYCODE_SEARCH, ReaderAction.NORMAL, ReaderAction.SEARCH),
1562 				new DefKeyAction(KeyEvent.KEYCODE_SEARCH, ReaderAction.LONG, ReaderAction.TOGGLE_SELECTION_MODE),
1563 
1564 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_UP, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1565 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_UP, ReaderAction.LONG, ReaderAction.NONE),
1566 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_UP, ReaderAction.DOUBLE, ReaderAction.NONE),
1567 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_DOWN, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1568 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_DOWN, ReaderAction.LONG, ReaderAction.NONE),
1569 				new DefKeyAction(KeyEvent.KEYCODE_PAGE_DOWN, ReaderAction.DOUBLE, ReaderAction.NONE),
1570 
1571 				new DefKeyAction(ReaderView.SONY_DPAD_DOWN_SCANCODE, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1572 				new DefKeyAction(ReaderView.SONY_DPAD_UP_SCANCODE, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1573 				new DefKeyAction(ReaderView.SONY_DPAD_DOWN_SCANCODE, ReaderAction.LONG, ReaderAction.PAGE_DOWN_10),
1574 				new DefKeyAction(ReaderView.SONY_DPAD_UP_SCANCODE, ReaderAction.LONG, ReaderAction.PAGE_UP_10),
1575 
1576 				new DefKeyAction(KeyEvent.KEYCODE_8, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1577 				new DefKeyAction(KeyEvent.KEYCODE_2, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1578 				new DefKeyAction(KeyEvent.KEYCODE_8, ReaderAction.LONG, ReaderAction.PAGE_DOWN_10),
1579 				new DefKeyAction(KeyEvent.KEYCODE_2, ReaderAction.LONG, ReaderAction.PAGE_UP_10),
1580 
1581 				new DefKeyAction(ReaderView.KEYCODE_ESCAPE, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1582 				new DefKeyAction(ReaderView.KEYCODE_ESCAPE, ReaderAction.LONG, ReaderAction.REPEAT),
1583 
1584 //		    public static final int KEYCODE_PAGE_BOTTOMLEFT = 0x5d; // fwd
1585 //		    public static final int KEYCODE_PAGE_BOTTOMRIGHT = 0x5f; // fwd
1586 //		    public static final int KEYCODE_PAGE_TOPLEFT = 0x5c; // back
1587 //		    public static final int KEYCODE_PAGE_TOPRIGHT = 0x5e; // back
1588 
1589 		};
1590 		// Some key codes on Nook devices conflicted with standard keyboard, for example, KEYCODE_PAGE_BOTTOMLEFT with PAGE_DOWN
1591 		private static DefKeyAction[] DEF_NOOK_KEY_ACTIONS = {
1592 				new DefKeyAction(ReaderView.NOOK_KEY_NEXT_RIGHT, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1593 				new DefKeyAction(ReaderView.NOOK_KEY_SHIFT_DOWN, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1594 				new DefKeyAction(ReaderView.NOOK_KEY_PREV_LEFT, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1595 				new DefKeyAction(ReaderView.NOOK_KEY_PREV_RIGHT, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1596 				new DefKeyAction(ReaderView.NOOK_KEY_SHIFT_UP, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1597 
1598 				new DefKeyAction(ReaderView.NOOK_12_KEY_NEXT_LEFT, ReaderAction.NORMAL, (DeviceInfo.EINK_NOOK ? ReaderAction.PAGE_UP : ReaderAction.PAGE_DOWN)),
1599 				new DefKeyAction(ReaderView.NOOK_12_KEY_NEXT_LEFT, ReaderAction.LONG, (DeviceInfo.EINK_NOOK ? ReaderAction.PAGE_UP_10 : ReaderAction.PAGE_DOWN_10)),
1600 
1601 				new DefKeyAction(ReaderView.KEYCODE_PAGE_BOTTOMLEFT, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1602 //			    new DefKeyAction(ReaderView.KEYCODE_PAGE_BOTTOMRIGHT, ReaderAction.NORMAL, ReaderAction.PAGE_UP),
1603 				new DefKeyAction(ReaderView.KEYCODE_PAGE_TOPLEFT, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1604 				new DefKeyAction(ReaderView.KEYCODE_PAGE_TOPRIGHT, ReaderAction.NORMAL, ReaderAction.PAGE_DOWN),
1605 				new DefKeyAction(ReaderView.KEYCODE_PAGE_BOTTOMLEFT, ReaderAction.LONG, ReaderAction.PAGE_UP_10),
1606 //			    new DefKeyAction(ReaderView.KEYCODE_PAGE_BOTTOMRIGHT, ReaderAction.LONG, ReaderAction.PAGE_UP_10),
1607 				new DefKeyAction(ReaderView.KEYCODE_PAGE_TOPLEFT, ReaderAction.LONG, ReaderAction.PAGE_DOWN_10),
1608 				new DefKeyAction(ReaderView.KEYCODE_PAGE_TOPRIGHT, ReaderAction.LONG, ReaderAction.PAGE_DOWN_10),
1609 		};
1610 		private static DefTapAction[] DEF_TAP_ACTIONS = {
1611 				new DefTapAction(1, false, ReaderAction.PAGE_UP),
1612 				new DefTapAction(2, false, ReaderAction.PAGE_UP),
1613 				new DefTapAction(4, false, ReaderAction.PAGE_UP),
1614 				new DefTapAction(1, true, ReaderAction.GO_BACK), // back by link
1615 				new DefTapAction(2, true, ReaderAction.TOGGLE_DAY_NIGHT),
1616 				new DefTapAction(4, true, ReaderAction.PAGE_UP_10),
1617 				new DefTapAction(3, false, ReaderAction.PAGE_DOWN),
1618 				new DefTapAction(6, false, ReaderAction.PAGE_DOWN),
1619 				new DefTapAction(7, false, ReaderAction.PAGE_DOWN),
1620 				new DefTapAction(8, false, ReaderAction.PAGE_DOWN),
1621 				new DefTapAction(9, false, ReaderAction.PAGE_DOWN),
1622 				new DefTapAction(3, true, ReaderAction.TOGGLE_AUTOSCROLL),
1623 				new DefTapAction(6, true, ReaderAction.PAGE_DOWN_10),
1624 				new DefTapAction(7, true, ReaderAction.PAGE_DOWN_10),
1625 				new DefTapAction(8, true, ReaderAction.PAGE_DOWN_10),
1626 				new DefTapAction(9, true, ReaderAction.PAGE_DOWN_10),
1627 				new DefTapAction(5, false, ReaderAction.READER_MENU),
1628 				new DefTapAction(5, true, ReaderAction.OPTIONS),
1629 		};
1630 
1631 
isValidFontFace(String face)1632 		private boolean isValidFontFace(String face) {
1633 			String[] fontFaces = Engine.getFontFaceList();
1634 			if (fontFaces == null)
1635 				return true;
1636 			for (String item : fontFaces) {
1637 				if (item.equals(face))
1638 					return true;
1639 			}
1640 			return false;
1641 		}
1642 
applyDefaultFont(Properties props, String propName, String defFontFace)1643 		private boolean applyDefaultFont(Properties props, String propName, String defFontFace) {
1644 			String currentValue = props.getProperty(propName);
1645 			boolean changed = false;
1646 			if (currentValue == null) {
1647 				currentValue = defFontFace;
1648 				changed = true;
1649 			}
1650 			if (!isValidFontFace(currentValue)) {
1651 				if (isValidFontFace("Droid Sans"))
1652 					currentValue = "Droid Sans";
1653 				else if (isValidFontFace("Roboto"))
1654 					currentValue = "Roboto";
1655 				else if (isValidFontFace("Droid Serif"))
1656 					currentValue = "Droid Serif";
1657 				else if (isValidFontFace("Arial"))
1658 					currentValue = "Arial";
1659 				else if (isValidFontFace("Times New Roman"))
1660 					currentValue = "Times New Roman";
1661 				else if (isValidFontFace("Droid Sans Fallback"))
1662 					currentValue = "Droid Sans Fallback";
1663 				else {
1664 					String[] fontFaces = Engine.getFontFaceList();
1665 					if (fontFaces != null)
1666 						currentValue = fontFaces[0];
1667 				}
1668 				changed = true;
1669 			}
1670 			if (changed)
1671 				props.setProperty(propName, currentValue);
1672 			return changed;
1673 		}
1674 
applyDefaultFallbackFontList(Properties props, String propName, String defFontList)1675 		private boolean applyDefaultFallbackFontList(Properties props, String propName, String defFontList) {
1676 			String currentValue = props.getProperty(propName);
1677 			boolean changed = false;
1678 			if (currentValue == null) {
1679 				currentValue = defFontList;
1680 				changed = true;
1681 			}
1682 			List<String> faces = Arrays.asList(currentValue.split(";"));
1683 			StringBuilder allowedFaces = new StringBuilder();
1684 			Iterator<String> it = faces.iterator();
1685 			while (it.hasNext()) {
1686 				String face = it.next().trim();
1687 				if (isValidFontFace(face)) {
1688 					allowedFaces.append(face);
1689 					if (it.hasNext())
1690 						allowedFaces.append("; ");
1691 				}
1692 			}
1693 			if (!changed)
1694 				changed = !allowedFaces.toString().equals(currentValue);
1695 			if (changed) {
1696 				currentValue = allowedFaces.toString();
1697 				props.setProperty(propName, currentValue);
1698 			}
1699 			return changed;
1700 		}
1701 
fixFontSettings(Properties props)1702 		public boolean fixFontSettings(Properties props) {
1703 			boolean res = false;
1704 			res = applyDefaultFont(props, ReaderView.PROP_FONT_FACE, DeviceInfo.DEF_FONT_FACE) || res;
1705 			res = applyDefaultFont(props, ReaderView.PROP_STATUS_FONT_FACE, DeviceInfo.DEF_FONT_FACE) || res;
1706 			res = applyDefaultFallbackFontList(props, ReaderView.PROP_FALLBACK_FONT_FACES, "Droid Sans Fallback; Noto Sans CJK SC; Noto Sans Arabic UI; Noto Sans Devanagari UI; Roboto; FreeSans; FreeSerif; Noto Serif; Noto Sans; Arial Unicode MS") || res;
1707 			return res;
1708 		}
1709 
upgradeSettings(Properties props)1710 		private void upgradeSettings(Properties props) {
1711 			String oldHyphenCode = props.getProperty("crengine.hyphenation.dictionary.code");
1712 			if (null != oldHyphenCode && oldHyphenCode.length() > 1) {
1713 				String newHyphenValue = props.getProperty(ReaderView.PROP_HYPHENATION_DICT);
1714 				if (null == newHyphenValue || newHyphenValue.length() == 0) {
1715 					if ("RUSSIAN".equals(oldHyphenCode)) {
1716 						newHyphenValue = "Russian_EnUS";
1717 					} else if ("ENGLISH".equals(oldHyphenCode)) {
1718 						newHyphenValue = "English_US";
1719 					} else {
1720 						newHyphenValue = oldHyphenCode.substring(0, 1);
1721 						newHyphenValue = newHyphenValue + oldHyphenCode.substring(1).toLowerCase();
1722 					}
1723 					props.applyDefault(ReaderView.PROP_HYPHENATION_DICT, newHyphenValue);
1724 					props.remove("crengine.hyphenation.dictionary.code");
1725 				}
1726 			}
1727 		}
1728 
loadSettings(BaseActivity activity, File file)1729 		public Properties loadSettings(BaseActivity activity, File file) {
1730 			Properties props = new Properties();
1731 
1732 			if (file.exists() && !DEBUG_RESET_OPTIONS) {
1733 				try {
1734 					FileInputStream is = new FileInputStream(file);
1735 					props.load(is);
1736 					log.v("" + props.size() + " settings items loaded from file " + propsFile.getAbsolutePath());
1737 				} catch (Exception e) {
1738 					log.e("error while reading settings");
1739 				}
1740 			}
1741 
1742 			// default key actions
1743 			boolean menuKeyActionFound = false;
1744 			for (DefKeyAction ka : DEF_KEY_ACTIONS) {
1745 				props.applyDefault(ka.getProp(), ka.action.id);
1746 				if (ReaderAction.READER_MENU.id.equals(ka.action.id))
1747 					menuKeyActionFound = true;
1748 			}
1749 			if (DeviceInfo.NOOK_NAVIGATION_KEYS) {
1750 				// Add default key mappings for Nook devices & also override defaults for some keys (PAGE_UP, PAGE_DOWN)
1751 				for (DefKeyAction ka : DEF_NOOK_KEY_ACTIONS) {
1752 					props.applyDefault(ka.getProp(), ka.action.id);
1753 				}
1754 			}
1755 
1756 			boolean menuTapActionFound = false;
1757 			for (DefTapAction ka : DEF_TAP_ACTIONS) {
1758 				String paramName = ka.longPress ? ReaderView.PROP_APP_TAP_ZONE_ACTIONS_TAP + ".long." + ka.zone : ReaderView.PROP_APP_TAP_ZONE_ACTIONS_TAP + "." + ka.zone;
1759 				String value = props.getProperty(paramName);
1760 				if (ReaderAction.READER_MENU.id.equals(value))
1761 					menuTapActionFound = true;
1762 			}
1763 
1764 			// default tap zone actions
1765 			for (DefTapAction ka : DEF_TAP_ACTIONS) {
1766 				String paramName = ka.longPress ? ReaderView.PROP_APP_TAP_ZONE_ACTIONS_TAP + ".long." + ka.zone : ReaderView.PROP_APP_TAP_ZONE_ACTIONS_TAP + "." + ka.zone;
1767 
1768 				if (ka.zone == 5 && !activity.hasHardwareMenuKey() && !menuTapActionFound && !menuKeyActionFound) {
1769 					// force assignment of central tap zone
1770 					props.setProperty(paramName, ka.action.id);
1771 				} else {
1772 					props.applyDefault(paramName, ka.action.id);
1773 				}
1774 			}
1775 
1776 			if (DeviceInfo.EINK_NOOK) {
1777 				props.applyDefault(ReaderView.PROP_PAGE_ANIMATION, ReaderView.PAGE_ANIMATION_NONE);
1778 			} else {
1779 				props.applyDefault(ReaderView.PROP_PAGE_ANIMATION, ReaderView.PAGE_ANIMATION_SLIDE2);
1780 			}
1781 
1782 			props.applyDefault(ReaderView.PROP_APP_LOCALE, Lang.DEFAULT.code);
1783 
1784 			props.applyDefault(ReaderView.PROP_APP_THEME, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST1" : "LIGHT");
1785 			props.applyDefault(ReaderView.PROP_APP_THEME_DAY, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST1" : "LIGHT");
1786 			props.applyDefault(ReaderView.PROP_APP_THEME_NIGHT, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST2" : "DARK");
1787 			props.applyDefault(ReaderView.PROP_APP_SELECTION_PERSIST, "0");
1788 			props.applyDefault(ReaderView.PROP_APP_SCREEN_BACKLIGHT_LOCK, "3");
1789 			if ("1".equals(props.getProperty(ReaderView.PROP_APP_SCREEN_BACKLIGHT_LOCK)))
1790 				props.setProperty(ReaderView.PROP_APP_SCREEN_BACKLIGHT_LOCK, "3");
1791 			props.applyDefault(ReaderView.PROP_APP_MOTION_TIMEOUT, "0");
1792 			props.applyDefault(ReaderView.PROP_APP_BOOK_PROPERTY_SCAN_ENABLED, "1");
1793 			props.applyDefault(ReaderView.PROP_APP_KEY_BACKLIGHT_OFF, DeviceInfo.SAMSUNG_BUTTONS_HIGHLIGHT_PATCH ? "0" : "1");
1794 			props.applyDefault(ReaderView.PROP_LANDSCAPE_PAGES, DeviceInfo.ONE_COLUMN_IN_LANDSCAPE ? "0" : "1");
1795 			//props.applyDefault(ReaderView.PROP_TOOLBAR_APPEARANCE, "0");
1796 			// autodetect best initial font size based on display resolution
1797 			int screenHeight = displayMetrics.heightPixels;
1798 			int screenWidth = displayMetrics.widthPixels;//getWindowManager().getDefaultDisplay().getWidth();
1799 			if (screenWidth > screenHeight)
1800 				screenWidth = screenHeight;
1801 			int fontSize = 20;
1802 			int statusFontSize = 16;
1803 			String hmargin = "4";
1804 			String vmargin = "2";
1805 			if (screenWidth <= 320) {
1806 				fontSize = 20;
1807 				statusFontSize = 16;
1808 				hmargin = "4";
1809 				vmargin = "2";
1810 			} else if (screenWidth <= 400) {
1811 				fontSize = 24;
1812 				statusFontSize = 20;
1813 				hmargin = "10";
1814 				vmargin = "4";
1815 			} else if (screenWidth <= 600) {
1816 				fontSize = 28;
1817 				statusFontSize = 24;
1818 				hmargin = "20";
1819 				vmargin = "8";
1820 			} else if (screenWidth <= 800) {
1821 				fontSize = 32;
1822 				statusFontSize = 28;
1823 				hmargin = "25";
1824 				vmargin = "15";
1825 			} else {
1826 				fontSize = 48;
1827 				statusFontSize = 32;
1828 				hmargin = "30";
1829 				vmargin = "20";
1830 			}
1831 			if (DeviceInfo.DEF_FONT_SIZE != null)
1832 				fontSize = DeviceInfo.DEF_FONT_SIZE;
1833 
1834 			int statusLocation = props.getInt(PROP_STATUS_LOCATION, VIEWER_STATUS_PAGE_HEADER);
1835 			if (statusLocation == VIEWER_STATUS_BOTTOM || statusLocation == VIEWER_STATUS_TOP)
1836 				statusLocation = VIEWER_STATUS_PAGE_HEADER;
1837 			props.setInt(PROP_STATUS_LOCATION, statusLocation);
1838 
1839 
1840 			fixFontSettings(props);
1841 			upgradeSettings(props);
1842 			props.applyDefault(ReaderView.PROP_FONT_SIZE, String.valueOf(fontSize));
1843 			props.applyDefault(ReaderView.PROP_FONT_HINTING, "2");
1844 			props.applyDefault(ReaderView.PROP_STATUS_FONT_SIZE, DeviceInfo.EINK_NOOK ? "15" : String.valueOf(statusFontSize));
1845 			props.applyDefault(ReaderView.PROP_FONT_COLOR, "#000000");
1846 			props.applyDefault(ReaderView.PROP_FONT_COLOR_DAY, "#000000");
1847 			props.applyDefault(ReaderView.PROP_FONT_COLOR_NIGHT, !DeviceInfo.EINK_SCREEN ? "#D0B070" : "#FFFFFF");
1848 			props.applyDefault(ReaderView.PROP_BACKGROUND_COLOR, "#FFFFFF");
1849 			props.applyDefault(ReaderView.PROP_BACKGROUND_COLOR_DAY, "#FFFFFF");
1850 			props.applyDefault(ReaderView.PROP_BACKGROUND_COLOR_NIGHT, !DeviceInfo.EINK_SCREEN ? "#101010" : "#000000");
1851 			props.applyDefault(ReaderView.PROP_STATUS_FONT_COLOR, "#FF000000"); // don't use separate color
1852 			props.applyDefault(ReaderView.PROP_STATUS_FONT_COLOR_DAY, "#FF000000"); // don't use separate color
1853 			props.applyDefault(ReaderView.PROP_STATUS_FONT_COLOR_NIGHT, "#80000000"); // don't use separate color
1854 			props.setProperty(ReaderView.PROP_ROTATE_ANGLE, "0"); // crengine's rotation will not be user anymore
1855 			props.setProperty(ReaderView.PROP_DISPLAY_INVERSE, "0");
1856 			props.applyDefault(ReaderView.PROP_APP_FULLSCREEN, "0");
1857 			props.applyDefault(ReaderView.PROP_APP_VIEW_AUTOSCROLL_SPEED, "1500");
1858 			props.applyDefault(ReaderView.PROP_APP_SCREEN_BACKLIGHT, "-1");
1859 			props.applyDefault(ReaderView.PROP_APP_SCREEN_WARM_BACKLIGHT, "-1");
1860 			props.applyDefault(ReaderView.PROP_SHOW_BATTERY, "1");
1861 			props.applyDefault(ReaderView.PROP_SHOW_POS_PERCENT, "0");
1862 			props.applyDefault(ReaderView.PROP_SHOW_PAGE_COUNT, "1");
1863 			props.applyDefault(ReaderView.PROP_FONT_KERNING_ENABLED, "0");		// by default disabled
1864 			props.applyDefault(ReaderView.PROP_FONT_SHAPING, "1");				// by default 'Light (HarfBuzz without ligatures)'
1865 			props.applyDefault(ReaderView.PROP_SHOW_TIME, "1");
1866 			props.applyDefault(ReaderView.PROP_FONT_ANTIALIASING, "2");
1867 			props.applyDefault(ReaderView.PROP_APP_GESTURE_PAGE_FLIPPING, "1");
1868 			props.applyDefault(ReaderView.PROP_APP_SHOW_COVERPAGES, "1");
1869 			props.applyDefault(ReaderView.PROP_APP_COVERPAGE_SIZE, "1");
1870 			props.applyDefault(ReaderView.PROP_APP_SCREEN_ORIENTATION, "0"); // "0"
1871 			props.applyDefault(ReaderView.PROP_CONTROLS_ENABLE_VOLUME_KEYS, "1");
1872 			props.applyDefault(ReaderView.PROP_APP_TAP_ZONE_HILIGHT, "0");
1873 			props.applyDefault(ReaderView.PROP_APP_BOOK_SORT_ORDER, FileInfo.DEF_SORT_ORDER.name());
1874 			DictInfo dict = Dictionaries.defaultDictionary();
1875 			props.applyDefault(ReaderView.PROP_APP_DICTIONARY, (dict != null) ? dict.id : "");
1876 			props.applyDefault(ReaderView.PROP_APP_FILE_BROWSER_HIDE_EMPTY_FOLDERS, "0");
1877 			props.applyDefault(ReaderView.PROP_APP_FILE_BROWSER_HIDE_EMPTY_GENRES, "0");
1878 			props.applyDefault(ReaderView.PROP_APP_SELECTION_ACTION, "0");
1879 			props.applyDefault(ReaderView.PROP_APP_MULTI_SELECTION_ACTION, "0");
1880 
1881 			props.setProperty(ReaderView.PROP_RENDER_DPI, Integer.valueOf((int)(96*mActivity.getDensityFactor())).toString());
1882 
1883 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMOUT_BLOCK_MODE, "1");
1884 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMIN_BLOCK_MODE, "1");
1885 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMOUT_INLINE_MODE, "1");
1886 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMIN_INLINE_MODE, "1");
1887 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMOUT_BLOCK_SCALE, "0");
1888 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMIN_BLOCK_SCALE, "0");
1889 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMOUT_INLINE_SCALE, "0");
1890 			props.applyDefault(ReaderView.PROP_IMG_SCALING_ZOOMIN_INLINE_SCALE, "0");
1891 
1892 			props.applyDefault(ReaderView.PROP_PAGE_MARGIN_LEFT, hmargin);
1893 			props.applyDefault(ReaderView.PROP_PAGE_MARGIN_RIGHT, hmargin);
1894 			props.applyDefault(ReaderView.PROP_PAGE_MARGIN_TOP, vmargin);
1895 			props.applyDefault(ReaderView.PROP_PAGE_MARGIN_BOTTOM, vmargin);
1896 			props.applyDefault(ReaderView.PROP_ROUNDED_CORNERS_MARGIN, "0");
1897 
1898 			if (DeviceInfo.EINK_SCREEN_REGAL)
1899 				props.applyDefault(ReaderView.PROP_APP_SCREEN_UPDATE_MODE, String.valueOf(EinkScreen.EinkUpdateMode.Regal.code));
1900 			else
1901 				props.applyDefault(ReaderView.PROP_APP_SCREEN_UPDATE_MODE, String.valueOf(EinkScreen.EinkUpdateMode.Clear.code));
1902 			props.applyDefault(ReaderView.PROP_APP_SCREEN_UPDATE_INTERVAL, "10");
1903 
1904 			props.applyDefault(ReaderView.PROP_NIGHT_MODE, "0");
1905 			if (DeviceInfo.FORCE_HC_THEME) {
1906 				props.applyDefault(ReaderView.PROP_PAGE_BACKGROUND_IMAGE, Engine.NO_TEXTURE.id);
1907 			} else {
1908 				if (props.getBool(ReaderView.PROP_NIGHT_MODE, false))
1909 					props.applyDefault(ReaderView.PROP_PAGE_BACKGROUND_IMAGE, Engine.DEF_NIGHT_BACKGROUND_TEXTURE);
1910 				else
1911 					props.applyDefault(ReaderView.PROP_PAGE_BACKGROUND_IMAGE, Engine.DEF_DAY_BACKGROUND_TEXTURE);
1912 			}
1913 			props.applyDefault(ReaderView.PROP_PAGE_BACKGROUND_IMAGE_DAY, !DeviceInfo.EINK_SCREEN ? Engine.DEF_DAY_BACKGROUND_TEXTURE : Engine.NO_TEXTURE.id);
1914 			props.applyDefault(ReaderView.PROP_PAGE_BACKGROUND_IMAGE_NIGHT, !DeviceInfo.EINK_SCREEN ? Engine.DEF_NIGHT_BACKGROUND_TEXTURE : Engine.NO_TEXTURE.id);
1915 
1916 			props.applyDefault(ReaderView.PROP_FONT_GAMMA, DeviceInfo.EINK_SCREEN ? "1.5" : "1.0");
1917 
1918 			props.setProperty(ReaderView.PROP_MIN_FILE_SIZE_TO_CACHE, "100000");
1919 			props.setProperty(ReaderView.PROP_FORCED_MIN_FILE_SIZE_TO_CACHE, "32768");
1920 			props.applyDefault(ReaderView.PROP_HYPHENATION_DICT, Engine.HyphDict.RUSSIAN.toString());
1921 			props.applyDefault(ReaderView.PROP_APP_FILE_BROWSER_SIMPLE_MODE, "0");
1922 
1923 			props.applyDefault(ReaderView.PROP_TEXTLANG_EMBEDDED_LANGS_ENABLED, "0");
1924 			props.applyDefault(ReaderView.PROP_TEXTLANG_HYPHENATION_ENABLED, "1");
1925 			props.applyDefault(ReaderView.PROP_TEXTLANG_HYPH_SOFT_HYPHENS_ONLY, "0");
1926 			props.applyDefault(ReaderView.PROP_TEXTLANG_HYPH_FORCE_ALGORITHMIC, "0");
1927 
1928 			props.applyDefault(ReaderView.PROP_STATUS_LOCATION, !DeviceInfo.EINK_SCREEN ? Settings.VIEWER_STATUS_PAGE_HEADER : Settings.VIEWER_STATUS_PAGE_FOOTER);
1929 			//props.applyDefault(ReaderView.PROP_TOOLBAR_LOCATION, DeviceInfo.getSDKLevel() < DeviceInfo.HONEYCOMB ? Settings.VIEWER_TOOLBAR_NONE : Settings.VIEWER_TOOLBAR_SHORT_SIDE);
1930 			props.applyDefault(ReaderView.PROP_TOOLBAR_LOCATION, Settings.VIEWER_TOOLBAR_NONE);
1931 			props.applyDefault(ReaderView.PROP_TOOLBAR_HIDE_IN_FULLSCREEN, "0");
1932 
1933 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_ENABLED, "0");
1934 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_SETTINGS, "0");
1935 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_BOOKMARKS, "0");
1936 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_CURRENTBOOK_INFO, "0");
1937 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_CURRENTBOOK_BODY, "0");
1938 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_GOOGLEDRIVE_AUTOSAVEPERIOD, "5");		// 5 min.
1939 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_CONFIRMATIONS, "1");
1940 			props.applyDefault(ReaderView.PROP_APP_CLOUDSYNC_DATA_KEEPALIVE, "14");				// 2 weeks
1941 
1942 			if (!DeviceInfo.EINK_SCREEN) {
1943 				props.applyDefault(ReaderView.PROP_APP_HIGHLIGHT_BOOKMARKS, "1");
1944 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_SELECTION_COLOR, "#AAAAAA");
1945 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT, "#AAAA55");
1946 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION, "#C07070");
1947 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_SELECTION_COLOR_DAY, "#AAAAAA");
1948 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT_DAY, "#AAAA55");
1949 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION_DAY, "#C07070");
1950 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_SELECTION_COLOR_NIGHT, "#808080");
1951 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT_NIGHT, "#A09060");
1952 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION_NIGHT, "#906060");
1953 			} else {
1954 				props.applyDefault(ReaderView.PROP_APP_HIGHLIGHT_BOOKMARKS, "2");
1955 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_SELECTION_COLOR, "#808080");
1956 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_COMMENT, "#000000");
1957 				props.applyDefault(ReaderView.PROP_HIGHLIGHT_BOOKMARK_COLOR_CORRECTION, "#000000");
1958 			}
1959 
1960 			return props;
1961 		}
1962 
getSettingsFile(int profile)1963 		public File getSettingsFile(int profile) {
1964 			if (profile == 0)
1965 				return propsFile;
1966 			return new File(propsFile.getAbsolutePath() + ".profile" + profile);
1967 		}
1968 
1969 		File propsFile;
1970 		private static final String SETTINGS_FILE_NAME = "cr3.ini";
1971 		private static boolean DEBUG_RESET_OPTIONS = false;
1972 
loadSettings()1973 		private Properties loadSettings() {
1974 			File[] dataDirs = Engine.getDataDirectories(null, false, true);
1975 			File existingFile = null;
1976 			for (File dir : dataDirs) {
1977 				File f = new File(dir, SETTINGS_FILE_NAME);
1978 				if (f.exists() && f.isFile()) {
1979 					existingFile = f;
1980 					break;
1981 				}
1982 			}
1983 			if (existingFile != null) {
1984 				propsFile = existingFile;
1985 			} else {
1986 				File propsDir = defaultSettingsDir;
1987 				propsFile = new File(propsDir, SETTINGS_FILE_NAME);
1988 				File dataDir = Engine.getExternalSettingsDir();
1989 				if (dataDir != null) {
1990 					log.d("external settings dir: " + dataDir);
1991 					propsFile = Engine.checkOrMoveFile(dataDir, propsDir, SETTINGS_FILE_NAME);
1992 				} else {
1993 					propsDir.mkdirs();
1994 				}
1995 			}
1996 
1997 			Properties props = loadSettings(mActivity, propsFile);
1998 
1999 			return props;
2000 		}
2001 
loadSettings(int profile)2002 		public Properties loadSettings(int profile) {
2003 			File f = getSettingsFile(profile);
2004 			if (!f.exists() && profile != 0)
2005 				f = getSettingsFile(0);
2006 			Properties res = loadSettings(mActivity, f);
2007 			if (profile != 0) {
2008 				res = filterProfileSettings(res);
2009 				res.setInt(Settings.PROP_PROFILE_NUMBER, profile);
2010 			}
2011 			return res;
2012 		}
2013 
filterProfileSettings(Properties settings)2014 		public static Properties filterProfileSettings(Properties settings) {
2015 			Properties res = new Properties();
2016 			res.entrySet();
2017 			for (Object k : settings.keySet()) {
2018 				String key = (String) k;
2019 				String value = settings.getProperty(key);
2020 				boolean found = false;
2021 				for (String pattern : Settings.PROFILE_SETTINGS) {
2022 					if (pattern.endsWith("*")) {
2023 						if (key.startsWith(pattern.substring(0, pattern.length() - 1))) {
2024 							found = true;
2025 							break;
2026 						}
2027 					} else if (pattern.equalsIgnoreCase(key)) {
2028 						found = true;
2029 						break;
2030 					} else if (key.startsWith("styles.")) {
2031 						found = true;
2032 						break;
2033 					}
2034 				}
2035 				if (found) {
2036 					res.setProperty(key, value);
2037 				}
2038 			}
2039 			return res;
2040 		}
2041 
saveSettings(int profile, Properties settings)2042 		public void saveSettings(int profile, Properties settings) {
2043 			if (settings == null)
2044 				settings = mSettings;
2045 			File f = getSettingsFile(profile);
2046 			if (profile != 0) {
2047 				settings = filterProfileSettings(settings);
2048 				settings.setInt(Settings.PROP_PROFILE_NUMBER, profile);
2049 			}
2050 			saveSettings(f, settings);
2051 		}
2052 
2053 		DelayedExecutor saveSettingsTask = DelayedExecutor.createBackground("saveSettings");
2054 
saveSettings(File f, Properties settings)2055 		public void saveSettings(File f, Properties settings) {
2056 			try {
2057 				log.v("saveSettings()");
2058 				FileOutputStream os = new FileOutputStream(f);
2059 				settings.store(os, "Cool Reader 3 settings");
2060 				log.i("Settings successfully saved to file " + f.getAbsolutePath());
2061 			} catch (Exception e) {
2062 				log.e("exception while saving settings", e);
2063 			}
2064 		}
2065 
saveSettings(Properties settings)2066 		public void saveSettings(Properties settings) {
2067 			saveSettings(propsFile, settings);
2068 		}
2069 
2070 
getSetting(String name)2071 		public String getSetting(String name) {
2072 			return mSettings.getProperty(name);
2073 		}
2074 
getSetting(String name, String defaultValue)2075 		public String getSetting(String name, String defaultValue) {
2076 			return mSettings.getProperty(name, defaultValue);
2077 		}
2078 
getBool(String name, boolean defaultValue)2079 		public boolean getBool(String name, boolean defaultValue) {
2080 			return mSettings.getBool(name, defaultValue);
2081 		}
2082 
getInt(String name, int defaultValue)2083 		public int getInt(String name, int defaultValue) {
2084 			return mSettings.getInt(name, defaultValue);
2085 		}
2086 
get()2087 		public Properties get() {
2088 			return new Properties(mSettings);
2089 		}
2090 
2091 	}
2092 
2093 
getDictList()2094 	public static DictInfo[] getDictList() {
2095 		return Dictionaries.getDictList();
2096 	}
2097 
isPackageInstalled(String packageName)2098 	public boolean isPackageInstalled(String packageName) {
2099 		PackageManager pm = getPackageManager();
2100 		try {
2101 			pm.getPackageInfo(packageName, 0); //PackageManager.GET_ACTIVITIES);
2102 			return true;
2103 		} catch (PackageManager.NameNotFoundException e) {
2104 			return false;
2105 		}
2106 	}
2107 
2108 	private Boolean hasHardwareMenuKey = null;
2109 
hasHardwareMenuKey()2110 	public boolean hasHardwareMenuKey() {
2111 		if (hasHardwareMenuKey == null) {
2112 			ViewConfiguration vc = ViewConfiguration.get(this);
2113 			if (DeviceInfo.getSDKLevel() >= 14) {
2114 				//boolean vc.hasPermanentMenuKey();
2115 				try {
2116 					Method m = vc.getClass().getMethod("hasPermanentMenuKey", new Class<?>[]{});
2117 					try {
2118 						hasHardwareMenuKey = (Boolean) m.invoke(vc, new Object[]{});
2119 					} catch (IllegalArgumentException e) {
2120 						hasHardwareMenuKey = false;
2121 					} catch (IllegalAccessException e) {
2122 						hasHardwareMenuKey = false;
2123 					} catch (InvocationTargetException e) {
2124 						hasHardwareMenuKey = false;
2125 					}
2126 				} catch (NoSuchMethodException e) {
2127 					hasHardwareMenuKey = false;
2128 				}
2129 			}
2130 			if (hasHardwareMenuKey == null) {
2131 				if (DeviceInfo.EINK_SCREEN)
2132 					hasHardwareMenuKey = false;
2133 				else if (DeviceInfo.getSDKLevel() < DeviceInfo.ICE_CREAM_SANDWICH)
2134 					hasHardwareMenuKey = true;
2135 				else
2136 					hasHardwareMenuKey = false;
2137 			}
2138 		}
2139 		return hasHardwareMenuKey;
2140 	}
2141 }
2142