comparison cagou/core/platform_/android.py @ 382:c7f1176cd2a9

android: handle Android specific content (wich `content:` scheme) in intent manager: when an Android content is received, it is dumped to a temporary file, and a friendly filename is used when possible.
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2020 20:47:17 +0100
parents eb3f622d8791
children a5457241c17f
comparison
equal deleted inserted replaced
381:eb3f622d8791 382:c7f1176cd2a9
22 import json 22 import json
23 from functools import partial 23 from functools import partial
24 from urllib.parse import urlparse 24 from urllib.parse import urlparse
25 from pathlib import Path 25 from pathlib import Path
26 import shutil 26 import shutil
27 import mimetypes
27 from jnius import autoclass, cast 28 from jnius import autoclass, cast
28 from android import activity 29 from android import activity
29 from sat.core.i18n import _ 30 from sat.core.i18n import _
30 from sat.core import log as logging 31 from sat.core import log as logging
31 from sat_frontends.tools import jid 32 from sat_frontends.tools import jid
46 Uri = autoclass('android.net.Uri') 47 Uri = autoclass('android.net.Uri')
47 ImagesMedia = autoclass('android.provider.MediaStore$Images$Media') 48 ImagesMedia = autoclass('android.provider.MediaStore$Images$Media')
48 AudioMedia = autoclass('android.provider.MediaStore$Audio$Media') 49 AudioMedia = autoclass('android.provider.MediaStore$Audio$Media')
49 VideoMedia = autoclass('android.provider.MediaStore$Video$Media') 50 VideoMedia = autoclass('android.provider.MediaStore$Video$Media')
50 51
52 DISPLAY_NAME = '_display_name'
51 DATA = '_data' 53 DATA = '_data'
52 54
53 55
54 STATE_RUNNING = b"running" 56 STATE_RUNNING = b"running"
55 STATE_PAUSED = b"paused" 57 STATE_PAUSED = b"paused"
184 ["Connection", "autodisconnect"], 186 ["Connection", "autodisconnect"],
185 ], 187 ],
186 } 188 }
187 ) 189 )
188 190
189 def getPathFromUri(self, uri): 191 def getColDataFromUri(self, uri, col_name):
190 cursor = mActivity.getContentResolver().query(uri, None, None, None, None) 192 cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
191 if cursor is None: 193 if cursor is None:
192 return uri.getPath() 194 return None
193 else: 195 try:
194 cursor.moveToFirst() 196 cursor.moveToFirst()
195 # FIXME: using DATA is not recommended (and DATA is deprecated) 197 col_idx = cursor.getColumnIndex(col_name);
196 # we should read directly the file with
197 # ContentResolver#openFileDescriptor(Uri, String)
198 col_idx = cursor.getColumnIndex(DATA);
199 if col_idx == -1: 198 if col_idx == -1:
200 return uri.getPath() 199 return None
201 return cursor.getString(col_idx) 200 return cursor.getString(col_idx)
201 finally:
202 cursor.close()
203
204 def getFilenameFromUri(self, uri, media_type):
205 filename = self.getColDataFromUri(uri, DISPLAY_NAME)
206 if filename is None:
207 uri_p = Path(uri.toString())
208 filename = uri_p.name or "unnamed"
209 if not uri_p.suffix and media_type:
210 suffix = mimetypes.guess_extension(media_type, strict=False)
211 if suffix:
212 filename = filename + suffix
213 return filename
214
215 def getPathFromUri(self, uri):
216 # FIXME: using DATA is not recommended (and DATA is deprecated)
217 # we should read directly the file with
218 # ContentResolver#openFileDescriptor(Uri, String)
219 path = self.getColDataFromUri(uri, DATA)
220 return uri.getPath() if path is None else path
202 221
203 def on_new_intent(self, intent): 222 def on_new_intent(self, intent):
204 log.debug("on_new_intent") 223 log.debug("on_new_intent")
205 action = intent.getAction(); 224 action = intent.getAction();
206 intent_type = intent.getType(); 225 intent_type = intent.getType();
233 # and show the share widget 252 # and show the share widget
234 data = {} 253 data = {}
235 text = intent.getStringExtra(Intent.EXTRA_TEXT) 254 text = intent.getStringExtra(Intent.EXTRA_TEXT)
236 if text is not None: 255 if text is not None:
237 data['text'] = text 256 data['text'] = text
257
238 item = intent.getParcelableExtra(Intent.EXTRA_STREAM) 258 item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
239 if item is not None: 259 if item is not None:
240 uri = cast('android.net.Uri', item) 260 uri = cast('android.net.Uri', item)
241 data['uri'] = uri.toString() 261 if uri.getScheme() == 'content':
242 path = self.getPathFromUri(uri) 262 # Android content, we'll dump it to a temporary file
243 if path is not None: 263 filename = self.getFilenameFromUri(uri, intent_type)
244 data['path'] = path 264 filepath = self.tmp_dir / filename
265 input_stream = mActivity.getContentResolver().openInputStream(uri)
266 buff = bytearray(4096)
267 with open(filepath, 'wb') as f:
268 while True:
269 ret = input_stream.read(buff, 0, 4096)
270 if ret != -1:
271 f.write(buff[:ret])
272 else:
273 break
274 input_stream.close()
275 data['path'] = path = str(filepath)
276 else:
277 data['uri'] = uri.toString()
278 path = self.getPathFromUri(uri)
279 if path is not None and path not in data:
280 data['path'] = path
245 else: 281 else:
246 uri = None 282 uri = None
247 path = None 283 path = None
284
248 285
249 Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0) 286 Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
250 else: 287 else:
251 text = None 288 text = None
252 uri = None 289 uri = None