-
Notifications
You must be signed in to change notification settings - Fork 30
/
LightningView.java
1256 lines (1152 loc) · 42.3 KB
/
LightningView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2014 A.C.R. Development
*/
package com.jtechme.jumpgo.view;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebSettings.PluginState;
import android.webkit.WebView;
import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.Single;
import com.anthonycr.bonsai.SingleAction;
import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.bonsai.SingleSubscriber;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Map;
import javax.inject.Inject;
import com.jtechme.jumpgo.BrowserApp;
import com.jtechme.jumpgo.constant.BookmarkPage;
import com.jtechme.jumpgo.constant.Constants;
import com.jtechme.jumpgo.constant.DownloadsPage;
import com.jtechme.jumpgo.constant.StartPage;
import com.jtechme.jumpgo.controller.UIController;
import com.jtechme.jumpgo.dialog.LightningDialogBuilder;
import com.jtechme.jumpgo.download.LightningDownloadListener;
import com.jtechme.jumpgo.preference.PreferenceManager;
import com.jtechme.jumpgo.utils.Preconditions;
import com.jtechme.jumpgo.utils.ProxyUtils;
import com.jtechme.jumpgo.utils.UrlUtils;
import com.jtechme.jumpgo.utils.Utils;
/**
* {@link LightningView} acts as a tab for the browser,
* handling WebView creation and handling logic, as well
* as properly initialing it. All interactions with the
* WebView should be made through this class.
*/
public class LightningView {
private static final String TAG = "LightningView";
public static final String HEADER_REQUESTED_WITH = "X-Requested-With";
public static final String HEADER_WAP_PROFILE = "X-Wap-Profile";
private static final String HEADER_DNT = "DNT";
private static final int API = android.os.Build.VERSION.SDK_INT;
private static final int SCROLL_UP_THRESHOLD = Utils.dpToPx(10);
@Nullable private static String sHomepage;
@Nullable private static String sDefaultUserAgent;
private static float sMaxFling;
private static final float[] sNegativeColorArray = {
-1.0f, 0, 0, 0, 255, // red
0, -1.0f, 0, 0, 255, // green
0, 0, -1.0f, 0, 255, // blue
0, 0, 0, 1.0f, 0 // alpha
};
private static final float[] sIncreaseContrastColorArray = {
2.0f, 0, 0, 0, -160.f, // red
0, 2.0f, 0, 0, -160.f, // green
0, 0, 2.0f, 0, -160.f, // blue
0, 0, 0, 1.0f, 0 // alpha
};
@NonNull private final LightningViewTitle mTitle;
@Nullable private WebView mWebView;
@NonNull private final UIController mUIController;
@NonNull private final GestureDetector mGestureDetector;
@NonNull private final Activity mActivity;
@NonNull private final Paint mPaint = new Paint();
private boolean mIsNewTab;
private final boolean mIsIncognitoTab;
private boolean mIsForegroundTab;
private boolean mInvertPage = false;
private boolean mToggleDesktop = false;
@NonNull private final WebViewHandler mWebViewHandler = new WebViewHandler(this);
@NonNull private final Map<String, String> mRequestHeaders = new ArrayMap<>();
@Inject PreferenceManager mPreferences;
@Inject LightningDialogBuilder mDialogBuilder;
@Inject ProxyUtils mProxyUtils;
private final LightningWebClient mLightningWebClient;
public LightningView(@NonNull Activity activity, @Nullable String url, boolean isIncognito) {
BrowserApp.getAppComponent().inject(this);
mActivity = activity;
mUIController = (UIController) activity;
mWebView = new WebView(activity);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
mWebView.setId(View.generateViewId());
}
mIsIncognitoTab = isIncognito;
mTitle = new LightningViewTitle(activity);
sMaxFling = ViewConfiguration.get(activity).getScaledMaximumFlingVelocity();
mWebView.setDrawingCacheBackgroundColor(Color.WHITE);
mWebView.setFocusableInTouchMode(true);
mWebView.setFocusable(true);
mWebView.setDrawingCacheEnabled(false);
mWebView.setWillNotCacheDrawing(true);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
//noinspection deprecation
mWebView.setAnimationCacheEnabled(false);
//noinspection deprecation
mWebView.setAlwaysDrawnWithCacheEnabled(false);
}
mWebView.setBackgroundColor(Color.WHITE);
mWebView.setScrollbarFadingEnabled(true);
mWebView.setSaveEnabled(true);
mWebView.setNetworkAvailable(true);
mWebView.setWebChromeClient(new LightningChromeClient(activity, this));
mLightningWebClient = new LightningWebClient(activity, this);
mWebView.setWebViewClient(mLightningWebClient);
mWebView.setDownloadListener(new LightningDownloadListener(activity));
mGestureDetector = new GestureDetector(activity, new CustomGestureListener());
mWebView.setOnTouchListener(new TouchListener());
sDefaultUserAgent = mWebView.getSettings().getUserAgentString();
initializeSettings();
initializePreferences(activity);
if (url != null) {
if (!url.trim().isEmpty()) {
mWebView.loadUrl(url, mRequestHeaders);
} else {
// don't load anything, the user is looking for a blank tab
}
} else {
loadHomepage();
}
}
/**
* Sets whether this tab was the
* result of a new intent sent
* to the browser.
*
* @param isNewTab true if it's from
* a new intent,
* false otherwise.
*/
public void setIsNewTab(boolean isNewTab) {
mIsNewTab = isNewTab;
}
/**
* Returns whether this tab was created
* as a result of a new intent.
*
* @return true if it was a new intent,
* false otherwise.
*/
public boolean isNewTab() {
return mIsNewTab;
}
/**
* This method loads the homepage for the browser. Either
* it loads the URL stored as the homepage, or loads the
* startpage or bookmark page if either of those are set
* as the homepage.
*/
public void loadHomepage() {
if (mWebView == null) {
return;
}
Preconditions.checkNonNull(sHomepage);
switch (sHomepage) {
case Constants.SCHEME_HOMEPAGE:
loadStartpage();
break;
case Constants.SCHEME_BOOKMARKS:
loadBookmarkpage();
break;
default:
mWebView.loadUrl(sHomepage, mRequestHeaders);
break;
}
}
/**
* This method gets the startpage URL from the {@link StartPage}
* class asynchronously and loads the URL in the WebView on the
* UI thread.
*/
private void loadStartpage() {
new StartPage().getHomepage()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<String>() {
@Override
public void onItem(@Nullable String item) {
Preconditions.checkNonNull(item);
loadUrl(item);
}
});
}
/**
* This method gets the bookmark page URL from the {@link BookmarkPage}
* class asynchronously and loads the URL in the WebView on the
* UI thread. It also caches the default folder icon locally.
*/
public void loadBookmarkpage() {
new BookmarkPage(mActivity).getBookmarkPage()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<String>() {
@Override
public void onItem(@Nullable String item) {
Preconditions.checkNonNull(item);
loadUrl(item);
}
});
}
/**
* This method gets the bookmark page URL from the {@link BookmarkPage}
* class asynchronously and loads the URL in the WebView on the
* UI thread. It also caches the default folder icon locally.
*/
public void loadDownloadspage() {
new DownloadsPage().getDownloadsPage()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<String>() {
@Override
public void onItem(@Nullable String item) {
Preconditions.checkNonNull(item);
loadUrl(item);
}
});
}
/**
* Initialize the preference driven settings of the WebView. This method
* must be called whenever the preferences are changed within SharedPreferences.
*
* @param context the context in which the WebView was created, it is used
* to get the default UserAgent for the WebView.
*/
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
public synchronized void initializePreferences(@NonNull Context context) {
if (mWebView == null) {
return;
}
mLightningWebClient.updatePreferences();
WebSettings settings = mWebView.getSettings();
if (mPreferences.getDoNotTrackEnabled()) {
mRequestHeaders.put(HEADER_DNT, "1");
} else {
mRequestHeaders.remove(HEADER_DNT);
}
if (mPreferences.getRemoveIdentifyingHeadersEnabled()) {
mRequestHeaders.put(HEADER_REQUESTED_WITH, "");
mRequestHeaders.put(HEADER_WAP_PROFILE, "");
} else {
mRequestHeaders.remove(HEADER_REQUESTED_WITH);
mRequestHeaders.remove(HEADER_WAP_PROFILE);
}
settings.setDefaultTextEncodingName(mPreferences.getTextEncoding());
sHomepage = mPreferences.getHomepage();
setColorMode(mPreferences.getRenderingMode());
if (!mIsIncognitoTab) {
settings.setGeolocationEnabled(mPreferences.getLocationEnabled());
} else {
settings.setGeolocationEnabled(false);
}
if (API < Build.VERSION_CODES.KITKAT) {
switch (mPreferences.getFlashSupport()) {
case 0:
//noinspection deprecation
settings.setPluginState(PluginState.OFF);
break;
case 1:
//noinspection deprecation
settings.setPluginState(PluginState.ON_DEMAND);
break;
case 2:
//noinspection deprecation
settings.setPluginState(PluginState.ON);
break;
default:
break;
}
}
setUserAgent(context, mPreferences.getUserAgentChoice());
if (mPreferences.getSavePasswordsEnabled() && !mIsIncognitoTab) {
if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//noinspection deprecation
settings.setSavePassword(true);
}
settings.setSaveFormData(true);
} else {
if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//noinspection deprecation
settings.setSavePassword(false);
}
settings.setSaveFormData(false);
}
if (mPreferences.getJavaScriptEnabled()) {
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
} else {
settings.setJavaScriptEnabled(false);
settings.setJavaScriptCanOpenWindowsAutomatically(false);
}
if (mPreferences.getTextReflowEnabled()) {
settings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
if (API >= android.os.Build.VERSION_CODES.KITKAT) {
try {
settings.setLayoutAlgorithm(LayoutAlgorithm.TEXT_AUTOSIZING);
} catch (Exception e) {
// This shouldn't be necessary, but there are a number
// of KitKat devices that crash trying to set this
Log.e(TAG, "Problem setting LayoutAlgorithm to TEXT_AUTOSIZING");
}
}
} else {
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
}
settings.setBlockNetworkImage(mPreferences.getBlockImagesEnabled());
if (!mIsIncognitoTab) {
settings.setSupportMultipleWindows(mPreferences.getPopupsEnabled());
} else {
settings.setSupportMultipleWindows(false);
}
settings.setUseWideViewPort(mPreferences.getUseWideViewportEnabled());
settings.setLoadWithOverviewMode(mPreferences.getOverviewModeEnabled());
switch (mPreferences.getTextSize()) {
case 0:
settings.setTextZoom(200);
break;
case 1:
settings.setTextZoom(150);
break;
case 2:
settings.setTextZoom(125);
break;
case 3:
settings.setTextZoom(100);
break;
case 4:
settings.setTextZoom(75);
break;
case 5:
settings.setTextZoom(50);
break;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView,
!mPreferences.getBlockThirdPartyCookiesEnabled());
}
}
/**
* Initialize the settings of the WebView that are intrinsic to Lightning and cannot
* be altered by the user. Distinguish between Incognito and Regular tabs here.
*/
@SuppressLint("NewApi")
private void initializeSettings() {
if (mWebView == null) {
return;
}
final WebSettings settings = mWebView.getSettings();
if (API < Build.VERSION_CODES.JELLY_BEAN_MR2) {
//noinspection deprecation
settings.setAppCacheMaxSize(Long.MAX_VALUE);
}
if (API < Build.VERSION_CODES.JELLY_BEAN_MR1) {
//noinspection deprecation
settings.setEnableSmoothTransition(true);
}
if (API > Build.VERSION_CODES.JELLY_BEAN) {
settings.setMediaPlaybackRequiresUserGesture(true);
}
if (API >= Build.VERSION_CODES.LOLLIPOP && !mIsIncognitoTab) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
} else if (API >= Build.VERSION_CODES.LOLLIPOP) {
// We're in Incognito mode, reject
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
}
if (!mIsIncognitoTab) {
settings.setDomStorageEnabled(true);
settings.setAppCacheEnabled(true);
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setDatabaseEnabled(true);
} else {
settings.setDomStorageEnabled(false);
settings.setAppCacheEnabled(false);
settings.setDatabaseEnabled(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
}
settings.setSupportZoom(true);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setAllowContentAccess(true);
settings.setAllowFileAccess(true);
if (API >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);
}
getPathObservable("appcache").subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<File>() {
@Override
public void onItem(@Nullable File item) {
Preconditions.checkNonNull(item);
settings.setAppCachePath(item.getPath());
}
});
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
getPathObservable("geolocation").subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<File>() {
@Override
public void onItem(@Nullable File item) {
Preconditions.checkNonNull(item);
//noinspection deprecation
settings.setGeolocationDatabasePath(item.getPath());
}
});
}
getPathObservable("databases").subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<File>() {
@Override
public void onItem(@Nullable File item) {
if (API < Build.VERSION_CODES.KITKAT) {
Preconditions.checkNonNull(item);
//noinspection deprecation
settings.setDatabasePath(item.getPath());
}
}
@Override
public void onComplete() {
}
});
}
@NonNull
private Single<File> getPathObservable(final String subFolder) {
return Single.create(new SingleAction<File>() {
@Override
public void onSubscribe(@NonNull SingleSubscriber<File> subscriber) {
File file = mActivity.getDir(subFolder, 0);
subscriber.onItem(file);
subscriber.onComplete();
}
});
}
/**
* Getter for the {@link LightningViewTitle} of the
* current LightningView instance.
*
* @return a NonNull instance of LightningViewTitle
*/
@NonNull
public LightningViewTitle getTitleInfo() {
return mTitle;
}
/**
* Returns whether or not the current tab is incognito or not.
*
* @return true if this tab is incognito, false otherwise
*/
public boolean isIncognito() {
return mIsIncognitoTab;
}
/**
* This method is used to toggle the user agent between desktop
* and the current preference of the user.
*
* @param context the Context needed to set the user agent
*/
public void toggleDesktopUA(@NonNull Context context) {
if (mWebView == null)
return;
if (!mToggleDesktop)
mWebView.getSettings().setUserAgentString(Constants.DESKTOP_USER_AGENT);
else
setUserAgent(context, mPreferences.getUserAgentChoice());
mToggleDesktop = !mToggleDesktop;
}
/**
* This method sets the user agent of the current tab.
* There are four options, 1, 2, 3, 4.
* <p/>
* 1. use the default user agent
* <p/>
* 2. use the desktop user agent
* <p/>
* 3. use the mobile user agent
* <p/>
* 4. use a custom user agent, or the default user agent
* if none was set.
*
* @param context the context needed to get the default user agent.
* @param choice the choice of user agent to use, see above comments.
*/
@SuppressLint("NewApi")
private void setUserAgent(Context context, int choice) {
if (mWebView == null) return;
WebSettings settings = mWebView.getSettings();
switch (choice) {
case 1:
if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
settings.setUserAgentString(WebSettings.getDefaultUserAgent(context));
} else {
settings.setUserAgentString(sDefaultUserAgent);
}
break;
case 2:
settings.setUserAgentString(Constants.DESKTOP_USER_AGENT);
break;
case 3:
settings.setUserAgentString(Constants.MOBILE_USER_AGENT);
break;
case 4:
String ua = mPreferences.getUserAgentString(sDefaultUserAgent);
if (ua == null || ua.isEmpty()) {
ua = " ";
}
settings.setUserAgentString(ua);
break;
}
}
/**
* This method gets the additional headers that should be
* added with each request the browser makes.
*
* @return a non null Map of Strings with the additional
* request headers.
*/
@NonNull
Map<String, String> getRequestHeaders() {
return mRequestHeaders;
}
/**
* This method determines whether the current tab is visible or not.
*
* @return true if the WebView is non-null and visible, false otherwise.
*/
public boolean isShown() {
return mWebView != null && mWebView.isShown();
}
/**
* Pause the current WebView instance.
*/
public synchronized void onPause() {
if (mWebView != null) {
mWebView.onPause();
Log.d(TAG, "WebView onPause: " + mWebView.getId());
}
}
/**
* Resume the current WebView instance.
*/
public synchronized void onResume() {
if (mWebView != null) {
mWebView.onResume();
Log.d(TAG, "WebView onResume: " + mWebView.getId());
}
}
/**
* Notify the LightningView that there is low memory and
* for the WebView to free memory. Only applicable on
* pre-Lollipop devices.
*/
@Deprecated
public synchronized void freeMemory() {
if (mWebView != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
//noinspection deprecation
mWebView.freeMemory();
}
}
/**
* This method sets the tab as the foreground tab or
* the background tab.
*
* @param isForeground true if the tab should be set as
* foreground, false otherwise.
*/
public void setIsForegroundTab(boolean isForeground) {
mIsForegroundTab = isForeground;
mUIController.tabChanged(this);
}
/**
* Determines if the tab is in the foreground or not.
*
* @return true if the tab is the foreground tab,
* false otherwise.
*/
public boolean isForegroundTab() {
return mIsForegroundTab;
}
/**
* Gets the current progress of the WebView.
*
* @return returns a number between 0 and 100 with
* the current progress of the WebView. If the WebView
* is null, then the progress returned will be 100.
*/
public int getProgress() {
if (mWebView != null) {
return mWebView.getProgress();
} else {
return 100;
}
}
/**
* Notify the WebView to stop the current load.
*/
public synchronized void stopLoading() {
if (mWebView != null) {
mWebView.stopLoading();
}
}
/**
* This method forces the layer type to hardware, which
* enables hardware rendering on the WebView instance
* of the current LightningView.
*/
private void setHardwareRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
}
/**
* This method sets the layer type to none, which
* means that either the GPU and CPU can both compose
* the layers when necessary.
*/
private void setNormalRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_NONE, null);
}
/**
* This method forces the layer type to software, which
* disables hardware rendering on the WebView instance
* of the current LightningView and makes the CPU render
* the view.
*/
public void setSoftwareRendering() {
if (mWebView == null) {
return;
}
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
/**
* Sets the current rendering color of the WebView instance
* of the current LightningView. The for modes are normal
* rendering (0), inverted rendering (1), grayscale rendering (2),
* and inverted grayscale rendering (3)
*
* @param mode the integer mode to set as the rendering mode.
* see the numbers in documentation above for the
* values this method accepts.
*/
private void setColorMode(int mode) {
mInvertPage = false;
switch (mode) {
case 0:
mPaint.setColorFilter(null);
// setSoftwareRendering(); // Some devices get segfaults
// in the WebView with Hardware Acceleration enabled,
// the only fix is to disable hardware rendering
setNormalRendering();
mInvertPage = false;
break;
case 1:
ColorMatrixColorFilter filterInvert = new ColorMatrixColorFilter(
sNegativeColorArray);
mPaint.setColorFilter(filterInvert);
setHardwareRendering();
mInvertPage = true;
break;
case 2:
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter filterGray = new ColorMatrixColorFilter(cm);
mPaint.setColorFilter(filterGray);
setHardwareRendering();
break;
case 3:
ColorMatrix matrix = new ColorMatrix();
matrix.set(sNegativeColorArray);
ColorMatrix matrixGray = new ColorMatrix();
matrixGray.setSaturation(0);
ColorMatrix concat = new ColorMatrix();
concat.setConcat(matrix, matrixGray);
ColorMatrixColorFilter filterInvertGray = new ColorMatrixColorFilter(concat);
mPaint.setColorFilter(filterInvertGray);
setHardwareRendering();
mInvertPage = true;
break;
case 4:
ColorMatrixColorFilter IncreaseHighContrast = new ColorMatrixColorFilter(
sIncreaseContrastColorArray);
mPaint.setColorFilter(IncreaseHighContrast);
setHardwareRendering();
break;
}
}
/**
* Pauses the JavaScript timers of the
* WebView instance, which will trigger a
* pause for all WebViews in the app.
*/
public synchronized void pauseTimers() {
if (mWebView != null) {
mWebView.pauseTimers();
Log.d(TAG, "Pausing JS timers");
}
}
/**
* Resumes the JavaScript timers of the
* WebView instance, which will trigger a
* resume for all WebViews in the app.
*/
public synchronized void resumeTimers() {
if (mWebView != null) {
mWebView.resumeTimers();
Log.d(TAG, "Resuming JS timers");
}
}
/**
* Requests focus down on the WebView instance
* if the view does not already have focus.
*/
public void requestFocus() {
if (mWebView != null && !mWebView.hasFocus()) {
mWebView.requestFocus();
}
}
/**
* Sets the visibility of the WebView to either
* View.GONE, View.VISIBLE, or View.INVISIBLE.
* other values passed in will have no effect.
*
* @param visible the visibility to set on the WebView.
*/
public void setVisibility(int visible) {
if (mWebView != null) {
mWebView.setVisibility(visible);
}
}
/**
* Tells the WebView to reload the current page.
* If the proxy settings are not ready then the
* this method will not have an affect as the
* proxy must start before the load occurs.
*/
public synchronized void reload() {
// Check if configured proxy is available
if (!mProxyUtils.isProxyReady(mActivity)) {
// User has been notified
return;
}
if (mWebView != null) {
mWebView.reload();
}
}
/**
* Finds all the instances of the text passed to this
* method and highlights the instances of that text
* in the WebView.
*
* @param text the text to search for.
*/
@SuppressLint("NewApi")
public synchronized void find(String text) {
if (mWebView != null) {
if (API >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mWebView.findAllAsync(text);
} else {
//noinspection deprecation
mWebView.findAll(text);
}
}
}
/**
* Notify the tab to shutdown and destroy
* its WebView instance and to remove the reference
* to it. After this method is called, the current
* instance of the LightningView is useless as
* the WebView cannot be recreated using the public
* api.
*/
// TODO fix bug where WebView.destroy is being called before the tab
// is removed and would cause a memory leak if the parent check
// was not in place.
public synchronized void onDestroy() {
if (mWebView != null) {
// Check to make sure the WebView has been removed
// before calling destroy() so that a memory leak is not created
ViewGroup parent = (ViewGroup) mWebView.getParent();
if (parent != null) {
Log.e(TAG, "WebView was not detached from window before onDestroy");
parent.removeView(mWebView);
}
mWebView.stopLoading();
mWebView.onPause();
mWebView.clearHistory();
mWebView.setVisibility(View.GONE);
mWebView.removeAllViews();
mWebView.destroyDrawingCache();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//this is causing the segfault occasionally below 4.2
mWebView.destroy();
}
mWebView = null;
}
}
/**
* Tell the WebView to navigate backwards
* in its history to the previous page.
*/
public synchronized void goBack() {
if (mWebView != null) {
mWebView.goBack();
}
}
/**
* Tell the WebView to navigate forwards
* in its history to the next page.
*/
public synchronized void goForward() {
if (mWebView != null) {
mWebView.goForward();
}
}
/**
* Get the current user agent used
* by the WebView.
*
* @return retuns the current user agent
* of the WebView instance, or an empty
* string if the WebView is null.
*/
@NonNull
private String getUserAgent() {
if (mWebView != null) {
return mWebView.getSettings().getUserAgentString();
} else {
return "";
}
}
/**
* Move the highlighted text in the WebView
* to the next matched text. This method will
* only have an affect after {@link LightningView#find(String)}
* is called. Otherwise it will do nothing.
*/
public synchronized void findNext() {
if (mWebView != null) {
mWebView.findNext(true);
}
}
/**
* Move the highlighted text in the WebView
* to the previous matched text. This method will
* only have an affect after {@link LightningView#find(String)}
* is called. Otherwise it will do nothing.
*/
public synchronized void findPrevious() {
if (mWebView != null) {
mWebView.findNext(false);
}
}
/**
* Clear the highlighted text in the WebView after
* {@link LightningView#find(String)} has been called.
* Otherwise it will have no affect.
*/
public synchronized void clearFindMatches() {
if (mWebView != null) {
mWebView.clearMatches();
}
}
/**
* Gets whether or not the page rendering is inverted or not.
* The main purpose of this is to indicate that JavaScript
* should be run at the end of a page load to invert only
* the images back to their uninverted states.
*
* @return true if the page is in inverted mode, false otherwise.
*/
public boolean getInvertePage() {
return mInvertPage;
}
/**
* Handles a long click on the page and delegates the URL to the
* proper dialog if it is not null, otherwise, it tries to get the
* URL using HitTestResult.
*
* @param url the url that should have been obtained from the WebView touch node
* thingy, if it is null, this method tries to deal with it and find
* a workaround.
*/
private void longClickPage(@Nullable final String url) {
if (mWebView == null) {
return;
}
final WebView.HitTestResult result = mWebView.getHitTestResult();
String currentUrl = mWebView.getUrl();
if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) {
if (UrlUtils.isHistoryUrl(currentUrl)) {
if (url != null) {
mDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, mUIController, url);
} else if (result != null && result.getExtra() != null) {
final String newUrl = result.getExtra();