1'''This is the Android implementation of NFC Scanning using the
2built in NFC adapter of some android phones.
3'''
4
5from kivy.app import App
6from kivy.clock import Clock
7#Detect which platform we are on
8from kivy.utils import platform
9if platform != 'android':
10    raise ImportError
11import threading
12
13from . import NFCBase
14from jnius import autoclass, cast
15from android.runnable import run_on_ui_thread
16from android import activity
17
18BUILDVERSION = autoclass('android.os.Build$VERSION').SDK_INT
19NfcAdapter = autoclass('android.nfc.NfcAdapter')
20PythonActivity = autoclass('org.kivy.android.PythonActivity')
21JString = autoclass('java.lang.String')
22Charset = autoclass('java.nio.charset.Charset')
23locale = autoclass('java.util.Locale')
24Intent = autoclass('android.content.Intent')
25IntentFilter = autoclass('android.content.IntentFilter')
26PendingIntent = autoclass('android.app.PendingIntent')
27Ndef = autoclass('android.nfc.tech.Ndef')
28NdefRecord = autoclass('android.nfc.NdefRecord')
29NdefMessage = autoclass('android.nfc.NdefMessage')
30
31app = None
32
33
34
35class ScannerAndroid(NFCBase):
36    ''' This is the class responsible for handling the interface with the
37    Android NFC adapter. See Module Documentation for details.
38    '''
39
40    name = 'NFCAndroid'
41
42    def nfc_init(self):
43        ''' This is where we initialize NFC adapter.
44        '''
45        # Initialize NFC
46        global app
47        app = App.get_running_app()
48
49        # Make sure we are listening to new intent
50        activity.bind(on_new_intent=self.on_new_intent)
51
52        # Configure nfc
53        self.j_context = context = PythonActivity.mActivity
54        self.nfc_adapter = NfcAdapter.getDefaultAdapter(context)
55        # Check if adapter exists
56        if not self.nfc_adapter:
57            return False
58
59        # specify that we want our activity to remain on top when a new intent
60        # is fired
61        self.nfc_pending_intent = PendingIntent.getActivity(context, 0,
62            Intent(context, context.getClass()).addFlags(
63                Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
64
65        # Filter for different types of action, by default we enable all.
66        # These are only for handling different NFC technologies when app is in foreground
67        self.ndef_detected = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
68        #self.tech_detected = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
69        #self.tag_detected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
70
71        # setup tag discovery for ourt tag type
72        try:
73            self.ndef_detected.addCategory(Intent.CATEGORY_DEFAULT)
74            # setup the foreground dispatch to detect all mime types
75            self.ndef_detected.addDataType('*/*')
76
77            self.ndef_exchange_filters = [self.ndef_detected]
78        except Exception as err:
79            raise Exception(repr(err))
80        return True
81
82    def get_ndef_details(self, tag):
83        ''' Get all the details from the tag.
84        '''
85        details = {}
86
87        try:
88            #print 'id'
89            details['uid'] = ':'.join(['{:02x}'.format(bt & 0xff) for bt in tag.getId()])
90            #print 'technologies'
91            details['Technologies'] = tech_list = [tech.split('.')[-1] for tech in tag.getTechList()]
92            #print 'get NDEF tag details'
93            ndefTag = cast('android.nfc.tech.Ndef', Ndef.get(tag))
94            #print 'tag size'
95            details['MaxSize'] = ndefTag.getMaxSize()
96            #details['usedSize'] = '0'
97            #print 'is tag writable?'
98            details['writable'] = ndefTag.isWritable()
99            #print 'Data format'
100            # Can be made readonly
101            # get NDEF message details
102            ndefMesg = ndefTag.getCachedNdefMessage()
103            # get size of current records
104            details['consumed'] = len(ndefMesg.toByteArray())
105            #print 'tag type'
106            details['Type'] = ndefTag.getType()
107
108            # check if tag is empty
109            if not ndefMesg:
110                details['Message'] = None
111                return details
112
113            ndefrecords =  ndefMesg.getRecords()
114            length = len(ndefrecords)
115            #print 'length', length
116            # will contain the NDEF record types
117            recTypes = []
118            for record in ndefrecords:
119                recTypes.append({
120                    'type': ''.join(map(chr, record.getType())),
121                    'payload': ''.join(map(chr, record.getPayload()))
122                    })
123
124            details['recTypes'] = recTypes
125        except Exception as err:
126            print(str(err))
127
128        return details
129
130    def on_new_intent(self, intent):
131        ''' This function is called when the application receives a
132        new intent, for the ones the application has registered previously,
133        either in the manifest or in the foreground dispatch setup in the
134        nfc_init function above.
135        '''
136
137        action_list = (NfcAdapter.ACTION_NDEF_DISCOVERED,)
138        # get TAG
139        #tag = cast('android.nfc.Tag', intent.getParcelableExtra(NfcAdapter.EXTRA_TAG))
140
141        #details = self.get_ndef_details(tag)
142
143        if intent.getAction() not in action_list:
144            print('unknow action, avoid.')
145            return
146
147        rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
148        if not rawmsgs:
149            return
150        for message in rawmsgs:
151            message = cast(NdefMessage, message)
152            payload = message.getRecords()[0].getPayload()
153            print('payload: {}'.format(''.join(map(chr, payload))))
154
155    def nfc_disable(self):
156        '''Disable app from handling tags.
157        '''
158        self.disable_foreground_dispatch()
159
160    def nfc_enable(self):
161        '''Enable app to handle tags when app in foreground.
162        '''
163        self.enable_foreground_dispatch()
164
165    def create_AAR(self):
166        '''Create the record responsible for linking our application to the tag.
167        '''
168        return NdefRecord.createApplicationRecord(JString("org.electrum.kivy"))
169
170    def create_TNF_EXTERNAL(self, data):
171        '''Create our actual payload record.
172        '''
173        if BUILDVERSION >= 14:
174            domain = "org.electrum"
175            stype = "externalType"
176            extRecord = NdefRecord.createExternal(domain, stype, data)
177        else:
178            # Creating the NdefRecord manually:
179            extRecord = NdefRecord(
180                NdefRecord.TNF_EXTERNAL_TYPE,
181                "org.electrum:externalType",
182                '',
183                data)
184        return extRecord
185
186    def create_ndef_message(self, *recs):
187        ''' Create the Ndef message that will be written to tag
188        '''
189        records = []
190        for record in recs:
191            if record:
192                records.append(record)
193
194        return NdefMessage(records)
195
196
197    @run_on_ui_thread
198    def disable_foreground_dispatch(self):
199        '''Disable foreground dispatch when app is paused.
200        '''
201        self.nfc_adapter.disableForegroundDispatch(self.j_context)
202
203    @run_on_ui_thread
204    def enable_foreground_dispatch(self):
205        '''Start listening for new tags
206        '''
207        self.nfc_adapter.enableForegroundDispatch(self.j_context,
208                self.nfc_pending_intent, self.ndef_exchange_filters, self.ndef_tech_list)
209
210    @run_on_ui_thread
211    def _nfc_enable_ndef_exchange(self, data):
212        # Enable p2p exchange
213        # Create record
214        ndef_record = NdefRecord(
215                NdefRecord.TNF_MIME_MEDIA,
216                'org.electrum.kivy', '', data)
217
218        # Create message
219        ndef_message = NdefMessage([ndef_record])
220
221        # Enable ndef push
222        self.nfc_adapter.enableForegroundNdefPush(self.j_context, ndef_message)
223
224        # Enable dispatch
225        self.nfc_adapter.enableForegroundDispatch(self.j_context,
226                self.nfc_pending_intent, self.ndef_exchange_filters, [])
227
228    @run_on_ui_thread
229    def _nfc_disable_ndef_exchange(self):
230        # Disable p2p exchange
231        self.nfc_adapter.disableForegroundNdefPush(self.j_context)
232        self.nfc_adapter.disableForegroundDispatch(self.j_context)
233
234    def nfc_enable_exchange(self, data):
235        '''Enable Ndef exchange for p2p
236        '''
237        self._nfc_enable_ndef_exchange()
238
239    def nfc_disable_exchange(self):
240        ''' Disable Ndef exchange for p2p
241        '''
242        self._nfc_disable_ndef_exchange()
243