comparison cagou/core/platform_/android.py @ 342:89799148f894

core: use classes and factory to handle platform specific behaviours in a generic way
author Goffi <goffi@goffi.org>
date Sat, 04 Jan 2020 16:24:57 +0100
parents e2b51663d8b8
children a3cefa7158dc
comparison
equal deleted inserted replaced
341:89b17a841c2f 342:89799148f894
15 15
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 import sys 19 import sys
20 import os
21 import socket
20 from jnius import autoclass, cast 22 from jnius import autoclass, cast
21 from android import activity 23 from android import activity
22 from sat.core import log as logging 24 from sat.core import log as logging
23 from cagou.core.constants import Const as C 25 from cagou.core.constants import Const as C
24 from cagou import G 26 from cagou import G
25 from kivy.clock import Clock 27 from kivy.clock import Clock
28 from .base import Platform as BasePlatform
26 29
27 30
28 log = logging.getLogger(__name__) 31 log = logging.getLogger(__name__)
29 32
30 service = autoclass('org.salutatoi.cagou.ServiceBackend') 33 service = autoclass('org.salutatoi.cagou.ServiceBackend')
39 STATE_PAUSED = b"paused" 42 STATE_PAUSED = b"paused"
40 STATE_STOPPED = b"stopped" 43 STATE_STOPPED = b"stopped"
41 SOCKET_DIR = "/data/data/org.salutatoi.cagou/" 44 SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
42 SOCKET_FILE = ".socket" 45 SOCKET_FILE = ".socket"
43 46
44 # cache for callbacks to run when profile is plugged
45 cache = []
46 47
48 class Platform(BasePlatform):
47 49
48 def on_new_intent(intent): 50 def __init__(self):
49 log.debug("on_new_intent") 51 super().__init__()
50 Intent = autoclass('android.content.Intent') 52 # cache for callbacks to run when profile is plugged
51 action = intent.getAction(); 53 self.cache = []
52 intent_type = intent.getType(); 54
53 if action == "android.intent.action.SEND": 55 def init_platform(self):
54 # we have receiving data to share, we parse the intent data 56 # sys.platform is "linux" on android by default
55 # and show the share widget 57 # so we change it to allow backend to detect android
56 data = {} 58 sys.platform = "android"
57 text = intent.getStringExtra(Intent.EXTRA_TEXT) 59 C.PLUGIN_EXT = 'pyc'
58 if text is not None: 60
59 data['text'] = text 61 def on_app_build(self, wid):
60 item = intent.getParcelableExtra(Intent.EXTRA_STREAM) 62 # we don't want menu on Android
61 if item is not None: 63 wid.root_menus.height = 0
62 uri = cast('android.net.Uri', item) 64
63 data['uri'] = uri.toString() 65 def on_host_init(self, host):
64 path = getPathFromUri(uri) 66 argument = ''
65 if path is not None: 67 service.start(mActivity, argument)
66 data['path'] = path 68
69 activity.bind(on_new_intent=self.on_new_intent)
70 self.cache.append((self.on_new_intent, mActivity.getIntent()))
71 host.addListener('profilePlugged', self.onProfilePlugged)
72
73 def on_initFrontendState(self):
74 # XXX: we use a separated socket instead of bridge because if we
75 # try to call a bridge method in on_pause method, the call data
76 # is not written before the actual pause
77 s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
78 s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE))
79 s.sendall(STATE_RUNNING)
80
81 def onProfilePlugged(self, profile):
82 log.debug("ANDROID profilePlugged")
83 for method, *args in self.cache:
84 method(*args)
85 del self.cache
86 G.host.removeListener("profilePlugged", self.onProfilePlugged)
87
88 def on_pause(self):
89 G.host.sync = False
90 self._frontend_status_socket.sendall(STATE_PAUSED)
91 return True
92
93 def on_resume(self):
94 self._frontend_status_socket.sendall(STATE_RUNNING)
95 G.host.sync = True
96
97 def on_stop(self):
98 self._frontend_status_socket.sendall(STATE_STOPPED)
99 self._frontend_status_socket.close()
100
101 def getPathFromUri(self, uri):
102 cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
103 if cursor is None:
104 return uri.getPath()
67 else: 105 else:
106 cursor.moveToFirst()
107 # FIXME: using DATA is not recommended (and DATA is deprecated)
108 # we should read directly the file with
109 # ContentResolver#openFileDescriptor(Uri, String)
110 col_idx = cursor.getColumnIndex(DATA);
111 if col_idx == -1:
112 return uri.getPath()
113 return cursor.getString(col_idx)
114
115 def on_new_intent(self, intent):
116 log.debug("on_new_intent")
117 Intent = autoclass('android.content.Intent')
118 action = intent.getAction();
119 intent_type = intent.getType();
120 if action == "android.intent.action.SEND":
121 # we have receiving data to share, we parse the intent data
122 # and show the share widget
123 data = {}
124 text = intent.getStringExtra(Intent.EXTRA_TEXT)
125 if text is not None:
126 data['text'] = text
127 item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
128 if item is not None:
129 uri = cast('android.net.Uri', item)
130 data['uri'] = uri.toString()
131 path = self.getPathFromUri(uri)
132 if path is not None:
133 data['path'] = path
134 else:
135 uri = None
136 path = None
137
138 Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
139 else:
140 text = None
68 uri = None 141 uri = None
69 path = None 142 path = None
70 143
71 Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0) 144 msg = (f"NEW INTENT RECEIVED\n"
72 else: 145 f"type: {intent_type}\n"
73 text = None 146 f"action: {action}\n"
74 uri = None 147 f"text: {text}\n"
75 path = None 148 f"uri: {uri}\n"
149 f"path: {path}")
76 150
77 msg = (f"NEW INTENT RECEIVED\n" 151 log.debug(msg)
78 f"type: {intent_type}\n"
79 f"action: {action}\n"
80 f"text: {text}\n"
81 f"uri: {uri}\n"
82 f"path: {path}")
83
84 log.debug(msg)
85
86
87 def onProfilePlugged(profile):
88 log.debug("ANDROID profilePlugged")
89 global cache
90 for method, *args in cache:
91 method(*args)
92 del cache
93 G.host.removeListener("profilePlugged", onProfilePlugged)
94
95
96 def init_platform():
97 # sys.platform is "linux" on android by default
98 # so we change it to allow backend to detect android
99 sys.platform = "android"
100 C.PLUGIN_EXT = 'pyc'
101
102
103 def host_init(host):
104 argument = ''
105 service.start(mActivity, argument)
106
107 activity.bind(on_new_intent=on_new_intent)
108 cache.append((on_new_intent, mActivity.getIntent()))
109 host.addListener('profilePlugged', onProfilePlugged)
110
111
112 def getPathFromUri(uri):
113 cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
114 if cursor is None:
115 return uri.getPath()
116 else:
117 cursor.moveToFirst()
118 # FIXME: using DATA is not recommended (and DATA is deprecated)
119 # we should read directly the file with
120 # ContentResolver#openFileDescriptor(Uri, String)
121 col_idx = cursor.getColumnIndex(DATA);
122 if col_idx == -1:
123 return uri.getPath()
124 return cursor.getString(col_idx)