view sat/plugins/plugin_misc_android.py @ 2846:58ea675d0f05

quick frontend (app): added AUTO_RESYNC attribute in QuickApp, when set (default) the resync method is called on all widgets automatically: if AUTO_RESYNC is False, frontend must call itself the resync method. This is useful to improve performance, resynchronising all widgets at once may be resource intensive, and it may be more efficient to resync a widget only when it is visible.
author Goffi <goffi@goffi.org>
date Sun, 10 Mar 2019 18:02:42 +0100
parents 90115cf4e731
children 26d6ac4e4a66
line wrap: on
line source

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# SAT plugin for file tansfer
# Copyright (C) 2009-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 os.path
from sat.core.i18n import _, D_
from sat.core.constants import Const as C
from sat.core.log import getLogger
from sat.core import exceptions
from twisted.internet import reactor
from twisted.internet import protocol
from twisted.internet import error as int_error

log = getLogger(__name__)

PLUGIN_INFO = {
    C.PI_NAME: "Android ",
    C.PI_IMPORT_NAME: "android",
    C.PI_TYPE: C.PLUG_TYPE_MISC,
    C.PI_MAIN: "AndroidPlugin",
    C.PI_HANDLER: "no",
    C.PI_DESCRIPTION: D_(
        """Manage Android platform specificities, like pause or notifications"""
    ),
}

if sys.platform != "android":
    raise exceptions.CancelError(u"this module is not needed on this platform")

from plyer import notification, vibrator

PARAM_VIBRATE_CATEGORY = "Notifications"
PARAM_VIBRATE_NAME = "vibrate"
PARAM_VIBRATE_LABEL = D_(u"Vibrate on notifications")
SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
SOCKET_FILE = ".socket"
STATE_RUNNING = "running"
STATE_PAUSED = "paused"
STATE_STOPPED = "stopped"
STATES = (STATE_RUNNING, STATE_PAUSED, STATE_STOPPED)


class FrontendStateProtocol(protocol.Protocol):

    def __init__(self, android_plugin):
        self.android_plugin = android_plugin

    def dataReceived(self, data):
        if data in STATES:
            self.android_plugin.state = data
        else:
            log.warning(u"Unexpected data: {data}".format(data=data))


class FrontendStateFactory(protocol.Factory):

    def __init__(self, android_plugin):
        self.android_plugin = android_plugin

    def buildProtocol(self, addr):
        return FrontendStateProtocol(self.android_plugin)



class AndroidPlugin(object):

    params = """
    <params>
    <individual>
    <category name="{category_name}" label="{category_label}">
        <param name="{param_name}" label="{param_label}" value="true" type="bool" security="0" />
     </category>
    </individual>
    </params>
    """.format(
        category_name=PARAM_VIBRATE_CATEGORY,
        category_label=D_(PARAM_VIBRATE_CATEGORY),
        param_name=PARAM_VIBRATE_NAME,
        param_label=PARAM_VIBRATE_LABEL,
    )

    def __init__(self, host):
        log.info(_(u"plugin Android initialization"))
        self.host = host
        host.memory.updateParams(self.params)
        try:
            os.mkdir(SOCKET_DIR, 0700)
        except OSError as e:
            if e.errno == 17:
                # dir already exists
                pass
            else:
                raise e
        self._state = None
        factory = FrontendStateFactory(self)
        socket_path = os.path.join(SOCKET_DIR, SOCKET_FILE)
        try:
            reactor.listenUNIX(socket_path, factory)
        except int_error.CannotListenError as e:
            if e.socketError.errno == 98:
                # the address is already in use, we need to remove it
                os.unlink(socket_path)
                reactor.listenUNIX(socket_path, factory)
            else:
                raise e
        # we set a low priority because we want the notification to be sent after all
        # plugins have done their job
        host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=-1000)

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, new_state):
        log.debug(u"frontend state has changed: {state}".format(state=new_state))
        previous_state = self._state
        self._state = new_state
        if new_state == STATE_RUNNING:
            self._onRunning(previous_state)
        elif new_state == STATE_PAUSED:
            self._onPaused(previous_state)
        elif new_state == STATE_STOPPED:
            self._onStopped(previous_state)

    @property
    def cagou_active(self):
        return self._state == STATE_RUNNING

    def _onRunning(self, previous_state):
        if previous_state is not None:
            self.host.bridge.bridgeReactivateSignals()

    def _onPaused(self, previous_state):
        self.host.bridge.bridgeDeactivateSignals()

    def _onStopped(self, previous_state):
        pass

    def _notifyMessage(self, mess_data, client):
        # send notification if there is a message and it is not a groupchat
        if mess_data["message"] and mess_data["type"] != C.MESS_TYPE_GROUPCHAT:
            message = mess_data["message"].itervalues().next()
            try:
                subject = mess_data["subject"].itervalues().next()
            except StopIteration:
                subject = u"Cagou new message"

            notification.notify(title=subject, message=message)
            if self.host.memory.getParamA(
                PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile
            ):
                vibrator.vibrate()
        return mess_data

    def messageReceivedTrigger(self, client, message_elt, post_treat):
        if not self.cagou_active:
            # we only send notification is the frontend is not displayed
            post_treat.addCallback(self._notifyMessage, client)

        return True