view cagou/core/platform_/android.py @ 356:307c2501d8b2

chat: keep as many instances of opened chat as there are active WHWrapper, instead of just one: Chat instances can be shown on any WHWrapper, even in all at the same time, so we keep as many instances as active wrapper to avoid unnecessary widget (re)creation
author Goffi <goffi@goffi.org>
date Fri, 17 Jan 2020 20:35:57 +0100
parents a3cefa7158dc
children 4d3a0c4f2430
line wrap: on
line source

#!/usr/bin/env python3

# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
# Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import os
import socket
from jnius import autoclass, cast
from android import activity
from sat.core import log as logging
from urllib.parse import urlparse
from cagou.core.constants import Const as C
from cagou import G
from kivy.clock import Clock
from .base import Platform as BasePlatform


log = logging.getLogger(__name__)

service = autoclass('org.salutatoi.cagou.ServiceBackend')
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
Intent = autoclass('android.content.Intent')
AndroidString = autoclass('java.lang.String')
Uri = autoclass('android.net.Uri')
ImagesMedia = autoclass('android.provider.MediaStore$Images$Media')
AudioMedia = autoclass('android.provider.MediaStore$Audio$Media')
VideoMedia = autoclass('android.provider.MediaStore$Video$Media')

DATA = '_data'


STATE_RUNNING = b"running"
STATE_PAUSED = b"paused"
STATE_STOPPED = b"stopped"
SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
SOCKET_FILE = ".socket"


class Platform(BasePlatform):

    def __init__(self):
        super().__init__()
        # cache for callbacks to run when profile is plugged
        self.cache = []

    def init_platform(self):
        # sys.platform is "linux" on android by default
        # so we change it to allow backend to detect android
        sys.platform = "android"
        C.PLUGIN_EXT = 'pyc'

    def on_app_build(self, wid):
        # we don't want menu on Android
        wid.root_menus.height = 0

    def on_host_init(self, host):
        argument = ''
        service.start(mActivity, argument)

        activity.bind(on_new_intent=self.on_new_intent)
        self.cache.append((self.on_new_intent, mActivity.getIntent()))
        host.addListener('profilePlugged', self.onProfilePlugged)

    def on_initFrontendState(self):
        # XXX: we use a separated socket instead of bridge because if we
        #      try to call a bridge method in on_pause method, the call data
        #      is not written before the actual pause
        s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE))
        s.sendall(STATE_RUNNING)

    def onProfilePlugged(self, profile):
        log.debug("ANDROID profilePlugged")
        for method, *args in self.cache:
            method(*args)
        del self.cache
        G.host.removeListener("profilePlugged", self.onProfilePlugged)

    def on_pause(self):
        G.host.sync = False
        self._frontend_status_socket.sendall(STATE_PAUSED)
        return True

    def on_resume(self):
        self._frontend_status_socket.sendall(STATE_RUNNING)
        G.host.sync = True

    def on_stop(self):
        self._frontend_status_socket.sendall(STATE_STOPPED)
        self._frontend_status_socket.close()

    def getPathFromUri(self, uri):
        cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
        if cursor is None:
            return uri.getPath()
        else:
            cursor.moveToFirst()
            # FIXME: using DATA is not recommended (and DATA is deprecated)
            # we should read directly the file with
            # ContentResolver#openFileDescriptor(Uri, String)
            col_idx = cursor.getColumnIndex(DATA);
            if col_idx == -1:
                return uri.getPath()
            return cursor.getString(col_idx)

    def on_new_intent(self, intent):
        log.debug("on_new_intent")
        action = intent.getAction();
        intent_type = intent.getType();
        if action == Intent.ACTION_SEND:
            # we have receiving data to share, we parse the intent data
            # and show the share widget
            data = {}
            text = intent.getStringExtra(Intent.EXTRA_TEXT)
            if text is not None:
                data['text'] = text
            item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
            if item is not None:
                uri = cast('android.net.Uri', item)
                data['uri'] = uri.toString()
                path = self.getPathFromUri(uri)
                if path is not None:
                    data['path'] = path
            else:
                uri = None
                path = None

            Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
        else:
            text = None
            uri = None
            path = None

        msg = (f"NEW INTENT RECEIVED\n"
               f"type: {intent_type}\n"
               f"action: {action}\n"
               f"text: {text}\n"
               f"uri: {uri}\n"
               f"path: {path}")

        log.debug(msg)

    def open_url(self, url, wid=None):
        parsed_url = urlparse(url)
        if parsed_url.scheme == "geo":
            intent = Intent(Intent.ACTION_VIEW)
            intent.setData(Uri.parse(url))
            if mActivity.getPackageManager() is not None:
                activity = cast('android.app.Activity', mActivity)
                activity.startActivity(intent)
        else:
            super().open_url(self, url, wid)