11package io.github.edsuns.adfilter
22
33import android.content.Context
4- import android.net.Uri
54import android.webkit.WebResourceRequest
6- import android.webkit.WebResourceResponse
75import android.webkit.WebView
8- import androidx.lifecycle.MutableLiveData
9- import androidx.work.WorkInfo
6+ import android.webkit.WebViewClient
107import io.github.edsuns.adblockclient.ResourceType
11- import io.github.edsuns.adfilter.script.ElementHiding
12- import io.github.edsuns.adfilter.script.ScriptInjection
13- import io.github.edsuns.adfilter.script.Scriptlet
14- import io.github.edsuns.adfilter.util.None
15- import kotlinx.coroutines.Dispatchers
16- import kotlinx.coroutines.runBlocking
17- import kotlinx.coroutines.withContext
18- import java.io.File
8+ import io.github.edsuns.adfilter.impl.AdFilterImpl
199
2010/* *
2111 * Created by Edsuns@qq.com on 2020/10/24.
2212 */
23- class AdFilter internal constructor( appContext : Context ) {
13+ interface AdFilter {
2414
25- private val detector: Detector = Detector ()
26- internal val binaryDataStore: BinaryDataStore =
27- BinaryDataStore (File (appContext.filesDir, FILE_STORE_DIR ))
28- private val filterDataLoader: FilterDataLoader = FilterDataLoader (detector, binaryDataStore)
29- private val elementHiding: ElementHiding = ElementHiding (detector)
30- private val scriptlet: Scriptlet = Scriptlet (detector)
31- val customFilter = filterDataLoader.getCustomFilter()
32- val viewModel = FilterViewModel (appContext, filterDataLoader)
15+ /* *
16+ * View model of AdFilter.
17+ */
18+ val viewModel: FilterViewModel
3319
20+ /* *
21+ * Whether any filters have been added since the app was installed.
22+ */
3423 val hasInstallation: Boolean
35- get() = viewModel.sharedPreferences.hasInstallation
36-
37- init {
38- viewModel.isEnabled.observeForever { enable ->
39- if (enable) {
40- viewModel.filters.value?.values?.forEach {
41- if (it.isEnabled && it.hasDownloaded()) {
42- viewModel.enableFilter(it)
43- }
44- }
45- filterDataLoader.load(FilterDataLoader .ID_CUSTOM )
46- } else {
47- filterDataLoader.unloadAll()
48- filterDataLoader.unloadCustomFilter()
49- }
50- viewModel.updateEnabledFilterCount()
51- viewModel.sharedPreferences.isEnabled = enable
52- // notify onDirty
53- (viewModel.onDirty as MutableLiveData ).value = None .Value
54- }
55- viewModel.workInfo.observeForever { list -> processWorkInfo(list) }
56- }
57-
58- private fun processWorkInfo (workInfoList : List <WorkInfo >) {
59- workInfoList.forEach { workInfo ->
60- val filterId = viewModel.downloadFilterIdMap[workInfo.id.toString()]
61- viewModel.filters.value?.get(filterId)?.let {
62- updateFilter(it, workInfo)
63- }
64- }
65- }
66-
67- private fun updateFilter (filter : Filter , workInfo : WorkInfo ) {
68- val state = workInfo.state
69- val isInstallation = workInfo.tags.contains(TAG_INSTALLATION )
70- var downloadState = filter.downloadState
71- if (isInstallation) {
72- downloadState =
73- when (state) {
74- WorkInfo .State .RUNNING -> DownloadState .INSTALLING
75- WorkInfo .State .SUCCEEDED -> {
76- val alreadyUpToDate =
77- workInfo.outputData.getBoolean(KEY_ALREADY_UP_TO_DATE , false )
78- if (! alreadyUpToDate) {
79- filter.filtersCount =
80- workInfo.outputData.getInt(KEY_FILTERS_COUNT , 0 )
81- workInfo.outputData.getString(KEY_RAW_CHECKSUM )
82- ?.let { filter.checksum = it }
83- if (filter.isEnabled || ! filter.hasDownloaded()) {
84- viewModel.enableFilter(filter)
85- }
86- }
87- if (filter.name.isBlank()) {
88- workInfo.outputData.getString(KEY_FILTER_NAME )
89- ?.let { filter.name = it }
90- }
91- filter.updateTime = System .currentTimeMillis()
92- DownloadState .SUCCESS
93- }
94- WorkInfo .State .FAILED -> DownloadState .FAILED
95- WorkInfo .State .CANCELLED -> DownloadState .CANCELLED
96- else -> downloadState
97- }
98- } else {
99- downloadState = when (state) {
100- WorkInfo .State .ENQUEUED -> DownloadState .ENQUEUED
101- WorkInfo .State .RUNNING -> DownloadState .DOWNLOADING
102- WorkInfo .State .FAILED -> DownloadState .FAILED
103- else -> downloadState
104- }
105- }
106- if (state.isFinished) {
107- viewModel.downloadFilterIdMap.remove(workInfo.id.toString())
108- // save shared preferences
109- viewModel.sharedPreferences.downloadFilterIdMap = viewModel.downloadFilterIdMap
110- // notify download work removed
111- (viewModel.workToFilterMap as MutableLiveData ).postValue(viewModel.downloadFilterIdMap)
112- }
113- if (downloadState != filter.downloadState) {
114- filter.downloadState = downloadState
115- viewModel.flushFilter()
116- }
117- }
11824
11925 /* *
120- * Notify the application of a resource request and allow the application to return the data.
121- *
122- * If the return value is null, the WebView will continue to load the resource as usual.
123- * Otherwise, the return response and data will be used.
124- *
125- * NOTE: This method is called on a thread other than the UI thread so clients should exercise
126- * caution when accessing private data or the view system.
26+ * Custom filter.
12727 */
128- fun shouldIntercept (
129- webView : WebView ,
130- request : WebResourceRequest
131- ): FilterResult = runBlocking {
132- val url = request.url.toString()
133- if (request.isForMainFrame) {
134- return @runBlocking FilterResult (null , url, null )
135- }
136-
137- val documentUrl = withContext(Dispatchers .Main ) { webView.url }
138- ? : return @runBlocking FilterResult (null , url, null )
139-
140- val resourceType = ResourceType .from(request)
28+ val customFilter: CustomFilter
14129
142- val result = shouldIntercept(url, documentUrl, resourceType)
143- if (result.shouldBlock && resourceType.isVisibleResource()) {
144- elementHiding.elemhideBlockedResource(webView, url)
145- }
146-
147- return @runBlocking result
148- }
30+ /* *
31+ * Call this function when [WebViewClient.shouldInterceptRequest],
32+ * and use [FilterResult.resourceResponse] as return value.
33+ */
34+ fun shouldIntercept (webView : WebView , request : WebResourceRequest ): FilterResult
14935
36+ /* *
37+ * @param url url of the request
38+ * @param documentUrl the origin of the request
39+ * @param resourceType [ResourceType]
40+ */
15041 fun shouldIntercept (
15142 url : String ,
15243 documentUrl : String = url,
15344 resourceType : ResourceType ? = null
154- ): FilterResult {
155- val type = resourceType ? : ResourceType .from(Uri .parse(url)) ? : ResourceType .UNKNOWN
156- val rule = detector.shouldBlock(url, documentUrl, type)
157-
158- return if (rule != null ) {
159- FilterResult (rule, url, WebResourceResponse (null , null , null ))
160- } else {
161- FilterResult (null , url, null )
162- }
163- }
164-
165- private fun ResourceType.isVisibleResource (): Boolean =
166- this == = ResourceType .IMAGE || this == = ResourceType .MEDIA || this == = ResourceType .SUBDOCUMENT
45+ ): FilterResult
16746
168- fun setupWebView ( webView : WebView ) {
169- webView.addJavascriptInterface(elementHiding, ScriptInjection .bridgeNameFor(elementHiding))
170- webView.addJavascriptInterface(scriptlet, ScriptInjection .bridgeNameFor(scriptlet))
171- }
47+ /* *
48+ * Call this function when [WebViewClient.onPageStarted], it will run the filter script.
49+ */
50+ fun performScript ( webView : WebView ? , url : String? )
17251
173- fun performScript (webView : WebView ? , url : String? ) {
174- if (viewModel.isEnabled.value == true ) {
175- elementHiding.perform(webView, url)
176- scriptlet.perform(webView, url)
177- }
178- }
52+ /* *
53+ * Call this function when [webView] is created to setup filter on it.
54+ */
55+ fun setupWebView (webView : WebView )
17956
18057 companion object {
18158 @Volatile
18259 private var instance: AdFilter ? = null
18360
61+ /* *
62+ * @return [AdFilter] singleton (if it is not instantiated, an exception is thrown)
63+ */
18464 fun get (): AdFilter =
18565 instance ? : throw RuntimeException (" Should call create() before get()" )
18666
67+ /* *
68+ * @return [AdFilter] singleton (if it is not instantiated, singleton will be created)
69+ */
18770 fun get (context : Context ): AdFilter = instance ? : synchronized(this ) {
18871 // keep application context rather than any other context to avoid memory leak
189- instance = instance ? : AdFilter (context.applicationContext)
72+ instance = instance ? : AdFilterImpl (context.applicationContext)
19073 instance!!
19174 }
19275
76+ /* *
77+ * Instantiate [AdFilter] singleton.
78+ */
19379 fun create (context : Context ): AdFilter = get(context)
19480 }
19581}
0 commit comments