├── BUILDOZER_README.txt
├── README.md
├── main.py
└── webview.py
/BUILDOZER_README.txt:
--------------------------------------------------------------------------------
1 | # Requires internet permission
2 |
3 | android.permissions = INTERNET
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Webview
2 | =======
3 |
4 | **2023-11-13 This repository is archived.**
5 |
6 | *An embedded Kivy Android web page viewer*
7 |
8 | Provides full screen display of "https://" and "file://" urls. To close the viewer use the back gesture or the back button.
9 |
10 | The buildozer options are documented in [BUILDOZER_README.txt](https://github.com/Android-for-Python/Webview-Example/blob/main/BUILDOZER_README.txt)
11 |
12 |
13 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from kivy.app import App
2 | from kivy.core.window import Window
3 | from kivy.uix.boxlayout import BoxLayout
4 | from kivy.uix.button import Button
5 | from kivy.uix.label import Label
6 | from webview import WebView
7 | from os import listdir
8 | from textwrap import fill
9 |
10 | class BrowserApp(App):
11 | def build(self):
12 | self._create_local_file()
13 | self.browser = None
14 | b1 = Button(text='Tap for Google.\nBack button/gesture to return.',
15 | on_press=self.view_google)
16 | b2 = Button(text='Tap for local file.\nBack button/gesture to return.',
17 | on_press=self.view_local_file)
18 | b3 = Button(text='List downloads',
19 | on_press=self.view_downloads)
20 | self.label = Label(text='')
21 | box = BoxLayout(orientation='vertical')
22 | box.add_widget(b1)
23 | box.add_widget(b2)
24 | box.add_widget(b3)
25 | box.add_widget(self.label)
26 | return box
27 |
28 | def view_google(self,b):
29 | self.browser = WebView('https://www.google.com',
30 | enable_javascript = True,
31 | enable_downloads = True,
32 | enable_zoom = True)
33 |
34 | def view_local_file(self,b):
35 | self.browser = WebView('file://'+self.filename)
36 |
37 | def view_downloads(self,b):
38 | if self.browser:
39 | d = self.browser.downloads_directory()
40 | self.label.text = fill(d,40) + '\n'
41 | l = listdir(d)
42 | if l:
43 | for f in l:
44 | self.label.text += f + '\n'
45 | else:
46 | self.label.text = 'No files downloaded'
47 | else:
48 | self.label.text = 'Open a browser first'
49 |
50 | def on_pause(self):
51 | if self.browser:
52 | self.browser.pause()
53 | return True
54 |
55 | def on_resume(self):
56 | if self.browser:
57 | self.browser.resume()
58 | pass
59 |
60 | def _create_local_file(self):
61 | # Create a file for testing
62 | from android.storage import app_storage_path
63 | from jnius import autoclass
64 | from os.path import join, exists
65 | from os import mkdir
66 |
67 | Environment = autoclass('android.os.Environment')
68 | path = join(app_storage_path(), Environment.DIRECTORY_DOCUMENTS)
69 | if not exists(path):
70 | mkdir(path)
71 | self.filename = join(path,'from_space.html')
72 | with open(self.filename, "w") as f:
73 | f.write("\n")
74 | f.write("
\n")
75 | f.write(" \n")
76 | f.write(" \n")
77 | f.write(" Greetings Earthlings\n")
78 | f.write(" \n")
79 | f.write("\n")
80 |
81 | BrowserApp().run()
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/webview.py:
--------------------------------------------------------------------------------
1 | # Android **only** HTML viewer, always full screen.
2 | #
3 | # Back button or gesture has the usual browser behavior, except for the final
4 | # back event which returns the UI to the view before the browser was opened.
5 | #
6 | # Base Class: https://kivy.org/doc/stable/api-kivy.uix.modalview.html
7 | #
8 | # Requires: android.permissions = INTERNET
9 | # Uses: orientation = landscape, portrait, or all
10 | # Arguments:
11 | # url : required string, https:// file:// (content:// ?)
12 | # enable_javascript : optional boolean, defaults False
13 | # enable_downloads : optional boolean, defaults False
14 | # enable_zoom : optional boolean, defaults False
15 | #
16 | # Downloads are delivered to app storage see downloads_directory() below.
17 | #
18 | # Tested on api=27 and api=30
19 | #
20 | # Note:
21 | # For api>27 http:// gives net::ERR_CLEARTEXT_NOT_PERMITTED
22 | # This is Android implemented behavior.
23 | #
24 | # Source https://github.com/Android-for-Python/Webview-Example
25 |
26 | from kivy.uix.modalview import ModalView
27 | from kivy.clock import Clock
28 | from android.runnable import run_on_ui_thread
29 | from jnius import autoclass, cast, PythonJavaClass, java_method
30 |
31 | WebViewA = autoclass('android.webkit.WebView')
32 | WebViewClient = autoclass('android.webkit.WebViewClient')
33 | LayoutParams = autoclass('android.view.ViewGroup$LayoutParams')
34 | LinearLayout = autoclass('android.widget.LinearLayout')
35 | KeyEvent = autoclass('android.view.KeyEvent')
36 | ViewGroup = autoclass('android.view.ViewGroup')
37 | DownloadManager = autoclass('android.app.DownloadManager')
38 | DownloadManagerRequest = autoclass('android.app.DownloadManager$Request')
39 | Uri = autoclass('android.net.Uri')
40 | Environment = autoclass('android.os.Environment')
41 | Context = autoclass('android.content.Context')
42 | PythonActivity = autoclass('org.kivy.android.PythonActivity')
43 |
44 |
45 | class DownloadListener(PythonJavaClass):
46 | #https://stackoverflow.com/questions/10069050/download-file-inside-webview
47 | __javacontext__ = 'app'
48 | __javainterfaces__ = ['android/webkit/DownloadListener']
49 |
50 | @java_method('(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V')
51 | def onDownloadStart(self, url, userAgent, contentDisposition, mimetype,
52 | contentLength):
53 | mActivity = PythonActivity.mActivity
54 | context = mActivity.getApplicationContext()
55 | visibility = DownloadManagerRequest.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
56 | dir_type = Environment.DIRECTORY_DOWNLOADS
57 | uri = Uri.parse(url)
58 | filepath = uri.getLastPathSegment()
59 | request = DownloadManagerRequest(uri)
60 | request.setNotificationVisibility(visibility)
61 | request.setDestinationInExternalFilesDir(context,dir_type, filepath)
62 | dm = cast(DownloadManager,
63 | mActivity.getSystemService(Context.DOWNLOAD_SERVICE))
64 | dm.enqueue(request)
65 |
66 |
67 | class KeyListener(PythonJavaClass):
68 | __javacontext__ = 'app'
69 | __javainterfaces__ = ['android/view/View$OnKeyListener']
70 |
71 | def __init__(self, listener):
72 | super().__init__()
73 | self.listener = listener
74 |
75 | @java_method('(Landroid/view/View;ILandroid/view/KeyEvent;)Z')
76 | def onKey(self, v, key_code, event):
77 | if event.getAction() == KeyEvent.ACTION_DOWN and\
78 | key_code == KeyEvent.KEYCODE_BACK:
79 | return self.listener()
80 |
81 |
82 | class WebView(ModalView):
83 | # https://developer.android.com/reference/android/webkit/WebView
84 |
85 | def __init__(self, url, enable_javascript = False, enable_downloads = False,
86 | enable_zoom = False, **kwargs):
87 | super().__init__(**kwargs)
88 | self.url = url
89 | self.enable_javascript = enable_javascript
90 | self.enable_downloads = enable_downloads
91 | self.enable_zoom = enable_zoom
92 | self.webview = None
93 | self.enable_dismiss = True
94 | self.open()
95 |
96 | @run_on_ui_thread
97 | def on_open(self):
98 | mActivity = PythonActivity.mActivity
99 | webview = WebViewA(mActivity)
100 | webview.setWebViewClient(WebViewClient())
101 | webview.getSettings().setJavaScriptEnabled(self.enable_javascript)
102 | webview.getSettings().setBuiltInZoomControls(self.enable_zoom)
103 | webview.getSettings().setDisplayZoomControls(False)
104 | webview.getSettings().setAllowFileAccess(True) #default False api>29
105 | layout = LinearLayout(mActivity)
106 | layout.setOrientation(LinearLayout.VERTICAL)
107 | layout.addView(webview, self.width, self.height)
108 | mActivity.addContentView(layout, LayoutParams(-1,-1))
109 | webview.setOnKeyListener(KeyListener(self._back_pressed))
110 | if self.enable_downloads:
111 | webview.setDownloadListener(DownloadListener())
112 | self.webview = webview
113 | self.layout = layout
114 | try:
115 | webview.loadUrl(self.url)
116 | except Exception as e:
117 | print('Webview.on_open(): ' + str(e))
118 | self.dismiss()
119 |
120 | @run_on_ui_thread
121 | def on_dismiss(self):
122 | if self.enable_dismiss:
123 | self.enable_dismiss = False
124 | parent = cast(ViewGroup, self.layout.getParent())
125 | if parent is not None: parent.removeView(self.layout)
126 | self.webview.clearHistory()
127 | self.webview.clearCache(True)
128 | self.webview.clearFormData()
129 | self.webview.destroy()
130 | self.layout = None
131 | self.webview = None
132 |
133 | @run_on_ui_thread
134 | def on_size(self, instance, size):
135 | if self.webview:
136 | params = self.webview.getLayoutParams()
137 | params.width = self.width
138 | params.height = self.height
139 | self.webview.setLayoutParams(params)
140 |
141 | def pause(self):
142 | if self.webview:
143 | self.webview.pauseTimers()
144 | self.webview.onPause()
145 |
146 | def resume(self):
147 | if self.webview:
148 | self.webview.onResume()
149 | self.webview.resumeTimers()
150 |
151 | def downloads_directory(self):
152 | # e.g. Android/data/org.test.myapp/files/Download
153 | dir_type = Environment.DIRECTORY_DOWNLOADS
154 | context = PythonActivity.mActivity.getApplicationContext()
155 | directory = context.getExternalFilesDir(dir_type)
156 | return str(directory.getPath())
157 |
158 | def _back_pressed(self):
159 | if self.webview.canGoBack():
160 | self.webview.goBack()
161 | else:
162 | self.dismiss()
163 | return True
164 |
165 |
166 |
--------------------------------------------------------------------------------