|
| 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/RobertFlatt/Android-for-Python/webview |
| 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 | + |
0 commit comments