├── 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 | --------------------------------------------------------------------------------