Repository: wizos/loread
Branch: master
Commit: bec024a07bee
Files: 522
Total size: 2.2 MB
Directory structure:
gitextract_1feoynfu/
├── .github/
│ └── workflows/
│ └── android.yml
├── .gitignore
├── README.md
├── agentweb-core/
│ ├── .gitignore
│ ├── build.gradle
│ ├── providedLibs/
│ │ └── alipaySdk-20180601.jar
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── just/
│ │ └── agentweb/
│ │ ├── AbsAgentWebSettings.java
│ │ ├── AbsAgentWebUIController.java
│ │ ├── Action.java
│ │ ├── ActionActivity.java
│ │ ├── AgentWeb.java
│ │ ├── AgentWebConfig.java
│ │ ├── AgentWebFileProvider.java
│ │ ├── AgentWebJsInterfaceCompat.java
│ │ ├── AgentWebPermissions.java
│ │ ├── AgentWebSettingsImpl.java
│ │ ├── AgentWebUIControllerImplBase.java
│ │ ├── AgentWebUtils.java
│ │ ├── AgentWebView.java
│ │ ├── BaseIndicatorSpec.java
│ │ ├── BaseIndicatorView.java
│ │ ├── BaseJsAccessEntrace.java
│ │ ├── DefaultChromeClient.java
│ │ ├── DefaultDesignUIController.java
│ │ ├── DefaultDownloadImpl.java
│ │ ├── DefaultUIController.java
│ │ ├── DefaultWebClient.java
│ │ ├── DefaultWebCreator.java
│ │ ├── DefaultWebLifeCycleImpl.java
│ │ ├── EventHandlerImpl.java
│ │ ├── EventInterceptor.java
│ │ ├── HookManager.java
│ │ ├── HttpHeaders.java
│ │ ├── IAgentWebSettings.java
│ │ ├── IEventHandler.java
│ │ ├── IUrlLoader.java
│ │ ├── IVideo.java
│ │ ├── IWebIndicator.java
│ │ ├── IWebLayout.java
│ │ ├── IndicatorController.java
│ │ ├── IndicatorHandler.java
│ │ ├── JsAccessEntrace.java
│ │ ├── JsAccessEntraceImpl.java
│ │ ├── JsBaseInterfaceHolder.java
│ │ ├── JsCallJava.java
│ │ ├── JsCallback.java
│ │ ├── JsInterfaceHolder.java
│ │ ├── JsInterfaceHolderImpl.java
│ │ ├── JsInterfaceObjectException.java
│ │ ├── LayoutParamsOffer.java
│ │ ├── LogUtils.java
│ │ ├── LollipopFixedWebView.java
│ │ ├── MiddlewareWebChromeBase.java
│ │ ├── MiddlewareWebClientBase.java
│ │ ├── NestedScrollAgentWebView.java
│ │ ├── PermissionInterceptor.java
│ │ ├── ProcessUtils.java
│ │ ├── Provider.java
│ │ ├── QuickCallJs.java
│ │ ├── UrlCommonException.java
│ │ ├── UrlLoaderImpl.java
│ │ ├── VideoImpl.java
│ │ ├── WebChromeClient.java
│ │ ├── WebChromeClientDelegate.java
│ │ ├── WebCreator.java
│ │ ├── WebIndicator.java
│ │ ├── WebLifeCycle.java
│ │ ├── WebListenerManager.java
│ │ ├── WebParentLayout.java
│ │ ├── WebSecurityCheckLogic.java
│ │ ├── WebSecurityController.java
│ │ ├── WebSecurityControllerImpl.java
│ │ ├── WebSecurityLogicImpl.java
│ │ ├── WebViewClient.java
│ │ └── WebViewClientDelegate.java
│ └── res/
│ ├── layout/
│ │ └── agentweb_error_page.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── style.xml
│ └── values-zh/
│ └── strings.xml
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── libs/
│ │ └── okgo-3.0.4.jar
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── me/
│ │ └── wizos/
│ │ └── loread/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ ├── css/
│ │ │ ├── android_studio.css
│ │ │ ├── article_theme_day.css
│ │ │ ├── article_theme_night.css
│ │ │ └── normalize.css
│ │ └── js/
│ │ ├── highlight.pack.js
│ │ ├── lazyload.js
│ │ └── media.js
│ ├── java/
│ │ └── me/
│ │ └── wizos/
│ │ └── loread/
│ │ ├── App.java
│ │ ├── Contract.java
│ │ ├── activity/
│ │ │ ├── ArticleActivity.java
│ │ │ ├── BaseActivity.java
│ │ │ ├── FeedActivity.java
│ │ │ ├── LabActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── MusicActivity.java
│ │ │ ├── ProviderActivity.java
│ │ │ ├── RuleGenerateActivity.java
│ │ │ ├── SearchActivity.java
│ │ │ ├── SettingActivity.java
│ │ │ ├── SplashActivity.java
│ │ │ ├── TTSActivity.java
│ │ │ ├── WebActivity.java
│ │ │ └── login/
│ │ │ ├── LoginFormState.java
│ │ │ ├── LoginInoReaderActivity.java
│ │ │ ├── LoginResult.java
│ │ │ └── LoginTinyRSSActivity.java
│ │ ├── adapter/
│ │ │ ├── ArticlePagedListAdapter.java
│ │ │ ├── ArticleViewBinder.java
│ │ │ └── ExpandedAdapter.java
│ │ ├── bean/
│ │ │ ├── Enclosure.java
│ │ │ ├── LogLevel.java
│ │ │ ├── Token.java
│ │ │ ├── UserAgent.java
│ │ │ ├── domain/
│ │ │ │ ├── OutFeed.java
│ │ │ │ └── OutTag.java
│ │ │ ├── feedly/
│ │ │ │ ├── CategoryItem.java
│ │ │ │ ├── Collection.java
│ │ │ │ ├── ContentDirection.java
│ │ │ │ ├── Counts.java
│ │ │ │ ├── Entry.java
│ │ │ │ ├── FeedItem.java
│ │ │ │ ├── Origin.java
│ │ │ │ ├── Profile.java
│ │ │ │ ├── StreamContents.java
│ │ │ │ ├── StreamIds.java
│ │ │ │ ├── Subscription.java
│ │ │ │ ├── Unreadcount.java
│ │ │ │ ├── Visual.java
│ │ │ │ └── input/
│ │ │ │ ├── EditCollection.java
│ │ │ │ ├── EditFeed.java
│ │ │ │ └── MarkerAction.java
│ │ │ ├── fever/
│ │ │ │ ├── BaseResponse.java
│ │ │ │ ├── Feed.java
│ │ │ │ ├── Feeds.java
│ │ │ │ ├── Group.java
│ │ │ │ ├── GroupFeeds.java
│ │ │ │ ├── Groups.java
│ │ │ │ ├── Item.java
│ │ │ │ ├── Items.java
│ │ │ │ ├── SavedItemIds.java
│ │ │ │ └── UnreadItemIds.java
│ │ │ ├── inoreader/
│ │ │ │ ├── EditTag.java
│ │ │ │ ├── GsItemContents.java
│ │ │ │ ├── GsTag.java
│ │ │ │ ├── GsTags.java
│ │ │ │ ├── GsUnreadCount.java
│ │ │ │ ├── ItemIds.java
│ │ │ │ ├── ItemRefs.java
│ │ │ │ ├── LoginResult.java
│ │ │ │ ├── Readability.java
│ │ │ │ ├── StreamContents.java
│ │ │ │ ├── StreamPref.java
│ │ │ │ ├── StreamPrefs.java
│ │ │ │ ├── SubCategories.java
│ │ │ │ ├── Subscription.java
│ │ │ │ ├── Subscriptions.java
│ │ │ │ ├── UnreadCounts.java
│ │ │ │ ├── UserInfo.java
│ │ │ │ └── itemContents/
│ │ │ │ ├── Item.java
│ │ │ │ ├── Origin.java
│ │ │ │ ├── Self.java
│ │ │ │ └── Summary.java
│ │ │ ├── loread/
│ │ │ │ ├── LoginParam.java
│ │ │ │ ├── RequestJsonBody.java
│ │ │ │ └── Response.java
│ │ │ ├── proxynode/
│ │ │ │ ├── AnonymityLevel.java
│ │ │ │ ├── ProxyNode.java
│ │ │ │ └── ProxyType.java
│ │ │ ├── search/
│ │ │ │ ├── QuickAdd.java
│ │ │ │ ├── SearchFeedItem.java
│ │ │ │ ├── SearchFeeds.java
│ │ │ │ └── StreamFeed.java
│ │ │ └── ttrss/
│ │ │ ├── request/
│ │ │ │ ├── GetArticles.java
│ │ │ │ ├── GetCategories.java
│ │ │ │ ├── GetFeeds.java
│ │ │ │ ├── GetHeadlines.java
│ │ │ │ ├── GetSavedItemIds.java
│ │ │ │ ├── GetUnreadItemIds.java
│ │ │ │ ├── LoginParam.java
│ │ │ │ ├── RequestParam.java
│ │ │ │ ├── SearchHeadlines.java
│ │ │ │ ├── SearchMode.java
│ │ │ │ ├── SubscribeToFeed.java
│ │ │ │ ├── UnsubscribeFeed.java
│ │ │ │ └── UpdateArticle.java
│ │ │ └── result/
│ │ │ ├── ArticleItem.java
│ │ │ ├── Attachment.java
│ │ │ ├── CategoryItem.java
│ │ │ ├── FeedItem.java
│ │ │ ├── SubscribeToFeedResult.java
│ │ │ ├── TTRSSLoginResult.java
│ │ │ ├── TinyResponse.java
│ │ │ └── UpdateArticleResult.java
│ │ ├── behavior/
│ │ │ ├── BottomNavigationBehavior.java
│ │ │ └── BottomNavigationViewBehavior.java
│ │ ├── bridge/
│ │ │ ├── ArticleBridge.java
│ │ │ └── WebBridge.java
│ │ ├── config/
│ │ │ ├── AdBlock.java
│ │ │ ├── ArticleActionConfig.java
│ │ │ ├── ArticleExtractConfig.java
│ │ │ ├── ArticleTags.java
│ │ │ ├── HostConfig.java
│ │ │ ├── LinkRewriteConfig.java
│ │ │ ├── NetworkRefererConfig.java
│ │ │ ├── NetworkUserAgentConfig.java
│ │ │ ├── SaveDirectory.java
│ │ │ ├── TestConfig.java
│ │ │ ├── Unsubscribe.java
│ │ │ ├── article_action_rule/
│ │ │ │ ├── ArticleActionRule.java
│ │ │ │ └── Condition.java
│ │ │ └── article_extract_rule/
│ │ │ ├── ArticleExtractRule.java
│ │ │ └── Selector.java
│ │ ├── db/
│ │ │ ├── Article.java
│ │ │ ├── ArticleDao.java
│ │ │ ├── ArticleTag.java
│ │ │ ├── ArticleTagDao.java
│ │ │ ├── Category.java
│ │ │ ├── CategoryDao.java
│ │ │ ├── CategoryView.java
│ │ │ ├── Collection.java
│ │ │ ├── CoreDB.java
│ │ │ ├── CorePref.java
│ │ │ ├── Entry.java
│ │ │ ├── Feed.java
│ │ │ ├── FeedCategory.java
│ │ │ ├── FeedCategoryDao.java
│ │ │ ├── FeedDao.java
│ │ │ ├── FeedView.java
│ │ │ ├── Tag.java
│ │ │ ├── TagDao.java
│ │ │ ├── User.java
│ │ │ └── UserDao.java
│ │ ├── extractor/
│ │ │ ├── Extractor.java
│ │ │ ├── ExtractorUtil.java
│ │ │ └── ModPage.java
│ │ ├── gson/
│ │ │ ├── GsonEnum.java
│ │ │ ├── GsonEnumTypeAdapter.java
│ │ │ └── GsonUtil.java
│ │ ├── network/
│ │ │ ├── HttpClientManager.java
│ │ │ ├── StringConverterFactory.java
│ │ │ ├── SyncWorker.java
│ │ │ ├── api/
│ │ │ │ ├── AuthApi.java
│ │ │ │ ├── BaseApi.java
│ │ │ │ ├── FeedlyApi.java
│ │ │ │ ├── FeedlyService.java
│ │ │ │ ├── FeverApi.java
│ │ │ │ ├── FeverService.java
│ │ │ │ ├── InoReaderApi.java
│ │ │ │ ├── InoReaderService.java
│ │ │ │ ├── LoginInterface.java
│ │ │ │ ├── LoreadApi.java
│ │ │ │ ├── LoreadService.java
│ │ │ │ ├── OAuthApi.java
│ │ │ │ ├── TinyRSSApi.java
│ │ │ │ └── TinyRSSService.java
│ │ │ ├── callback/
│ │ │ │ └── CallbackX.java
│ │ │ ├── glide/
│ │ │ │ └── OkHttpAppGlideModule.java
│ │ │ └── interceptor/
│ │ │ ├── InoreaderHeaderInterceptor.java
│ │ │ ├── LoggerInterceptor.java
│ │ │ ├── LoreadTokenInterceptor.java
│ │ │ ├── RedirectInterceptor.java
│ │ │ ├── RefererInterceptor.java
│ │ │ ├── RelyInterceptor.java
│ │ │ ├── TTRSSTokenInterceptor.java
│ │ │ ├── TokenAuthenticator.java
│ │ │ └── TokenInterceptor.java
│ │ ├── service/
│ │ │ ├── AudioService.java
│ │ │ ├── MainService.java
│ │ │ ├── MusicService.java
│ │ │ └── NetworkStateReceiver.java
│ │ ├── utils/
│ │ │ ├── ArticleUtil.java
│ │ │ ├── ColorModifier.java
│ │ │ ├── DataUtil.java
│ │ │ ├── EncryptUtil.java
│ │ │ ├── FileUtil.java
│ │ │ ├── ImageUtil.java
│ │ │ ├── ImgFileType.java
│ │ │ ├── NetworkUtil.java
│ │ │ ├── RGB.java
│ │ │ ├── ScreenUtil.java
│ │ │ ├── ScriptUtil.java
│ │ │ ├── SnackbarUtil.java
│ │ │ ├── StringJoiner.java
│ │ │ ├── StringUtils.java
│ │ │ ├── SymbolUtil.java
│ │ │ ├── TimeUtil.java
│ │ │ ├── Tool.java
│ │ │ ├── UriUtil.java
│ │ │ └── VideoInjectUtil.java
│ │ ├── view/
│ │ │ ├── ExpandableListViewS.java
│ │ │ ├── FriendlyCardView.java
│ │ │ ├── IconFontView.java
│ │ │ ├── SwipeRefreshLayoutS.java
│ │ │ ├── WebViewS.java
│ │ │ ├── colorful/
│ │ │ │ ├── Colorful.java
│ │ │ │ ├── StatusBarUtil.java
│ │ │ │ ├── StatusBarView.java
│ │ │ │ └── setter/
│ │ │ │ ├── TextColorSetter.java
│ │ │ │ ├── ViewBackgroundColorSetter.java
│ │ │ │ ├── ViewBackgroundDrawableSetter.java
│ │ │ │ ├── ViewGroupSetter.java
│ │ │ │ └── ViewSetter.java
│ │ │ ├── fastscroll/
│ │ │ │ ├── FastScrollDelegate.java
│ │ │ │ ├── FastScrollListView.java
│ │ │ │ ├── FastScrollRecyclerView.java
│ │ │ │ └── ListViewS.java
│ │ │ ├── slideback/
│ │ │ │ ├── SlideBack.java
│ │ │ │ ├── SlideBackManager.java
│ │ │ │ ├── SlideLayout.java
│ │ │ │ ├── callback/
│ │ │ │ │ ├── SlideBackCallBack.java
│ │ │ │ │ └── SlideCallBack.java
│ │ │ │ └── widget/
│ │ │ │ ├── SlideBackIconView.java
│ │ │ │ └── SlideBackInterceptLayout.java
│ │ │ └── webview/
│ │ │ ├── DownloadListenerS.java
│ │ │ ├── FastScrollWebView.java
│ │ │ ├── LongClickPopWindow.java
│ │ │ ├── NestedScrollWebView.java
│ │ │ ├── SlowlyProgressBar.java
│ │ │ └── VideoImpl.java
│ │ └── viewmodel/
│ │ ├── ArticleViewModel.java
│ │ ├── InoReaderUserViewModel.java
│ │ └── TinyRSSUserViewModel.java
│ └── res/
│ ├── anim/
│ │ ├── fade_in.xml
│ │ ├── fade_out.xml
│ │ ├── in_from_bottom.xml
│ │ └── out_from_bottom.xml
│ ├── drawable/
│ │ ├── corners_bg_checked.xml
│ │ ├── corners_bg_uncheck.xml
│ │ ├── custom_progress_bar_thumb.xml
│ │ ├── custom_thumb_src.xml
│ │ ├── flyme_style_switch_button_rectangle.xml
│ │ ├── flyme_style_switch_button_round.xml
│ │ ├── ic_arrow_auto_mark_readed.xml
│ │ ├── ic_arrow_right.xml
│ │ ├── ic_browser.xml
│ │ ├── ic_close.xml
│ │ ├── ic_copy_link.xml
│ │ ├── ic_eye.xml
│ │ ├── ic_favor.xml
│ │ ├── ic_favor_fill.xml
│ │ ├── ic_mark_down.xml
│ │ ├── ic_mark_unread.xml
│ │ ├── ic_mark_up.xml
│ │ ├── ic_music.xml
│ │ ├── ic_panorama.xml
│ │ ├── ic_read.xml
│ │ ├── ic_refresh.xml
│ │ ├── ic_rename.xml
│ │ ├── ic_share.xml
│ │ ├── ic_state_all.xml
│ │ ├── ic_state_star.xml
│ │ ├── ic_state_unread.xml
│ │ ├── ic_state_unstar.xml
│ │ ├── ic_stop_loading.xml
│ │ ├── ic_unsubscribe.xml
│ │ ├── ic_user_agent.xml
│ │ ├── ic_volume.xml
│ │ ├── logo_feedly.xml
│ │ ├── logo_inoreader.xml
│ │ ├── progress_bg.xml
│ │ ├── seekbar_audio.xml
│ │ ├── selector_corners_bg.xml
│ │ ├── selector_star.xml
│ │ ├── splash_layers.xml
│ │ ├── textview_border_day.xml
│ │ └── textview_border_night.xml
│ ├── drawable-v23/
│ │ ├── logo_feedly_icon.xml
│ │ ├── logo_inoreader_icon.xml
│ │ └── logo_ttrss_icon.xml
│ ├── layout/
│ │ ├── activity_article.xml
│ │ ├── activity_feed.xml
│ │ ├── activity_lab.xml
│ │ ├── activity_login_inoreader.xml
│ │ ├── activity_login_tiny_rss.xml
│ │ ├── activity_main.xml
│ │ ├── activity_main_list_item.xml
│ │ ├── activity_music.xml
│ │ ├── activity_provider.xml
│ │ ├── activity_provider_low_version.xml
│ │ ├── activity_rule_generate.xml
│ │ ├── activity_search.xml
│ │ ├── activity_search_list_header_result_count.xml
│ │ ├── activity_search_list_header_word.xml
│ │ ├── activity_search_list_item_feed.xml
│ │ ├── activity_setting.xml
│ │ ├── activity_tts.xml
│ │ ├── activity_web.xml
│ │ ├── bottom_sheet_category.xml
│ │ ├── config_download_view.xml
│ │ ├── main_bottom_sheet_more.xml
│ │ ├── main_item_header.xml
│ │ ├── md_simplelist_item.xml
│ │ ├── setting_item_arrow.xml
│ │ ├── setting_item_session.xml
│ │ ├── setting_item_switch.xml
│ │ ├── tag_expandable_item_child.xml
│ │ ├── tag_expandable_item_group.xml
│ │ └── webview_long_clicked_popwindow.xml
│ ├── menu/
│ │ ├── menu_article.xml
│ │ └── menu_web.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── attr.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ └── xml/
│ ├── account_authenticator.xml
│ ├── account_preferences.xml
│ ├── account_sync_adapter.xml
│ └── network_security_config.xml
├── build.gradle
├── config.json
├── floatwindow/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── yhao/
│ │ └── floatwindow/
│ │ ├── adaptation/
│ │ │ ├── Miui.java
│ │ │ └── Rom.java
│ │ ├── base/
│ │ │ └── FloatLifecycle.java
│ │ ├── constant/
│ │ │ ├── MoveType.java
│ │ │ └── Screen.java
│ │ ├── interfaces/
│ │ │ ├── FloatView.java
│ │ │ ├── IFloatWindow.java
│ │ │ ├── LifecycleListener.java
│ │ │ ├── PermissionListener.java
│ │ │ ├── ResumedListener.java
│ │ │ └── ViewStateListener.java
│ │ ├── permission/
│ │ │ └── FloatActivity.java
│ │ ├── util/
│ │ │ ├── ActivityCounter.java
│ │ │ ├── DensityUtil.java
│ │ │ ├── LogUtil.java
│ │ │ └── PermissionUtil.java
│ │ └── view/
│ │ ├── FloatPhone.java
│ │ ├── FloatToast.java
│ │ ├── FloatWindow.java
│ │ └── IFloatWindowImpl.java
│ └── res/
│ └── values/
│ └── style.xml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── luban/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── top/
│ │ └── zibin/
│ │ └── luban/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── top/
│ │ └── zibin/
│ │ └── luban/
│ │ ├── Checker.java
│ │ ├── CompressionPredicate.java
│ │ ├── Engine.java
│ │ ├── FileProvider.java
│ │ ├── InputStreamProvider.java
│ │ ├── Luban.java
│ │ ├── OnCompressListener.java
│ │ └── OnRenameListener.java
│ └── res/
│ └── values/
│ └── strings.xml
├── privacy_and_security.md
├── settings.gradle
├── support/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.txt
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── yanzhenjie/
│ │ └── recyclerview/
│ │ ├── AdapterWrapper.java
│ │ ├── Controller.java
│ │ ├── ExpandableAdapter.java
│ │ ├── Horizontal.java
│ │ ├── LeftHorizontal.java
│ │ ├── OnItemClickListener.java
│ │ ├── OnItemLongClickListener.java
│ │ ├── OnItemMenuClickListener.java
│ │ ├── OnItemSwipeListener.java
│ │ ├── RightHorizontal.java
│ │ ├── StickyCreator.java
│ │ ├── StickyHeaderLayout.java
│ │ ├── StickyViewHolder.java
│ │ ├── SwipeDragLayout.java
│ │ ├── SwipeMenu.java
│ │ ├── SwipeMenuBridge.java
│ │ ├── SwipeMenuCreator.java
│ │ ├── SwipeMenuItem.java
│ │ ├── SwipeMenuLayout.java
│ │ ├── SwipeMenuView.java
│ │ ├── SwipeRecyclerView.java
│ │ ├── listview/
│ │ │ ├── FastScrollDelegate.java
│ │ │ └── FastScrollRecyclerView.java
│ │ ├── touch/
│ │ │ ├── DefaultItemTouchHelper.java
│ │ │ ├── ItemTouchHelperCallback.java
│ │ │ ├── OnItemMoveListener.java
│ │ │ ├── OnItemMovementListener.java
│ │ │ └── OnItemStateChangedListener.java
│ │ └── widget/
│ │ ├── BorderItemDecoration.java
│ │ ├── ColorDrawer.java
│ │ ├── DefaultItemDecoration.java
│ │ ├── DefaultLoadMoreView.java
│ │ ├── Drawer.java
│ │ └── StickyNestedScrollView.java
│ └── res/
│ ├── layout/
│ │ ├── support_recycler_view_item.xml
│ │ ├── support_recycler_view_item2.xml
│ │ └── support_recycler_view_load_more.xml
│ ├── values/
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ └── strings.xml
│ ├── values-zh/
│ │ └── strings.xml
│ ├── values-zh-rHK/
│ │ └── strings.xml
│ └── values-zh-rTW/
│ └── strings.xml
└── swipelayout/
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── ditclear/
│ └── swipelayout/
│ └── SwipeDragLayout.java
└── res/
└── values/
├── attrs.xml
└── strings.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/android.yml
================================================
name: Loread CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Make Gradle executable
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Release apk
uses: ncipollo/release-action@v1.5.0
with:
artifacts: "build/app/outputs/apk/release/*.apk"
token: ${{ secrets.GITHUB_RElEASE_TOKEN }}
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
.DS_Store
/build
/.idea
================================================
FILE: README.md
================================================
# 序
> 路很长,纵然远望,却不知方向。
> 抽支烟,思绪无常,奔跑着彷徨。
> 逃不脱的苟且,到不了的远方…
# 简介
RSS 第三方客户端,支持 Inoreader、Feedly、TinyTinyRSS。
下载地址:[http://www.coolapk.com/apk/168423](http://www.coolapk.com/apk/168423)
# 截图

如上图,从左至右依次为“登录、首页、文章页、分类、快速设置、设置”
# 功能
目前实现以下几个功能:
- [x] 黑夜主题
- [x] 获取全文:支持根据规则或智能识别全文
- [x] 保存近期文章的阅读进度
- [x] 左右切换文章
- [x] 自动清理过期文章
- [x] 不同状态下(未读/加星/全部),各分组内文章的数量
- [x] ~~保存 离线状态下的一些网络请求(文章状态处理,图片下载),待有网再同步~~
对文章列表项的手势操作:
- [x] 左滑是切换文章的“已读/未读”状态
- [x] 右滑是切换文章的“加星/取消加星”状态
- [x] 长按是“上面的文章标记为已读,下面的文章标记为已读”
PS:
* 由于开发中本人也还在不断学习,难免有些历史遗留的错误代码以及注释,暂时未被清理,但不影响使用
# 后期规划
### Bug
- [x] 优化反色算法,解决灰反色问题
- [ ] 优化音频莫名暂停问题
- [ ] 优化 ROOM 库带来的问题
### 功能
- [x] 支持全文搜索
- [ ] 优化朗读、播放音乐的界面
- [ ] 支持本地 RSS
- [ ] 支持获取不支持 RSS 站点的文章
- [ ] 支持更换主题
- [ ] 支持设置排版:字体、字号、字距、行距、背景色
- [ ] 支持长按视频,图片,iframe候展示菜单
- [ ] 本地训练机器学习模型,判断文章喜好
- [ ] 检查添加的订阅地址是否有相似的订阅
### 技术
- [ ] 优化代码结构,拆成不同模块
- [ ] 改用最新的技术,例如 Kotlin
- [ ] 使用 CI 自动构建 APK 包
# 库的使用
* OkHttp, Gson, ROOM, Glide 等等
================================================
FILE: agentweb-core/.gitignore
================================================
/build
/src/androidTest/
/src/test/
================================================
FILE: agentweb-core/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
defaultConfig {
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions{
abortOnError false
}
repositories {
flatDir {
dirs 'libs', 'providedLibs'
}
}
// defaultPublishConfig "debug"
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testImplementation 'junit:junit:4.13'
implementation 'com.download.library:Downloader:4.1.2'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation fileTree(include: ['*.jar'], dir: 'providedLibs')
}
================================================
FILE: agentweb-core/src/main/AndroidManifest.xml
================================================
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebSettings.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* @author cenxiaozhong
* @update 4.0.0 ,WebDefaultSettingsManager rename to AbsAgentWebSettings
* @since 1.0.0
*/
public abstract class AbsAgentWebSettings implements IAgentWebSettings, WebListenerManager {
private WebSettings mWebSettings;
private static final String TAG = AbsAgentWebSettings.class.getSimpleName();
public static final String USERAGENT_UC = " UCBrowser/11.6.4.950 ";
public static final String USERAGENT_QQ_BROWSER = " MQQBrowser/8.0 ";
public static final String USERAGENT_AGENTWEB = AgentWebConfig.AGENTWEB_VERSION;
protected AgentWeb mAgentWeb;
public static AbsAgentWebSettings getInstance() {
return new AgentWebSettingsImpl();
}
public AbsAgentWebSettings() {
}
final void bindAgentWeb(AgentWeb agentWeb) {
this.mAgentWeb = agentWeb;
this.bindAgentWebSupport(agentWeb);
}
protected abstract void bindAgentWebSupport(AgentWeb agentWeb);
@Override
public IAgentWebSettings toSetting(WebView webView) {
settings(webView);
return this;
}
private void settings(WebView webView) {
mWebSettings = webView.getSettings();
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setSupportZoom(true);
mWebSettings.setBuiltInZoomControls(false);
mWebSettings.setSavePassword(false);
if (AgentWebUtils.checkNetwork(webView.getContext())) {
//根据cache-control获取数据。
mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
//没网,则从本地获取,即离线加载
mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//适配5.0不允许http和https混合使用情况
mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
mWebSettings.setTextZoom(100);
mWebSettings.setDatabaseEnabled(true);
mWebSettings.setAppCacheEnabled(true);
mWebSettings.setLoadsImagesAutomatically(true);
mWebSettings.setSupportMultipleWindows(false);
// 是否阻塞加载网络图片 协议http or https
mWebSettings.setBlockNetworkImage(false);
// 允许加载本地文件html file协议
mWebSettings.setAllowFileAccess(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// 通过 file url 加载的 Javascript 读取其他的本地文件 .建议关闭
mWebSettings.setAllowFileAccessFromFileURLs(false);
// 允许通过 file url 加载的 Javascript 可以访问其他的源,包括其他的文件和 http,https 等其他的源
mWebSettings.setAllowUniversalAccessFromFileURLs(false);
}
mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
} else {
mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
}
mWebSettings.setLoadWithOverviewMode(false);
mWebSettings.setUseWideViewPort(false);
mWebSettings.setDomStorageEnabled(true);
mWebSettings.setNeedInitialFocus(true);
mWebSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
mWebSettings.setDefaultFontSize(16);
mWebSettings.setMinimumFontSize(12);//设置 WebView 支持的最小字体大小,默认为 8
mWebSettings.setGeolocationEnabled(true);
String dir = AgentWebConfig.getCachePath(webView.getContext());
LogUtils.i(TAG, "dir:" + dir + " appcache:" + AgentWebConfig.getCachePath(webView.getContext()));
//设置数据库路径 api19 已经废弃,这里只针对 webkit 起作用
mWebSettings.setGeolocationDatabasePath(dir);
mWebSettings.setDatabasePath(dir);
mWebSettings.setAppCachePath(dir);
//缓存文件最大值
mWebSettings.setAppCacheMaxSize(Long.MAX_VALUE);
mWebSettings.setUserAgentString(getWebSettings()
.getUserAgentString()
.concat(USERAGENT_AGENTWEB)
.concat(USERAGENT_UC)
);
LogUtils.i(TAG, "UserAgentString : " + mWebSettings.getUserAgentString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 安卓9.0后不允许多进程使用同一个数据目录,需设置前缀来区分
// 参阅 https://blog.csdn.net/lvshuchangyin/article/details/89446629
Context context = webView.getContext();
String processName = ProcessUtils.getCurrentProcessName(context);
if (!context.getApplicationContext().getPackageName().equals(processName)) {
WebView.setDataDirectorySuffix(processName);
}
}
}
@Override
public WebSettings getWebSettings() {
return mWebSettings;
}
@Override
public WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient) {
webview.setWebChromeClient(webChromeClient);
return this;
}
@Override
public WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient) {
webView.setWebViewClient(webViewClient);
return this;
}
@Override
public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) {
webView.setDownloadListener(downloadListener);
return this;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AbsAgentWebUIController.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.app.Dialog;
import android.os.Handler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
/**
* 该类统一控制了与用户交互的界面
*
* @author cenxiaozhong
* @since 3.0.0
*/
public abstract class AbsAgentWebUIController {
public static boolean HAS_DESIGN_LIB = false;
private Activity mActivity;
private WebParentLayout mWebParentLayout;
private volatile boolean mIsBindWebParent = false;
protected AbsAgentWebUIController mAgentWebUIControllerDelegate;
protected String TAG = this.getClass().getSimpleName();
static {
try {
Class.forName("android.support.design.widget.Snackbar");
Class.forName("android.support.design.widget.BottomSheetDialog");
HAS_DESIGN_LIB = true;
} catch (Throwable ignore) {
HAS_DESIGN_LIB = false;
}
}
protected AbsAgentWebUIController create() {
return HAS_DESIGN_LIB ? new DefaultDesignUIController() : new DefaultUIController();
}
protected AbsAgentWebUIController getDelegate() {
AbsAgentWebUIController mAgentWebUIController = this.mAgentWebUIControllerDelegate;
if (mAgentWebUIController == null) {
this.mAgentWebUIControllerDelegate = mAgentWebUIController = create();
}
return mAgentWebUIController;
}
final synchronized void bindWebParent(WebParentLayout webParentLayout, Activity activity) {
if (!mIsBindWebParent) {
mIsBindWebParent = true;
this.mWebParentLayout = webParentLayout;
this.mActivity = activity;
bindSupportWebParent(webParentLayout, activity);
}
}
protected void toDismissDialog(Dialog dialog) {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
protected void toShowDialog(Dialog dialog) {
if (dialog != null && !dialog.isShowing()) {
dialog.show();
}
}
protected abstract void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity);
/**
* WebChromeClient#onJsAlert
*
* @param view
* @param url
* @param message
*/
public abstract void onJsAlert(WebView view, String url, String message);
/**
* 咨询用户是否前往其他页面
*
* @param view
* @param url
* @param callback
*/
public abstract void onOpenPagePrompt(WebView view, String url, Handler.Callback callback);
/**
* WebChromeClient#onJsConfirm
*
* @param view
* @param url
* @param message
* @param jsResult
*/
public abstract void onJsConfirm(WebView view, String url, String message, JsResult jsResult);
public abstract void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback);
/**
* 强制下载弹窗
*
* @param url 当前下载地址。
* @param callback 用户操作回调回调
*/
public abstract void onForceDownloadAlert(String url, Handler.Callback callback);
/**
* WebChromeClient#onJsPrompt
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param jsPromptResult
*/
public abstract void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult);
/**
* 显示错误页
*
* @param view
* @param errorCode
* @param description
* @param failingUrl
*/
public abstract void onMainFrameError(WebView view, int errorCode, String description, String failingUrl);
/**
* 隐藏错误页
*/
public abstract void onShowMainFrame();
/**
* 正在加载...
*
* @param msg
*/
public abstract void onLoading(String msg);
/**
* 取消正在加载...
*/
public abstract void onCancelLoading();
/**
* @param message 消息
* @param intent 说明message的来源,意图
*/
public abstract void onShowMessage(String message, String intent);
/**
* 当权限被拒回调该方法
*
* @param permissions
* @param permissionType
* @param action
*/
public abstract void onPermissionsDeny(String[] permissions, String permissionType, String action);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/Action.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author cenxiaozhong
* @since 2.0.0
*/
public class Action implements Parcelable {
public transient static final int ACTION_PERMISSION = 1;
public transient static final int ACTION_FILE = 2;
public transient static final int ACTION_CAMERA = 3;
public transient static final int ACTION_VIDEO = 4;
private ArrayList mPermissions = new ArrayList();
private int mAction;
private int mFromIntention;
public Action() {
}
public ArrayList getPermissions() {
return mPermissions;
}
public void setPermissions(ArrayList permissions) {
this.mPermissions = permissions;
}
public void setPermissions(String[] permissions) {
this.mPermissions = new ArrayList<>(Arrays.asList(permissions));
}
public int getAction() {
return mAction;
}
public void setAction(int action) {
this.mAction = action;
}
protected Action(Parcel in) {
mPermissions = in.createStringArrayList();
mAction = in.readInt();
mFromIntention = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringList(mPermissions);
dest.writeInt(mAction);
dest.writeInt(mFromIntention);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator CREATOR = new Creator() {
@Override
public Action createFromParcel(Parcel in) {
return new Action(in);
}
@Override
public Action[] newArray(int size) {
return new Action[size];
}
};
public int getFromIntention() {
return mFromIntention;
}
public static Action createPermissionsAction(String[] permissions) {
Action mAction = new Action();
mAction.setAction(Action.ACTION_PERMISSION);
List mList = Arrays.asList(permissions);
mAction.setPermissions(new ArrayList(mList));
return mAction;
}
public Action setFromIntention(int fromIntention) {
this.mFromIntention = fromIntention;
return this;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/ActionActivity.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.util.List;
import static android.provider.MediaStore.EXTRA_OUTPUT;
/**
* @since 2.0.0
* @author cenxiaozhong
*/
public final class ActionActivity extends Activity {
public static final String KEY_ACTION = "KEY_ACTION";
public static final String KEY_URI = "KEY_URI";
public static final String KEY_FROM_INTENTION = "KEY_FROM_INTENTION";
public static final String KEY_FILE_CHOOSER_INTENT = "KEY_FILE_CHOOSER_INTENT";
private static RationaleListener mRationaleListener;
private static PermissionListener mPermissionListener;
private static ChooserListener mChooserListener;
private static final String TAG = ActionActivity.class.getSimpleName();
private Action mAction;
public static final int REQUEST_CODE = 0x254;
public static void start(Activity activity, Action action) {
Intent mIntent = new Intent(activity, ActionActivity.class);
mIntent.putExtra(KEY_ACTION, action);
// mIntent.setExtrasClassLoader(Action.class.getClassLoader());
activity.startActivity(mIntent);
}
public static void setChooserListener(ChooserListener chooserListener) {
mChooserListener = chooserListener;
}
public static void setPermissionListener(PermissionListener permissionListener) {
mPermissionListener = permissionListener;
}
private void cancelAction() {
mChooserListener = null;
mPermissionListener = null;
mRationaleListener = null;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
LogUtils.i(TAG, "savedInstanceState:" + savedInstanceState);
return;
}
Intent intent = getIntent();
mAction = intent.getParcelableExtra(KEY_ACTION);
if (mAction == null) {
cancelAction();
this.finish();
return;
}
if (mAction.getAction() == Action.ACTION_PERMISSION) {
permission(mAction);
} else if (mAction.getAction() == Action.ACTION_CAMERA) {
realOpenCamera();
} else if (mAction.getAction() == Action.ACTION_VIDEO){
realOpenVideo();
} else {
fetchFile(mAction);
}
}
private void fetchFile(Action action) {
if (mChooserListener == null) {
finish();
}
realOpenFileChooser();
}
private void realOpenFileChooser() {
try {
if (mChooserListener == null) {
finish();
return;
}
Intent mIntent = getIntent().getParcelableExtra(KEY_FILE_CHOOSER_INTENT);
if (mIntent == null) {
cancelAction();
return;
}
this.startActivityForResult(mIntent, REQUEST_CODE);
} catch (Throwable throwable) {
LogUtils.i(TAG, "找不到文件选择器");
chooserActionCallback(-1, null);
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
}
}
private void chooserActionCallback(int resultCode, Intent data) {
if (mChooserListener != null) {
mChooserListener.onChoiceResult(REQUEST_CODE, resultCode, data);
mChooserListener = null;
}
finish();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
chooserActionCallback(resultCode, mUri != null ? new Intent().putExtra(KEY_URI, mUri) : data);
}
}
private void permission(Action action) {
List permissions = action.getPermissions();
if (AgentWebUtils.isEmptyCollection(permissions)) {
mPermissionListener = null;
mRationaleListener = null;
finish();
return;
}
if (mRationaleListener != null) {
boolean rationale = false;
for (String permission : permissions) {
rationale = shouldShowRequestPermissionRationale(permission);
if (rationale) {
break;
}
}
mRationaleListener.onRationaleResult(rationale, new Bundle());
mRationaleListener = null;
finish();
return;
}
if (mPermissionListener != null){
requestPermissions(permissions.toArray(new String[]{}), 1);
}
}
private Uri mUri;
private void realOpenCamera() {
try {
if (mChooserListener == null){
finish();
}
File mFile = AgentWebUtils.createImageFile(this);
if (mFile == null) {
mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null);
mChooserListener = null;
finish();
}
Intent intent = AgentWebUtils.getIntentCaptureCompat(this, mFile);
// 指定开启系统相机的Action
mUri = intent.getParcelableExtra(EXTRA_OUTPUT);
this.startActivityForResult(intent, REQUEST_CODE);
} catch (Throwable ignore) {
LogUtils.e(TAG, "找不到系统相机");
if (mChooserListener != null) {
mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null);
}
mChooserListener = null;
if (LogUtils.isDebug()){
ignore.printStackTrace();
}
}
}
private void realOpenVideo(){
try {
if (mChooserListener == null){
finish();
}
File mFile = AgentWebUtils.createVideoFile(this);
if (mFile == null) {
mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null);
mChooserListener = null;
finish();
}
Intent intent = AgentWebUtils.getIntentVideoCompat(this, mFile);
// 指定开启系统相机的Action
mUri = intent.getParcelableExtra(EXTRA_OUTPUT);
this.startActivityForResult(intent, REQUEST_CODE);
} catch (Throwable ignore) {
LogUtils.e(TAG, "找不到系统相机");
if (mChooserListener != null) {
mChooserListener.onChoiceResult(REQUEST_CODE, Activity.RESULT_CANCELED, null);
}
mChooserListener = null;
if (LogUtils.isDebug()){
ignore.printStackTrace();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (mPermissionListener != null) {
Bundle mBundle = new Bundle();
mBundle.putInt(KEY_FROM_INTENTION, mAction.getFromIntention());
mPermissionListener.onRequestPermissionsResult(permissions, grantResults, mBundle);
}
mPermissionListener = null;
finish();
}
public interface RationaleListener {
void onRationaleResult(boolean showRationale, Bundle extras);
}
public interface PermissionListener {
void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras);
}
public interface ChooserListener {
void onChoiceResult(int requestCode, int resultCode, Intent data);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWeb.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import androidx.fragment.app.Fragment;
import java.lang.ref.WeakReference;
import java.util.Map;
/**
* @author cenxiaozhong
* @update 4.0.0
* @since 1.0.0
*/
public final class AgentWeb {
/**
* AgentWeb 's TAG
*/
private static final String TAG = AgentWeb.class.getSimpleName();
/**
* Activity
*/
private Activity mActivity;
/**
* 承载 WebParentLayout 的 ViewGroup
*/
private ViewGroup mViewGroup;
/**
* 负责创建布局 WebView ,WebParentLayout Indicator等。
*/
private WebCreator mWebCreator;
/**
* 管理 WebSettings
*/
private IAgentWebSettings mAgentWebSettings;
/**
* AgentWeb
*/
private AgentWeb mAgentWeb = null;
/**
* IndicatorController 控制Indicator
*/
private IndicatorController mIndicatorController;
/**
* WebChromeClient
*/
private com.just.agentweb.WebChromeClient mWebChromeClient;
/**
* WebViewClient
*/
private com.just.agentweb.WebViewClient mWebViewClient;
/**
* is show indicator
*/
private boolean mEnableIndicator;
/**
* IEventHandler 处理WebView相关返回事件
*/
private IEventHandler mIEventHandler;
/**
* WebView 注入对象
*/
private ArrayMap mJavaObjects = new ArrayMap<>();
/**
* flag
*/
private int TAG_TARGET = 0;
/**
* WebListenerManager
*/
private WebListenerManager mWebListenerManager;
/**
* 安全 Controller
*/
private WebSecurityController mWebSecurityController = null;
/**
* WebSecurityCheckLogic
*/
private WebSecurityCheckLogic mWebSecurityCheckLogic = null;
/**
* WebChromeClient
*/
private WebChromeClient mTargetChromeClient;
/**
* flag security 's mode
*/
private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK;
/**
* Activity
*/
private static final int ACTIVITY_TAG = 0;
/**
* Fragment
*/
private static final int FRAGMENT_TAG = 1;
/**
* AgentWeb 默认注入对像
*/
private AgentWebJsInterfaceCompat mAgentWebJsInterfaceCompat = null;
/**
* JsAccessEntrace 提供快速JS方法调用
*/
private JsAccessEntrace mJsAccessEntrace = null;
/**
* URL Loader , 提供了 WebView#loadUrl(url) reload() stopLoading() postUrl()等方法
*/
private IUrlLoader mIUrlLoader = null;
/**
* WebView 生命周期 , 跟随生命周期释放CPU
*/
private WebLifeCycle mWebLifeCycle;
/**
* Video 视屏播放管理类
*/
private IVideo mIVideo = null;
/**
* WebViewClient 辅助控制开关
*/
private boolean mWebClientHelper = true;
/**
* PermissionInterceptor 权限拦截
*/
private PermissionInterceptor mPermissionInterceptor;
/**
* 是否拦截未知的Url, @link{DefaultWebClient}
*/
private boolean mIsInterceptUnkownUrl = false;
private int mUrlHandleWays = -1;
/**
* MiddlewareWebClientBase WebViewClient 中间件
*/
private MiddlewareWebClientBase mMiddleWrareWebClientBaseHeader;
/**
* MiddlewareWebChromeBase WebChromeClient 中间件
*/
private MiddlewareWebChromeBase mMiddlewareWebChromeBaseHeader;
/**
* 事件拦截
*/
private EventInterceptor mEventInterceptor;
/**
* 注入对象管理类
*/
private JsInterfaceHolder mJsInterfaceHolder = null;
private AgentWeb(AgentBuilder agentBuilder) {
TAG_TARGET = agentBuilder.mTag;
this.mActivity = agentBuilder.mActivity;
this.mViewGroup = agentBuilder.mViewGroup;
this.mIEventHandler = agentBuilder.mIEventHandler;
this.mEnableIndicator = agentBuilder.mEnableIndicator;
mWebCreator = agentBuilder.mWebCreator == null ? configWebCreator(agentBuilder.mBaseIndicatorView, agentBuilder.mIndex, agentBuilder.mLayoutParams, agentBuilder.mIndicatorColor, agentBuilder.mHeight, agentBuilder.mWebView, agentBuilder.mWebLayout) : agentBuilder.mWebCreator;
mIndicatorController = agentBuilder.mIndicatorController;
this.mWebChromeClient = agentBuilder.mWebChromeClient;
this.mWebViewClient = agentBuilder.mWebViewClient;
mAgentWeb = this;
this.mAgentWebSettings = agentBuilder.mAgentWebSettings;
if (agentBuilder.mJavaObject != null && !agentBuilder.mJavaObject.isEmpty()) {
this.mJavaObjects.putAll((Map extends String, ?>) agentBuilder.mJavaObject);
LogUtils.i(TAG, "mJavaObject size:" + this.mJavaObjects.size());
}
this.mPermissionInterceptor = agentBuilder.mPermissionInterceptor == null ? null : new PermissionInterceptorWrapper(agentBuilder.mPermissionInterceptor);
this.mSecurityType = agentBuilder.mSecurityType;
this.mIUrlLoader = new UrlLoaderImpl(mWebCreator.create().getWebView(), agentBuilder.mHttpHeaders);
if (this.mWebCreator.getWebParentLayout() instanceof WebParentLayout) {
WebParentLayout mWebParentLayout = (WebParentLayout) this.mWebCreator.getWebParentLayout();
mWebParentLayout.bindController(agentBuilder.mAgentWebUIController == null ? AgentWebUIControllerImplBase.build() : agentBuilder.mAgentWebUIController);
mWebParentLayout.setErrorLayoutRes(agentBuilder.mErrorLayout, agentBuilder.mReloadId);
mWebParentLayout.setErrorView(agentBuilder.mErrorView);
}
this.mWebLifeCycle = new DefaultWebLifeCycleImpl(mWebCreator.getWebView());
mWebSecurityController = new WebSecurityControllerImpl(mWebCreator.getWebView(), this.mAgentWeb.mJavaObjects, this.mSecurityType);
this.mWebClientHelper = agentBuilder.mWebClientHelper;
this.mIsInterceptUnkownUrl = agentBuilder.mIsInterceptUnkownUrl;
if (agentBuilder.mOpenOtherPage != null) {
this.mUrlHandleWays = agentBuilder.mOpenOtherPage.code;
}
this.mMiddleWrareWebClientBaseHeader = agentBuilder.mMiddlewareWebClientBaseHeader;
this.mMiddlewareWebChromeBaseHeader = agentBuilder.mChromeMiddleWareHeader;
init();
}
/**
* @return PermissionInterceptor 权限控制者
*/
public PermissionInterceptor getPermissionInterceptor() {
return this.mPermissionInterceptor;
}
public WebLifeCycle getWebLifeCycle() {
return this.mWebLifeCycle;
}
public JsAccessEntrace getJsAccessEntrace() {
JsAccessEntrace mJsAccessEntrace = this.mJsAccessEntrace;
if (mJsAccessEntrace == null) {
this.mJsAccessEntrace = mJsAccessEntrace = JsAccessEntraceImpl.getInstance(mWebCreator.getWebView());
}
return mJsAccessEntrace;
}
public AgentWeb clearWebCache() {
if (this.getWebCreator().getWebView() != null) {
AgentWebUtils.clearWebViewAllCache(mActivity, this.getWebCreator().getWebView());
} else {
AgentWebUtils.clearWebViewAllCache(mActivity);
}
return this;
}
public static AgentBuilder with(@NonNull Activity activity) {
if (activity == null) {
throw new NullPointerException("activity can not be null .");
}
return new AgentBuilder(activity);
}
public static AgentBuilder with(@NonNull Fragment fragment) {
Activity mActivity = null;
if ((mActivity = fragment.getActivity()) == null) {
throw new NullPointerException("activity can not be null .");
}
return new AgentBuilder(mActivity, fragment);
}
public boolean handleKeyEvent(int keyCode, KeyEvent keyEvent) {
if (mIEventHandler == null) {
mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor());
}
return mIEventHandler.onKeyDown(keyCode, keyEvent);
}
public boolean back() {
if (mIEventHandler == null) {
mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor());
}
return mIEventHandler.back();
}
public WebCreator getWebCreator() {
return this.mWebCreator;
}
public IEventHandler getIEventHandler() {
return this.mIEventHandler == null ? (this.mIEventHandler = EventHandlerImpl.getInstantce(mWebCreator.getWebView(), getInterceptor())) : this.mIEventHandler;
}
public IAgentWebSettings getAgentWebSettings() {
return this.mAgentWebSettings;
}
public IndicatorController getIndicatorController() {
return this.mIndicatorController;
}
public JsInterfaceHolder getJsInterfaceHolder() {
return this.mJsInterfaceHolder;
}
public IUrlLoader getUrlLoader() {
return this.mIUrlLoader;
}
public void destroy() {
this.mWebLifeCycle.onDestroy();
}
public static class PreAgentWeb {
private AgentWeb mAgentWeb;
private boolean isReady = false;
PreAgentWeb(AgentWeb agentWeb) {
this.mAgentWeb = agentWeb;
}
public PreAgentWeb ready() {
if (!isReady) {
mAgentWeb.ready();
isReady = true;
}
return this;
}
public AgentWeb get() {
ready();
return mAgentWeb;
}
public AgentWeb go(@Nullable String url) {
if (!isReady) {
ready();
}
return mAgentWeb.go(url);
}
}
private void doSafeCheck() {
WebSecurityCheckLogic mWebSecurityCheckLogic = this.mWebSecurityCheckLogic;
if (mWebSecurityCheckLogic == null) {
this.mWebSecurityCheckLogic = mWebSecurityCheckLogic = WebSecurityLogicImpl.getInstance();
}
mWebSecurityController.check(mWebSecurityCheckLogic);
}
private void doCompat() {
mJavaObjects.put("agentWeb", mAgentWebJsInterfaceCompat = new AgentWebJsInterfaceCompat(this, mActivity));
}
private WebCreator configWebCreator(BaseIndicatorView progressView, int index, ViewGroup.LayoutParams lp, int indicatorColor, int height_dp, WebView webView, IWebLayout webLayout) {
if (progressView != null && mEnableIndicator) {
return new DefaultWebCreator(mActivity, mViewGroup, lp, index, progressView, webView, webLayout);
} else {
return mEnableIndicator ?
new DefaultWebCreator(mActivity, mViewGroup, lp, index, indicatorColor, height_dp, webView, webLayout)
: new DefaultWebCreator(mActivity, mViewGroup, lp, index, webView, webLayout);
}
}
private AgentWeb go(String url) {
this.getUrlLoader().loadUrl(url);
IndicatorController mIndicatorController = null;
if (!TextUtils.isEmpty(url) && (mIndicatorController = getIndicatorController()) != null && mIndicatorController.offerIndicator() != null) {
getIndicatorController().offerIndicator().show();
}
return this;
}
private EventInterceptor getInterceptor() {
if (this.mEventInterceptor != null) {
return this.mEventInterceptor;
}
if (mIVideo instanceof VideoImpl) {
return this.mEventInterceptor = (EventInterceptor) this.mIVideo;
}
return null;
}
private void init() {
doCompat();
doSafeCheck();
}
private IVideo getIVideo() {
return mIVideo == null ? new VideoImpl(mActivity, mWebCreator.getWebView()) : mIVideo;
}
private WebViewClient getWebViewClient() {
LogUtils.i(TAG, "getDelegate:" + this.mMiddleWrareWebClientBaseHeader);
DefaultWebClient mDefaultWebClient = DefaultWebClient
.createBuilder()
.setActivity(this.mActivity)
.setWebClientHelper(this.mWebClientHelper)
.setPermissionInterceptor(this.mPermissionInterceptor)
.setWebView(this.mWebCreator.getWebView())
.setInterceptUnkownUrl(this.mIsInterceptUnkownUrl)
.setUrlHandleWays(this.mUrlHandleWays)
.build();
MiddlewareWebClientBase header = this.mMiddleWrareWebClientBaseHeader;
if (this.mWebViewClient != null) {
this.mWebViewClient.enq(this.mMiddleWrareWebClientBaseHeader);
header = this.mWebViewClient;
}
if (header != null) {
MiddlewareWebClientBase tail = header;
int count = 1;
MiddlewareWebClientBase tmp = header;
while (tmp.next() != null) {
tail = tmp = tmp.next();
count++;
}
LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count);
tail.setDelegate(mDefaultWebClient);
return header;
} else {
return mDefaultWebClient;
}
}
private AgentWeb ready() {
AgentWebConfig.initCookiesManager(mActivity.getApplicationContext());
IAgentWebSettings mAgentWebSettings = this.mAgentWebSettings;
if (mAgentWebSettings == null) {
this.mAgentWebSettings = mAgentWebSettings = AgentWebSettingsImpl.getInstance();
}
if (mAgentWebSettings instanceof AbsAgentWebSettings) {
((AbsAgentWebSettings) mAgentWebSettings).bindAgentWeb(this);
}
if (mWebListenerManager == null && mAgentWebSettings instanceof AbsAgentWebSettings) {
mWebListenerManager = (WebListenerManager) mAgentWebSettings;
}
mAgentWebSettings.toSetting(mWebCreator.getWebView());
if (mJsInterfaceHolder == null) {
mJsInterfaceHolder = JsInterfaceHolderImpl.getJsInterfaceHolder(mWebCreator.getWebView(), this.mSecurityType);
}
LogUtils.i(TAG, "mJavaObjects:" + mJavaObjects.size());
if (mJavaObjects != null && !mJavaObjects.isEmpty()) {
mJsInterfaceHolder.addJavaObjects(mJavaObjects);
}
if (mWebListenerManager != null) {
mWebListenerManager.setDownloader(mWebCreator.getWebView(), null);
mWebListenerManager.setWebChromeClient(mWebCreator.getWebView(), getChromeClient());
mWebListenerManager.setWebViewClient(mWebCreator.getWebView(), getWebViewClient());
}
return this;
}
private WebChromeClient getChromeClient() {
IndicatorController mIndicatorController =
(this.mIndicatorController == null) ?
IndicatorHandler.getInstance().inJectIndicator(mWebCreator.offer())
: this.mIndicatorController;
DefaultChromeClient mDefaultChromeClient =
new DefaultChromeClient(this.mActivity,
this.mIndicatorController = mIndicatorController,
null, this.mIVideo = getIVideo(),
this.mPermissionInterceptor, mWebCreator.getWebView());
LogUtils.i(TAG, "WebChromeClient:" + this.mWebChromeClient);
MiddlewareWebChromeBase header = this.mMiddlewareWebChromeBaseHeader;
if (this.mWebChromeClient != null) {
this.mWebChromeClient.enq(header);
header = this.mWebChromeClient;
}
if (header != null) {
MiddlewareWebChromeBase tail = header;
int count = 1;
MiddlewareWebChromeBase tmp = header;
for (; tmp.next() != null; ) {
tail = tmp = tmp.next();
count++;
}
LogUtils.i(TAG, "MiddlewareWebClientBase middleware count:" + count);
tail.setDelegate(mDefaultChromeClient);
return this.mTargetChromeClient = header;
} else {
return this.mTargetChromeClient = mDefaultChromeClient;
}
}
public enum SecurityType {
DEFAULT_CHECK, STRICT_CHECK;
}
public static final class AgentBuilder {
private Activity mActivity;
private Fragment mFragment;
private ViewGroup mViewGroup;
private boolean mIsNeedDefaultProgress;
private int mIndex = -1;
private BaseIndicatorView mBaseIndicatorView;
private IndicatorController mIndicatorController = null;
/*默认进度条是显示的*/
private boolean mEnableIndicator = true;
private ViewGroup.LayoutParams mLayoutParams = null;
private com.just.agentweb.WebViewClient mWebViewClient;
private com.just.agentweb.WebChromeClient mWebChromeClient;
private int mIndicatorColor = -1;
private IAgentWebSettings mAgentWebSettings;
private WebCreator mWebCreator;
private HttpHeaders mHttpHeaders = null;
private IEventHandler mIEventHandler;
private int mHeight = -1;
private ArrayMap mJavaObject;
private SecurityType mSecurityType = SecurityType.DEFAULT_CHECK;
private WebView mWebView;
private boolean mWebClientHelper = true;
private IWebLayout mWebLayout = null;
private PermissionInterceptor mPermissionInterceptor = null;
private AbsAgentWebUIController mAgentWebUIController;
private DefaultWebClient.OpenOtherPageWays mOpenOtherPage = null;
private boolean mIsInterceptUnkownUrl = false;
private MiddlewareWebClientBase mMiddlewareWebClientBaseHeader;
private MiddlewareWebClientBase mMiddlewareWebClientBaseTail;
private MiddlewareWebChromeBase mChromeMiddleWareHeader = null;
private MiddlewareWebChromeBase mChromeMiddleWareTail = null;
private View mErrorView;
private int mErrorLayout;
private int mReloadId;
private int mTag = -1;
public AgentBuilder(@NonNull Activity activity, @NonNull Fragment fragment) {
mActivity = activity;
mFragment = fragment;
mTag = AgentWeb.FRAGMENT_TAG;
}
public AgentBuilder(@NonNull Activity activity) {
mActivity = activity;
mTag = AgentWeb.ACTIVITY_TAG;
}
public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, @NonNull ViewGroup.LayoutParams lp) {
this.mViewGroup = v;
this.mLayoutParams = lp;
return new IndicatorBuilder(this);
}
public IndicatorBuilder setAgentWebParent(@NonNull ViewGroup v, int index, @NonNull ViewGroup.LayoutParams lp) {
this.mViewGroup = v;
this.mLayoutParams = lp;
this.mIndex = index;
return new IndicatorBuilder(this);
}
private PreAgentWeb buildAgentWeb() {
if (mTag == AgentWeb.FRAGMENT_TAG && this.mViewGroup == null) {
throw new NullPointerException("ViewGroup is null,Please check your parameters .");
}
return new PreAgentWeb(HookManager.hookAgentWeb(new AgentWeb(this), this));
}
private void addJavaObject(String key, Object o) {
if (mJavaObject == null) {
mJavaObject = new ArrayMap<>();
}
mJavaObject.put(key, o);
}
private void addHeader(String baseUrl, String k, String v) {
if (mHttpHeaders == null) {
mHttpHeaders = HttpHeaders.create();
}
mHttpHeaders.additionalHttpHeader(baseUrl, k, v);
}
private void addHeader(String baseUrl, Map headers) {
if (mHttpHeaders == null) {
mHttpHeaders = HttpHeaders.create();
}
mHttpHeaders.additionalHttpHeaders(baseUrl, headers);
}
}
public static class IndicatorBuilder {
private AgentBuilder mAgentBuilder = null;
public IndicatorBuilder(AgentBuilder agentBuilder) {
this.mAgentBuilder = agentBuilder;
}
public CommonBuilder useDefaultIndicator(int color) {
this.mAgentBuilder.mEnableIndicator = true;
this.mAgentBuilder.mIndicatorColor = color;
return new CommonBuilder(mAgentBuilder);
}
public CommonBuilder useDefaultIndicator() {
this.mAgentBuilder.mEnableIndicator = true;
return new CommonBuilder(mAgentBuilder);
}
public CommonBuilder closeIndicator() {
this.mAgentBuilder.mEnableIndicator = false;
this.mAgentBuilder.mIndicatorColor = -1;
this.mAgentBuilder.mHeight = -1;
return new CommonBuilder(mAgentBuilder);
}
public CommonBuilder setCustomIndicator(@NonNull BaseIndicatorView v) {
if (v != null) {
this.mAgentBuilder.mEnableIndicator = true;
this.mAgentBuilder.mBaseIndicatorView = v;
this.mAgentBuilder.mIsNeedDefaultProgress = false;
} else {
this.mAgentBuilder.mEnableIndicator = true;
this.mAgentBuilder.mIsNeedDefaultProgress = true;
}
return new CommonBuilder(mAgentBuilder);
}
public CommonBuilder useDefaultIndicator(@ColorInt int color, int height_dp) {
this.mAgentBuilder.mIndicatorColor = color;
this.mAgentBuilder.mHeight = height_dp;
return new CommonBuilder(this.mAgentBuilder);
}
}
public static class CommonBuilder {
private AgentBuilder mAgentBuilder;
public CommonBuilder(AgentBuilder agentBuilder) {
this.mAgentBuilder = agentBuilder;
}
public CommonBuilder setEventHanadler(@Nullable IEventHandler iEventHandler) {
mAgentBuilder.mIEventHandler = iEventHandler;
return this;
}
public CommonBuilder closeWebViewClientHelper() {
mAgentBuilder.mWebClientHelper = false;
return this;
}
public CommonBuilder setWebChromeClient(@Nullable com.just.agentweb.WebChromeClient webChromeClient) {
this.mAgentBuilder.mWebChromeClient = webChromeClient;
return this;
}
public CommonBuilder setWebViewClient(@Nullable com.just.agentweb.WebViewClient webChromeClient) {
this.mAgentBuilder.mWebViewClient = webChromeClient;
return this;
}
public CommonBuilder useMiddlewareWebClient(@NonNull MiddlewareWebClientBase middleWrareWebClientBase) {
if (middleWrareWebClientBase == null) {
return this;
}
if (this.mAgentBuilder.mMiddlewareWebClientBaseHeader == null) {
this.mAgentBuilder.mMiddlewareWebClientBaseHeader = this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase;
} else {
this.mAgentBuilder.mMiddlewareWebClientBaseTail.enq(middleWrareWebClientBase);
this.mAgentBuilder.mMiddlewareWebClientBaseTail = middleWrareWebClientBase;
}
return this;
}
public CommonBuilder useMiddlewareWebChrome(@NonNull MiddlewareWebChromeBase middlewareWebChromeBase) {
if (middlewareWebChromeBase == null) {
return this;
}
if (this.mAgentBuilder.mChromeMiddleWareHeader == null) {
this.mAgentBuilder.mChromeMiddleWareHeader = this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase;
} else {
this.mAgentBuilder.mChromeMiddleWareTail.enq(middlewareWebChromeBase);
this.mAgentBuilder.mChromeMiddleWareTail = middlewareWebChromeBase;
}
return this;
}
public CommonBuilder setMainFrameErrorView(@NonNull View view) {
this.mAgentBuilder.mErrorView = view;
return this;
}
public CommonBuilder setMainFrameErrorView(@LayoutRes int errorLayout, @IdRes int clickViewId) {
this.mAgentBuilder.mErrorLayout = errorLayout;
this.mAgentBuilder.mReloadId = clickViewId;
return this;
}
public CommonBuilder setAgentWebWebSettings(@Nullable IAgentWebSettings agentWebSettings) {
this.mAgentBuilder.mAgentWebSettings = agentWebSettings;
return this;
}
public PreAgentWeb createAgentWeb() {
return this.mAgentBuilder.buildAgentWeb();
}
public CommonBuilder addJavascriptInterface(@NonNull String name, @NonNull Object o) {
this.mAgentBuilder.addJavaObject(name, o);
return this;
}
public CommonBuilder setSecurityType(@NonNull SecurityType type) {
this.mAgentBuilder.mSecurityType = type;
return this;
}
public CommonBuilder setWebView(@Nullable WebView webView) {
this.mAgentBuilder.mWebView = webView;
return this;
}
public CommonBuilder setWebLayout(@Nullable IWebLayout iWebLayout) {
this.mAgentBuilder.mWebLayout = iWebLayout;
return this;
}
public CommonBuilder additionalHttpHeader(String baseUrl, String k, String v) {
this.mAgentBuilder.addHeader(baseUrl, k, v);
return this;
}
public CommonBuilder additionalHttpHeader(String baseUrl, Map headers) {
this.mAgentBuilder.addHeader(baseUrl, headers);
return this;
}
public CommonBuilder setPermissionInterceptor(@Nullable PermissionInterceptor permissionInterceptor) {
this.mAgentBuilder.mPermissionInterceptor = permissionInterceptor;
return this;
}
public CommonBuilder setAgentWebUIController(@Nullable AgentWebUIControllerImplBase agentWebUIController) {
this.mAgentBuilder.mAgentWebUIController = agentWebUIController;
return this;
}
public CommonBuilder setOpenOtherPageWays(@Nullable DefaultWebClient.OpenOtherPageWays openOtherPageWays) {
this.mAgentBuilder.mOpenOtherPage = openOtherPageWays;
return this;
}
public CommonBuilder interceptUnkownUrl() {
this.mAgentBuilder.mIsInterceptUnkownUrl = true;
return this;
}
}
private static final class PermissionInterceptorWrapper implements PermissionInterceptor {
private WeakReference mWeakReference;
private PermissionInterceptorWrapper(PermissionInterceptor permissionInterceptor) {
this.mWeakReference = new WeakReference(permissionInterceptor);
}
@Override
public boolean intercept(String url, String[] permissions, String a) {
if (this.mWeakReference.get() == null) {
return false;
}
return mWeakReference.get().intercept(url, permissions, a);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebConfig.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import android.text.TextUtils;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import java.io.File;
import static com.just.agentweb.AgentWebUtils.getAgentWebFilePath;
/**
* @since 1.0.0
* @author cenxiaozhong
*/
public class AgentWebConfig {
static final String FILE_CACHE_PATH = "agentweb-cache";
static final String AGENTWEB_CACHE_PATCH = File.separator + "agentweb-cache";
/**
* 缓存路径
*/
static String AGENTWEB_FILE_PATH;
/**
* DEBUG 模式 , 如果需要查看日志请设置为 true
*/
public static boolean DEBUG = false;
/**
* 当前操作系统是否低于 KITKAT
*/
static final boolean IS_KITKAT_OR_BELOW_KITKAT = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
/**
* 默认 WebView 类型 。
*/
public static final int WEBVIEW_DEFAULT_TYPE = 1;
/**
* 使用 AgentWebView
*/
public static final int WEBVIEW_AGENTWEB_SAFE_TYPE = 2;
/**
* 自定义 WebView
*/
public static final int WEBVIEW_CUSTOM_TYPE = 3;
static int WEBVIEW_TYPE = WEBVIEW_DEFAULT_TYPE;
private static volatile boolean IS_INITIALIZED = false;
private static final String TAG = AgentWebConfig.class.getSimpleName();
/**
* AgentWeb 的版本
*/
public static final String AGENTWEB_VERSION = " agentweb/4.0.2 ";
public static final String AGENTWEB_NAME="AgentWeb";
/**
* 通过JS获取的文件大小, 这里限制最大为5MB ,太大会抛出 OutOfMemoryError
*/
public static int MAX_FILE_LENGTH = 1024 * 1024 * 5;
//获取Cookie
public static String getCookiesByUrl(String url) {
return CookieManager.getInstance() == null ? null : CookieManager.getInstance().getCookie(url);
}
public static void debug() {
DEBUG = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
}
/**
* 删除所有已经过期的 Cookies
*/
public static void removeExpiredCookies() {
CookieManager mCookieManager = null;
if ((mCookieManager = CookieManager.getInstance()) != null) { //同步清除
mCookieManager.removeExpiredCookie();
toSyncCookies();
}
}
/**
* 删除所有 Cookies
*/
public static void removeAllCookies() {
removeAllCookies(null);
}
// 解决兼容 Android 4.4 java.lang.NoSuchMethodError: android.webkit.CookieManager.removeSessionCookies
public static void removeSessionCookies() {
removeSessionCookies(null);
}
/**
* 同步cookie
*
* @param url
* @param cookies
*/
public static void syncCookie(String url, String cookies) {
CookieManager mCookieManager = CookieManager.getInstance();
if (mCookieManager != null) {
mCookieManager.setCookie(url, cookies);
toSyncCookies();
}
}
public static void removeSessionCookies(ValueCallback callback) {
if (callback == null) {
callback = getDefaultIgnoreCallback();
}
if (CookieManager.getInstance() == null) {
callback.onReceiveValue(new Boolean(false));
return;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeSessionCookie();
toSyncCookies();
callback.onReceiveValue(new Boolean(true));
return;
}
CookieManager.getInstance().removeSessionCookies(callback);
toSyncCookies();
}
/**
* @param context
* @return WebView 的缓存路径
*/
public static String getCachePath(Context context) {
return context.getCacheDir().getAbsolutePath() + AGENTWEB_CACHE_PATCH;
}
/**
* @param context
* @return AgentWeb 缓存路径
*/
public static String getExternalCachePath(Context context) {
return getAgentWebFilePath(context);
}
//Android 4.4 NoSuchMethodError: android.webkit.CookieManager.removeAllCookies
public static void removeAllCookies(@Nullable ValueCallback callback) {
if (callback == null) {
callback = getDefaultIgnoreCallback();
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookie();
toSyncCookies();
callback.onReceiveValue(!CookieManager.getInstance().hasCookies());
return;
}
CookieManager.getInstance().removeAllCookies(callback);
toSyncCookies();
}
/**
* 清空缓存
*
* @param context
*/
public static synchronized void clearDiskCache(Context context) {
try {
AgentWebUtils.clearCacheFolder(new File(getCachePath(context)), 0);
String path = getExternalCachePath(context);
if (!TextUtils.isEmpty(path)) {
File mFile = new File(path);
AgentWebUtils.clearCacheFolder(mFile, 0);
}
} catch (Throwable throwable) {
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
}
}
static synchronized void initCookiesManager(Context context) {
if (!IS_INITIALIZED) {
createCookiesSyncInstance(context);
IS_INITIALIZED = true;
}
}
private static void createCookiesSyncInstance(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(context);
}
}
private static void toSyncCookies() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.getInstance().sync();
return;
}
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
CookieManager.getInstance().flush();
}
});
}
static String getDatabasesCachePath(Context context) {
return context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
}
private static ValueCallback getDefaultIgnoreCallback() {
return new ValueCallback() {
@Override
public void onReceiveValue(Boolean ignore) {
LogUtils.i(TAG, "removeExpiredCookies:" + ignore);
}
};
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebFileProvider.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.content.Context;
import android.content.pm.ProviderInfo;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
/**
* @since 2.0.0
* @author cenxiaozhong
*/
public class AgentWebFileProvider extends FileProvider {
@Override
public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
super.attachInfo(context, info);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebJsInterfaceCompat.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.webkit.JavascriptInterface;
import java.lang.ref.WeakReference;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class AgentWebJsInterfaceCompat {
private WeakReference mReference = null;
private WeakReference mActivityWeakReference = null;
private String TAG = this.getClass().getSimpleName();
AgentWebJsInterfaceCompat(AgentWeb agentWeb, Activity activity) {
mReference = new WeakReference(agentWeb);
mActivityWeakReference = new WeakReference(activity);
}
@JavascriptInterface
public void uploadFile() {
uploadFile("*/*");
}
@JavascriptInterface
public void uploadFile(String acceptType) {
LogUtils.i(TAG, acceptType + " " + mActivityWeakReference.get() + " " + mReference.get());
if (mActivityWeakReference.get() != null && mReference.get() != null) {
AgentWebUtils.showFileChooserCompat(mActivityWeakReference.get(),
mReference.get().getWebCreator().getWebView(),
null,
null,
mReference.get().getPermissionInterceptor(),
null,
acceptType,
new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (mReference.get() != null) {
mReference.get().getJsAccessEntrace()
.quickCallJs("uploadFileResult",
msg.obj instanceof String ? (String) msg.obj : null);
}
return true;
}
}
);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebPermissions.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.Manifest;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class AgentWebPermissions {
public static final String[] CAMERA;
public static final String[] LOCATION;
public static final String[] STORAGE;
public static final String ACTION_CAMERA = "Camera";
public static final String ACTION_LOCATION = "Location";
public static final String ACTION_STORAGE = "Storage";
static {
CAMERA = new String[]{
Manifest.permission.CAMERA};
LOCATION = new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION};
STORAGE = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebSettingsImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.webkit.DownloadListener;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class AgentWebSettingsImpl extends AbsAgentWebSettings {
private AgentWeb mAgentWeb;
@Override
protected void bindAgentWebSupport(AgentWeb agentWeb) {
this.mAgentWeb = agentWeb;
}
@Override
public WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener) {
// Fix Android 5.1 crashing:
// ClassCastException: android.app.ContextImpl cannot be cast to android.app.Activity
if (downloadListener == null) {
Activity activity = getActivityByContext(webView.getContext());
downloadListener = DefaultDownloadImpl.create(activity, webView, mAgentWeb.getPermissionInterceptor());
}
return super.setDownloader(webView, downloadListener);
}
/**
* Copy from com.blankj.utilcode.util.ActivityUtils#getActivityByView
*/
private Activity getActivityByContext(Context context) {
if (context instanceof Activity) return (Activity) context;
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
LogUtils.e( "获取 B :", context + "" );
System.out.println("输出:" + context + "");
return null;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebUIControllerImplBase.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.os.Handler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @date 2017/12/6
* @since 3.0.0
*/
public class AgentWebUIControllerImplBase extends AbsAgentWebUIController {
public static AbsAgentWebUIController build() {
return new AgentWebUIControllerImplBase();
}
@Override
public void onJsAlert(WebView view, String url, String message) {
getDelegate().onJsAlert(view, url, message);
}
@Override
public void onOpenPagePrompt(WebView view, String url, Handler.Callback callback) {
getDelegate().onOpenPagePrompt(view, url, callback);
}
@Override
public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) {
getDelegate().onJsConfirm(view, url, message, jsResult);
}
@Override
public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) {
getDelegate().onSelectItemsPrompt(view, url, ways, callback);
}
@Override
public void onForceDownloadAlert(String url, Handler.Callback callback) {
getDelegate().onForceDownloadAlert(url, callback);
}
@Override
public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) {
getDelegate().onJsPrompt(view, url, message, defaultValue, jsPromptResult);
}
@Override
public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) {
getDelegate().onMainFrameError(view, errorCode, description, failingUrl);
}
@Override
public void onShowMainFrame() {
getDelegate().onShowMainFrame();
}
@Override
public void onLoading(String msg) {
getDelegate().onLoading(msg);
}
@Override
public void onCancelLoading() {
getDelegate().onCancelLoading();
}
@Override
public void onShowMessage(String message, String from) {
getDelegate().onShowMessage(message, from);
}
@Override
public void onPermissionsDeny(String[] permissions, String permissionType, String action) {
getDelegate().onPermissionsDeny(permissions, permissionType, action);
}
@Override
protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) {
getDelegate().bindSupportWebParent(webParentLayout, activity);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebUtils.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.StatFs;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.telephony.TelephonyManager;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.core.app.AppOpsManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.os.EnvironmentCompat;
import androidx.loader.content.CursorLoader;
import com.google.android.material.snackbar.Snackbar;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static com.just.agentweb.AgentWebConfig.AGENTWEB_FILE_PATH;
import static com.just.agentweb.AgentWebConfig.FILE_CACHE_PATH;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class AgentWebUtils {
private static final String TAG = AgentWebUtils.class.getSimpleName();
private static Handler mHandler = null;
private AgentWebUtils() {
throw new UnsupportedOperationException("u can't init me");
}
public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
static final void clearWebView(WebView m) {
if (m == null) {
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
return;
}
m.loadUrl("about:blank");
m.stopLoading();
if (m.getHandler() != null) {
m.getHandler().removeCallbacksAndMessages(null);
}
m.removeAllViews();
ViewGroup mViewGroup = null;
if ((mViewGroup = ((ViewGroup) m.getParent())) != null) {
mViewGroup.removeView(m);
}
m.setWebChromeClient(null);
m.setWebViewClient(null);
m.setTag(null);
m.clearHistory();
m.destroy();
m = null;
}
public static String getAgentWebFilePath(Context context) {
if (!TextUtils.isEmpty(AGENTWEB_FILE_PATH)) {
return AGENTWEB_FILE_PATH;
}
String dir = getDiskExternalCacheDir(context);
File mFile = new File(dir, FILE_CACHE_PATH);
try {
if (!mFile.exists()) {
mFile.mkdirs();
}
} catch (Throwable throwable) {
LogUtils.i(TAG, "create dir exception");
}
LogUtils.i(TAG, "path:" + mFile.getAbsolutePath() + " path:" + mFile.getPath());
return AGENTWEB_FILE_PATH = mFile.getAbsolutePath();
}
public static File createFileByName(Context context, String name, boolean cover) throws IOException {
String path = getAgentWebFilePath(context);
if (TextUtils.isEmpty(path)) {
return null;
}
File mFile = new File(path, name);
if (mFile.exists()) {
if (cover) {
mFile.delete();
mFile.createNewFile();
}
} else {
mFile.createNewFile();
}
return mFile;
}
public static int checkNetworkType(Context context) {
int netType = 0;
//连接管理对象
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//获取NetworkInfo对象
@SuppressLint("MissingPermission") NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo == null) {
return netType;
}
switch (networkInfo.getType()) {
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
case ConnectivityManager.TYPE_ETHERNET:
return 1;
case ConnectivityManager.TYPE_MOBILE:
switch (networkInfo.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
return 2;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
return 3;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
case TelephonyManager.NETWORK_TYPE_EDGE:
return 4;
default:
return netType;
}
default:
return netType;
}
}
public static long getAvailableStorage() {
try {
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().toString());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
} else {
return (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
}
} catch (RuntimeException ex) {
return 0;
}
}
static Uri getUriFromFile(Context context, File file) {
Uri uri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = getUriFromFileForN(context, file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
static Uri getUriFromFileForN(Context context, File file) {
Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".AgentWebFileProvider", file);
return fileUri;
}
static void setIntentDataAndType(Context context,
Intent intent,
String type,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= 24) {
intent.setDataAndType(getUriFromFile(context, file), type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
}
static void setIntentData(Context context,
Intent intent,
File file,
boolean writeAble) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setData(getUriFromFile(context, file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setData(Uri.fromFile(file));
}
}
static String getDiskExternalCacheDir(Context context) {
File mFile = context.getExternalCacheDir();
if (Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(mFile))) {
return mFile.getAbsolutePath();
}
return null;
}
static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) {
int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (writeAble) {
flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
intent.addFlags(flag);
List resInfoList = context.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, flag);
}
}
private static String getMIMEType(File f) {
String type = "";
String fName = f.getName();
/* 取得扩展名 */
String end = fName.substring(fName.lastIndexOf(".") + 1, fName.length()).toLowerCase();
/* 依扩展名的类型决定MimeType */
if (end.equals("pdf")) {
type = "application/pdf";//
} else if (end.equals("m4a") || end.equals("mp3") || end.equals("mid") ||
end.equals("xmf") || end.equals("ogg") || end.equals("wav")) {
type = "audio/*";
} else if (end.equals("3gp") || end.equals("mp4")) {
type = "video/*";
} else if (end.equals("jpg") || end.equals("gif") || end.equals("png") ||
end.equals("jpeg") || end.equals("bmp")) {
type = "image/*";
} else if (end.equals("apk")) {
type = "application/vnd.android.package-archive";
} else if (end.equals("pptx") || end.equals("ppt")) {
type = "application/vnd.ms-powerpoint";
} else if (end.equals("docx") || end.equals("doc")) {
type = "application/vnd.ms-word";
} else if (end.equals("xlsx") || end.equals("xls")) {
type = "application/vnd.ms-excel";
} else {
type = "*/*";
}
return type;
}
private static WeakReference snackbarWeakReference;
static void show(View parent,
CharSequence text,
int duration,
@ColorInt int textColor,
@ColorInt int bgColor,
CharSequence actionText,
@ColorInt int actionTextColor,
View.OnClickListener listener) {
SpannableString spannableString = new SpannableString(text);
ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor);
spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
snackbarWeakReference = new WeakReference<>(Snackbar.make(parent, spannableString, duration));
Snackbar snackbar = snackbarWeakReference.get();
View view = snackbar.getView();
view.setBackgroundColor(bgColor);
if (actionText != null && actionText.length() > 0 && listener != null) {
snackbar.setActionTextColor(actionTextColor);
snackbar.setAction(actionText, listener);
}
snackbar.show();
}
static void dismiss() {
if (snackbarWeakReference != null && snackbarWeakReference.get() != null) {
snackbarWeakReference.get().dismiss();
snackbarWeakReference = null;
}
}
public static boolean checkWifi(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
return false;
}
@SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo();
return info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI;
}
public static boolean checkNetwork(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
return false;
}
@SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo();
return info != null && info.isConnected();
}
static boolean isOverriedMethod(Object currentObject, String methodName, String method, Class... clazzs) {
LogUtils.i(TAG, " methodName:" + methodName + " method:" + method);
boolean tag = false;
if (currentObject == null) {
return tag;
}
try {
Class clazz = currentObject.getClass();
Method mMethod = clazz.getMethod(methodName, clazzs);
String gStr = mMethod.toGenericString();
tag = !gStr.contains(method);
} catch (Exception igonre) {
if (LogUtils.isDebug()) {
igonre.printStackTrace();
}
}
LogUtils.i(TAG, "isOverriedMethod:" + tag);
return tag;
}
static Method isExistMethod(Object o, String methodName, Class... clazzs) {
if (null == o) {
return null;
}
try {
Class clazz = o.getClass();
Method mMethod = clazz.getDeclaredMethod(methodName, clazzs);
mMethod.setAccessible(true);
return mMethod;
} catch (Throwable ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
return null;
}
static void clearAgentWebCache(Context context) {
try {
clearCacheFolder(new File(getAgentWebFilePath(context)), 0);
} catch (Throwable throwable) {
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
}
}
static void clearWebViewAllCache(Context context, WebView webView) {
try {
AgentWebConfig.removeAllCookies(null);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
context.deleteDatabase("webviewCache.db");
context.deleteDatabase("webview.db");
webView.clearCache(true);
webView.clearHistory();
webView.clearFormData();
clearCacheFolder(new File(AgentWebConfig.getCachePath(context)), 0);
} catch (Exception ignore) {
//ignore.printStackTrace();
if (AgentWebConfig.DEBUG) {
ignore.printStackTrace();
}
}
}
static void clearWebViewAllCache(Context context) {
try {
clearWebViewAllCache(context, new LollipopFixedWebView(context.getApplicationContext()));
} catch (Exception e) {
e.printStackTrace();
}
}
static int clearCacheFolder(final File dir, final int numDays) {
int deletedFiles = 0;
if (dir != null) {
Log.i("Info", "dir:" + dir.getAbsolutePath());
}
if (dir != null && dir.isDirectory()) {
try {
for (File child : dir.listFiles()) {
//first delete subdirectories recursively
if (child.isDirectory()) {
deletedFiles += clearCacheFolder(child, numDays);
}
//then delete the files and subdirectories in this dir
//only empty directories can be deleted, so subdirs have been done first
if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) {
Log.i(TAG, "file name:" + child.getName());
if (child.delete()) {
deletedFiles++;
}
}
}
} catch (Exception e) {
Log.e("Info", String.format("Failed to clean the cache, result %s", e.getMessage()));
}
}
return deletedFiles;
}
static void clearCache(final Context context, final int numDays) {
Log.i("Info", String.format("Starting cache prune, deleting files older than %d days", numDays));
int numDeletedFiles = clearCacheFolder(context.getCacheDir(), numDays);
Log.i("Info", String.format("Cache pruning completed, %d files deleted", numDeletedFiles));
}
public static String[] uriToPath(Activity activity, Uri[] uris) {
if (activity == null || uris == null || uris.length == 0) {
return null;
}
try {
String[] paths = new String[uris.length];
int i = 0;
for (Uri mUri : uris) {
paths[i++] = Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 ? getFileAbsolutePath(activity, mUri) : getRealPathBelowVersion(activity, mUri);
}
return paths;
} catch (Throwable throwable) {
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
}
return null;
}
private static String getRealPathBelowVersion(Context context, Uri uri) {
String filePath = null;
LogUtils.i(TAG, "method -> getRealPathBelowVersion " + uri + " path:" + uri.getPath() + " getAuthority:" + uri.getAuthority());
String[] projection = {MediaStore.Images.Media.DATA};
CursorLoader loader = new CursorLoader(context, uri, projection, null,
null, null);
Cursor cursor = loader.loadInBackground();
if (cursor != null) {
cursor.moveToFirst();
filePath = cursor.getString(cursor.getColumnIndex(projection[0]));
cursor.close();
}
if (filePath == null) {
filePath = uri.getPath();
}
return filePath;
}
static File createImageFile(Context context) {
File mFile = null;
try {
String timeStamp =
new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
String imageName = String.format("aw_%s.jpg", timeStamp);
mFile = createFileByName(context, imageName, true);
} catch (Throwable e) {
e.printStackTrace();
}
return mFile;
}
static File createVideoFile(Context context){
File mFile = null;
try {
String timeStamp =
new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
String imageName = String.format("aw_%s.mp4", timeStamp); //默认生成mp4
mFile = createFileByName(context, imageName, true);
} catch (Throwable e) {
e.printStackTrace();
}
return mFile;
}
public static void closeIO(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@TargetApi(19)
static String getFileAbsolutePath(Activity context, Uri fileUri) {
if (context == null || fileUri == null) {
return null;
}
LogUtils.i(TAG, "getAuthority:" + fileUri.getAuthority() + " getHost:" + fileUri.getHost() + " getPath:" + fileUri.getPath() + " getScheme:" + fileUri.getScheme() + " query:" + fileUri.getQuery());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, fileUri)) {
if (isExternalStorageDocument(fileUri)) {
String docId = DocumentsContract.getDocumentId(fileUri);
String[] split = docId.split(":");
String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
} else if (isDownloadsDocument(fileUri)) {
String id = DocumentsContract.getDocumentId(fileUri);
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
} else if (isMediaDocument(fileUri)) {
String docId = DocumentsContract.getDocumentId(fileUri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = new String[]{split[1]};
return getDataColumn(context, contentUri, selection, selectionArgs);
} else {
}
} // MediaStore (and general)
else if (fileUri.getAuthority().equalsIgnoreCase(context.getPackageName() + ".AgentWebFileProvider")) {
String path = fileUri.getPath();
int index = path.lastIndexOf("/");
return getAgentWebFilePath(context) + File.separator + path.substring(index + 1, path.length());
} else if ("content".equalsIgnoreCase(fileUri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(fileUri)) {
return fileUri.getLastPathSegment();
}
return getDataColumn(context, fileUri, null, null);
}
// File
else if ("file".equalsIgnoreCase(fileUri.getScheme())) {
return fileUri.getPath();
}
return null;
}
static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
String[] projection = {MediaStore.Images.Media.DATA};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
return cursor.getString(index);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
static Intent getInstallApkIntentCompat(Context context, File file) {
Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW);
setIntentDataAndType(context, mIntent, "application/vnd.android.package-archive", file, false);
return mIntent;
}
public static Intent getCommonFileIntentCompat(Context context, File file) {
Intent mIntent = new Intent().setAction(Intent.ACTION_VIEW);
setIntentDataAndType(context, mIntent, getMIMEType(file), file, false);
return mIntent;
}
static Intent getIntentCaptureCompat(Context context, File file) {
Intent mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri mUri = getUriFromFile(context, file);
mIntent.addCategory(Intent.CATEGORY_DEFAULT);
mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
return mIntent;
}
static Intent getIntentVideoCompat(Context context, File file){
Intent mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
Uri mUri = getUriFromFile(context, file);
mIntent.addCategory(Intent.CATEGORY_DEFAULT);
mIntent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
return mIntent;
}
static boolean isJson(String target) {
if (TextUtils.isEmpty(target)) {
return false;
}
boolean tag = false;
try {
if (target.startsWith("[")) {
new JSONArray(target);
} else {
new JSONObject(target);
}
tag = true;
} catch (JSONException ignore) {
// ignore.printStackTrace();
tag = false;
}
return tag;
}
public static boolean isUIThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
static boolean isEmptyCollection(Collection collection) {
return collection == null || collection.isEmpty();
}
static boolean isEmptyMap(Map map) {
return map == null || map.isEmpty();
}
private static Toast mToast = null;
static void toastShowShort(Context context, String msg) {
if (mToast == null) {
mToast = Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT);
} else {
mToast.setText(msg);
}
mToast.show();
}
@Deprecated
static void getUIControllerAndShowMessage(Activity activity, String message, String from) {
if (activity == null || activity.isFinishing()) {
return;
}
WebParentLayout mWebParentLayout = (WebParentLayout) activity.findViewById(R.id.web_parent_layout_id);
AbsAgentWebUIController mAgentWebUIController = mWebParentLayout.provide();
if (mAgentWebUIController != null) {
mAgentWebUIController.onShowMessage(message, from);
}
}
public static boolean hasPermission(@NonNull Context context, @NonNull String... permissions) {
return hasPermission(context, Arrays.asList(permissions));
}
public static boolean hasPermission(@NonNull Context context, @NonNull List permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
for (String permission : permissions) {
int result = ContextCompat.checkSelfPermission(context, permission);
if (result == PackageManager.PERMISSION_DENIED) {
return false;
}
String op = AppOpsManagerCompat.permissionToOp(permission);
if (TextUtils.isEmpty(op)) {
continue;
}
result = AppOpsManagerCompat.noteProxyOp(context, op, context.getPackageName());
if (result != AppOpsManagerCompat.MODE_ALLOWED) {
return false;
}
}
return true;
}
public static List getDeniedPermissions(Activity activity, String[] permissions) {
if (permissions == null || permissions.length == 0) {
return null;
}
List deniedPermissions = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
if (!hasPermission(activity, permissions[i])) {
deniedPermissions.add(permissions[i]);
}
}
return deniedPermissions;
}
public static AbsAgentWebUIController getAgentWebUIControllerByWebView(WebView webView) {
WebParentLayout mWebParentLayout = getWebParentLayoutByWebView(webView);
return mWebParentLayout.provide();
}
//获取应用的名称
public static String getApplicationName(Context context) {
PackageManager packageManager = null;
ApplicationInfo applicationInfo = null;
try {
packageManager = context.getApplicationContext().getPackageManager();
applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
applicationInfo = null;
}
String applicationName =
(String) packageManager.getApplicationLabel(applicationInfo);
return applicationName;
}
static WebParentLayout getWebParentLayoutByWebView(WebView webView) {
ViewGroup mViewGroup = null;
if (!(webView.getParent() instanceof ViewGroup)) {
throw new IllegalStateException("please check webcreator's create method was be called ?");
}
mViewGroup = (ViewGroup) webView.getParent();
AbsAgentWebUIController mAgentWebUIController;
while (mViewGroup != null) {
LogUtils.i(TAG, "ViewGroup:" + mViewGroup);
if (mViewGroup.getId() == R.id.web_parent_layout_id) {
WebParentLayout mWebParentLayout = (WebParentLayout) mViewGroup;
LogUtils.i(TAG, "found WebParentLayout");
return mWebParentLayout;
} else {
ViewParent mViewParent = mViewGroup.getParent();
if (mViewParent instanceof ViewGroup) {
mViewGroup = (ViewGroup) mViewParent;
} else {
mViewGroup = null;
}
}
}
throw new IllegalStateException("please check webcreator's create method was be called ?");
}
public static void runInUiThread(Runnable runnable) {
if (mHandler == null) {
mHandler = new Handler(Looper.getMainLooper());
}
mHandler.post(runnable);
}
static boolean showFileChooserCompat(Activity activity,
WebView webView,
ValueCallback valueCallbacks,
WebChromeClient.FileChooserParams fileChooserParams,
PermissionInterceptor permissionInterceptor,
ValueCallback valueCallback,
String mimeType,
Handler.Callback jsChannelCallback
) {
try {
Class> clz = Class.forName("com.just.agentweb.filechooser.FileChooser");
Object mFileChooser$Builder = clz.getDeclaredMethod("newBuilder",
Activity.class, WebView.class)
.invoke(null, activity, webView);
clz = mFileChooser$Builder.getClass();
Method mMethod = null;
if (valueCallbacks != null) {
mMethod = clz.getDeclaredMethod("setUriValueCallbacks", ValueCallback.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, valueCallbacks);
}
if (fileChooserParams != null) {
mMethod = clz.getDeclaredMethod("setFileChooserParams", WebChromeClient.FileChooserParams.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, fileChooserParams);
}
if (valueCallback != null) {
mMethod = clz.getDeclaredMethod("setUriValueCallback", ValueCallback.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, valueCallback);
}
if (!TextUtils.isEmpty(mimeType)) {
// LogUtils.i(TAG, Arrays.toString(clz.getDeclaredMethods()));
mMethod = clz.getDeclaredMethod("setAcceptType", String.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, mimeType);
}
if (jsChannelCallback != null) {
mMethod = clz.getDeclaredMethod("setJsChannelCallback", Handler.Callback.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, jsChannelCallback);
}
mMethod = clz.getDeclaredMethod("setPermissionInterceptor", PermissionInterceptor.class);
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser$Builder, permissionInterceptor);
mMethod = clz.getDeclaredMethod("build");
mMethod.setAccessible(true);
Object mFileChooser = mMethod.invoke(mFileChooser$Builder);
mMethod = mFileChooser.getClass().getDeclaredMethod("openFileChooser");
mMethod.setAccessible(true);
mMethod.invoke(mFileChooser);
} catch (Throwable throwable) {
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
if (throwable instanceof ClassNotFoundException) {
LogUtils.e(TAG, "Please check whether compile'com.just.agentweb:filechooser:x.x.x' dependency was added.");
}
if (valueCallbacks != null) {
LogUtils.i(TAG, "onReceiveValue empty");
return false;
}
if (valueCallback != null) {
valueCallback.onReceiveValue(null);
}
}
return true;
}
public static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
return new BigInteger(1, md.digest()).toString(16);
} catch (Exception e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
return "";
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/AgentWebView.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityManager;
import android.webkit.JsPromptResult;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class AgentWebView extends LollipopFixedWebView {
private static final String TAG = AgentWebView.class.getSimpleName();
private Map mJsCallJavas;
private Map mInjectJavaScripts;
private FixedOnReceivedTitle mFixedOnReceivedTitle;
private boolean mIsInited;
private Boolean mIsAccessibilityEnabledOriginal;
public AgentWebView(Context context) {
this(context, null);
}
public AgentWebView(Context context, AttributeSet attrs) {
super(context, attrs);
removeSearchBoxJavaBridge();
mIsInited = true;
mFixedOnReceivedTitle = new FixedOnReceivedTitle();
}
/**
* 经过大量的测试,按照以下方式才能保证JS脚本100%注入成功:
* 1、在第一次loadUrl之前注入JS(在addJavascriptInterface里面注入即可,setWebViewClient和setWebChromeClient要在addJavascriptInterface之前执行);
* 2、在webViewClient.onPageStarted中都注入JS;
* 3、在webChromeClient.onProgressChanged中都注入JS,并且不能通过自检查(onJsPrompt里面判断)JS是否注入成功来减少注入JS的次数,因为网页中的JS可以同时打开多个url导致无法控制检查的准确性;
*
* @deprecated Android 4.2.2及以上版本的 addJavascriptInterface 方法已经解决了安全问题,如果不使用“网页能将JS函数传到Java层”功能,不建议使用该类,毕竟系统的JS注入效率才是最高的;
*/
@Override
@Deprecated
public final void addJavascriptInterface(Object interfaceObj, String interfaceName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
super.addJavascriptInterface(interfaceObj, interfaceName);
Log.i(TAG, "注入");
return;
} else {
Log.i(TAG, "use mJsCallJavas:" + interfaceName);
}
LogUtils.i(TAG, "addJavascriptInterface:" + interfaceObj + " interfaceName:" + interfaceName);
if (mJsCallJavas == null) {
mJsCallJavas = new HashMap();
}
mJsCallJavas.put(interfaceName, new JsCallJava(interfaceObj, interfaceName));
injectJavaScript();
if (LogUtils.isDebug()) {
Log.d(TAG, "injectJavaScript, addJavascriptInterface.interfaceObj = " + interfaceObj + ", interfaceName = " + interfaceName);
}
addJavascriptInterfaceSupport(interfaceObj, interfaceName);
}
protected void addJavascriptInterfaceSupport(Object interfaceObj, String interfaceName) {
}
@Override
public final void setWebChromeClient(WebChromeClient client) {
AgentWebChrome mAgentWebChrome = new AgentWebChrome(this);
mAgentWebChrome.setDelegate(client);
mFixedOnReceivedTitle.setWebChromeClient(client);
super.setWebChromeClient(mAgentWebChrome);
setWebChromeClientSupport(mAgentWebChrome);
}
protected final void setWebChromeClientSupport(WebChromeClient client) {
}
@Override
public final void setWebViewClient(WebViewClient client) {
AgentWebClient mAgentWebClient = new AgentWebClient(this);
mAgentWebClient.setDelegate(client);
super.setWebViewClient(mAgentWebClient);
setWebViewClientSupport(mAgentWebClient);
}
public final void setWebViewClientSupport(WebViewClient client) {
}
@Override
public void destroy() {
setVisibility(View.GONE);
if (mJsCallJavas != null) {
mJsCallJavas.clear();
}
if (mInjectJavaScripts != null) {
mInjectJavaScripts.clear();
}
removeAllViewsInLayout();
fixedStillAttached();
releaseConfigCallback();
if (mIsInited) {
resetAccessibilityEnabled();
LogUtils.i(TAG, "destroy web");
super.destroy();
}
}
@Override
public void clearHistory() {
if (mIsInited) {
super.clearHistory();
}
}
public static Pair isWebViewPackageException(Throwable e) {
String messageCause = e.getCause() == null ? e.toString() : e.getCause().toString();
String trace = Log.getStackTraceString(e);
if (trace.contains("android.content.pm.PackageManager$NameNotFoundException")
|| trace.contains("java.lang.RuntimeException: Cannot load WebView")
|| trace.contains("android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed")) {
LogUtils.safeCheckCrash(TAG, "isWebViewPackageException", e);
return new Pair(true, "WebView load failed, " + messageCause);
}
return new Pair(false, messageCause);
}
@Override
public void setOverScrollMode(int mode) {
try {
super.setOverScrollMode(mode);
} catch (Throwable e) {
Pair pair = isWebViewPackageException(e);
if (pair.first) {
Toast.makeText(getContext(), pair.second, Toast.LENGTH_SHORT).show();
destroy();
} else {
throw e;
}
}
}
@Override
public boolean isPrivateBrowsingEnabled() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
&& getSettings() == null) {
return false; // getSettings().isPrivateBrowsingEnabled()
} else {
return super.isPrivateBrowsingEnabled();
}
}
/**
* 添加并注入JavaScript脚本(和“addJavascriptInterface”注入对象的注入时机一致,100%能注入成功);
* 注意:为了做到能100%注入,需要在注入的js中自行判断对象是否已经存在(如:if (typeof(window.Android) = 'undefined'));
*
* @param javaScript
*/
public void addInjectJavaScript(String javaScript) {
if (mInjectJavaScripts == null) {
mInjectJavaScripts = new HashMap();
}
mInjectJavaScripts.put(String.valueOf(javaScript.hashCode()), javaScript);
injectExtraJavaScript();
}
private void injectJavaScript() {
for (Map.Entry entry : mJsCallJavas.entrySet()) {
this.loadUrl(buildNotRepeatInjectJS(entry.getKey(), entry.getValue().getPreloadInterfaceJs()));
}
}
private void injectExtraJavaScript() {
for (Map.Entry entry : mInjectJavaScripts.entrySet()) {
this.loadUrl(buildNotRepeatInjectJS(entry.getKey(), entry.getValue()));
}
}
/**
* 构建一个“不会重复注入”的js脚本;
*
* @param key
* @param js
* @return
*/
public String buildNotRepeatInjectJS(String key, String js) {
String obj = String.format("__injectFlag_%1$s__", key);
StringBuilder sb = new StringBuilder();
sb.append("javascript:try{(function(){if(window.");
sb.append(obj);
sb.append("){console.log('");
sb.append(obj);
sb.append(" has been injected');return;}window.");
sb.append(obj);
sb.append("=true;");
sb.append(js);
sb.append("}())}catch(e){console.warn(e)}");
return sb.toString();
}
/**
* 构建一个“带try catch”的js脚本;
*
* @param js
* @return
*/
public String buildTryCatchInjectJS(String js) {
StringBuilder sb = new StringBuilder();
sb.append("javascript:try{");
sb.append(js);
sb.append("}catch(e){console.warn(e)}");
return sb.toString();
}
public static class AgentWebClient extends MiddlewareWebClientBase {
private AgentWebView mAgentWebView;
private AgentWebClient(AgentWebView agentWebView) {
this.mAgentWebView = agentWebView;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
if (mAgentWebView.mJsCallJavas != null) {
mAgentWebView.injectJavaScript();
if (LogUtils.isDebug()) {
Log.d(TAG, "injectJavaScript, onPageStarted.url = " + view.getUrl());
}
}
if (mAgentWebView.mInjectJavaScripts != null) {
mAgentWebView.injectExtraJavaScript();
}
mAgentWebView.mFixedOnReceivedTitle.onPageStarted();
mAgentWebView.fixedAccessibilityInjectorExceptionForOnPageFinished(url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mAgentWebView.mFixedOnReceivedTitle.onPageFinished(view);
if (LogUtils.isDebug()) {
Log.d(TAG, "onPageFinished.url = " + view.getUrl());
}
}
}
public static class AgentWebChrome extends MiddlewareWebChromeBase {
private AgentWebView mAgentWebView;
private AgentWebChrome(AgentWebView agentWebView) {
this.mAgentWebView = agentWebView;
}
@Override
public void onReceivedTitle(WebView view, String title) {
this.mAgentWebView.mFixedOnReceivedTitle.onReceivedTitle();
super.onReceivedTitle(view, title);
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (this.mAgentWebView.mJsCallJavas != null) {
this.mAgentWebView.injectJavaScript();
if (LogUtils.isDebug()) {
Log.d(TAG, "injectJavaScript, onProgressChanged.newProgress = " + newProgress + ", url = " + view.getUrl());
}
}
if (this.mAgentWebView.mInjectJavaScripts != null) {
this.mAgentWebView.injectExtraJavaScript();
}
super.onProgressChanged(view, newProgress);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.i(TAG, "onJsPrompt:" + url + " message:" + message + " d:" + defaultValue + " ");
if (this.mAgentWebView.mJsCallJavas != null && JsCallJava.isSafeWebViewCallMsg(message)) {
JSONObject jsonObject = JsCallJava.getMsgJSONObject(message);
String interfacedName = JsCallJava.getInterfacedName(jsonObject);
if (interfacedName != null) {
JsCallJava mJsCallJava = this.mAgentWebView.mJsCallJavas.get(interfacedName);
if (mJsCallJava != null) {
result.confirm(mJsCallJava.call(view, jsonObject));
}
}
return true;
} else {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}
}
/**
* 解决部分手机webView返回时不触发onReceivedTitle的问题(如:三星SM-G9008V 4.4.2);
*/
private static class FixedOnReceivedTitle {
private WebChromeClient mWebChromeClient;
private boolean mIsOnReceivedTitle;
public void setWebChromeClient(WebChromeClient webChromeClient) {
mWebChromeClient = webChromeClient;
}
public void onPageStarted() {
mIsOnReceivedTitle = false;
}
public void onPageFinished(WebView view) {
if (!mIsOnReceivedTitle && mWebChromeClient != null) {
WebBackForwardList list = null;
try {
list = view.copyBackForwardList();
} catch (NullPointerException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
if (list != null
&& list.getSize() > 0
&& list.getCurrentIndex() >= 0
&& list.getItemAtIndex(list.getCurrentIndex()) != null) {
String previousTitle = list.getItemAtIndex(list.getCurrentIndex()).getTitle();
mWebChromeClient.onReceivedTitle(view, previousTitle);
}
}
}
public void onReceivedTitle() {
mIsOnReceivedTitle = true;
}
}
// Activity在onDestory时调用webView的destroy,可以停止播放页面中的音频
private void fixedStillAttached() {
// java.lang.Throwable: Error: WebView.destroy() called while still attached!
// at android.webkit.WebViewClassic.destroy(WebViewClassic.java:4142)
// at android.webkit.WebView.destroy(WebView.java:707)
ViewParent parent = getParent();
if (parent instanceof ViewGroup) { // 由于自定义webView构建时传入了该Activity的context对象,因此需要先从父容器中移除webView,然后再销毁webView;
ViewGroup mWebViewContainer = (ViewGroup) getParent();
mWebViewContainer.removeAllViewsInLayout();
}
}
// 解决WebView内存泄漏问题;
private void releaseConfigCallback() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { // JELLY_BEAN
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
field.set(null, null);
} catch (NoSuchFieldException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // KITKAT
try {
Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
if (sConfigCallback != null) {
sConfigCallback.setAccessible(true);
sConfigCallback.set(null, null);
}
} catch (NoSuchFieldException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
} catch (IllegalAccessException e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
}
}
/**
* Android 4.4 KitKat 使用Chrome DevTools 远程调试WebView
* WebView.setWebContentsDebuggingEnabled(true);
* http://blog.csdn.net/t12x3456/article/details/14225235
*/
@TargetApi(19)
protected void trySetWebDebuggEnabled() {
if (LogUtils.isDebug() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
Class> clazz = WebView.class;
Method method = clazz.getMethod("setWebContentsDebuggingEnabled", boolean.class);
method.invoke(null, true);
} catch (Throwable e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
}
}
@TargetApi(11)
protected boolean removeSearchBoxJavaBridge() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
Method method = this.getClass().getMethod("removeJavascriptInterface", String.class);
method.invoke(this, "searchBoxJavaBridge_");
return true;
}
} catch (Exception e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
return false;
}
protected void fixedAccessibilityInjectorException() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1
&& mIsAccessibilityEnabledOriginal == null
&& isAccessibilityEnabled()) {
mIsAccessibilityEnabledOriginal = true;
setAccessibilityEnabled(false);
}
}
protected void fixedAccessibilityInjectorExceptionForOnPageFinished(String url) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN
&& getSettings().getJavaScriptEnabled()
&& mIsAccessibilityEnabledOriginal == null
&& isAccessibilityEnabled()) {
try {
try {
URLEncoder.encode(String.valueOf(new URI(url)), "utf-8");
// URLEncodedUtils.parse(new URI(url), null); // AccessibilityInjector.getAxsUrlParameterValue
} catch (IllegalArgumentException e) {
if ("bad parameter".equals(e.getMessage())) {
mIsAccessibilityEnabledOriginal = true;
setAccessibilityEnabled(false);
LogUtils.safeCheckCrash(TAG, "fixedAccessibilityInjectorExceptionForOnPageFinished.url = " + url, e);
}
}
} catch (Throwable e) {
if (LogUtils.isDebug()) {
LogUtils.e(TAG, "fixedAccessibilityInjectorExceptionForOnPageFinished", e);
}
}
}
}
private boolean isAccessibilityEnabled() {
AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
return am.isEnabled();
}
private void setAccessibilityEnabled(boolean enabled) {
AccessibilityManager am = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
try {
Method setAccessibilityState = am.getClass().getDeclaredMethod("setAccessibilityState", boolean.class);
setAccessibilityState.setAccessible(true);
setAccessibilityState.invoke(am, enabled);
setAccessibilityState.setAccessible(false);
} catch (Throwable e) {
if (LogUtils.isDebug()) {
LogUtils.e(TAG, "setAccessibilityEnabled", e);
}
}
}
private void resetAccessibilityEnabled() {
if (mIsAccessibilityEnabledOriginal != null) {
setAccessibilityEnabled(mIsAccessibilityEnabledOriginal);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorSpec.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public interface BaseIndicatorSpec {
void show();
void hide();
void reset();
void setProgress(int newProgress);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/BaseIndicatorView.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
/**
* @author cenxiaozhong
* @date 2017/5/12
* @since 1.0.0
*/
public abstract class BaseIndicatorView extends FrameLayout implements BaseIndicatorSpec,LayoutParamsOffer{
public BaseIndicatorView(Context context) {
super(context);
}
public BaseIndicatorView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BaseIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void reset() {
}
@Override
public void setProgress(int newProgress) {
}
@Override
public void show() {
}
@Override
public void hide() {
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/BaseJsAccessEntrace.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Build;
import android.webkit.ValueCallback;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @date 2017/5/26
* @since 1.0.0
*/
public abstract class BaseJsAccessEntrace implements JsAccessEntrace {
private WebView mWebView;
public static final String TAG=BaseJsAccessEntrace.class.getSimpleName();
BaseJsAccessEntrace(WebView webView){
this.mWebView=webView;
}
@Override
public void callJs(String js, final ValueCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
this.evaluateJs(js, callback);
} else {
this.loadJs(js);
}
}
@Override
public void callJs(String js) {
this.callJs(js, null);
}
private void loadJs(String js) {
mWebView.loadUrl(js);
}
private void evaluateJs(String js, final ValueCallbackcallback){
mWebView.evaluateJavascript(js, new ValueCallback() {
@Override
public void onReceiveValue(String value) {
if (callback != null){
callback.onReceiveValue(value);
}
}
});
}
@Override
public void quickCallJs(String method, ValueCallback callback, String... params) {
StringBuilder sb=new StringBuilder();
sb.append("javascript:"+method);
if(params==null||params.length==0){
sb.append("()");
}else{
sb.append("(").append(concat(params)).append(")");
}
callJs(sb.toString(),callback);
}
private String concat(String...params){
StringBuilder mStringBuilder=new StringBuilder();
for(int i=0;i mActivityWeakReference = null;
/**
* DefaultChromeClient 's TAG
*/
private String TAG = DefaultChromeClient.class.getSimpleName();
/**
* Android WebChromeClient path ,用于反射,用户是否重写来该方法
*/
public static final String ANDROID_WEBCHROMECLIENT_PATH = "android.webkit.WebChromeClient";
/**
* WebChromeClient
*/
private WebChromeClient mWebChromeClient;
/**
* 包装Flag
*/
private boolean mIsWrapper = false;
/**
* Video 处理类
*/
private IVideo mIVideo;
/**
* PermissionInterceptor 权限拦截器
*/
private PermissionInterceptor mPermissionInterceptor;
/**
* 当前 WebView
*/
private WebView mWebView;
/**
* Web端触发的定位 mOrigin
*/
private String mOrigin = null;
/**
* Web 端触发的定位 Callback 回调成功,或者失败
*/
private GeolocationPermissions.Callback mCallback = null;
/**
* 标志位
*/
public static final int FROM_CODE_INTENTION = 0x18;
/**
* 标识当前是获取定位权限
*/
public static final int FROM_CODE_INTENTION_LOCATION = FROM_CODE_INTENTION << 2;
/**
* AbsAgentWebUIController
*/
private WeakReference mAgentWebUIController = null;
/**
* IndicatorController 进度条控制器
*/
private IndicatorController mIndicatorController;
/**
* 文件选择器
*/
private Object mFileChooser;
DefaultChromeClient(Activity activity,
IndicatorController indicatorController,
WebChromeClient chromeClient,
@Nullable IVideo iVideo,
PermissionInterceptor permissionInterceptor, WebView webView) {
super(chromeClient);
this.mIndicatorController = indicatorController;
mIsWrapper = chromeClient != null ? true : false;
this.mWebChromeClient = chromeClient;
mActivityWeakReference = new WeakReference(activity);
this.mIVideo = iVideo;
this.mPermissionInterceptor = permissionInterceptor;
this.mWebView = webView;
mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView));
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (mIndicatorController != null) {
mIndicatorController.progress(view, newProgress);
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
if (mIsWrapper) {
super.onReceivedTitle(view, title);
}
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
if (mAgentWebUIController.get() != null) {
mAgentWebUIController.get().onJsAlert(view, url, message);
}
result.confirm();
return true;
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
@Override
public void onGeolocationPermissionsHidePrompt() {
super.onGeolocationPermissionsHidePrompt();
}
//location
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
onGeolocationPermissionsShowPromptInternal(origin, callback);
}
private void onGeolocationPermissionsShowPromptInternal(String origin, GeolocationPermissions.Callback callback) {
if (mPermissionInterceptor != null) {
if (mPermissionInterceptor.intercept(this.mWebView.getUrl(), AgentWebPermissions.LOCATION, "location")) {
callback.invoke(origin, false, false);
return;
}
}
Activity mActivity = mActivityWeakReference.get();
if (mActivity == null) {
callback.invoke(origin, false, false);
return;
}
List deniedPermissions = null;
if ((deniedPermissions = AgentWebUtils.getDeniedPermissions(mActivity, AgentWebPermissions.LOCATION)).isEmpty()) {
LogUtils.i(TAG, "onGeolocationPermissionsShowPromptInternal:" + true);
callback.invoke(origin, true, false);
} else {
Action mAction = Action.createPermissionsAction(deniedPermissions.toArray(new String[]{}));
mAction.setFromIntention(FROM_CODE_INTENTION_LOCATION);
ActionActivity.setPermissionListener(mPermissionListener);
this.mCallback = callback;
this.mOrigin = origin;
ActionActivity.start(mActivity, mAction);
}
}
private ActionActivity.PermissionListener mPermissionListener = new ActionActivity.PermissionListener() {
@Override
public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) {
if (extras.getInt(KEY_FROM_INTENTION) == FROM_CODE_INTENTION_LOCATION) {
boolean hasPermission = AgentWebUtils.hasPermission(mActivityWeakReference.get(), permissions);
if (mCallback != null) {
if (hasPermission) {
mCallback.invoke(mOrigin, true, false);
} else {
mCallback.invoke(mOrigin, false, false);
}
mCallback = null;
mOrigin = null;
}
if (!hasPermission && null != mAgentWebUIController.get()) {
mAgentWebUIController
.get()
.onPermissionsDeny(
AgentWebPermissions.LOCATION,
AgentWebPermissions.ACTION_LOCATION,
"Location");
}
}
}
};
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
try {
if (this.mAgentWebUIController.get() != null) {
this.mAgentWebUIController.get().onJsPrompt(mWebView, url, message, defaultValue, result);
}
} catch (Exception e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
if (mAgentWebUIController.get() != null) {
mAgentWebUIController.get().onJsConfirm(view, url, message, result);
}
return true;
}
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(totalQuota * 2);
}
@Override
public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(requiredStorage * 2);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) {
LogUtils.i(TAG, "openFileChooser>=5.0");
return openFileChooserAboveL(webView, filePathCallback, fileChooserParams);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private boolean openFileChooserAboveL(WebView webView, ValueCallback valueCallbacks, FileChooserParams fileChooserParams) {
LogUtils.i(TAG, "fileChooserParams:" + fileChooserParams.getAcceptTypes() + " getTitle:" + fileChooserParams.getTitle() + " accept:" + Arrays.toString(fileChooserParams.getAcceptTypes()) + " length:" + fileChooserParams.getAcceptTypes().length + " :" + fileChooserParams.isCaptureEnabled() + " " + fileChooserParams.getFilenameHint() + " intent:" + fileChooserParams.createIntent().toString() + " mode:" + fileChooserParams.getMode());
Activity mActivity = this.mActivityWeakReference.get();
if (mActivity == null || mActivity.isFinishing()) {
return false;
}
return AgentWebUtils.showFileChooserCompat(mActivity,
mWebView,
valueCallbacks,
fileChooserParams,
this.mPermissionInterceptor,
null,
null,
null
);
}
/**
* Android >= 4.1
*
* @param uploadFile ValueCallback , File URI callback
* @param acceptType
* @param capture
*/
@Override
public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) {
/*believe me , i never want to do this */
LogUtils.i(TAG, "openFileChooser>=4.1");
createAndOpenCommonFileChooser(uploadFile, acceptType);
}
// Android < 3.0
@Override
public void openFileChooser(ValueCallback valueCallback) {
Log.i(TAG, "openFileChooser<3.0");
createAndOpenCommonFileChooser(valueCallback, "*/*");
}
// Android >= 3.0
@Override
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
Log.i(TAG, "openFileChooser>3.0");
createAndOpenCommonFileChooser(valueCallback, acceptType);
}
private void createAndOpenCommonFileChooser(ValueCallback valueCallback, String mimeType) {
Activity mActivity = this.mActivityWeakReference.get();
if (mActivity == null || mActivity.isFinishing()) {
valueCallback.onReceiveValue(new Object());
return;
}
AgentWebUtils.showFileChooserCompat(mActivity,
mWebView,
null,
null,
this.mPermissionInterceptor,
valueCallback,
mimeType,
null
);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
super.onConsoleMessage(consoleMessage);
return true;
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (mIVideo != null) {
mIVideo.onShowCustomView(view, callback);
}
}
@Override
public void onHideCustomView() {
if (mIVideo != null) {
mIVideo.onHideCustomView();
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultDesignUIController.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.DialogInterface;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
import android.widget.TextView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.snackbar.Snackbar;
/**
* @author cenxiaozhong
* @date 2017/12/8
* @since 3.0.0
*/
public class DefaultDesignUIController extends DefaultUIController {
private BottomSheetDialog mBottomSheetDialog;
private static final int RECYCLERVIEW_ID = 0x1001;
private Activity mActivity = null;
private WebParentLayout mWebParentLayout;
private LayoutInflater mLayoutInflater;
@Override
public void onJsAlert(WebView view, String url, String message) {
onJsAlertInternal(view, message);
}
private void onJsAlertInternal(WebView view, String message) {
Activity mActivity = this.mActivity;
if (mActivity == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
try {
AgentWebUtils.show(view,
message,
Snackbar.LENGTH_SHORT,
Color.WHITE,
mActivity.getResources().getColor(R.color.black),
null,
-1,
null);
} catch (Throwable throwable) {
if (LogUtils.isDebug()){
throwable.printStackTrace();
}
}
}
@Override
public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) {
super.onJsConfirm(view, url, message, jsResult);
}
@Override
public void onSelectItemsPrompt(WebView view, String url, String[] ways, Handler.Callback callback) {
showChooserInternal(view, url, ways, callback);
}
@Override
public void onForceDownloadAlert(String url, final Handler.Callback callback) {
super.onForceDownloadAlert(url, callback);
}
private void showChooserInternal(WebView view, String url, final String[] ways, final Handler.Callback callback) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
LogUtils.i(TAG, "url:" + url + " ways:" + ways[0]);
RecyclerView mRecyclerView;
if (mBottomSheetDialog == null) {
mBottomSheetDialog = new BottomSheetDialog(mActivity);
mRecyclerView = new RecyclerView(mActivity);
mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
mRecyclerView.setId(RECYCLERVIEW_ID);
mBottomSheetDialog.setContentView(mRecyclerView);
}
mRecyclerView = (RecyclerView) mBottomSheetDialog.getDelegate().findViewById(RECYCLERVIEW_ID);
mRecyclerView.setAdapter(getAdapter(ways, callback));
mBottomSheetDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (callback != null) {
callback.handleMessage(Message.obtain(null, -1));
}
}
});
mBottomSheetDialog.show();
}
private RecyclerView.Adapter getAdapter(final String[] ways, final Handler.Callback callback) {
return new RecyclerView.Adapter() {
@Override
public BottomSheetHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new BottomSheetHolder(mLayoutInflater.inflate(android.R.layout.simple_list_item_1, viewGroup, false));
}
@Override
public void onBindViewHolder(BottomSheetHolder bottomSheetHolder, final int i) {
TypedValue outValue = new TypedValue();
mActivity.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
bottomSheetHolder.mTextView.setBackgroundResource(outValue.resourceId);
bottomSheetHolder.mTextView.setText(ways[i]);
bottomSheetHolder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mBottomSheetDialog != null && mBottomSheetDialog.isShowing()) {
mBottomSheetDialog.dismiss();
}
Message mMessage = Message.obtain();
mMessage.what = i;
callback.handleMessage(mMessage);
}
});
}
@Override
public int getItemCount() {
return ways.length;
}
};
}
private static class BottomSheetHolder extends RecyclerView.ViewHolder {
TextView mTextView;
public BottomSheetHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(android.R.id.text1);
}
}
@Override
public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) {
super.onJsPrompt(view, url, message, defaultValue, jsPromptResult);
}
@Override
protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) {
super.bindSupportWebParent(webParentLayout, activity);
this.mActivity = activity;
this.mWebParentLayout = webParentLayout;
mLayoutInflater = LayoutInflater.from(mActivity);
}
@Override
public void onShowMessage(String message, String from) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
if (!TextUtils.isEmpty(from) && from.contains("performDownload")) {
return;
}
onJsAlertInternal(mWebParentLayout.getWebView(), message);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultDownloadImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.download.library.DownloadImpl;
import com.download.library.DownloadListenerAdapter;
import com.download.library.Extra;
import com.download.library.ResourceRequest;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author cenxiaozhong
* @date 2017/5/13
*/
public class DefaultDownloadImpl implements android.webkit.DownloadListener {
/**
* Application Context
*/
protected Context mContext;
protected ConcurrentHashMap mDownloadTasks = new ConcurrentHashMap<>();
/**
* Activity
*/
protected WeakReference mActivityWeakReference = null;
/**
* TAG 用于打印,标识
*/
private static final String TAG = DefaultDownloadImpl.class.getSimpleName();
/**
* 权限拦截
*/
protected PermissionInterceptor mPermissionListener = null;
/**
* AbsAgentWebUIController
*/
protected WeakReference mAgentWebUIController;
private static Handler mHandler = new Handler(Looper.getMainLooper());
protected DefaultDownloadImpl(Activity activity, WebView webView, PermissionInterceptor permissionInterceptor) {
this.mContext = activity.getApplicationContext();
this.mActivityWeakReference = new WeakReference(activity);
this.mPermissionListener = permissionInterceptor;
this.mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(webView));
}
@Override
public void onDownloadStart(final String url, final String userAgent, final String contentDisposition, final String mimetype, final long contentLength) {
mHandler.post(new Runnable() {
@Override
public void run() {
onDownloadStartInternal(url, userAgent, contentDisposition, mimetype, contentLength);
}
});
}
protected void onDownloadStartInternal(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
if (null == mActivityWeakReference.get() || mActivityWeakReference.get().isFinishing()) {
return;
}
if (null != this.mPermissionListener) {
if (this.mPermissionListener.intercept(url, AgentWebPermissions.STORAGE, "download")) {
return;
}
}
ResourceRequest resourceRequest = createResourceRequest(url);
this.mDownloadTasks.put(url, resourceRequest);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
List mList = null;
if ((mList = checkNeedPermission()).isEmpty()) {
preDownload(url);
} else {
Action mAction = Action.createPermissionsAction(mList.toArray(new String[]{}));
ActionActivity.setPermissionListener(getPermissionListener(url));
ActionActivity.start(mActivityWeakReference.get(), mAction);
}
} else {
preDownload(url);
}
}
protected ResourceRequest createResourceRequest(String url) {
return DownloadImpl.getInstance().with(url).setEnableIndicator(true).autoOpenIgnoreMD5();
}
protected ActionActivity.PermissionListener getPermissionListener(final String url) {
return new ActionActivity.PermissionListener() {
@Override
public void onRequestPermissionsResult(@NonNull String[] permissions, @NonNull int[] grantResults, Bundle extras) {
if (checkNeedPermission().isEmpty()) {
preDownload(url);
} else {
if (null != mAgentWebUIController.get()) {
mAgentWebUIController
.get()
.onPermissionsDeny(
checkNeedPermission().
toArray(new String[]{}),
AgentWebPermissions.ACTION_STORAGE, "Download");
}
LogUtils.e(TAG, "储存权限获取失败~");
}
}
};
}
protected List checkNeedPermission() {
List deniedPermissions = new ArrayList<>();
if (!AgentWebUtils.hasPermission(mActivityWeakReference.get(), AgentWebPermissions.STORAGE)) {
deniedPermissions.addAll(Arrays.asList(AgentWebPermissions.STORAGE));
}
return deniedPermissions;
}
protected void preDownload(String url) {
// 移动数据
if (!isForceRequest(url) &&
AgentWebUtils.checkNetworkType(mContext) > 1) {
showDialog(url);
return;
}
performDownload(url);
}
protected boolean isForceRequest(String url) {
ResourceRequest resourceRequest = mDownloadTasks.get(url);
if (null != resourceRequest) {
return resourceRequest.getDownloadTask().isForceDownload();
}
return false;
}
protected void forceDownload(final String url) {
ResourceRequest resourceRequest = mDownloadTasks.get(url);
resourceRequest.setForceDownload(true);
performDownload(url);
}
protected void showDialog(final String url) {
Activity mActivity;
if (null == (mActivity = mActivityWeakReference.get()) || mActivity.isFinishing()) {
return;
}
AbsAgentWebUIController mAgentWebUIController;
if (null != (mAgentWebUIController = this.mAgentWebUIController.get())) {
mAgentWebUIController.onForceDownloadAlert(url, createCallback(url));
}
}
protected Handler.Callback createCallback(final String url) {
return new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
forceDownload(url);
return true;
}
};
}
protected void performDownload(String url) {
try {
LogUtils.e(TAG, "performDownload:" + url + " exist:" + DownloadImpl.getInstance().exist(url));
// 该链接是否正在下载
if (DownloadImpl.getInstance().exist(url)) {
if (null != mAgentWebUIController.get()) {
mAgentWebUIController.get().onShowMessage(
mActivityWeakReference.get()
.getString(R.string.agentweb_download_task_has_been_exist), "preDownload");
}
return;
}
ResourceRequest resourceRequest = mDownloadTasks.get(url);
resourceRequest.addHeader("Cookie", AgentWebConfig.getCookiesByUrl(url));
taskEnqueue(resourceRequest);
} catch (Throwable ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
}
protected void taskEnqueue(ResourceRequest resourceRequest) {
resourceRequest.enqueue(new DownloadListenerAdapter() {
@Override
public boolean onResult(Throwable throwable, Uri path, String url, Extra extra) {
mDownloadTasks.remove(url);
return super.onResult(throwable, path, url, extra);
}
});
}
public static DefaultDownloadImpl create(@NonNull Activity activity,
@NonNull WebView webView,
@Nullable PermissionInterceptor permissionInterceptor) {
try {
DownloadImpl.getInstance().with(activity.getApplication());
} catch (Throwable throwable) {
LogUtils.e(TAG, "implementation 'com.download.library:Downloader:x.x.x'");
if (LogUtils.isDebug()) {
throwable.printStackTrace();
}
}
return new DefaultDownloadImpl(activity, webView, permissionInterceptor);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultUIController.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebView;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
/**
* @author cenxiaozhong
* @date 2017/12/8
* @since 3.0.0
*/
public class DefaultUIController extends AbsAgentWebUIController {
private AlertDialog mAlertDialog;
protected AlertDialog mConfirmDialog;
private JsPromptResult mJsPromptResult = null;
private JsResult mJsResult = null;
private AlertDialog mPromptDialog = null;
private Activity mActivity;
private WebParentLayout mWebParentLayout;
private AlertDialog mAskOpenOtherAppDialog = null;
private ProgressDialog mProgressDialog;
private Resources mResources = null;
@Override
public void onJsAlert(WebView view, String url, String message) {
AgentWebUtils.toastShowShort(view.getContext().getApplicationContext(), message);
}
@Override
public void onOpenPagePrompt(WebView view, String url, final Handler.Callback callback) {
LogUtils.i(TAG, "onOpenPagePrompt");
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
if (mAskOpenOtherAppDialog == null) {
mAskOpenOtherAppDialog = new AlertDialog
.Builder(mActivity)
.setMessage(mResources.getString(R.string.agentweb_leave_app_and_go_other_page,
AgentWebUtils.getApplicationName(mActivity)))
.setTitle(mResources.getString(R.string.agentweb_tips))
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (callback != null) {
callback.handleMessage(Message.obtain(null, -1));
}
}
})//
.setPositiveButton(mResources.getString(R.string.agentweb_leave), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (callback != null) {
callback.handleMessage(Message.obtain(null, 1));
}
}
})
.create();
}
mAskOpenOtherAppDialog.show();
}
@Override
public void onJsConfirm(WebView view, String url, String message, JsResult jsResult) {
onJsConfirmInternal(message, jsResult);
}
@Override
public void onSelectItemsPrompt(WebView view, String url, final String[] ways, final Handler.Callback callback) {
showChooserInternal(ways, callback);
}
@Override
public void onForceDownloadAlert(String url, final Handler.Callback callback) {
onForceDownloadAlertInternal(callback);
}
private void onForceDownloadAlertInternal(final Handler.Callback callback) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
AlertDialog mAlertDialog = null;
mAlertDialog = new AlertDialog.Builder(mActivity)
.setTitle(mResources.getString(R.string.agentweb_tips))
.setMessage(mResources.getString(R.string.agentweb_honeycomblow))
.setNegativeButton(mResources.getString(R.string.agentweb_download), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (dialog != null) {
dialog.dismiss();
}
if (callback != null) {
callback.handleMessage(Message.obtain());
}
}
})//
.setPositiveButton(mResources.getString(R.string.agentweb_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (dialog != null) {
dialog.dismiss();
}
}
}).create();
mAlertDialog.show();
}
private void showChooserInternal(String[] ways, final Handler.Callback callback) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
mAlertDialog = new AlertDialog.Builder(mActivity)
.setSingleChoiceItems(ways, -1, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.i(TAG, "which:" + which);
if (callback != null) {
Message mMessage = Message.obtain();
mMessage.what = which;
callback.handleMessage(mMessage);
}
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
if (callback != null) {
callback.handleMessage(Message.obtain(null, -1));
}
}
}).create();
mAlertDialog.show();
}
private void onJsConfirmInternal(String message, JsResult jsResult) {
LogUtils.i(TAG, "activity:" + mActivity.hashCode() + " ");
Activity mActivity = this.mActivity;
if (mActivity == null || mActivity.isFinishing()) {
toCancelJsresult(jsResult);
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
toCancelJsresult(jsResult);
return;
}
}
if (mConfirmDialog == null) {
mConfirmDialog = new AlertDialog.Builder(mActivity)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
toDismissDialog(mConfirmDialog);
toCancelJsresult(mJsResult);
}
})//
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
toDismissDialog(mConfirmDialog);
if (mJsResult != null) {
mJsResult.confirm();
}
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
toCancelJsresult(mJsResult);
}
})
.create();
}
mConfirmDialog.setMessage(message);
this.mJsResult = jsResult;
mConfirmDialog.show();
}
private void onJsPromptInternal(String message, String defaultValue, JsPromptResult jsPromptResult) {
Activity mActivity = this.mActivity;
if (mActivity == null || mActivity.isFinishing()) {
jsPromptResult.cancel();
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
jsPromptResult.cancel();
return;
}
}
if (mPromptDialog == null) {
final EditText et = new EditText(mActivity);
et.setText(defaultValue);
mPromptDialog = new AlertDialog.Builder(mActivity)
.setView(et)
.setTitle(message)
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
toDismissDialog(mPromptDialog);
toCancelJsresult(mJsPromptResult);
}
})//
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
toDismissDialog(mPromptDialog);
if (mJsPromptResult != null) {
mJsPromptResult.confirm(et.getText().toString());
}
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
toCancelJsresult(mJsPromptResult);
}
})
.create();
}
this.mJsPromptResult = jsPromptResult;
mPromptDialog.show();
}
@Override
public void onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult jsPromptResult) {
onJsPromptInternal(message, defaultValue, jsPromptResult);
}
@Override
public void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) {
LogUtils.i(TAG, "mWebParentLayout onMainFrameError:" + mWebParentLayout);
if (mWebParentLayout != null) {
mWebParentLayout.showPageMainFrameError();
}
}
@Override
public void onShowMainFrame() {
if (mWebParentLayout != null) {
mWebParentLayout.hideErrorLayout();
}
}
@Override
public void onLoading(String msg) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(mActivity);
}
mProgressDialog.setCancelable(false);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setMessage(msg);
mProgressDialog.show();
}
@Override
public void onCancelLoading() {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (mActivity.isDestroyed()) {
return;
}
}
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
mProgressDialog = null;
}
@Override
public void onShowMessage(String message, String from) {
if (!TextUtils.isEmpty(from) && from.contains("performDownload")) {
return;
}
AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), message);
}
@Override
public void onPermissionsDeny(String[] permissions, String permissionType, String action) {
// AgentWebUtils.toastShowShort(mActivity.getApplicationContext(), "权限被冻结");
}
private void toCancelJsresult(JsResult result) {
if (result != null) {
result.cancel();
}
}
@Override
protected void bindSupportWebParent(WebParentLayout webParentLayout, Activity activity) {
this.mActivity = activity;
this.mWebParentLayout = webParentLayout;
mResources = this.mActivity.getResources();
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebClient.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.RequiresApi;
import com.alipay.sdk.app.H5PayCallback;
import com.alipay.sdk.app.PayTask;
import com.alipay.sdk.util.H5PayResultModel;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author cenxiaozhong
* @since 3.0.0
*/
public class DefaultWebClient extends MiddlewareWebClientBase {
/**
* Activity's WeakReference
*/
private WeakReference mWeakReference = null;
/**
* 缩放
*/
private static final int CONSTANTS_ABNORMAL_BIG = 7;
/**
* WebViewClient
*/
private WebViewClient mWebViewClient;
/**
* mWebClientHelper
*/
private boolean webClientHelper = true;
/**
* intent ' s scheme
*/
public static final String INTENT_SCHEME = "intent://";
/**
* Wechat pay scheme ,用于唤醒微信支付
*/
public static final String WEBCHAT_PAY_SCHEME = "weixin://wap/pay?";
/**
* 支付宝
*/
public static final String ALIPAYS_SCHEME = "alipays://";
/**
* http scheme
*/
public static final String HTTP_SCHEME = "http://";
/**
* https scheme
*/
public static final String HTTPS_SCHEME = "https://";
/**
* true 表示当前应用内依赖了 alipay library , false 反之
*/
private static final boolean HAS_ALIPAY_LIB;
/**
* WebViewClient's tag 用于打印
*/
private static final String TAG = DefaultWebClient.class.getSimpleName();
/**
* 直接打开其他页面
*/
public static final int DERECT_OPEN_OTHER_PAGE = 1001;
/**
* 弹窗咨询用户是否前往其他页面
*/
public static final int ASK_USER_OPEN_OTHER_PAGE = DERECT_OPEN_OTHER_PAGE >> 2;
/**
* 不允许打开其他页面
*/
public static final int DISALLOW_OPEN_OTHER_APP = DERECT_OPEN_OTHER_PAGE >> 4;
/**
* 默认为咨询用户
*/
private int mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE;
/**
* 是否拦截找不到相应页面的Url,默认拦截
*/
private boolean mIsInterceptUnkownUrl = true;
/**
* AbsAgentWebUIController
*/
private WeakReference mAgentWebUIController = null;
/**
* WebView
*/
private WebView mWebView;
/**
* 弹窗回调
*/
private Handler.Callback mCallback = null;
/**
* MainFrameErrorMethod
*/
private Method onMainFrameErrorMethod = null;
/**
* Alipay PayTask 对象
*/
private Object mPayTask;
/**
* SMS scheme
*/
public static final String SCHEME_SMS = "sms:";
/**
* 缓存当前出现错误的页面
*/
private Set mErrorUrlsSet = new HashSet<>();
/**
* 缓存等待加载完成的页面 onPageStart()执行之后 ,onPageFinished()执行之前
*/
private Set mWaittingFinishSet = new HashSet<>();
static {
boolean tag = true;
try {
Class.forName("com.alipay.sdk.app.PayTask");
} catch (Throwable ignore) {
tag = false;
}
HAS_ALIPAY_LIB = tag;
LogUtils.i(TAG, "HAS_ALIPAY_LIB:" + HAS_ALIPAY_LIB);
}
DefaultWebClient(Builder builder) {
super(builder.mClient);
this.mWebView = builder.mWebView;
this.mWebViewClient = builder.mClient;
mWeakReference = new WeakReference(builder.mActivity);
this.webClientHelper = builder.mWebClientHelper;
mAgentWebUIController = new WeakReference(AgentWebUtils.getAgentWebUIControllerByWebView(builder.mWebView));
mIsInterceptUnkownUrl = builder.mIsInterceptUnkownScheme;
if (builder.mUrlHandleWays <= 0) {
mUrlHandleWays = ASK_USER_OPEN_OTHER_PAGE;
} else {
mUrlHandleWays = builder.mUrlHandleWays;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) {
return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url));
}
if (!webClientHelper) {
return super.shouldOverrideUrlLoading(view, request);
}
if (handleCommonLink(url)) {
return true;
}
// intent
if (url.startsWith(INTENT_SCHEME)) {
handleIntentUrl(url);
LogUtils.i(TAG, "intent url ");
return true;
}
// 微信支付
if (url.startsWith(WEBCHAT_PAY_SCHEME)) {
LogUtils.i(TAG, "lookup wechat to pay ~~");
startActivity(url);
return true;
}
if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) {
LogUtils.i(TAG, "alipays url lookup alipay ~~ ");
return true;
}
if (queryActiviesNumber(url) > 0 && deepLink(url)) {
LogUtils.i(TAG, "intercept url:" + url);
return true;
}
if (mIsInterceptUnkownUrl) {
LogUtils.i(TAG, "intercept UnkownUrl :" + request.getUrl());
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return super.shouldInterceptRequest(view, url);
}
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
private boolean deepLink(String url) {
switch (mUrlHandleWays) {
// 直接打开其他App
case DERECT_OPEN_OTHER_PAGE:
lookup(url);
return true;
// 咨询用户是否打开其他App
case ASK_USER_OPEN_OTHER_PAGE:
Activity mActivity = null;
if ((mActivity = mWeakReference.get()) == null) {
return false;
}
ResolveInfo resolveInfo = lookupResolveInfo(url);
if (null == resolveInfo) {
return false;
}
ActivityInfo activityInfo = resolveInfo.activityInfo;
LogUtils.e(TAG, "resolve package:" + resolveInfo.activityInfo.packageName + " app package:" + mActivity.getPackageName());
if (activityInfo != null
&& !TextUtils.isEmpty(activityInfo.packageName)
&& activityInfo.packageName.equals(mActivity.getPackageName())) {
return lookup(url);
}
if (mAgentWebUIController.get() != null) {
mAgentWebUIController.get()
.onOpenPagePrompt(this.mWebView,
mWebView.getUrl(),
getCallback(url));
}
return true;
// 默认不打开
default:
return false;
}
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(HTTP_SCHEME) || url.startsWith(HTTPS_SCHEME)) {
return (webClientHelper && HAS_ALIPAY_LIB && isAlipay(view, url));
}
if (!webClientHelper) {
return false;
}
//电话 , 邮箱 , 短信
if (handleCommonLink(url)) {
return true;
}
//Intent scheme
if (url.startsWith(INTENT_SCHEME)) {
handleIntentUrl(url);
return true;
}
//微信支付
if (url.startsWith(WEBCHAT_PAY_SCHEME)) {
startActivity(url);
return true;
}
//支付宝
if (url.startsWith(ALIPAYS_SCHEME) && lookup(url)) {
return true;
}
//打开url 相对应的页面
if (queryActiviesNumber(url) > 0 && deepLink(url)) {
LogUtils.i(TAG, "intercept OtherAppScheme");
return true;
}
// 手机里面没有页面能匹配到该链接 ,拦截下来。
if (mIsInterceptUnkownUrl) {
LogUtils.i(TAG, "intercept InterceptUnkownScheme : " + url);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
private int queryActiviesNumber(String url) {
try {
if (mWeakReference.get() == null) {
return 0;
}
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
PackageManager mPackageManager = mWeakReference.get().getPackageManager();
List mResolveInfos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return mResolveInfos == null ? 0 : mResolveInfos.size();
} catch (URISyntaxException ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
return 0;
}
}
private void handleIntentUrl(String intentUrl) {
try {
Intent intent = null;
if (TextUtils.isEmpty(intentUrl) || !intentUrl.startsWith(INTENT_SCHEME)) {
return;
}
if (lookup(intentUrl)) {
return;
}
} catch (Throwable e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
}
private ResolveInfo lookupResolveInfo(String url) {
try {
Intent intent;
Activity mActivity = null;
if ((mActivity = mWeakReference.get()) == null) {
return null;
}
PackageManager packageManager = mActivity.getPackageManager();
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
return info;
} catch (Throwable ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
return null;
}
private boolean lookup(String url) {
try {
Intent intent;
Activity mActivity = null;
if ((mActivity = mWeakReference.get()) == null) {
return true;
}
PackageManager packageManager = mActivity.getPackageManager();
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
ResolveInfo info = packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
// 跳到该应用
if (info != null) {
mActivity.startActivity(intent);
return true;
}
} catch (Throwable ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
return false;
}
private boolean isAlipay(final WebView view, String url) {
try {
Activity mActivity = null;
if ((mActivity = mWeakReference.get()) == null) {
return false;
}
/**
* 推荐采用的新的二合一接口(payInterceptorWithUrl),只需调用一次
*/
if (mPayTask == null) {
Class clazz = Class.forName("com.alipay.sdk.app.PayTask");
Constructor> mConstructor = clazz.getConstructor(Activity.class);
mPayTask = mConstructor.newInstance(mActivity);
}
final PayTask task = (PayTask) mPayTask;
boolean isIntercepted = task.payInterceptorWithUrl(url, true, new H5PayCallback() {
@Override
public void onPayResult(final H5PayResultModel result) {
final String url = result.getReturnUrl();
if (!TextUtils.isEmpty(url)) {
AgentWebUtils.runInUiThread(new Runnable() {
@Override
public void run() {
view.loadUrl(url);
}
});
}
}
});
if (isIntercepted) {
LogUtils.i(TAG, "alipay-isIntercepted:" + isIntercepted + " url:" + url);
}
return isIntercepted;
} catch (Throwable ignore) {
if (AgentWebConfig.DEBUG) {
ignore.printStackTrace();
}
}
return false;
}
private boolean handleCommonLink(String url) {
if (url.startsWith(WebView.SCHEME_TEL)
|| url.startsWith(SCHEME_SMS)
|| url.startsWith(WebView.SCHEME_MAILTO)
|| url.startsWith(WebView.SCHEME_GEO)) {
try {
Activity mActivity = null;
if ((mActivity = mWeakReference.get()) == null) {
return false;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
mActivity.startActivity(intent);
} catch (ActivityNotFoundException ignored) {
if (AgentWebConfig.DEBUG) {
ignored.printStackTrace();
}
}
return true;
}
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (!mWaittingFinishSet.contains(url)) {
mWaittingFinishSet.add(url);
}
super.onPageStarted(view, url, favicon);
}
/**
* MainFrame Error
*
* @param view
* @param errorCode
* @param description
* @param failingUrl
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
LogUtils.i(TAG, "onReceivedError:" + description + " CODE:" + errorCode);
onMainFrameError(view, errorCode, description, failingUrl);
}
@TargetApi(Build.VERSION_CODES.M)
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (request.isForMainFrame()) {
onMainFrameError(view,
error.getErrorCode(), error.getDescription().toString(),
request.getUrl().toString());
}
LogUtils.i(TAG, "onReceivedError:" + error.getDescription() + " code:" + error.getErrorCode());
}
private void onMainFrameError(WebView view, int errorCode, String description, String failingUrl) {
mErrorUrlsSet.add(failingUrl);
// 下面逻辑判断开发者是否重写了 onMainFrameError 方法 , 优先交给开发者处理
if (this.mWebViewClient != null && webClientHelper) {
Method mMethod = this.onMainFrameErrorMethod;
if (mMethod != null || (this.onMainFrameErrorMethod = mMethod = AgentWebUtils.isExistMethod(mWebViewClient, "onMainFrameError", AbsAgentWebUIController.class, WebView.class, int.class, String.class, String.class)) != null) {
try {
mMethod.invoke(this.mWebViewClient, mAgentWebUIController.get(), view, errorCode, description, failingUrl);
} catch (Throwable ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
return;
}
}
if (mAgentWebUIController.get() != null) {
mAgentWebUIController.get().onMainFrameError(view, errorCode, description, failingUrl);
}
// this.mWebView.setVisibility(View.GONE);
}
@Override
public void onPageFinished(WebView view, String url) {
if (!mErrorUrlsSet.contains(url) && mWaittingFinishSet.contains(url)) {
if (mAgentWebUIController.get() != null) {
mAgentWebUIController.get().onShowMainFrame();
}
} else {
view.setVisibility(View.VISIBLE);
}
if (mWaittingFinishSet.contains(url)) {
mWaittingFinishSet.remove(url);
}
if (!mErrorUrlsSet.isEmpty()) {
mErrorUrlsSet.clear();
}
super.onPageFinished(view, url);
}
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return super.shouldOverrideKeyEvent(view, event);
}
private void startActivity(String url) {
try {
if (mWeakReference.get() == null) {
return;
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
mWeakReference.get().startActivity(intent);
} catch (Exception e) {
if (LogUtils.isDebug()) {
e.printStackTrace();
}
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
LogUtils.i(TAG, "onScaleChanged:" + oldScale + " n:" + newScale);
if (newScale - oldScale > CONSTANTS_ABNORMAL_BIG) {
view.setInitialScale((int) (oldScale / newScale * 100));
}
}
private Handler.Callback getCallback(final String url) {
if (this.mCallback != null) {
return this.mCallback;
}
return this.mCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
lookup(url);
break;
default:
return true;
}
return true;
}
};
}
public static Builder createBuilder() {
return new Builder();
}
public static class Builder {
private Activity mActivity;
private WebViewClient mClient;
private boolean mWebClientHelper;
private PermissionInterceptor mPermissionInterceptor;
private WebView mWebView;
private boolean mIsInterceptUnkownScheme;
private int mUrlHandleWays;
public Builder setActivity(Activity activity) {
this.mActivity = activity;
return this;
}
public Builder setClient(WebViewClient client) {
this.mClient = client;
return this;
}
public Builder setWebClientHelper(boolean webClientHelper) {
this.mWebClientHelper = webClientHelper;
return this;
}
public Builder setPermissionInterceptor(PermissionInterceptor permissionInterceptor) {
this.mPermissionInterceptor = permissionInterceptor;
return this;
}
public Builder setWebView(WebView webView) {
this.mWebView = webView;
return this;
}
public Builder setInterceptUnkownUrl(boolean interceptUnkownScheme) {
this.mIsInterceptUnkownScheme = interceptUnkownScheme;
return this;
}
public Builder setUrlHandleWays(int urlHandleWays) {
this.mUrlHandleWays = urlHandleWays;
return this;
}
public DefaultWebClient build() {
return new DefaultWebClient(this);
}
}
public static enum OpenOtherPageWays {
/**
* 直接打开跳转页
*/
DERECT(DefaultWebClient.DERECT_OPEN_OTHER_PAGE),
/**
* 咨询用户是否打开
*/
ASK(DefaultWebClient.ASK_USER_OPEN_OTHER_PAGE),
/**
* 禁止打开其他页面
*/
DISALLOW(DefaultWebClient.DISALLOW_OPEN_OTHER_APP);
int code;
OpenOtherPageWays(int code) {
this.code = code;
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebCreator.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.webkit.WebView;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class DefaultWebCreator implements WebCreator {
private Activity mActivity;
private ViewGroup mViewGroup;
private boolean mIsNeedDefaultProgress;
private int mIndex;
private BaseIndicatorView mProgressView;
private ViewGroup.LayoutParams mLayoutParams = null;
private int mColor = -1;
/**
* 单位dp
*/
private int mHeight;
private boolean mIsCreated = false;
private IWebLayout mIWebLayout;
private BaseIndicatorSpec mBaseIndicatorSpec;
private WebView mWebView = null;
private FrameLayout mFrameLayout = null;
private View mTargetProgress;
private static final String TAG = DefaultWebCreator.class.getSimpleName();
/**
* 使用默认的进度条
* @param activity
* @param viewGroup
* @param lp
* @param index
* @param color
* @param mHeight
* @param webView
* @param webLayout
*/
protected DefaultWebCreator(@NonNull Activity activity,
@Nullable ViewGroup viewGroup,
ViewGroup.LayoutParams lp,
int index,
int color,
int mHeight,
WebView webView,
IWebLayout webLayout) {
this.mActivity = activity;
this.mViewGroup = viewGroup;
this.mIsNeedDefaultProgress = true;
this.mIndex = index;
this.mColor = color;
this.mLayoutParams = lp;
this.mHeight = mHeight;
this.mWebView = webView;
this.mIWebLayout = webLayout;
}
/**
* 关闭进度条
* @param activity
* @param viewGroup
* @param lp
* @param index
* @param webView
* @param webLayout
*/
protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, @Nullable WebView webView, IWebLayout webLayout) {
this.mActivity = activity;
this.mViewGroup = viewGroup;
this.mIsNeedDefaultProgress = false;
this.mIndex = index;
this.mLayoutParams = lp;
this.mWebView = webView;
this.mIWebLayout = webLayout;
}
/**
* 自定义Indicator
* @param activity
* @param viewGroup
* @param lp
* @param index
* @param progressView
* @param webView
* @param webLayout
*/
protected DefaultWebCreator(@NonNull Activity activity, @Nullable ViewGroup viewGroup, ViewGroup.LayoutParams lp, int index, BaseIndicatorView progressView, WebView webView, IWebLayout webLayout) {
this.mActivity = activity;
this.mViewGroup = viewGroup;
this.mIsNeedDefaultProgress = false;
this.mIndex = index;
this.mLayoutParams = lp;
this.mProgressView = progressView;
this.mWebView = webView;
this.mIWebLayout = webLayout;
}
public void setWebView(WebView webView) {
mWebView = webView;
}
public FrameLayout getFrameLayout() {
return mFrameLayout;
}
public View getTargetProgress() {
return mTargetProgress;
}
public void setTargetProgress(View targetProgress) {
this.mTargetProgress = targetProgress;
}
@Override
public DefaultWebCreator create() {
if (mIsCreated) {
return this;
}
mIsCreated = true;
ViewGroup mViewGroup = this.mViewGroup;
if (mViewGroup == null) {
mViewGroup = this.mFrameLayout = (FrameLayout) createLayout();
mActivity.setContentView(mViewGroup);
} else {
if (mIndex == -1) {
mViewGroup.addView(this.mFrameLayout = (FrameLayout) createLayout(), mLayoutParams);
} else {
mViewGroup.addView(this.mFrameLayout = (FrameLayout) createLayout(), mIndex, mLayoutParams);
}
}
return this;
}
@Override
public WebView getWebView() {
return mWebView;
}
@Override
public FrameLayout getWebParentLayout() {
return mFrameLayout;
}
private ViewGroup createLayout() {
Activity mActivity = this.mActivity;
WebParentLayout mFrameLayout = new WebParentLayout(mActivity);
mFrameLayout.setId(R.id.web_parent_layout_id);
mFrameLayout.setBackgroundColor(Color.WHITE);
View target = mIWebLayout == null ? (this.mWebView = (WebView) createWebView()) : webLayout();
FrameLayout.LayoutParams mLayoutParams = new FrameLayout.LayoutParams(-1, -1);
mFrameLayout.addView(target, mLayoutParams);
mFrameLayout.bindWebView(this.mWebView);
LogUtils.i(TAG, " instanceof AgentWebView:" + (this.mWebView instanceof AgentWebView));
if (this.mWebView instanceof AgentWebView) {
AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE;
}
ViewStub mViewStub = new ViewStub(mActivity);
mViewStub.setId(R.id.mainframe_error_viewsub_id);
mFrameLayout.addView(mViewStub, new FrameLayout.LayoutParams(-1, -1));
if (mIsNeedDefaultProgress) {
FrameLayout.LayoutParams lp = null;
WebIndicator mWebIndicator = new WebIndicator(mActivity);
if (mHeight > 0) {
lp = new FrameLayout.LayoutParams(-2, AgentWebUtils.dp2px(mActivity, mHeight));
} else {
lp = mWebIndicator.offerLayoutParams();
}
if (mColor != -1) {
mWebIndicator.setColor(mColor);
}
lp.gravity = Gravity.TOP;
mFrameLayout.addView((View) (this.mBaseIndicatorSpec = mWebIndicator), lp);
mWebIndicator.setVisibility(View.GONE);
} else if (!mIsNeedDefaultProgress && mProgressView != null) {
mFrameLayout.addView((View) (this.mBaseIndicatorSpec = (BaseIndicatorSpec) mProgressView), mProgressView.offerLayoutParams());
mProgressView.setVisibility(View.GONE);
}
return mFrameLayout;
}
private View webLayout() {
WebView mWebView = null;
if ((mWebView = mIWebLayout.getWebView()) == null) {
mWebView = createWebView();
mIWebLayout.getLayout().addView(mWebView, -1, -1);
LogUtils.i(TAG, "add webview");
} else {
AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_CUSTOM_TYPE;
}
this.mWebView = mWebView;
return mIWebLayout.getLayout();
}
private WebView createWebView() {
WebView mWebView = null;
if (this.mWebView != null) {
mWebView = this.mWebView;
AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_CUSTOM_TYPE;
} else if (AgentWebConfig.IS_KITKAT_OR_BELOW_KITKAT) {
mWebView = new AgentWebView(mActivity);
AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE;
} else {
mWebView = new LollipopFixedWebView(mActivity);
AgentWebConfig.WEBVIEW_TYPE = AgentWebConfig.WEBVIEW_DEFAULT_TYPE;
}
return mWebView;
}
@Override
public BaseIndicatorSpec offer() {
return mBaseIndicatorSpec;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/DefaultWebLifeCycleImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Build;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @date 2017/6/3
* @since 2.0.0
*/
public class DefaultWebLifeCycleImpl implements WebLifeCycle {
private WebView mWebView;
DefaultWebLifeCycleImpl(WebView webView) {
this.mWebView = webView;
}
@Override
public void onResume() {
if (this.mWebView != null) {
if (Build.VERSION.SDK_INT >= 11){
this.mWebView.onResume();
}
this.mWebView.resumeTimers();
}
}
@Override
public void onPause() {
if (this.mWebView != null) {
if (Build.VERSION.SDK_INT >= 11){
this.mWebView.onPause();
}
this.mWebView.pauseTimers();
}
}
@Override
public void onDestroy() {
if(this.mWebView!=null){
this.mWebView.resumeTimers();
}
AgentWebUtils.clearWebView(this.mWebView);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/EventHandlerImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.view.KeyEvent;
import android.webkit.WebView;
/**
* IEventHandler 对事件的处理,主要是针对
* 视屏状态进行了处理 , 如果当前状态为 视频状态
* 则先退出视频。
*
* @author cenxiaozhong
* @date 2017/6/3
* @since 2.0.0
*/
public class EventHandlerImpl implements IEventHandler {
private WebView mWebView;
private EventInterceptor mEventInterceptor;
public static final EventHandlerImpl getInstantce(WebView view, EventInterceptor eventInterceptor) {
return new EventHandlerImpl(view, eventInterceptor);
}
public EventHandlerImpl(WebView webView, EventInterceptor eventInterceptor) {
this.mWebView = webView;
this.mEventInterceptor = eventInterceptor;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
return back();
}
return false;
}
@Override
public boolean back() {
if (this.mEventInterceptor != null && this.mEventInterceptor.event()) {
return true;
}
if (mWebView != null && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return false;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/EventInterceptor.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2017/6/3
* @since 1.0.0
*/
public interface EventInterceptor {
boolean event();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/HookManager.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class HookManager {
public static AgentWeb hookAgentWeb(AgentWeb agentWeb, AgentWeb.AgentBuilder agentBuilder) {
return agentWeb;
}
public static boolean permissionHook(String url,String[]permissions){
return true;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/HttpHeaders.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.net.Uri;
import android.text.TextUtils;
import androidx.collection.ArrayMap;
import java.util.Map;
/**
* @author cenxiaozhong
* @date 2017/7/5
* @since 2.0.0
*/
public class HttpHeaders {
public static HttpHeaders create() {
return new HttpHeaders();
}
private final Map> mHeaders;
HttpHeaders() {
mHeaders = new ArrayMap>();
}
public Map getHeaders(String url) {
String subUrl = subBaseUrl(url);
if (mHeaders.get(subUrl) == null) {
Map headers = new ArrayMap<>();
mHeaders.put(subUrl, headers);
return headers;
}
return mHeaders.get(subUrl);
}
public void additionalHttpHeader(String url, String k, String v) {
if (null == url) {
return;
}
url = subBaseUrl(url);
Map> mHeaders = getHeaders();
Map headersMap = mHeaders.get(subBaseUrl(url));
if (null == headersMap) {
headersMap = new ArrayMap<>();
}
headersMap.put(k, v);
mHeaders.put(url, headersMap);
}
public void additionalHttpHeaders(String url, Map headers) {
if (null == url) {
return;
}
String subUrl = subBaseUrl(url);
Map> mHeaders = getHeaders();
Map headersMap = headers;
if (null == headersMap) {
headersMap = new ArrayMap<>();
}
mHeaders.put(subUrl, headersMap);
}
public void removeHttpHeader(String url, String k) {
if (null == url) {
return;
}
String subUrl = subBaseUrl(url);
Map> mHeaders = getHeaders();
Map headersMap = mHeaders.get(subUrl);
if (null != headersMap) {
headersMap.remove(k);
}
}
public boolean isEmptyHeaders(String url) {
url = subBaseUrl(url);
Map heads = getHeaders(url);
return heads == null || heads.isEmpty();
}
public Map> getHeaders() {
return this.mHeaders;
}
private String subBaseUrl(String originUrl) {
if (TextUtils.isEmpty(originUrl)) {
return originUrl;
}
Uri originUri = Uri.parse(originUrl);
return originUri.getScheme() + "://" + originUri.getAuthority();
}
@Override
public String toString() {
return "HttpHeaders{" +
"mHeaders=" + mHeaders +
'}';
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IAgentWebSettings.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public interface IAgentWebSettings {
IAgentWebSettings toSetting(WebView webView);
T getWebSettings();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IEventHandler.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.view.KeyEvent;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public interface IEventHandler {
boolean onKeyDown(int keyCode, KeyEvent event);
boolean back();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IUrlLoader.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import java.util.Map;
/**
* @author cenxiaozhong
* @date 2017/6/3
* @update 4.0.0
* @since 2.0.0
*/
public interface IUrlLoader {
void loadUrl(String url);
void loadUrl(String url, Map headers);
void reload();
void loadData(String data, String mimeType, String encoding);
void stopLoading();
void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl);
void postUrl(String url, byte[] params);
HttpHeaders getHttpHeaders();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IVideo.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.view.View;
import android.webkit.WebChromeClient;
/**
* @author cenxiaozhong
* @date 2017/6/10
* @since 2.0.0
*/
public interface IVideo {
void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback);
void onHideCustomView();
boolean isVideoState();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IWebIndicator.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public interface IWebIndicator {
T offer();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IWebLayout.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.view.ViewGroup;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Created by cenxiaozhong on 2017/7/1.
*/
/**
* @author cenxiaozhong
* @date 2017/7/1
* @update 4.0.0
* @since 1.0.0
*/
public interface IWebLayout {
/**
*
* @return WebView 的父控件
*/
@NonNull
V getLayout();
/**
*
* @return 返回 WebView 或 WebView 的子View ,返回null AgentWeb 内部会创建适当 WebView
*/
@Nullable
T getWebView();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IndicatorController.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @update 4.0.0
* @since 1.0.0
*/
public interface IndicatorController {
void progress(WebView v, int newProgress);
BaseIndicatorSpec offerIndicator();
void showIndicator();
void setProgress(int newProgress);
void finish();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/IndicatorHandler.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class IndicatorHandler implements IndicatorController {
private BaseIndicatorSpec mBaseIndicatorSpec;
@Override
public void progress(WebView v, int newProgress) {
if (newProgress == 0) {
reset();
} else if (newProgress > 0 && newProgress <= 10) {
showIndicator();
} else if (newProgress > 10 && newProgress < 95) {
setProgress(newProgress);
} else {
setProgress(newProgress);
finish();
}
}
@Override
public BaseIndicatorSpec offerIndicator() {
return this.mBaseIndicatorSpec;
}
public void reset() {
if (mBaseIndicatorSpec != null) {
mBaseIndicatorSpec.reset();
}
}
@Override
public void finish() {
if (mBaseIndicatorSpec != null) {
mBaseIndicatorSpec.hide();
}
}
@Override
public void setProgress(int n) {
if (mBaseIndicatorSpec != null) {
mBaseIndicatorSpec.setProgress(n);
}
}
@Override
public void showIndicator() {
if (mBaseIndicatorSpec != null) {
mBaseIndicatorSpec.show();
}
}
static IndicatorHandler getInstance() {
return new IndicatorHandler();
}
IndicatorHandler inJectIndicator(BaseIndicatorSpec baseIndicatorSpec) {
this.mBaseIndicatorSpec = baseIndicatorSpec;
return this;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsAccessEntrace.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.ValueCallback;
/**
* @author cenxiaozhong
* @date 2017/5/14
* @since 1.0.0
*/
public interface JsAccessEntrace extends QuickCallJs {
void callJs(String js, ValueCallback callback);
void callJs(String js);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsAccessEntraceImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Handler;
import android.os.Looper;
import android.webkit.ValueCallback;
import android.webkit.WebView;
/**
* @author cenxiaozhong
* @date 2017/6/3
* @since 1.0.0
*/
public class JsAccessEntraceImpl extends BaseJsAccessEntrace {
private WebView mWebView;
private Handler mHandler = new Handler(Looper.getMainLooper());
public static JsAccessEntraceImpl getInstance(WebView webView) {
return new JsAccessEntraceImpl(webView);
}
private JsAccessEntraceImpl(WebView webView) {
super(webView);
this.mWebView = webView;
}
private void safeCallJs(final String s, final ValueCallback valueCallback) {
mHandler.post(new Runnable() {
@Override
public void run() {
callJs(s, valueCallback);
}
});
}
@Override
public void callJs(String params, final ValueCallback callback) {
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
safeCallJs(params, callback);
return;
}
super.callJs(params,callback);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsBaseInterfaceHolder.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Build;
import android.webkit.JavascriptInterface;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author cenxiaozhong
* @date 2017/5/13
* @since 1.0.0
*/
public abstract class JsBaseInterfaceHolder implements JsInterfaceHolder {
private AgentWeb.SecurityType mSecurityType;
protected JsBaseInterfaceHolder(AgentWeb.SecurityType securityType) {
this.mSecurityType = securityType;
}
@Override
public boolean checkObject(Object v) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return true;
}
if (AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE) {
return true;
}
boolean tag = false;
Class clazz = v.getClass();
Method[] mMethods = clazz.getMethods();
for (Method mMethod : mMethods) {
Annotation[] mAnnotations = mMethod.getAnnotations();
for (Annotation mAnnotation : mAnnotations) {
if (mAnnotation instanceof JavascriptInterface) {
tag = true;
break;
}
}
if (tag) {
break;
}
}
return tag;
}
protected boolean checkSecurity() {
return mSecurityType != AgentWeb.SecurityType.STRICT_CHECK
? true : AgentWebConfig.WEBVIEW_TYPE == AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE
? true : Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsCallJava.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Method;
import java.util.HashMap;
public class JsCallJava {
private final static String TAG = "JsCallJava";
private final static String RETURN_RESULT_FORMAT = "{\"CODE\": %d, \"result\": %s}";
private static final String MSG_PROMPT_HEADER = "AgentWeb:";
private static final String KEY_OBJ = "obj";
private static final String KEY_METHOD = "method";
private static final String KEY_TYPES = "types";
private static final String KEY_ARGS = "args";
private static final String[] IGNORE_UNSAFE_METHODS = {"getClass", "hashCode", "notify", "notifyAll", "equals", "toString", "wait"};
private HashMap mMethodsMap;
private Object mInterfaceObj;
private String mInterfacedName;
private String mPreloadInterfaceJs;
public JsCallJava(Object interfaceObj, String interfaceName) {
try {
if (TextUtils.isEmpty(interfaceName)) {
throw new Exception("injected name can not be null");
}
mInterfaceObj = interfaceObj;
mInterfacedName = interfaceName;
mMethodsMap = new HashMap();
// getMethods会获得所有继承与非继承的方法
Method[] methods = mInterfaceObj.getClass().getMethods();
// 拼接的js脚本可参照备份文件:./library/doc/injected.js
StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\"");
sb.append(mInterfacedName);
sb.append(" init begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};");
for (Method method : methods) {
Log.i("Info","method:"+method);
String sign;
if ((sign = genJavaMethodSign(method)) == null) {
continue;
}
mMethodsMap.put(sign, method);
sb.append(String.format("a.%s=", method.getName()));
}
sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\"");
sb.append(mInterfacedName);
sb.append(" call result, message:miss method name\"}var e=[];for(var h=1;h 0) {
Class[] methodTypes = currMethod.getParameterTypes();
int currIndex;
Class currCls;
while (numIndex > 0) {
currIndex = numIndex - numIndex / 10 * 10 - 1;
currCls = methodTypes[currIndex];
if (currCls == int.class) {
values[currIndex] = argsVals.getInt(currIndex);
} else if (currCls == long.class) {
//WARN: argsJson.getLong(k + defValue) will return a bigger incorrect number
values[currIndex] = Long.parseLong(argsVals.getString(currIndex));
} else {
values[currIndex] = argsVals.getDouble(currIndex);
}
numIndex /= 10;
}
}
return getReturn(jsonObject, 200, currMethod.invoke(mInterfaceObj, values), time);
} catch (Exception e) {
LogUtils.safeCheckCrash(TAG, "call", e);
//优先返回详细的错误信息
if (e.getCause() != null) {
return getReturn(jsonObject, 500, "method execute result:" + e.getCause().getMessage(), time);
}
return getReturn(jsonObject, 500, "method execute result:" + e.getMessage(), time);
}
} else {
return getReturn(jsonObject, 500, "call data empty", time);
}
}
private String getReturn(JSONObject reqJson, int stateCode, Object result, long time) {
String insertRes;
if (result == null) {
insertRes = "null";
} else if (result instanceof String) {
result = ((String) result).replace("\"", "\\\"");
insertRes = "\"".concat(String.valueOf(result)).concat("\"");
} else { // 其他类型直接转换
insertRes = String.valueOf(result);
// 兼容:如果在解决WebView注入安全漏洞时,js注入采用的是XXX:function(){return prompt(...)}的形式,函数返回类型包括:void、int、boolean、String;
// 在返回给网页(onJsPrompt方法中jsPromptResult.confirm)的时候强制返回的是String类型,所以在此将result的值加双引号兼容一下;
// insertRes = "\"".concat(String.valueOf(result)).concat("\"");
}
String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes);
if (LogUtils.isDebug()) {
Log.d(TAG, "call time: " + (android.os.SystemClock.uptimeMillis() - time) + ", request: " + reqJson + ", result:" + resStr);
}
return resStr;
}
private static String promptMsgFormat(String object, String method, String types, String args) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append(KEY_OBJ).append(":").append(object).append(",");
sb.append(KEY_METHOD).append(":").append(method).append(",");
sb.append(KEY_TYPES).append(":").append(types).append(",");
sb.append(KEY_ARGS).append(":").append(args);
sb.append("}");
return sb.toString();
}
/**
* 是否是“Java接口类中方法调用”的内部消息;
*
* @param message
* @return
*/
static boolean isSafeWebViewCallMsg(String message) {
return message.startsWith(MSG_PROMPT_HEADER);
}
static JSONObject getMsgJSONObject(String message) {
message = message.substring(MSG_PROMPT_HEADER.length());
JSONObject jsonObject;
try {
jsonObject = new JSONObject(message);
} catch (JSONException e) {
e.printStackTrace();
jsonObject = new JSONObject();
}
return jsonObject;
}
static String getInterfacedName(JSONObject jsonObject) {
return jsonObject.optString(KEY_OBJ);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsCallback.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.util.Log;
import android.webkit.WebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
public class JsCallback {
private static final String CALLBACK_JS_FORMAT = "javascript:%s.callback(%d, %d %s);";
private int mIndex;
private boolean mCouldGoOn;
private WeakReference mWebViewRef;
private int mIsPermanent;
private String mInjectedName;
public JsCallback(WebView view, String injectedName, int index) {
mCouldGoOn = true;
mWebViewRef = new WeakReference(view);
mInjectedName = injectedName;
mIndex = index;
}
/**
* 向网页执行js回调;
* @param args
* @throws JsCallbackException
*/
public void apply (Object... args) throws JsCallbackException {
if (mWebViewRef.get() == null) {
throw new JsCallbackException("the WebView related to the JsCallback has been recycled");
}
if (!mCouldGoOn) {
throw new JsCallbackException("the JsCallback isn't permanent,cannot be called more than once");
}
StringBuilder sb = new StringBuilder();
for (Object arg : args){
sb.append(",");
boolean isStrArg = arg instanceof String;
// 有的接口将Json对象转换成了String返回,这里不能加双引号,否则网页会认为是String而不是JavaScript对象;
boolean isObjArg = isJavaScriptObject(arg);
if (isStrArg && !isObjArg) {
sb.append("\"");
}
sb.append(String.valueOf(arg));
if (isStrArg && !isObjArg) {
sb.append("\"");
}
}
String execJs = String.format(CALLBACK_JS_FORMAT, mInjectedName, mIndex, mIsPermanent, sb.toString());
if (LogUtils.isDebug()) {
Log.d("JsCallBack", execJs);
}
mWebViewRef.get().loadUrl(execJs);
mCouldGoOn = mIsPermanent > 0;
}
/**
* 是否是JSON(JavaScript Object Notation)对象;
* @param obj
* @return
*/
private boolean isJavaScriptObject(Object obj) {
if (obj instanceof JSONObject || obj instanceof JSONArray) {
return true;
} else {
String json = obj.toString();
try {
new JSONObject(json);
} catch (JSONException e) {
try {
new JSONArray(json);
} catch (JSONException e1) {
return false;
}
}
return true;
}
}
/**
* 一般传入到Java方法的js function是一次性使用的,即在Java层jsCallback.apply(...)之后不能再发起回调了;
* 如果需要传入的function能够在当前页面生命周期内多次使用,请在第一次apply前setPermanent(true);
* @param value
*/
public void setPermanent (boolean value) {
mIsPermanent = value ? 1 : 0;
}
public static class JsCallbackException extends Exception {
public JsCallbackException (String msg) {
super(msg);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolder.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import java.util.Map;
/**
* @author cenxiaozhong
* @date 2017/5/13
* @since 1.0.0
*/
public interface JsInterfaceHolder {
JsInterfaceHolder addJavaObjects(Map maps);
JsInterfaceHolder addJavaObject(String k, Object v);
boolean checkObject(Object v);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceHolderImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
import java.util.Map;
import java.util.Set;
/**
* @author cenxiaozhong
* @date 2017/5/13
* @since 1.0.0
*/
public class JsInterfaceHolderImpl extends JsBaseInterfaceHolder {
private static final String TAG = JsInterfaceHolderImpl.class.getSimpleName();
private WebView mWebView;
private AgentWeb.SecurityType mSecurityType;
static JsInterfaceHolderImpl getJsInterfaceHolder(WebView webView, AgentWeb.SecurityType securityType) {
return new JsInterfaceHolderImpl(webView, securityType);
}
JsInterfaceHolderImpl(WebView webView, AgentWeb.SecurityType securityType) {
super(securityType);
this.mWebView = webView;
this.mSecurityType = securityType;
}
@Override
public JsInterfaceHolder addJavaObjects(Map maps) {
if (!checkSecurity()) {
LogUtils.e(TAG, "The injected object is not safe, give up injection");
return this;
}
Set> sets = maps.entrySet();
for (Map.Entry mEntry : sets) {
Object v = mEntry.getValue();
boolean t = checkObject(v);
if (!t) {
throw new JsInterfaceObjectException("This object has not offer method javascript to call ,please check addJavascriptInterface annotation was be added");
} else {
addJavaObjectDirect(mEntry.getKey(), v);
}
}
return this;
}
@Override
public JsInterfaceHolder addJavaObject(String k, Object v) {
if (!checkSecurity()) {
return this;
}
boolean t = checkObject(v);
if (!t) {
throw new JsInterfaceObjectException("this object has not offer method javascript to call , please check addJavascriptInterface annotation was be added");
} else {
addJavaObjectDirect(k, v);
}
return this;
}
private JsInterfaceHolder addJavaObjectDirect(String k, Object v) {
LogUtils.i(TAG, "k:" + k + " v:" + v);
this.mWebView.addJavascriptInterface(v, k);
return this;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/JsInterfaceObjectException.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2017/5/13
* @since 1.0.0
*/
public class JsInterfaceObjectException extends RuntimeException {
JsInterfaceObjectException(String msg){
super(msg);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/LayoutParamsOffer.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.widget.FrameLayout;
/**
* @author cenxiaozhong
* @date 2017/5/12
* @since 1.0.0
*/
public interface LayoutParamsOffer {
T offerLayoutParams();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/LogUtils.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.util.Log;
/**
* @author cenxiaozhong
* @date 2017/5/28
* @since 1.0.0
*/
public class LogUtils {
private static final String PREFIX = " agentweb - ";
public static boolean isDebug() {
return AgentWebConfig.DEBUG;
}
public static void i(String tag, String message) {
if (isDebug()){
Log.i(PREFIX.concat(tag), message);
}
}
public static void v(String tag, String message) {
if (isDebug()){
Log.v(PREFIX.concat(tag), message);
}
}
public static void safeCheckCrash(String tag, String msg, Throwable tr) {
if (isDebug()) {
throw new RuntimeException(PREFIX.concat(tag) + " " + msg, tr);
} else {
Log.e(PREFIX.concat(tag), msg, tr);
}
}
public static void e(String tag, String msg, Throwable tr) {
Log.e(tag, msg, tr);
}
public static void e(String tag, String message) {
if (isDebug()){
Log.e(PREFIX.concat(tag), message);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/LollipopFixedWebView.java
================================================
package com.just.agentweb;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.webkit.WebView;
/**
* 修复 Android 5.0 & 5.1 打开 WebView 闪退问题:
* 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview
*/
@SuppressWarnings("unused")
public class LollipopFixedWebView extends WebView {
public LollipopFixedWebView(Context context) {
super(getFixedContext(context));
}
public LollipopFixedWebView(Context context, AttributeSet attrs) {
super(getFixedContext(context), attrs);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(getFixedContext(context), attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(getFixedContext(context), attrs, defStyleAttr, defStyleRes);
}
public LollipopFixedWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
super(getFixedContext(context), attrs, defStyleAttr, privateBrowsing);
}
public static Context getFixedContext(Context context) {
// if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23) {
// // A void crashing on Android 5 and 6 (API level 21 to 23)
// return context.createConfigurationContext(new Configuration());
// }
return context;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebChromeBase.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebChromeClient;
/**
* @author cenxiaozhong
* @date 2017/12/16
* @since 3.0.0
*/
public class MiddlewareWebChromeBase extends WebChromeClientDelegate {
private MiddlewareWebChromeBase mMiddlewareWebChromeBase;
protected MiddlewareWebChromeBase(WebChromeClient webChromeClient) {
super(webChromeClient);
}
protected MiddlewareWebChromeBase() {
super(null);
}
@Override
final void setDelegate(WebChromeClient delegate) {
super.setDelegate(delegate);
}
final MiddlewareWebChromeBase enq(MiddlewareWebChromeBase middlewareWebChromeBase) {
setDelegate(middlewareWebChromeBase);
this.mMiddlewareWebChromeBase = middlewareWebChromeBase;
return this.mMiddlewareWebChromeBase;
}
final MiddlewareWebChromeBase next() {
return this.mMiddlewareWebChromeBase;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/MiddlewareWebClientBase.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebViewClient;
/**
* @author cenxiaozhong
* @date 2017/12/15
* @since 3.0.0
*/
public class MiddlewareWebClientBase extends WebViewClientDelegate {
private MiddlewareWebClientBase mMiddleWrareWebClientBase;
private static String TAG = MiddlewareWebClientBase.class.getSimpleName();
MiddlewareWebClientBase(MiddlewareWebClientBase client) {
super(client);
this.mMiddleWrareWebClientBase = client;
}
protected MiddlewareWebClientBase(WebViewClient client) {
super(client);
}
protected MiddlewareWebClientBase() {
super(null);
}
final MiddlewareWebClientBase next() {
return this.mMiddleWrareWebClientBase;
}
@Override
final void setDelegate(WebViewClient delegate) {
super.setDelegate(delegate);
}
final MiddlewareWebClientBase enq(MiddlewareWebClientBase middleWrareWebClientBase) {
setDelegate(middleWrareWebClientBase);
this.mMiddleWrareWebClientBase = middleWrareWebClientBase;
return middleWrareWebClientBase;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/NestedScrollAgentWebView.java
================================================
/*
* Copyright (C) LeonDevLifeLog(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.content.Context;
import android.view.MotionEvent;
import androidx.core.view.NestedScrollingChild;
import androidx.core.view.NestedScrollingChildHelper;
import androidx.core.view.ViewCompat;
/**
* 结合CoordinatorLayout可以与Toolbar联动的webview
* @author LeonDevLifeLog
* @since 4.0.0
*/
public class NestedScrollAgentWebView extends AgentWebView implements NestedScrollingChild {
/***
* 方法一
* https://github.com/fashare2015/NestedScrollWebView
*/
public NestedScrollAgentWebView(Context context) {
super(context);
initView();
}
private static final int INVALID_POINTER = -1;
private void initView() {
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
/**
* Position of the last motion event.
*/
private int mLastMotionY;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Used during scrolling to retrieve the new offset within the window.
*/
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private NestedScrollingChildHelper mChildHelper;
boolean mIsBeingDragged;
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked();
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
mIsBeingDragged = false;
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
// downY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_MOVE:
// KLog.e("移动开始:");
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
break;
}
// if( !onlyVerticalMove(ev) ){
// break;
// }
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1];
}
// Scroll to follow the motion event
mLastMotionY = y - mScrollOffset[1];
final int oldY = getScrollY();
final int scrolledDeltaY = Math.max(0, oldY + deltaY) - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
mLastMotionY -= mScrollOffset[1];
}
// KLog.e("移动结束:");
break;
case MotionEvent.ACTION_UP:
mActivePointerId = INVALID_POINTER;
endDrag();
break;
case MotionEvent.ACTION_CANCEL:
mActivePointerId = INVALID_POINTER;
endDrag();
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionY = (int) ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
break;
default:
break;
}
return super.onTouchEvent(ev);
}
private void endDrag() {
mIsBeingDragged = false;
stopNestedScroll();
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
// KLog.e("分配滚动事件:" + dxConsumed + " " + dyConsumed );
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/PermissionInterceptor.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @since 3.0.0
*/
public interface PermissionInterceptor {
boolean intercept(String url, String[] permissions, String action);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/ProcessUtils.java
================================================
package com.just.agentweb;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
/**
* Adapted from com.blankj.utilcode.util.ProcessUtils#getCurrentProcessName
*/
class ProcessUtils {
static String getCurrentProcessName(Context context) {
String name = getCurrentProcessNameByFile();
if (!TextUtils.isEmpty(name)) return name;
name = getCurrentProcessNameByAms(context);
if (!TextUtils.isEmpty(name)) return name;
name = getCurrentProcessNameByReflect(context);
return name;
}
private static String getCurrentProcessNameByFile() {
try {
File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
String processName = mBufferedReader.readLine().trim();
mBufferedReader.close();
return processName;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
private static String getCurrentProcessNameByAms(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) return "";
List info = am.getRunningAppProcesses();
if (info == null || info.size() == 0) return "";
int pid = android.os.Process.myPid();
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
if (aInfo.pid == pid) {
if (aInfo.processName != null) {
return aInfo.processName;
}
}
}
return "";
}
private static String getCurrentProcessNameByReflect(Context context) {
String processName = "";
try {
Application app = (Application) context.getApplicationContext();
Field loadedApkField = app.getClass().getField("mLoadedApk");
loadedApkField.setAccessible(true);
Object loadedApk = loadedApkField.get(app);
Field activityThreadField = loadedApk.getClass().getDeclaredField("mActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(loadedApk);
Method getProcessName = activityThread.getClass().getDeclaredMethod("getProcessName");
processName = (String) getProcessName.invoke(activityThread);
} catch (Exception e) {
e.printStackTrace();
}
return processName;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/Provider.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2017/7/5
* @since 1.0.0
*/
public interface Provider {
T provide();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/QuickCallJs.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Build;
import android.webkit.ValueCallback;
import androidx.annotation.RequiresApi;
/**
* @author cenxiaozhong
* @date 2017/5/29
* @since 1.0.0
*/
public interface QuickCallJs {
@RequiresApi(Build.VERSION_CODES.KITKAT)
void quickCallJs(String method, ValueCallback callback, String... params);
void quickCallJs(String method, String... params);
void quickCallJs(String method);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/UrlCommonException.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class UrlCommonException extends RuntimeException {
public UrlCommonException() {
}
public UrlCommonException(String msg) {
super(msg);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/UrlLoaderImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Handler;
import android.os.Looper;
import android.webkit.WebView;
import java.util.Map;
/**
* @author cenxiaozhong
* @since 2.0.0
*/
public class UrlLoaderImpl implements IUrlLoader {
private Handler mHandler = null;
private WebView mWebView;
private HttpHeaders mHttpHeaders;
public static final String TAG = UrlLoaderImpl.class.getSimpleName();
UrlLoaderImpl(WebView webView, HttpHeaders httpHeaders) {
this.mWebView = webView;
if (this.mWebView == null) {
new NullPointerException("webview cannot be null .");
}
this.mHttpHeaders = httpHeaders;
if (this.mHttpHeaders == null) {
this.mHttpHeaders = HttpHeaders.create();
}
mHandler = new Handler(Looper.getMainLooper());
}
private void safeLoadUrl(final String url) {
mHandler.post(new Runnable() {
@Override
public void run() {
loadUrl(url);
}
});
}
private void safeReload() {
mHandler.post(new Runnable() {
@Override
public void run() {
reload();
}
});
}
@Override
public void loadUrl(String url) {
this.loadUrl(url, this.mHttpHeaders.getHeaders(url));
}
@Override
public void loadUrl(final String url, final Map headers) {
if (!AgentWebUtils.isUIThread()) {
AgentWebUtils.runInUiThread(new Runnable() {
@Override
public void run() {
loadUrl(url, headers);
}
});
}
LogUtils.i(TAG, "loadUrl:" + url + " headers:" + headers);
if (headers == null || headers.isEmpty()) {
this.mWebView.loadUrl(url);
} else {
this.mWebView.loadUrl(url, headers);
}
}
@Override
public void reload() {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
reload();
}
});
return;
}
this.mWebView.reload();
}
@Override
public void loadData(final String data, final String mimeType, final String encoding) {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
loadData(data, mimeType, encoding);
}
});
return;
}
this.mWebView.loadData(data, mimeType, encoding);
}
@Override
public void stopLoading() {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
stopLoading();
}
});
return;
}
this.mWebView.stopLoading();
}
@Override
public void loadDataWithBaseURL(final String baseUrl, final String data, final String mimeType, final String encoding, final String historyUrl) {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
});
return;
}
this.mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@Override
public void postUrl(final String url, final byte[] postData) {
if (!AgentWebUtils.isUIThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
postUrl(url, postData);
}
});
return;
}
this.mWebView.postUrl(url, postData);
}
@Override
public HttpHeaders getHttpHeaders() {
return this.mHttpHeaders == null ? this.mHttpHeaders = HttpHeaders.create() : this.mHttpHeaders;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/VideoImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.FrameLayout;
import androidx.core.util.Pair;
import java.util.HashSet;
import java.util.Set;
/**
* @author cenxiaozhong
*/
public class VideoImpl implements IVideo, EventInterceptor {
private Activity mActivity;
private WebView mWebView;
private static final String TAG = VideoImpl.class.getSimpleName();
private Set> mFlags = null;
private View mMoiveView = null;
private ViewGroup mMoiveParentView = null;
private WebChromeClient.CustomViewCallback mCallback;
public VideoImpl(Activity mActivity, WebView webView) {
this.mActivity = mActivity;
this.mWebView = webView;
mFlags = new HashSet<>();
}
@Override
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Activity mActivity;
if ((mActivity = this.mActivity) == null || mActivity.isFinishing()) {
return;
}
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Window mWindow = mActivity.getWindow();
Pair mPair = null;
// 保存当前屏幕的状态
if ((mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
mPair = new Pair<>(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 0);
mWindow.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mFlags.add(mPair);
}
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && (mWindow.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) == 0) {
mPair = new Pair<>(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 0);
mWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
mFlags.add(mPair);
}
if (mMoiveView != null) {
callback.onCustomViewHidden();
return;
}
if (mWebView != null) {
mWebView.setVisibility(View.GONE);
}
if (mMoiveParentView == null) {
FrameLayout mDecorView = (FrameLayout) mActivity.getWindow().getDecorView();
mMoiveParentView = new FrameLayout(mActivity);
mMoiveParentView.setBackgroundColor(Color.BLACK);
mDecorView.addView(mMoiveParentView);
}
this.mCallback = callback;
mMoiveParentView.addView(this.mMoiveView = view);
mMoiveParentView.setVisibility(View.VISIBLE);
}
@Override
public void onHideCustomView() {
if (mMoiveView == null) {
return;
}
if (mActivity != null && mActivity.getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
if (!mFlags.isEmpty()) {
for (Pair mPair : mFlags) {
mActivity.getWindow().setFlags(mPair.second, mPair.first);
}
mFlags.clear();
}
mMoiveView.setVisibility(View.GONE);
if (mMoiveParentView != null && mMoiveView != null) {
mMoiveParentView.removeView(mMoiveView);
}
if (mMoiveParentView != null) {
mMoiveParentView.setVisibility(View.GONE);
}
if (this.mCallback != null) {
mCallback.onCustomViewHidden();
}
this.mMoiveView = null;
if (mWebView != null) {
mWebView.setVisibility(View.VISIBLE);
}
}
@Override
public boolean isVideoState() {
return mMoiveView != null;
}
@Override
public boolean event() {
if (isVideoState()) {
onHideCustomView();
return true;
} else {
return false;
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebChromeClient.java
================================================
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2019/4/13
* @since 1.0.0
*/
public class WebChromeClient extends MiddlewareWebChromeBase{
public WebChromeClient() {
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebChromeClientDelegate.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.PermissionRequest;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import androidx.annotation.RequiresApi;
import java.lang.reflect.Method;
/**
* @update WebChromeClientWrapper rename to WebChromeClientDelegate
* @author cenxiaozhong
* @since 1.0.0
*/
public class WebChromeClientDelegate extends WebChromeClient {
private WebChromeClient mDelegate;
protected WebChromeClient getDelegate() {
return mDelegate;
}
public WebChromeClientDelegate(WebChromeClient webChromeClient) {
this.mDelegate = webChromeClient;
}
void setDelegate(WebChromeClient delegate) {
this.mDelegate = delegate;
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (this.mDelegate != null) {
this.mDelegate.onProgressChanged(view, newProgress);
return;
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
if (this.mDelegate != null) {
this.mDelegate.onReceivedTitle(view, title);
return;
}
super.onReceivedTitle(view, title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
if (this.mDelegate != null) {
this.mDelegate.onReceivedIcon(view, icon);
return;
}
super.onReceivedIcon(view, icon);
}
@Override
public void onReceivedTouchIconUrl(WebView view, String url,
boolean precomposed) {
if (this.mDelegate != null) {
this.mDelegate.onReceivedTouchIconUrl(view, url, precomposed);
return;
}
super.onReceivedTouchIconUrl(view, url, precomposed);
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (this.mDelegate != null) {
this.mDelegate.onShowCustomView(view, callback);
return;
}
super.onShowCustomView(view, callback);
}
@Override
public void onShowCustomView(View view, int requestedOrientation,
CustomViewCallback callback) {
if (this.mDelegate != null) {
this.mDelegate.onShowCustomView(view, requestedOrientation, callback);
return;
}
super.onShowCustomView(view, requestedOrientation, callback);
}
@Override
public void onHideCustomView() {
if (this.mDelegate != null) {
this.mDelegate.onHideCustomView();
return;
}
super.onHideCustomView();
}
@Override
public boolean onCreateWindow(WebView view, boolean isDialog,
boolean isUserGesture, Message resultMsg) {
if (this.mDelegate != null) {
return this.mDelegate.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
@Override
public void onRequestFocus(WebView view) {
if (this.mDelegate != null) {
this.mDelegate.onRequestFocus(view);
return;
}
super.onRequestFocus(view);
}
@Override
public void onCloseWindow(WebView window) {
if (this.mDelegate != null) {
this.mDelegate.onCloseWindow(window);
return;
}
super.onCloseWindow(window);
}
@Override
public boolean onJsAlert(WebView view, String url, String message,
JsResult result) {
if (this.mDelegate != null) {
return this.mDelegate.onJsAlert(view, url, message, result);
}
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message,
JsResult result) {
if (this.mDelegate != null) {
return this.mDelegate.onJsConfirm(view, url, message, result);
}
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
if (this.mDelegate != null) {
return this.mDelegate.onJsPrompt(view, url, message, defaultValue, result);
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message,
JsResult result) {
if (this.mDelegate != null) {
return this.mDelegate.onJsBeforeUnload(view, url, message, result);
}
return super.onJsBeforeUnload(view, url, message, result);
}
@Override
@Deprecated
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
long quota, long estimatedDatabaseSize, long totalQuota,
WebStorage.QuotaUpdater quotaUpdater) {
// This default implementation passes the current quota back to WebCore.
// WebCore will interpret this that new quota was declined.
if (this.mDelegate != null) {
this.mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater);
return;
}
super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater);
}
@Override
@Deprecated
public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
WebStorage.QuotaUpdater quotaUpdater) {
if (this.mDelegate != null) {
this.mDelegate.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
return;
}
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
if (this.mDelegate != null) {
this.mDelegate.onGeolocationPermissionsShowPrompt(origin, callback);
return;
}
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
/**
* notify the host application that a request for Geolocation permissions,
* made with a previous call to
* {@link #onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback) onGeolocationPermissionsShowPrompt()}
* has been canceled. Any related UI should therefore be hidden.
*/
@Override
public void onGeolocationPermissionsHidePrompt() {
if (this.mDelegate != null) {
this.mDelegate.onGeolocationPermissionsHidePrompt();
return;
}
super.onGeolocationPermissionsHidePrompt();
}
@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequest(PermissionRequest request) {
if (this.mDelegate != null) {
this.mDelegate.onPermissionRequest(request);
return;
}
super.onPermissionRequest(request);
}
@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequestCanceled(PermissionRequest request) {
if (this.mDelegate != null) {
this.mDelegate.onPermissionRequestCanceled(request);
return;
}
super.onPermissionRequestCanceled(request);
}
@Override
public boolean onJsTimeout() {
if (this.mDelegate != null) {
return this.mDelegate.onJsTimeout();
}
return super.onJsTimeout();
}
@Override
@Deprecated
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
if (this.mDelegate != null) {
this.mDelegate.onConsoleMessage(message, lineNumber, sourceID);
return;
}
super.onConsoleMessage(message, lineNumber, sourceID);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
/*onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
consoleMessage.sourceId());*/
if (this.mDelegate != null) {
return this.mDelegate.onConsoleMessage(consoleMessage);
}
return super.onConsoleMessage(consoleMessage);
}
@Override
public Bitmap getDefaultVideoPoster() {
if (this.mDelegate != null) {
return this.mDelegate.getDefaultVideoPoster();
}
return super.getDefaultVideoPoster();
}
@Override
public View getVideoLoadingProgressView() {
if (this.mDelegate != null) {
return this.mDelegate.getVideoLoadingProgressView();
}
return super.getVideoLoadingProgressView();
}
@Override
public void getVisitedHistory(ValueCallback callback) {
if (this.mDelegate != null) {
this.mDelegate.getVisitedHistory(callback);
return;
}
super.getVisitedHistory(callback);
}
@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback,
FileChooserParams fileChooserParams) {
if (this.mDelegate != null) {
return this.mDelegate.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
/**
* Android >= 4.1
* @param uploadFile
* @param acceptType
* @param capture
*/
public void openFileChooser(ValueCallback uploadFile, String acceptType, String capture) {
commonRefect(this.mDelegate, "openFileChooser", new Object[]{uploadFile, acceptType, capture}, ValueCallback.class, String.class, String.class);
}
/**
* Android < 3.0
* @param valueCallback
*/
public void openFileChooser(ValueCallback valueCallback) {
commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback}, ValueCallback.class);
}
/**
* Android >= 3.0
* @param valueCallback
* @param acceptType
*/
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
commonRefect(this.mDelegate, "openFileChooser", new Object[]{valueCallback, acceptType}, ValueCallback.class, String.class);
}
private void commonRefect(WebChromeClient o, String mothed, Object[] os, Class... clazzs) {
try {
if (o == null) {
return;
}
Class> clazz = o.getClass();
Method mMethod = clazz.getMethod(mothed, clazzs);
mMethod.invoke(o, os);
} catch (Exception ignore) {
if (LogUtils.isDebug()) {
ignore.printStackTrace();
}
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebCreator.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
import android.widget.FrameLayout;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public interface WebCreator extends IWebIndicator {
WebCreator create();
WebView getWebView();
FrameLayout getWebParentLayout();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebIndicator.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
/**
* @author cenxiaozhong
* @since 1.0.0
*/
public class WebIndicator extends BaseIndicatorView implements BaseIndicatorSpec {
/**
* 进度条颜色
*/
private int mColor;
/**
* 进度条的画笔
*/
private Paint mPaint;
/**
* 进度条动画
*/
private Animator mAnimator;
/**
* 控件的宽度
*/
private int mTargetWidth = 0;
/**
* 默认匀速动画最大的时长
*/
public static final int MAX_UNIFORM_SPEED_DURATION = 8 * 1000;
/**
* 默认加速后减速动画最大时长
*/
public static final int MAX_DECELERATE_SPEED_DURATION = 450;
/**
* 结束动画时长 , Fade out 。
*/
public static final int DO_END_ANIMATION_DURATION = 600;
/**
* 当前匀速动画最大的时长
*/
private static int CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION;
/**
* 当前加速后减速动画最大时长
*/
private static int CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION;
/**
* 标志当前进度条的状态
*/
private int TAG = 0;
public static final int UN_START = 0;
public static final int STARTED = 1;
public static final int FINISH = 2;
private float mTarget = 0f;
private float mCurrentProgress = 0F;
/**
* 默认的高度
*/
public static int WEB_INDICATOR_DEFAULT_HEIGHT = 3;
public WebIndicator(Context context) {
this(context, null);
}
public WebIndicator(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WebIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mPaint = new Paint();
mColor = Color.parseColor("#1aad19");
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
mPaint.setDither(true);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mTargetWidth = context.getResources().getDisplayMetrics().widthPixels;
WEB_INDICATOR_DEFAULT_HEIGHT = AgentWebUtils.dp2px(context, 3);
}
public void setColor(int color) {
this.mColor = color;
mPaint.setColor(color);
}
public void setColor(String color) {
this.setColor(Color.parseColor(color));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int w = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
if (wMode == MeasureSpec.AT_MOST) {
w = w <= getContext().getResources().getDisplayMetrics().widthPixels ? w : getContext().getResources().getDisplayMetrics().widthPixels;
}
if (hMode == MeasureSpec.AT_MOST) {
h = WEB_INDICATOR_DEFAULT_HEIGHT;
}
this.setMeasuredDimension(w, h);
}
@Override
protected void onDraw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawRect(0, 0, mCurrentProgress / 100 * Float.valueOf(this.getWidth()), this.getHeight(), mPaint);
}
@Override
public void show() {
if (getVisibility() == View.GONE) {
this.setVisibility(View.VISIBLE);
mCurrentProgress = 0f;
startAnim(false);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.mTargetWidth = getMeasuredWidth();
int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
if (mTargetWidth >= screenWidth) {
CURRENT_MAX_DECELERATE_SPEED_DURATION = MAX_DECELERATE_SPEED_DURATION;
CURRENT_MAX_UNIFORM_SPEED_DURATION = MAX_UNIFORM_SPEED_DURATION;
} else {
//取比值
float rate = this.mTargetWidth / Float.valueOf(screenWidth);
CURRENT_MAX_UNIFORM_SPEED_DURATION = (int) (MAX_UNIFORM_SPEED_DURATION * rate);
CURRENT_MAX_DECELERATE_SPEED_DURATION = (int) (MAX_DECELERATE_SPEED_DURATION * rate);
}
LogUtils.i("WebProgress", "CURRENT_MAX_UNIFORM_SPEED_DURATION" + CURRENT_MAX_UNIFORM_SPEED_DURATION);
}
public void setProgress(float progress) {
if (getVisibility() == View.GONE) {
setVisibility(View.VISIBLE);
}
if (progress < 95f){
return;
}
if (TAG != FINISH) {
startAnim(true);
}
}
@Override
public void hide() {
TAG = FINISH;
}
private void startAnim(boolean isFinished) {
float v = isFinished ? 100 : 95;
if (mAnimator != null && mAnimator.isStarted()) {
mAnimator.cancel();
}
mCurrentProgress = mCurrentProgress == 0f ? 0.00000001f : mCurrentProgress;
LogUtils.i("WebIndicator", "mCurrentProgress:" + mCurrentProgress + " v:" + v + " :" + (1f - mCurrentProgress));
if (!isFinished) {
ValueAnimator mAnimator = ValueAnimator.ofFloat(mCurrentProgress, v);
float residue = 1f - mCurrentProgress / 100 - 0.05f;
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setDuration((long) (residue * CURRENT_MAX_UNIFORM_SPEED_DURATION));
mAnimator.addUpdateListener(mAnimatorUpdateListener);
mAnimator.start();
this.mAnimator = mAnimator;
} else {
ValueAnimator segment95Animator = null;
if (mCurrentProgress < 95f) {
segment95Animator = ValueAnimator.ofFloat(mCurrentProgress, 95);
float residue = 1f - mCurrentProgress / 100f - 0.05f;
segment95Animator.setInterpolator(new LinearInterpolator());
segment95Animator.setDuration((long) (residue * CURRENT_MAX_DECELERATE_SPEED_DURATION));
segment95Animator.setInterpolator(new DecelerateInterpolator());
segment95Animator.addUpdateListener(mAnimatorUpdateListener);
}
ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
mObjectAnimator.setDuration(DO_END_ANIMATION_DURATION);
ValueAnimator mValueAnimatorEnd = ValueAnimator.ofFloat(95f, 100f);
mValueAnimatorEnd.setDuration(DO_END_ANIMATION_DURATION);
mValueAnimatorEnd.addUpdateListener(mAnimatorUpdateListener);
AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(mObjectAnimator, mValueAnimatorEnd);
if (segment95Animator != null) {
AnimatorSet mAnimatorSet1 = new AnimatorSet();
mAnimatorSet1.play(mAnimatorSet).after(segment95Animator);
mAnimatorSet = mAnimatorSet1;
}
mAnimatorSet.addListener(mAnimatorListenerAdapter);
mAnimatorSet.start();
mAnimator = mAnimatorSet;
}
TAG = STARTED;
mTarget = v;
}
private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = (float) animation.getAnimatedValue();
WebIndicator.this.mCurrentProgress = t;
WebIndicator.this.invalidate();
}
};
private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
doEnd();
}
};
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
/**
* animator cause leak , if not cancel;
*/
if (mAnimator != null && mAnimator.isStarted()) {
mAnimator.cancel();
mAnimator = null;
}
}
private void doEnd() {
if (TAG == FINISH && mCurrentProgress == 100f) {
setVisibility(GONE);
mCurrentProgress = 0f;
this.setAlpha(1f);
}
TAG = UN_START;
}
@Override
public void reset() {
mCurrentProgress = 0;
if (mAnimator != null && mAnimator.isStarted()){
mAnimator.cancel();
}
}
@Override
public void setProgress(int newProgress) {
setProgress(Float.valueOf(newProgress));
}
@Override
public LayoutParams offerLayoutParams() {
return new LayoutParams(-1, WEB_INDICATOR_DEFAULT_HEIGHT);
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebLifeCycle.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2017/5/30
* @since 1.0.0
*/
public interface WebLifeCycle {
void onResume();
void onPause();
void onDestroy();
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebListenerManager.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* @author cenxiaozhong
* @date 2017/5/13
* @since 1.0.0
*/
public interface WebListenerManager {
WebListenerManager setWebChromeClient(WebView webview, WebChromeClient webChromeClient);
WebListenerManager setWebViewClient(WebView webView, WebViewClient webViewClient);
WebListenerManager setDownloader(WebView webView, DownloadListener downloadListener);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebParentLayout.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.webkit.WebView;
import android.widget.FrameLayout;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @author cenxiaozhong
* @date 2017/12/8
* @since 3.0.0
*/
public class WebParentLayout extends FrameLayout implements Provider {
private AbsAgentWebUIController mAgentWebUIController = null;
private static final String TAG = WebParentLayout.class.getSimpleName();
@LayoutRes
private int mErrorLayoutRes;
@IdRes
private int mClickId = -1;
private View mErrorView;
private WebView mWebView;
private FrameLayout mErrorLayout = null;
WebParentLayout(@NonNull Context context) {
this(context, null);
LogUtils.i(TAG, "WebParentLayout");
}
WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
WebParentLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (!(context instanceof Activity)) {
throw new IllegalArgumentException("WebParentLayout context must be activity or activity sub class .");
}
this.mErrorLayoutRes = R.layout.agentweb_error_page;
}
void bindController(AbsAgentWebUIController agentWebUIController) {
this.mAgentWebUIController = agentWebUIController;
this.mAgentWebUIController.bindWebParent(this, (Activity) getContext());
}
void showPageMainFrameError() {
View container = this.mErrorLayout;
if (container != null) {
container.setVisibility(View.VISIBLE);
} else {
createErrorLayout();
container = this.mErrorLayout;
}
View clickView = null;
if (mClickId != -1 && (clickView = container.findViewById(mClickId)) != null) {
clickView.setClickable(true);
} else {
container.setClickable(true);
}
}
private void createErrorLayout() {
final FrameLayout mFrameLayout = new FrameLayout(getContext());
mFrameLayout.setBackgroundColor(Color.WHITE);
mFrameLayout.setId(R.id.mainframe_error_container_id);
if (this.mErrorView == null) {
LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
LogUtils.i(TAG, "mErrorLayoutRes:" + mErrorLayoutRes);
mLayoutInflater.inflate(mErrorLayoutRes, mFrameLayout, true);
} else {
mFrameLayout.addView(mErrorView);
}
ViewStub mViewStub = (ViewStub) this.findViewById(R.id.mainframe_error_viewsub_id);
final int index = this.indexOfChild(mViewStub);
this.removeViewInLayout(mViewStub);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
this.addView(this.mErrorLayout = mFrameLayout, index, layoutParams);
} else {
this.addView(this.mErrorLayout = mFrameLayout, index);
}
mFrameLayout.setVisibility(View.VISIBLE);
if (mClickId != -1) {
final View clickView = mFrameLayout.findViewById(mClickId);
if (clickView != null) {
clickView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (getWebView() != null) {
clickView.setClickable(false);
getWebView().reload();
}
}
});
return;
} else {
if (LogUtils.isDebug()) {
LogUtils.e(TAG, "ClickView is null , cannot bind accurate view to refresh or reload .");
}
}
}
mFrameLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (getWebView() != null) {
mFrameLayout.setClickable(false);
getWebView().reload();
}
}
});
}
void hideErrorLayout() {
View mView = null;
if ((mView = this.findViewById(R.id.mainframe_error_container_id)) != null) {
mView.setVisibility(View.GONE);
}
}
void setErrorView(@NonNull View errorView) {
this.mErrorView = errorView;
}
void setErrorLayoutRes(@LayoutRes int resLayout, @IdRes int id) {
this.mClickId = id;
if (this.mClickId <= 0) {
this.mClickId = -1;
}
this.mErrorLayoutRes = resLayout;
if (this.mErrorLayoutRes <= 0) {
this.mErrorLayoutRes = R.layout.agentweb_error_page;
}
}
@Override
public AbsAgentWebUIController provide() {
return this.mAgentWebUIController;
}
void bindWebView(WebView view) {
if (this.mWebView == null) {
this.mWebView = view;
}
}
WebView getWebView() {
return this.mWebView;
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityCheckLogic.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.webkit.WebView;
import androidx.collection.ArrayMap;
/**
* @author cenxiaozhong
*/
public interface WebSecurityCheckLogic {
void dealHoneyComb(WebView view);
void dealJsInterface(ArrayMap objects, AgentWeb.SecurityType securityType);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityController.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
/**
* @author cenxiaozhong
*/
public interface WebSecurityController {
void check(T t);
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityControllerImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.os.Build;
import android.webkit.WebView;
import androidx.collection.ArrayMap;
/**
* @author cenxiaozhong
*/
public class WebSecurityControllerImpl implements WebSecurityController {
private WebView mWebView;
private ArrayMap mMap;
private AgentWeb.SecurityType mSecurityType;
public WebSecurityControllerImpl(WebView view, ArrayMap map, AgentWeb.SecurityType securityType) {
this.mWebView = view;
this.mMap = map;
this.mSecurityType = securityType;
}
@Override
public void check(WebSecurityCheckLogic webSecurityCheckLogic) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
webSecurityCheckLogic.dealHoneyComb(mWebView);
}
if (mMap != null && mSecurityType == AgentWeb.SecurityType.STRICT_CHECK && !mMap.isEmpty()) {
webSecurityCheckLogic.dealJsInterface(mMap, mSecurityType);
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebSecurityLogicImpl.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.WebView;
import androidx.collection.ArrayMap;
/**
* @author cenxiaozhong
*/
public class WebSecurityLogicImpl implements WebSecurityCheckLogic {
private String TAG=this.getClass().getSimpleName();
public static WebSecurityLogicImpl getInstance() {
return new WebSecurityLogicImpl();
}
public WebSecurityLogicImpl(){}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void dealHoneyComb(WebView view) {
if (Build.VERSION_CODES.HONEYCOMB > Build.VERSION.SDK_INT || Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1){
return;
}
view.removeJavascriptInterface("searchBoxJavaBridge_");
view.removeJavascriptInterface("accessibility");
view.removeJavascriptInterface("accessibilityTraversal");
}
@Override
public void dealJsInterface(ArrayMap objects, AgentWeb.SecurityType securityType) {
if (securityType== AgentWeb.SecurityType.STRICT_CHECK
&&AgentWebConfig.WEBVIEW_TYPE!=AgentWebConfig.WEBVIEW_AGENTWEB_SAFE_TYPE
&&Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
LogUtils.e(TAG,"Give up all inject objects");
objects.clear();
objects = null;
System.gc();
}
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebViewClient.java
================================================
package com.just.agentweb;
/**
* @author cenxiaozhong
* @date 2019/4/13
* @since 1.0.0
*/
public class WebViewClient extends MiddlewareWebClientBase {
public WebViewClient() {
}
}
================================================
FILE: agentweb-core/src/main/java/com/just/agentweb/WebViewClientDelegate.java
================================================
/*
* Copyright (C) Justson(https://github.com/Justson/AgentWeb)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.just.agentweb;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
import android.view.KeyEvent;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* @update WrapperWebViewClient rename WebViewClientDelegate
* @author cenxiaozhong
* @date 2017/5/28
*/
public class WebViewClientDelegate extends WebViewClient {
private WebViewClient mDelegate;
private static final String TAG = WebViewClientDelegate.class.getSimpleName();
WebViewClientDelegate(WebViewClient client) {
this.mDelegate = client;
}
protected WebViewClient getDelegate() {
return mDelegate;
}
void setDelegate(WebViewClient delegate) {
this.mDelegate = delegate;
}
@Deprecated
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (mDelegate != null) {
return mDelegate.shouldOverrideUrlLoading(view, url);
}
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (mDelegate != null) {
return mDelegate.shouldOverrideUrlLoading(view, request);
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (mDelegate != null) {
mDelegate.onPageStarted(view, url, favicon);
return;
}
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
if (mDelegate != null) {
mDelegate.onPageFinished(view, url);
return;
}
super.onPageFinished(view, url);
}
@Override
public void onLoadResource(WebView view, String url) {
if (mDelegate != null) {
mDelegate.onLoadResource(view, url);
return;
}
super.onLoadResource(view, url);
}
@Override
public void onPageCommitVisible(WebView view, String url) {
if (mDelegate != null) {
mDelegate.onPageCommitVisible(view, url);
return;
}
super.onPageCommitVisible(view, url);
}
@Override
@Deprecated
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
if (mDelegate != null) {
return mDelegate.shouldInterceptRequest(view, url);
}
return super.shouldInterceptRequest(view, url);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
if (mDelegate != null) {
return mDelegate.shouldInterceptRequest(view, request);
}
return super.shouldInterceptRequest(view, request);
}
@Override
@Deprecated
public void onTooManyRedirects(WebView view, Message cancelMsg,
Message continueMsg) {
if (mDelegate != null) {
mDelegate.onTooManyRedirects(view, cancelMsg, continueMsg);
return;
}
super.onTooManyRedirects(view, cancelMsg, continueMsg);
}
@Override
@Deprecated
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
if (mDelegate != null) {
mDelegate.onReceivedError(view, errorCode, description, failingUrl);
return;
}
super.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (mDelegate != null) {
mDelegate.onReceivedError(view, request, error);
return;
}
super.onReceivedError(view, request, error);
}
@Override
public void onReceivedHttpError(
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
if (mDelegate != null) {
mDelegate.onReceivedHttpError(view, request, errorResponse);
return;
}
super.onReceivedHttpError(view, request, errorResponse);
}
@Override
public void onFormResubmission(WebView view, Message dontResend,
Message resend) {
if (mDelegate != null) {
mDelegate.onFormResubmission(view, dontResend, resend);
return;
}
super.onFormResubmission(view, dontResend, resend);
}
@Override
public void doUpdateVisitedHistory(WebView view, String url,
boolean isReload) {
if (mDelegate != null) {
mDelegate.doUpdateVisitedHistory(view, url, isReload);
return;
}
super.doUpdateVisitedHistory(view, url, isReload);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
if (mDelegate != null) {
mDelegate.onReceivedSslError(view, handler, error);
return;
}
super.onReceivedSslError(view, handler, error);
}
@Override
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
if (mDelegate != null) {
mDelegate.onReceivedClientCertRequest(view, request);
return;
}
super.onReceivedClientCertRequest(view, request);
}
@Override
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
if (mDelegate != null) {
mDelegate.onReceivedHttpAuthRequest(view, handler, host, realm);
return;
}
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
if (mDelegate != null) {
return mDelegate.shouldOverrideKeyEvent(view, event);
}
return super.shouldOverrideKeyEvent(view, event);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
if (mDelegate != null) {
mDelegate.onUnhandledKeyEvent(view, event);
return;
}
super.onUnhandledKeyEvent(view, event);
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
if (mDelegate != null) {
mDelegate.onScaleChanged(view, oldScale, newScale);
return;
}
super.onScaleChanged(view, oldScale, newScale);
}
@Override
public void onReceivedLoginRequest(WebView view, String realm,
String account, String args) {
if (mDelegate != null) {
mDelegate.onReceivedLoginRequest(view, realm, account, args);
return;
}
super.onReceivedLoginRequest(view, realm, account, args);
}
}
================================================
FILE: agentweb-core/src/main/res/layout/agentweb_error_page.xml
================================================
================================================
FILE: agentweb-core/src/main/res/values/colors.xml
================================================
#000000
#ffffff
#2e2e32
================================================
FILE: agentweb-core/src/main/res/values/ids.xml
================================================
================================================
FILE: agentweb-core/src/main/res/values/strings.xml
================================================
The task already exists, do not repeat click to download!
Note
Wi-Fi disconnected. Continue the download via mobile data network?
Download
Cancel
Download failed!
Downloading:%s
downloaded:%s
You have a new notice
Download
Tap to continue
Coming soon to download the file
Camera
Files
Loading ...
leaving %s and opening another app?
Go away
The selected file can not be larger than %s MB
error~
================================================
FILE: agentweb-core/src/main/res/values/style.xml
================================================
================================================
FILE: agentweb-core/src/main/res/values-zh/strings.xml
================================================
该任务已经存在 , 请勿重复点击下载!
提示
您正在使用手机流量 , 继续下载该文件吗?
下载
取消
下载失败!
当前进度:%s
已下载:%s
您有一条新通知
文件下载
点击打开
即将开始下载文件
相机
文件
加载中 ...
您需要离开%s前往其他应用吗?
离开
选择的文件不能大于%sMB
出错啦! 点击空白处刷新 ~
================================================
FILE: app/.gitignore
================================================
/build/
/src/androidTest/
/src/test/
/release/
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
defaultConfig {
minSdkVersion 22
targetSdkVersion 29
applicationId "me.wizos.loread"
versionCode 1
versionName "1.0.0"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
// 解决 Error:Execution failed for task ‘:app:javaPreCompileDebug 问题
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
}
buildTypes {
release {
minifyEnabled false //启动混淆
// proguardFile是混淆使用的配置文件,这里是module根目录下的proguard-rules.pro文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// AAI4F2S2LM1U 属于应用"知微"独有的 Android AppKey, 用于配置SDK;标注应用推广渠道用以区分新用户来源,可填写如应用宝,豌豆荚等
manifestPlaceholders = [MTA_APPKEY : "AAI4F2S2LM1U", MTA_CHANNEL: "酷安"]
}
debug {
//applicationIdSuffix '.baimu' //增加包名后缀
minifyEnabled false
debuggable true
manifestPlaceholders = [MTA_APPKEY : "AAI4F2S2LM1U", MTA_CHANNEL: "酷安"]
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// 开发工具
testImplementation 'junit:junit:4.13'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation "androidx.paging:paging-runtime:2.1.2"
implementation "androidx.work:work-runtime:2.3.4"
// 通用的背景设置库,通过标签直接生成shape,无需再写shape.xml [https://github.com/JavaNoober/BackgroundLibrary]
implementation 'com.noober.background:core:1.6.3'
// 列表项左右滑动布局
implementation project(path: ':swipelayout')
implementation project(path: ':support')
// 加载 view [https://github.com/ybq/Android-SpinKit]
// implementation 'com.github.ybq:Android-SpinKit:1.4.0'
// 浏览器
implementation project(path: ':agentweb-core')
// https://github.com/drakeet/MultiType, 'com.github.kelinZhou:MultiTypeAdapter:1.0.2'
implementation 'com.drakeet.multitype:multitype:4.2.0'
// 对话框
// implementation 'com.kongzue.dialog_v3x:dialog:3.1.6'
// implementation 'com.orhanobut:dialogplus:1.11@aar'
// implementation 'com.afollestad.material-dialogs:core:3.1.1'
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
implementation 'com.afollestad.material-dialogs:commons:0.9.6.0'
// 透明通知栏
// implementation 'com.readystatesoftware.systembartint:systembartint:1.0.3'
// popup [https://github.com/li-xiaojun/XPopup]
implementation 'com.lxj:xpopup:1.8.13'
// toast [https://github.com/getActivity/ToastUtils]
implementation 'com.hjq:toast:8.0'
// 开关按钮
implementation 'com.kyleduo.switchbutton:library:2.0.0'
// 让播放、暂停按钮优雅的过渡 [https://github.com/Lauzy/PlayPauseView]
implementation 'com.github.Lauzy:PlayPauseView:1.0.7'
// 带删除功能的EditText;显示或者隐藏密码;可设置自动添加分隔符分割电话号码、银行卡号等;支持禁止Emoji表情符号输入 [https://github.com/woxingxiao/XEditText]
// 用在了 activity_search.xml 中
implementation 'com.xw.repo:xedittext-androidx:2.2.6@aar'
// [https://github.com/zhanghai/materialedittext]
// 用在了 activity_login_tiny_tiny_rss_rss.xml 中
implementation 'me.zhanghai.android.materialedittext:library:1.0.5'
// 时间总线
// implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'com.jeremyliao:live-event-bus-x:1.6.1'
implementation "androidx.room:room-runtime:2.2.5"
annotationProcessor "androidx.room:room-compiler:2.2.5"
// implementation 'com.tencent.wcdb:room:1.0.8' // 代替 room-runtime,同时也不需要再引用 wcdb-android
// annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' // compiler 需要用 room 的
// 基于 mmap 的高性能通用 key-value 组件
// implementation 'com.tencent:mmkv:1.1.0'
// 内存泄漏检测工具
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
// releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// 日志工具
implementation 'com.orhanobut:logger:2.2.0'
implementation 'com.github.zhaokaiqiang.klog:library:1.6.0'
// 权限申请 [https://github.com/getActivity/XXPermissions]
implementation 'com.hjq:xxpermissions:6.2'
// 其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如:2.6.6.1
implementation 'com.tencent.bugly:crashreport:3.1.0'
// 腾讯统计MTA主包
implementation 'com.qq.mta:mta:3.4.7-release'
// 腾讯统计MID基础包
implementation 'com.tencent.mid:mid:4.06-Release'
// 依赖注入
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
// 序列化数据 [https://github.com/google/gson]
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.parceler:parceler-api:1.1.13'
annotationProcessor 'org.parceler:parceler:1.1.13'
// 抽取的正文没有html标签
// implementation 'com.chimbori.crux:crux:2.2.0'
// 正文抽取器
// implementation project(path: ':extractor')
// HTML 解析 [https://github.com/jhy/jsoup]
implementation 'org.jsoup:jsoup:1.13.1'
// HTML 解析
// implementation ('com.virjar:sipsoup:1.6'){ transitive = false }
// 使用xpath解析提取html数据 [https://github.com/zhegexiaohuozi/JsoupXpath]
// implementation 'cn.wanghaomiao:JsoupXpath:2.3.2'
// JSON 选择库 [https://github.com/json-path/JsonPath]
// implementation 'com.jayway.jsonpath:json-path:2.4.0'
// JS 库 [https://github.com/APISENSE/rhino-android]
implementation 'io.apisense:rhino-android:1.1.1'
// [https://github.com/bumptech/glide]
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
// Glide 底层使用okhttp3
implementation('com.github.bumptech.glide:okhttp3-integration:4.11.0') { transitive = false }
// 查看大图 [https://github.com/SherlockGougou/BigImageViewPager]
implementation 'com.github.SherlockGougou:BigImageViewPager:androidx-6.0.1'
// 音频部件 [https://github.com/yhaolpz/FloatWindow]
implementation project(path: ':floatwindow')
// https://github.com/SDKers/FloatWindow, https://github.com/fenggit/FloatWindow
// retrofit2
implementation('com.squareup.retrofit2:retrofit:2.8.1') { transitive = false }
implementation('com.squareup.retrofit2:converter-gson:2.8.1') { transitive = false }
// okHttp
implementation 'com.squareup.okhttp3:okhttp:4.4.1'
implementation 'com.squareup.okio:okio:2.5.0'
implementation files('libs/okgo-3.0.4.jar')
// 网络状态监听 [https://github.com/allenlzhang/NetworkState]
implementation 'com.github.allenlzhang:NetworkState:v1.0.0'
// 图片压缩 [https://github.com/Curzibn/Luban]
implementation project(path: ':luban')
// 防止三方 SDK 中常见的损害用户体验的行为 [https://github.com/oasisfeng/condom]
implementation 'com.oasisfeng.condom:library:2.5.0'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard replaceUrl here.
# By default, the flags in this file are appended to flags specified
# in C:\Program Files\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class userName to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/androidTest/java/me/wizos/loread/ApplicationTest.java
================================================
package me.wizos.loread;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/assets/css/android_studio.css
================================================
/*
Date: 24 Fev 2015
Author: Pedro Oliveira
*/
.hljs {
color: #a9b7c6;
background: #282b2e;
display: block;
overflow-x: auto;
padding: 0.5em;
}
.hljs-number,
.hljs-literal,
.hljs-symbol,
.hljs-bullet {
color: #6897BB;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-deletion {
color: #cc7832;
}
.hljs-variable,
.hljs-template-variable,
.hljs-link {
color: #629755;
}
.hljs-comment,
.hljs-quote {
color: #808080;
}
.hljs-meta {
color: #bbb529;
}
.hljs-string,
.hljs-attribute,
.hljs-addition {
color: #6A8759;
}
.hljs-section,
.hljs-title,
.hljs-type {
color: #ffc66d;
}
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #e8bf6a;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
================================================
FILE: app/src/main/assets/css/article_theme_day.css
================================================
/* #f8f8f8;灰白
background-color:rgba(0,0,0,0);*/
body {
//color: #333;
color:#293845;
background-color: #FFFFFF;
}
/* 标题
=============================================================================*/
/*
h1,h2 {
color: #000;
border-bottom: 1px dashed #ccc;
}
hr {
//background-color:#f8f8f8;
}
*/
/* 表格
=============================================================================*/
table{
background-color: #f8f8f8;
}
table th, table td {
border: 1px solid #ccc;
}
/* 代码
=============================================================================*/
code, tt ,pre {
border: 1px solid #eaeaea;
background-color: #f8f8f8;
}
blockquote{
background: rgba(0, 0, 0, 0.04) !important;
color: rgba(0, 0, 0, 0.5) !important;
}
================================================
FILE: app/src/main/assets/css/article_theme_night.css
================================================
::-webkit-scrollbar {
width: 8px;
height: 8px;
background: #2c3e50;
}
::-webkit-scrollbar-thumb {
background: #888;
}
a {
color: #c6cddb;
}
body{
color: #c6cddb;
//background: #494f5c;
background-color: #454952;
}
/* 表格
=============================================================================*/
/*background-color: #141e21;
border: 1px solid #203238;*/
pre {
color: #eee;
background: #2c3e50;
}
code {
color: #eee;
background: #7d828a;
}
table{
color: #eee;
border: 1px solid rgba(0, 0, 0, 0.1) !important;
background: rgba(0, 0, 0, 0.08) !important;
/*background-color: #203238;*/
}
tt {
background-color: #141e21;
border: 1px solid #203238;
}
table th, table td {
//border: 1px solid #212121;
border: 1px solid rgba(0, 0, 0, 0.1);
}
/* 降低亮度 */
img,video,iframe,embed {
-webkit-filter:brightness(0.75);
}
blockquote{
background: rgba(0, 0, 0, 0.08) !important;
border-left: 5px solid rgba(0, 0, 0, 0.3) !important;
color: rgba(255, 255, 255, 0.6) !important;
}
================================================
FILE: app/src/main/assets/css/normalize.css
================================================
/* 重置 */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
}
:focus{outline:none}
@font-face {
font-family: 'roboto_light';
src: url('file:///android_asset/fonts/roboto_light.ttf');
}
body {
padding: 20px 15px !important;
word-wrap: break-word !important;
}
body,div,p,font {
font-family: roboto_light,sans-serif !important;
font-size: 16px;
line-height: 160% !important;
letter-spacing:1px;
}
body > *:first-child {
margin-top: 0 !important;
}
body > *:last-child {
margin-bottom: 0 !important;
}
body * {
max-width: 100% !important;
}
/* BLOCKS */
p, ul, ol, dl, table, pre, figure {
margin: 10px 0;
}
table pre{
margin: 0px 0;
}
/*
* 设置最大宽度,防止超出屏幕
*/
div,p,tbody,textarea,ins,input{
max-width: 100% !important;
}
/*
* 如果表格内的div宽度过长,设置最大宽度是无法防止溢出的,所以得手动将其宽度改为自适应
*/
table div{
width:auto !important;
}
* {
background-image:none !important;
}
p{
text-indent:0 !important;
background:none !important;
width:auto !important;
height:auto !important;
}
/* HEADERS */
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}
section h1,h2,h3,h4,h5,h6{
font-family: roboto_light !important;
padding: 0 0px !important;
margin: 15px 0px !important;
}
h4:before{
content: "⭕️";
}
h5:before{
content: "💢";
}
h6:before{
content: "🌀";
}
section h1 {
font-size: 24px;
box-shadow: inset 0 -8px 0 #bf53b1;
}
h2 {
font-size: 20px;
box-shadow: inset 0 -6px 0 #f7615f;
}
h3 {
font-size: 16px;
box-shadow: inset 0 -2px 0 #e0af55;
}
h4 {
font-size: 16px;
color: #d2cd3e;
}
h5 {
font-size: 16px;
color: #6AC749;
}
h6 {
font-size: 16px;
color: #32b9a8;
}
/* LINKS */
a {
color: #51a1f1 !important;
}
a > svg{
width: 14px;
height:auto;
}
section a {
text-decoration: underline !important;
}
#title a {
text-decoration: none;
}
a[href=''],a:not([href]) {
text-decoration: none !important;
}
/* 列表 */
ul, ol {
padding: 0 0 0 1em;
list-style-position:inside;
}
ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}
ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}
li{
margin: .2em 0;
}
/*
dl {
padding: 0;
}
*/
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
margin: 15px 0 5px;
/*padding: 0;*/
}
dl dt:first-child {
padding: 0;
}
dl dt > :first-child {
margin-top: 0px;
}
dl dt > :last-child {
margin-bottom: 0px;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd > :first-child {
margin-top: 0px;
}
dl dd > :last-child {
margin-bottom: 0px;
}
/* 表格 */
table {
width: auto !important;
height: auto !important;
margin: 0 !important;
border-collapse: collapse !important;
border-spacing: 0;
font-size: .8em;
line-height: 1.1;
max-width: 100% !important;
padding: .5em .5em;
overflow: auto;
word-wrap: break-word !important;
letter-spacing: normal;
border-radius: 5px;
}
table th {
font-weight: bold;
}
table th,table td {
padding: 5px !important;
width: auto !important;
max-width: 100% !important;
height: auto !important;
}
thead > tr, tbody > tr:nth-child(2n) {
background: rgba(0, 0, 0, 0.06) !important;
}
table p{
margin: 5px 0;
}
/* 垂直居中 */
th img, td img{
vertical-align:middle;
}
/* 对齐是排版最重要的因素, 别让什么都居中 */
caption, th {
text-align: left;
}
td pre, td code,pre code, pre tt {
border: 0 !important;
background: transparent !important;
}
/* CODE */
pre code, pre, code, tt {
font-family: Consolas, Liberation Mono Courier, monospace;
}
pre {
font-size: .8em;
line-height: 1.5;
max-width: 100% !important;
padding: .5em .5em;
overflow: auto;
word-wrap: break-word !important;
letter-spacing: normal;
white-space: pre;
border-radius: 5px;
text-align: left;
}
pre code,table pre{
font-size: .8em;
}
code {
font-size: .8em;
border-radius: 3px;
padding: 0 3px;
margin: 0 3px;
word-break: break-all;
letter-spacing:normal;
}
tt {
margin: 0 0px;
padding: 0 0px;
}
/* QUOTES */
blockquote {
font-size: .8em !important;
padding: 5px 5px 5px 15px !important;
margin: 15px 0 15px 0px !important;
}
blockquote > :first-child {
margin-top: 0px !important;
}
blockquote > :last-child {
margin-bottom: 0px !important;
}
/* figure */
figure {
max-width: 100%;
height: auto;
text-align: center;
}
figcaption {
font-size: .8em;
font-style: italic;
opacity: .6;
}
textarea{
display:none;
}
/* 图片,视频 */
/* 隐藏"空/无效"元素 */
img[src=''],audio:not([src]),iframe:not([src]),[src=''],blockquote:empty,figure:empty,table:empty,pre:empty,code:empty,div:empty {
display: none !important;
}
img {
margin: 5px 0 !important;
max-width: 100% !important;
width: auto;
height: auto !important;
border-radius: 5px;
}
a > img{
display: block;
}
video,iframe,embed {
width: 100% !important;
height: 100% !important;
display: block !important;
}
.video_wrap,.iframe_wrap,.embed_wrap {
margin: 5px 0 !important;
border-bottom: 5px solid rgba(0, 0, 0, 0.1) !important;
border-top: 5px solid rgba(0, 0, 0, 0.1) !important;
border-radius: 5px !important;
background:repeating-linear-gradient(-50deg,#E8E8E8,#E8E8E8 1em,#d2d2d2 1em,#d2d2d2 2em) !important;
}
.table_wrap{
overflow-x:auto !important;
}
audio {
height: 32px;
width: 100% !important;
margin: 5px 0 !important;
}
video {
background:rgba(0, 0, 0);
}
/* 文章相关信息的排版*/
hr {
opacity: .2;
border-width: 0 0 5px;
border-style: dashed;
background: transparent;
width: 50%;
margin: .9em auto;
}
#title {
font-family:roboto_light,sans-serif !important;
font-size: 1.4em;
margin-bottom: 5px;
}
#author,#pubDate {
font-size: .8em !important;
margin: 0 !important;
opacity: 0.4;
}
#readability-button{
font-size:12px;
margin-top: 10px
}
#content{
margin-top: 10px
}
del{
opacity:0.4;
}
================================================
FILE: app/src/main/assets/js/highlight.pack.js
================================================
/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */
!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],o=Object.keys,_={},g={},C=!0,n=/^(no-?highlight|plain|text)$/i,E=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function R(e){return n.test(e)}function i(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function p(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),d(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function v(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""+d(e)+">"}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function l(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return i(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[i(n,{starts:n.starts?i(n.starts):null})]:Object.isFrozen(n)?[i(n)]:[n]}function u(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(u)}}function M(n,t){var i={};return"string"==typeof n?r("keyword",n):o(n).forEach(function(e){r(e,n[e])}),i;function r(a,e){t&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n,t,r=e.split("|");i[r[0]]=[a,(n=r[0],(t=r[1])?Number(t):function(e){return-1!=c.indexOf(e.toLowerCase())}(n)?0:1)]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var r=0;r')+n+(t?"":m)}function o(){p+=(null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=s(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||o(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function b(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function s(e){var n,t,r,a,i,o,c,l,u,s,f=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=E.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=f?T(f,i,!0):w(i),(t=p(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=v(t,p(a),i)),r.value=b(r.value),e.innerHTML=r.value,e.className=(o=e.className,c=f,l=r.language,u=c?g[c]:l,s=[o.trim()],o.match(/\bhljs\b/)||s.push("hljs"),-1===o.indexOf(u)&&s.push(u),s.join(" ").trim()),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,s)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[g[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=b,a.highlightBlock=s,a.configure=function(e){B=i(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}u(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){g[e]=n})},a.listLanguages=function(){return o(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=i,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("swift",function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t=e.C("/\\*","\\*/",{c:["self"]}),n={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},r={cN:"string",c:[e.BE,n],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return n.c=[a],{k:i,c:[r,e.CLCM,t,{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b:/,e:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,r,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{bK:"import",e:/$/,c:[e.CLCM,t]}]}});hljs.registerLanguage("ini",function(e){var b={cN:"number",relevance:0,v:[{b:/([\+\-]+)?[\d]+_[\d_]+/},{b:e.NR}]},a=e.C();a.v=[{b:/;/,e:/$/},{b:/#/,e:/$/}];var c={cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"literal",b:/\bon|off|true|false|yes|no\b/},n={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[a,{cN:"section",b:/\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,cN:"attr",starts:{e:/$/,c:[a,{b:/\[/,e:/\]/,c:[a,r,c,n,b,"self"],relevance:0},r,c,n,b]}}]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={b:/\{\{/,relevance:0},l={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,a,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,a,c]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,a,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,a,c]},e.ASM,e.QSM]},n={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,n,l,e.HCM]};return c.c=[l,n,b],{aliases:["py","gyp","ipython"],k:r,i:/(<\/|->|\?)|=>/,c:[b,n,{bK:"if",relevance:0},l,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},r={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},n=[e.C("#","$",{c:[r]}),e.C("^\\=begin","^\\=end",{c:[r],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:b},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:b},l=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:c}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:b},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),relevance:0}].concat(n);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:i.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:b,i:/\/\*/,c:n.concat(d).concat(l)}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a={cN:"string",relevance:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,{cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]}]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[{cN:"attr",v:[{b:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{b:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{b:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{cN:"meta",b:"^---s*$",relevance:10},{cN:"string",b:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:b,k:{literal:b}},{cN:"number",b:e.CNR+"\\b"},a]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",relevance:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]{0,3}(?=\\W)",relevance:0},{b:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",a={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,a]},{b:/[:\(,=]\s*/,relevance:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[a]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,relevance:0}])}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:"",c:[e.CLCM,e.C("/\\*","\\*/",{c:["self"]}),e.inherit(e.QSM,{b:/b?"/,i:null}),{cN:"string",v:[{b:/r(#*)"(.|\n)*?"\1(?!#)/},{b:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{cN:"symbol",b:/'[a-zA-Z_][a-zA-Z0-9_]*/},{cN:"number",v:[{b:"\\b0b([01_]+)"+t},{b:"\\b0o([0-7_]+)"+t},{b:"\\b0x([A-Fa-f0-9_]+)"+t},{b:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+t}],relevance:0},{cN:"function",bK:"fn",e:"(\\(|<)",eE:!0,c:[e.UTM]},{cN:"meta",b:"#\\!?\\[",e:"\\]",c:[{cN:"meta-string",b:/"/,e:/"/}]},{cN:"class",bK:"type",e:";",c:[e.inherit(e.UTM,{endsParent:!0})],i:"\\S"},{cN:"class",bK:"trait enum struct union",e:"{",c:[e.inherit(e.UTM,{endsParent:!0})],i:"[\\w\\d]"},{b:e.IR+"::",k:{built_in:r}},{b:"->"}]}});hljs.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",i=(t(a),t("<.*?>"),{cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"}),c={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},n={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(c,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},o={cN:"title",b:t(a)+e.IR,relevance:0},l=t(a)+e.IR+"\\s*\\(",u={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},p=[i,e.CLCM,e.CBCM,s,c],m={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:u,c:p.concat([{b:/\(/,e:/\)/,k:u,c:p.concat(["self"]),relevance:0}]),relevance:0},d={cN:"function",b:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+l,rB:!0,e:/[{;=]/,eE:!0,k:u,i:/[^\w\s\*&:<>]/,c:[{b:r,k:u,relevance:0},{b:l,rB:!0,c:[o],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:u,relevance:0,c:[e.CLCM,e.CBCM,c,s,i,{b:/\(/,e:/\)/,k:u,relevance:0,c:["self",e.CLCM,e.CBCM,c,s,i]}]},i,e.CLCM,e.CBCM,n]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:u,i:"",c:[].concat(m,d,p,[n,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:u,c:["self",i]},{b:e.IR+"::",k:u},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b:/,e:/>/,c:["self"]},e.TM]}]),exports:{preprocessor:n,strings:c,k:u}}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,relevance:0}]},c=[e.BE,r,n],a=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:c,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",relevance:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",relevance:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",relevance:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",relevance:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",relevance:5},{b:"qw\\s+q",e:"q",relevance:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],relevance:0},{b:"-?\\w+\\s*\\=\\>",c:[],relevance:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",relevance:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],relevance:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,relevance:5,c:[e.TM]},{b:"-\\w\\b",relevance:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=a,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s.c=a}});hljs.registerLanguage("scss",function(e){var t="@[a-z-]+",r={cN:"variable",b:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={cN:"number",b:"#[0-9A-Fa-f]+"};e.CSSNM,e.QSM,e.ASM,e.CBCM;return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",relevance:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",relevance:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{cN:"selector-pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"selector-pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,i,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@(page|font-face)",l:t,k:"@page @font-face"},{b:"@",e:"[{;]",rB:!0,k:"and or not only",c:[{b:t,cN:"keyword"},r,e.QSM,e.ASM,i,e.CSSNM]}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:"?",e:">"},{cN:"attribute",b:/\w+/,relevance:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,relevance:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("typescript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},n={cN:"meta",b:"@"+r},a={b:"\\(",e:/\)/,k:t,c:["self",e.QSM,e.ASM,e.NM]},c={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:[e.CLCM,e.CBCM,n,a]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},b={cN:"string",b:"`",e:"`",c:[e.BE,o]};return o.c=[e.ASM,e.QSM,i,l,b,s,e.RM],{aliases:["ts"],k:t,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,i,l,b,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:t,c:["self",e.inherit(e.TM,{b:r}),c],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",c]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},n,a]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},a={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,a,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("go",function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:n,i:"",c:[e.CLCM,e.CBCM,{cN:"string",v:[e.QSM,e.ASM,{b:"`",e:"`"}]},{cN:"number",v:[{b:e.CNR+"[i]",relevance:1},e.CNM]},{b:/:=/},{cN:"function",bK:"func",e:"\\s*(\\{|$)",eE:!0,c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:n,i:/["']/}]}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],relevance:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],relevance:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("java",function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",t={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0};return{aliases:["jsp"],k:a,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{b:/\w+@/,relevance:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",relevance:0},{cN:"function",b:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:a,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:a,relevance:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},t,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|[0-9]+;|[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/,relevance:0,c:[{cN:"attr",b:"[A-Za-z0-9\\._:-]+",relevance:0},{b:/=\s*/,relevance:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/,c:[c]},{b:/'/,e:/'/,c:[c]},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"" + // defer
"" +
"" +
"" +
"" +
"" +
"" +
"