28e29b6460949599aa820b267bc0c030af62bfd6
[juce.git] /
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2017 - ROLI Ltd.\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \r
10    The code included in this file is provided under the terms of the ISC license\r
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission\r
12    To use, copy, modify, and/or distribute this software for any purpose with or\r
13    without fee is hereby granted provided that the above copyright notice and\r
14    this permission notice appear in all copies.\r
15 \r
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER\r
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE\r
18    DISCLAIMED.\r
19 \r
20   ==============================================================================\r
21 */\r
22 \r
23 package com.juce.audioperformancetest;\r
24 \r
25 import android.app.Activity;\r
26 import android.app.AlertDialog;\r
27 import android.content.DialogInterface;\r
28 import android.content.Context;\r
29 import android.content.Intent;\r
30 import android.content.res.Configuration;\r
31 import android.content.pm.PackageInfo;\r
32 import android.content.pm.PackageManager;\r
33 import android.net.http.SslError;\r
34 import android.net.Uri;\r
35 import android.os.Bundle;\r
36 import android.os.Looper;\r
37 import android.os.Handler;\r
38 import android.os.Message;\r
39 import android.os.ParcelUuid;\r
40 import android.os.Environment;\r
41 import android.view.*;\r
42 import android.view.inputmethod.BaseInputConnection;\r
43 import android.view.inputmethod.EditorInfo;\r
44 import android.view.inputmethod.InputConnection;\r
45 import android.view.inputmethod.InputMethodManager;\r
46 import android.graphics.*;\r
47 import android.text.ClipboardManager;\r
48 import android.text.InputType;\r
49 import android.util.DisplayMetrics;\r
50 import android.util.Log;\r
51 import android.util.Pair;\r
52 import android.webkit.SslErrorHandler;\r
53 import android.webkit.WebChromeClient;\r
54 import android.webkit.WebResourceError;\r
55 import android.webkit.WebResourceRequest;\r
56 import android.webkit.WebResourceResponse;\r
57 import android.webkit.WebView;\r
58 import android.webkit.WebViewClient;\r
59 import java.lang.Runnable;\r
60 import java.lang.ref.WeakReference;\r
61 import java.lang.reflect.*;\r
62 import java.util.*;\r
63 import java.io.*;\r
64 import java.net.URL;\r
65 import java.net.HttpURLConnection;\r
66 import android.media.AudioManager;\r
67 import android.Manifest;\r
68 import java.util.concurrent.CancellationException;\r
69 import java.util.concurrent.Future;\r
70 import java.util.concurrent.Executors;\r
71 import java.util.concurrent.ExecutorService;\r
72 import java.util.concurrent.ExecutionException;\r
73 import java.util.concurrent.TimeUnit;\r
74 import java.util.concurrent.Callable;\r
75 import java.util.concurrent.TimeoutException;\r
76 import java.util.concurrent.locks.ReentrantLock;\r
77 import java.util.concurrent.atomic.*;\r
78 \r
79 import android.media.midi.*;\r
80 import android.bluetooth.*;\r
81 import android.bluetooth.le.*;\r
82 \r
83 \r
84 //==============================================================================\r
85 public class AudioPerformanceTest   extends Activity\r
86 {\r
87     //==============================================================================\r
88     static\r
89     {\r
90         System.loadLibrary ("juce_jni");\r
91     }\r
92 \r
93     //==============================================================================\r
94     public boolean isPermissionDeclaredInManifest (int permissionID)\r
95     {\r
96         String permissionToCheck = getAndroidPermissionName(permissionID);\r
97 \r
98         try\r
99         {\r
100             PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);\r
101 \r
102             if (info.requestedPermissions != null)\r
103                 for (String permission : info.requestedPermissions)\r
104                     if (permission.equals (permissionToCheck))\r
105                         return true;\r
106         }\r
107         catch (PackageManager.NameNotFoundException e)\r
108         {\r
109             Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());\r
110         }\r
111 \r
112         Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);\r
113         return false;\r
114     }\r
115 \r
116     //==============================================================================\r
117     // these have to match the values of enum PermissionID in C++ class RuntimePermissions:\r
118     private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;\r
119     private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;\r
120     private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;\r
121     private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;\r
122 \r
123     private static String getAndroidPermissionName (int permissionID)\r
124     {\r
125         switch (permissionID)\r
126         {\r
127             case JUCE_PERMISSIONS_RECORD_AUDIO:           return Manifest.permission.RECORD_AUDIO;\r
128             case JUCE_PERMISSIONS_BLUETOOTH_MIDI:         return Manifest.permission.ACCESS_COARSE_LOCATION;\r
129                                                           // use string value as this is not defined in SDKs < 16\r
130             case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE:  return "android.permission.READ_EXTERNAL_STORAGE";\r
131             case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;\r
132         }\r
133 \r
134         // unknown permission ID!\r
135         assert false;\r
136         return new String();\r
137     }\r
138 \r
139     public boolean isPermissionGranted (int permissionID)\r
140     {\r
141         return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;\r
142     }\r
143 \r
144     private Map<Integer, Long> permissionCallbackPtrMap;\r
145 \r
146     public void requestRuntimePermission (int permissionID, long ptrToCallback)\r
147     {\r
148         String permissionName = getAndroidPermissionName (permissionID);\r
149 \r
150         if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)\r
151         {\r
152             // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously\r
153             permissionCallbackPtrMap.put (permissionID, ptrToCallback);\r
154             requestPermissionsCompat (new String[]{permissionName}, permissionID);\r
155         }\r
156         else\r
157         {\r
158             // permissions were already granted before, we can call callback directly\r
159             androidRuntimePermissionsCallback (true, ptrToCallback);\r
160         }\r
161     }\r
162 \r
163     private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);\r
164 \r
165     @Override\r
166     public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)\r
167     {\r
168         boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\r
169 \r
170         if (! permissionsGranted)\r
171             Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));\r
172 \r
173         Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);\r
174         permissionCallbackPtrMap.remove (permissionID);\r
175         androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);\r
176     }\r
177 \r
178     //==============================================================================\r
179     public interface JuceMidiPort\r
180     {\r
181         boolean isInputPort();\r
182 \r
183         // start, stop does nothing on an output port\r
184         void start();\r
185         void stop();\r
186 \r
187         void close();\r
188 \r
189         // send will do nothing on an input port\r
190         void sendMidi (byte[] msg, int offset, int count);\r
191     }\r
192 \r
193     //==============================================================================\r
194     //==============================================================================\r
195     public class BluetoothManager extends ScanCallback\r
196     {\r
197         BluetoothManager()\r
198         {\r
199         }\r
200 \r
201         public String[] getMidiBluetoothAddresses()\r
202         {\r
203             return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);\r
204         }\r
205 \r
206         public String getHumanReadableStringForBluetoothAddress (String address)\r
207         {\r
208             BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);\r
209             return btDevice.getName();\r
210         }\r
211 \r
212         public int getBluetoothDeviceStatus (String address)\r
213         {\r
214             return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);\r
215         }\r
216 \r
217         public void startStopScan (boolean shouldStart)\r
218         {\r
219             BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();\r
220 \r
221             if (bluetoothAdapter == null)\r
222             {\r
223                 Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");\r
224                 return;\r
225             }\r
226 \r
227             BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();\r
228 \r
229             if (bluetoothLeScanner == null)\r
230             {\r
231                 Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");\r
232                 return;\r
233             }\r
234 \r
235             if (shouldStart)\r
236             {\r
237                 ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();\r
238                 scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));\r
239 \r
240                 ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();\r
241                 scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)\r
242                                    .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)\r
243                                    .setScanMode (ScanSettings.MATCH_MODE_STICKY);\r
244 \r
245                 bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),\r
246                                               scanSettingsBuilder.build(),\r
247                                               this);\r
248             }\r
249             else\r
250             {\r
251                 bluetoothLeScanner.stopScan (this);\r
252             }\r
253         }\r
254 \r
255         public boolean pairBluetoothMidiDevice(String address)\r
256         {\r
257             BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);\r
258 \r
259             if (btDevice == null)\r
260             {\r
261                 Log.d ("JUCE", "failed to create buletooth device from address");\r
262                 return false;\r
263             }\r
264 \r
265             return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);\r
266         }\r
267 \r
268         public void unpairBluetoothMidiDevice (String address)\r
269         {\r
270             getAndroidMidiDeviceManager().unpairBluetoothDevice (address);\r
271         }\r
272 \r
273         public void onScanFailed (int errorCode)\r
274         {\r
275         }\r
276 \r
277         public void onScanResult (int callbackType, ScanResult result)\r
278         {\r
279             if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES\r
280                  || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)\r
281             {\r
282                 BluetoothDevice device = result.getDevice();\r
283 \r
284                 if (device != null)\r
285                     bluetoothMidiDevices.add (device.getAddress());\r
286             }\r
287 \r
288             if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)\r
289             {\r
290                 Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");\r
291                 BluetoothDevice device = result.getDevice();\r
292 \r
293                 if (device != null)\r
294                 {\r
295                     bluetoothMidiDevices.remove (device.getAddress());\r
296                     unpairBluetoothMidiDevice (device.getAddress());\r
297                 }\r
298             }\r
299         }\r
300 \r
301         public void onBatchScanResults (List<ScanResult> results)\r
302         {\r
303             for (ScanResult result : results)\r
304                 onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);\r
305         }\r
306 \r
307         private BluetoothLeScanner scanner;\r
308         private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";\r
309 \r
310         private HashSet<String> bluetoothMidiDevices = new HashSet<String>();\r
311     }\r
312 \r
313     public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort\r
314     {\r
315         private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);\r
316 \r
317         public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)\r
318         {\r
319             owner = mm;\r
320             androidPort = actualPort;\r
321             portPath = portPathToUse;\r
322             juceHost = hostToUse;\r
323             isConnected = false;\r
324         }\r
325 \r
326         @Override\r
327         protected void finalize() throws Throwable\r
328         {\r
329             close();\r
330             super.finalize();\r
331         }\r
332 \r
333         @Override\r
334         public boolean isInputPort()\r
335         {\r
336             return true;\r
337         }\r
338 \r
339         @Override\r
340         public void start()\r
341         {\r
342             if (owner != null && androidPort != null && ! isConnected) {\r
343                 androidPort.connect(this);\r
344                 isConnected = true;\r
345             }\r
346         }\r
347 \r
348         @Override\r
349         public void stop()\r
350         {\r
351             if (owner != null && androidPort != null && isConnected) {\r
352                 androidPort.disconnect(this);\r
353                 isConnected = false;\r
354             }\r
355         }\r
356 \r
357         @Override\r
358         public void close()\r
359         {\r
360             if (androidPort != null) {\r
361                 try {\r
362                     androidPort.close();\r
363                 } catch (IOException exception) {\r
364                     Log.d("JUCE", "IO Exception while closing port");\r
365                 }\r
366             }\r
367 \r
368             if (owner != null)\r
369                 owner.removePort (portPath);\r
370 \r
371             owner = null;\r
372             androidPort = null;\r
373         }\r
374 \r
375         @Override\r
376         public void onSend (byte[] msg, int offset, int count, long timestamp)\r
377         {\r
378             if (count > 0)\r
379                 handleReceive (juceHost, msg, offset, count, timestamp);\r
380         }\r
381 \r
382         @Override\r
383         public void onFlush()\r
384         {}\r
385 \r
386         @Override\r
387         public void sendMidi (byte[] msg, int offset, int count)\r
388         {\r
389         }\r
390 \r
391         MidiDeviceManager owner;\r
392         MidiOutputPort androidPort;\r
393         MidiPortPath portPath;\r
394         long juceHost;\r
395         boolean isConnected;\r
396     }\r
397 \r
398     public static class JuceMidiOutputPort implements JuceMidiPort\r
399     {\r
400         public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)\r
401         {\r
402             owner = mm;\r
403             androidPort = actualPort;\r
404             portPath = portPathToUse;\r
405         }\r
406 \r
407         @Override\r
408         protected void finalize() throws Throwable\r
409         {\r
410             close();\r
411             super.finalize();\r
412         }\r
413 \r
414         @Override\r
415         public boolean isInputPort()\r
416         {\r
417             return false;\r
418         }\r
419 \r
420         @Override\r
421         public void start()\r
422         {\r
423         }\r
424 \r
425         @Override\r
426         public void stop()\r
427         {\r
428         }\r
429 \r
430         @Override\r
431         public void sendMidi (byte[] msg, int offset, int count)\r
432         {\r
433             if (androidPort != null)\r
434             {\r
435                 try {\r
436                     androidPort.send(msg, offset, count);\r
437                 } catch (IOException exception)\r
438                 {\r
439                     Log.d ("JUCE", "send midi had IO exception");\r
440                 }\r
441             }\r
442         }\r
443 \r
444         @Override\r
445         public void close()\r
446         {\r
447             if (androidPort != null) {\r
448                 try {\r
449                     androidPort.close();\r
450                 } catch (IOException exception) {\r
451                     Log.d("JUCE", "IO Exception while closing port");\r
452                 }\r
453             }\r
454 \r
455             if (owner != null)\r
456                 owner.removePort (portPath);\r
457 \r
458             owner = null;\r
459             androidPort = null;\r
460         }\r
461 \r
462         MidiDeviceManager owner;\r
463         MidiInputPort androidPort;\r
464         MidiPortPath portPath;\r
465     }\r
466 \r
467     private static class MidiPortPath extends Object\r
468     {\r
469         public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)\r
470         {\r
471             deviceId = deviceIdToUse;\r
472             isInput = direction;\r
473             portIndex = androidIndex;\r
474 \r
475         }\r
476 \r
477         public int deviceId;\r
478         public int portIndex;\r
479         public boolean isInput;\r
480 \r
481         @Override\r
482         public int hashCode()\r
483         {\r
484             Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));\r
485             return i.hashCode() * (isInput ? -1 : 1);\r
486         }\r
487 \r
488         @Override\r
489         public boolean equals (Object obj)\r
490         {\r
491             if (obj == null)\r
492                 return false;\r
493 \r
494             if (getClass() != obj.getClass())\r
495                 return false;\r
496 \r
497             MidiPortPath other = (MidiPortPath) obj;\r
498             return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);\r
499         }\r
500     }\r
501 \r
502     //==============================================================================\r
503     public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener\r
504     {\r
505         //==============================================================================\r
506         private class DummyBluetoothGattCallback extends BluetoothGattCallback\r
507         {\r
508             public DummyBluetoothGattCallback (MidiDeviceManager mm)\r
509             {\r
510                 super();\r
511                 owner = mm;\r
512             }\r
513 \r
514             public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)\r
515             {\r
516                 if (newState == BluetoothProfile.STATE_CONNECTED)\r
517                 {\r
518                     gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);\r
519                     owner.pairBluetoothDeviceStepTwo (gatt.getDevice());\r
520                 }\r
521             }\r
522             public void onServicesDiscovered(BluetoothGatt gatt, int status) {}\r
523             public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}\r
524             public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}\r
525             public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}\r
526             public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}\r
527             public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}\r
528             public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}\r
529             public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}\r
530             public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}\r
531 \r
532             private MidiDeviceManager owner;\r
533         }\r
534 \r
535         //==============================================================================\r
536         private class MidiDeviceOpenTask extends java.util.TimerTask\r
537         {\r
538             public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)\r
539             {\r
540                 owner = deviceManager;\r
541                 midiDevice = device;\r
542                 btGatt = gattToUse;\r
543             }\r
544 \r
545             @Override\r
546             public boolean cancel()\r
547             {\r
548                 synchronized (MidiDeviceOpenTask.class)\r
549                 {\r
550                     owner = null;\r
551                     boolean retval = super.cancel();\r
552 \r
553                     if (btGatt != null)\r
554                     {\r
555                         btGatt.disconnect();\r
556                         btGatt.close();\r
557 \r
558                         btGatt = null;\r
559                     }\r
560 \r
561                     if (midiDevice != null)\r
562                     {\r
563                         try\r
564                         {\r
565                             midiDevice.close();\r
566                         }\r
567                         catch (IOException e)\r
568                         {}\r
569 \r
570                         midiDevice = null;\r
571                     }\r
572 \r
573                     return retval;\r
574                 }\r
575             }\r
576 \r
577             public String getBluetoothAddress()\r
578             {\r
579                 synchronized (MidiDeviceOpenTask.class)\r
580                 {\r
581                     if (midiDevice != null)\r
582                     {\r
583                         MidiDeviceInfo info = midiDevice.getInfo();\r
584                         if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
585                         {\r
586                             BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
587                             if (btDevice != null)\r
588                                 return btDevice.getAddress();\r
589                         }\r
590                     }\r
591                 }\r
592 \r
593                 return "";\r
594             }\r
595 \r
596             public BluetoothGatt getGatt() { return btGatt; }\r
597 \r
598             public int getID()\r
599             {\r
600                 return midiDevice.getInfo().getId();\r
601             }\r
602 \r
603             @Override\r
604             public void run()\r
605             {\r
606                 synchronized (MidiDeviceOpenTask.class)\r
607                 {\r
608                     if (owner != null && midiDevice != null)\r
609                         owner.onDeviceOpenedDelayed (midiDevice);\r
610                 }\r
611             }\r
612 \r
613             private MidiDeviceManager owner;\r
614             private MidiDevice midiDevice;\r
615             private BluetoothGatt btGatt;\r
616         }\r
617 \r
618         //==============================================================================\r
619         public MidiDeviceManager()\r
620         {\r
621             manager = (MidiManager) getSystemService (MIDI_SERVICE);\r
622 \r
623             if (manager == null)\r
624             {\r
625                 Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");\r
626                 return;\r
627             }\r
628 \r
629             openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();\r
630             midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();\r
631             openTasks = new HashMap<Integer, MidiDeviceOpenTask>();\r
632             btDevicesPairing = new HashMap<String, BluetoothGatt>();\r
633 \r
634             MidiDeviceInfo[] foundDevices = manager.getDevices();\r
635             for (MidiDeviceInfo info : foundDevices)\r
636                 onDeviceAdded (info);\r
637 \r
638             manager.registerDeviceCallback (this, null);\r
639         }\r
640 \r
641         protected void finalize() throws Throwable\r
642         {\r
643             manager.unregisterDeviceCallback (this);\r
644 \r
645             synchronized (MidiDeviceManager.class)\r
646             {\r
647                 btDevicesPairing.clear();\r
648 \r
649                 for (Integer deviceID : openTasks.keySet())\r
650                     openTasks.get (deviceID).cancel();\r
651 \r
652                 openTasks = null;\r
653             }\r
654 \r
655             for (MidiPortPath key : openPorts.keySet())\r
656                 openPorts.get (key).get().close();\r
657 \r
658             openPorts = null;\r
659 \r
660             for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)\r
661             {\r
662                 if (device.second != null)\r
663                 {\r
664                     device.second.disconnect();\r
665                     device.second.close();\r
666                 }\r
667 \r
668                 device.first.close();\r
669             }\r
670 \r
671             midiDevices.clear();\r
672 \r
673             super.finalize();\r
674         }\r
675 \r
676         public String[] getJuceAndroidMidiInputDevices()\r
677         {\r
678             return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);\r
679         }\r
680 \r
681         public String[] getJuceAndroidMidiOutputDevices()\r
682         {\r
683             return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);\r
684         }\r
685 \r
686         private String[] getJuceAndroidMidiDevices (int portType)\r
687         {\r
688             // only update the list when JUCE asks for a new list\r
689             synchronized (MidiDeviceManager.class)\r
690             {\r
691                 deviceInfos = getDeviceInfos();\r
692             }\r
693 \r
694             ArrayList<String> portNames = new ArrayList<String>();\r
695 \r
696             int index = 0;\r
697             for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))\r
698                 portNames.add (getPortName (portInfo));\r
699 \r
700             String[] names = new String[portNames.size()];\r
701             return portNames.toArray (names);\r
702         }\r
703 \r
704         private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)\r
705         {\r
706             synchronized (MidiDeviceManager.class)\r
707             {\r
708                 int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);\r
709                 MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);\r
710 \r
711                 if (portInfo != null)\r
712                 {\r
713                     // ports must be opened exclusively!\r
714                     if (openPorts.containsKey (portInfo))\r
715                         return null;\r
716 \r
717                     Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);\r
718 \r
719                     if (devicePair != null)\r
720                     {\r
721                         MidiDevice device = devicePair.first;\r
722                         if (device != null)\r
723                         {\r
724                             JuceMidiPort juceMidiPort = null;\r
725 \r
726                             if (isInput)\r
727                             {\r
728                                 MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);\r
729 \r
730                                 if (outputPort != null)\r
731                                     juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);\r
732                             }\r
733                             else\r
734                             {\r
735                                 MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);\r
736 \r
737                                 if (inputPort != null)\r
738                                     juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);\r
739                             }\r
740 \r
741                             if (juceMidiPort != null)\r
742                             {\r
743                                 openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));\r
744 \r
745                                 return juceMidiPort;\r
746                             }\r
747                         }\r
748                     }\r
749                 }\r
750             }\r
751 \r
752             return null;\r
753         }\r
754 \r
755         public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)\r
756         {\r
757             return openMidiPortWithJuceIndex (index, host, true);\r
758         }\r
759 \r
760         public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)\r
761         {\r
762             return openMidiPortWithJuceIndex (index, 0, false);\r
763         }\r
764 \r
765         /* 0: unpaired, 1: paired, 2: pairing */\r
766         public int getBluetoothDeviceStatus (String address)\r
767         {\r
768             synchronized (MidiDeviceManager.class)\r
769             {\r
770                 if (! address.isEmpty())\r
771                 {\r
772                     if (findMidiDeviceForBluetoothAddress (address) != null)\r
773                         return 1;\r
774 \r
775                     if (btDevicesPairing.containsKey (address))\r
776                         return 2;\r
777 \r
778                     if (findOpenTaskForBluetoothAddress (address) != null)\r
779                         return 2;\r
780                 }\r
781             }\r
782 \r
783             return 0;\r
784         }\r
785 \r
786         public boolean pairBluetoothDevice (BluetoothDevice btDevice)\r
787         {\r
788             String btAddress = btDevice.getAddress();\r
789             if (btAddress.isEmpty())\r
790                 return false;\r
791 \r
792             synchronized (MidiDeviceManager.class)\r
793             {\r
794                 if (getBluetoothDeviceStatus (btAddress) != 0)\r
795                     return false;\r
796 \r
797 \r
798                 btDevicesPairing.put (btDevice.getAddress(), null);\r
799                 BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));\r
800 \r
801                 if (gatt != null)\r
802                 {\r
803                     btDevicesPairing.put (btDevice.getAddress(), gatt);\r
804                 }\r
805                 else\r
806                 {\r
807                     pairBluetoothDeviceStepTwo (btDevice);\r
808                 }\r
809             }\r
810 \r
811             return true;\r
812         }\r
813 \r
814         public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)\r
815         {\r
816             manager.openBluetoothDevice(btDevice, this, null);\r
817         }\r
818 \r
819         public void unpairBluetoothDevice (String address)\r
820         {\r
821             if (address.isEmpty())\r
822                 return;\r
823 \r
824             synchronized (MidiDeviceManager.class)\r
825             {\r
826                 if (btDevicesPairing.containsKey (address))\r
827                 {\r
828                     BluetoothGatt gatt = btDevicesPairing.get (address);\r
829                     if (gatt != null)\r
830                     {\r
831                         gatt.disconnect();\r
832                         gatt.close();\r
833                     }\r
834 \r
835                     btDevicesPairing.remove (address);\r
836                 }\r
837 \r
838                 MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);\r
839                 if (openTask != null)\r
840                 {\r
841                     int deviceID = openTask.getID();\r
842                     openTask.cancel();\r
843                     openTasks.remove (deviceID);\r
844                 }\r
845 \r
846                 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);\r
847                 if (midiDevicePair != null)\r
848                 {\r
849                     MidiDevice midiDevice = midiDevicePair.first;\r
850                     onDeviceRemoved (midiDevice.getInfo());\r
851 \r
852                     try {\r
853                         midiDevice.close();\r
854                     }\r
855                     catch (IOException exception)\r
856                     {\r
857                         Log.d ("JUCE", "IOException while closing midi device");\r
858                     }\r
859                 }\r
860             }\r
861         }\r
862 \r
863         private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)\r
864         {\r
865             for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
866             {\r
867                 MidiDeviceInfo info = midiDevice.first.getInfo();\r
868                 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
869                 {\r
870                     BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
871                     if (btDevice != null && btDevice.getAddress().equals (address))\r
872                         return midiDevice;\r
873                 }\r
874             }\r
875 \r
876             return null;\r
877         }\r
878 \r
879         private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)\r
880         {\r
881             for (Integer deviceID : openTasks.keySet())\r
882             {\r
883                 MidiDeviceOpenTask openTask = openTasks.get (deviceID);\r
884                 if (openTask.getBluetoothAddress().equals (address))\r
885                     return openTask;\r
886             }\r
887 \r
888             return null;\r
889         }\r
890 \r
891         public void removePort (MidiPortPath path)\r
892         {\r
893             openPorts.remove (path);\r
894         }\r
895 \r
896         public String getInputPortNameForJuceIndex (int index)\r
897         {\r
898             MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);\r
899             if (portInfo != null)\r
900                 return getPortName (portInfo);\r
901 \r
902             return "";\r
903         }\r
904 \r
905         public String getOutputPortNameForJuceIndex (int index)\r
906         {\r
907             MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);\r
908             if (portInfo != null)\r
909                 return getPortName (portInfo);\r
910 \r
911             return "";\r
912         }\r
913 \r
914         public void onDeviceAdded (MidiDeviceInfo info)\r
915         {\r
916             // only add standard midi devices\r
917             if (info.getType() == info.TYPE_BLUETOOTH)\r
918                 return;\r
919 \r
920             manager.openDevice (info, this, null);\r
921         }\r
922 \r
923         public void onDeviceRemoved (MidiDeviceInfo info)\r
924         {\r
925             synchronized (MidiDeviceManager.class)\r
926             {\r
927                 Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());\r
928 \r
929                 if (devicePair != null)\r
930                 {\r
931                     MidiDevice midiDevice = devicePair.first;\r
932                     BluetoothGatt gatt = devicePair.second;\r
933 \r
934                     // close all ports that use this device\r
935                     boolean removedPort = true;\r
936 \r
937                     while (removedPort == true)\r
938                     {\r
939                         removedPort = false;\r
940                         for (MidiPortPath key : openPorts.keySet())\r
941                         {\r
942                             if (key.deviceId == info.getId())\r
943                             {\r
944                                 openPorts.get(key).get().close();\r
945                                 removedPort = true;\r
946                                 break;\r
947                             }\r
948                         }\r
949                     }\r
950 \r
951                     if (gatt != null)\r
952                     {\r
953                         gatt.disconnect();\r
954                         gatt.close();\r
955                     }\r
956 \r
957                     midiDevices.remove (devicePair);\r
958                 }\r
959             }\r
960         }\r
961 \r
962         public void onDeviceStatusChanged (MidiDeviceStatus status)\r
963         {\r
964         }\r
965 \r
966         @Override\r
967         public void onDeviceOpened (MidiDevice theDevice)\r
968         {\r
969             synchronized (MidiDeviceManager.class)\r
970             {\r
971                 MidiDeviceInfo info = theDevice.getInfo();\r
972                 int deviceID = info.getId();\r
973                 BluetoothGatt gatt = null;\r
974                 boolean isBluetooth = false;\r
975 \r
976                 if (! openTasks.containsKey (deviceID))\r
977                 {\r
978                     if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)\r
979                     {\r
980                         isBluetooth = true;\r
981                         BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
982                         if (btDevice != null)\r
983                         {\r
984                             String btAddress = btDevice.getAddress();\r
985                             if (btDevicesPairing.containsKey (btAddress))\r
986                             {\r
987                                 gatt = btDevicesPairing.get (btAddress);\r
988                                 btDevicesPairing.remove (btAddress);\r
989                             }\r
990                             else\r
991                             {\r
992                                 // unpair was called in the mean time\r
993                                 try\r
994                                 {\r
995                                     Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());\r
996                                     if (midiDevicePair != null)\r
997                                     {\r
998                                         gatt = midiDevicePair.second;\r
999 \r
1000                                         if (gatt != null)\r
1001                                         {\r
1002                                             gatt.disconnect();\r
1003                                             gatt.close();\r
1004                                         }\r
1005                                     }\r
1006 \r
1007                                     theDevice.close();\r
1008                                 }\r
1009                                 catch (IOException e)\r
1010                                 {}\r
1011 \r
1012                                 return;\r
1013                             }\r
1014                         }\r
1015                     }\r
1016 \r
1017                     MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);\r
1018                     openTasks.put (deviceID, openTask);\r
1019 \r
1020                     new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));\r
1021                 }\r
1022             }\r
1023         }\r
1024 \r
1025         public void onDeviceOpenedDelayed (MidiDevice theDevice)\r
1026         {\r
1027             synchronized (MidiDeviceManager.class)\r
1028             {\r
1029                 int deviceID = theDevice.getInfo().getId();\r
1030 \r
1031                 if (openTasks.containsKey (deviceID))\r
1032                 {\r
1033                     if (! midiDevices.contains(theDevice))\r
1034                     {\r
1035                         BluetoothGatt gatt = openTasks.get (deviceID).getGatt();\r
1036                         openTasks.remove (deviceID);\r
1037                         midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));\r
1038                     }\r
1039                 }\r
1040                 else\r
1041                 {\r
1042                     // unpair was called in the mean time\r
1043                     MidiDeviceInfo info = theDevice.getInfo();\r
1044                     BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);\r
1045                     if (btDevice != null)\r
1046                     {\r
1047                         String btAddress = btDevice.getAddress();\r
1048                         Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());\r
1049                         if (midiDevicePair != null)\r
1050                         {\r
1051                             BluetoothGatt gatt = midiDevicePair.second;\r
1052 \r
1053                             if (gatt != null)\r
1054                             {\r
1055                                 gatt.disconnect();\r
1056                                 gatt.close();\r
1057                             }\r
1058                         }\r
1059                     }\r
1060 \r
1061                     try\r
1062                     {\r
1063                         theDevice.close();\r
1064                     }\r
1065                     catch (IOException e)\r
1066                     {}\r
1067                 }\r
1068             }\r
1069         }\r
1070 \r
1071         public String getPortName(MidiPortPath path)\r
1072         {\r
1073             int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);\r
1074 \r
1075             synchronized (MidiDeviceManager.class)\r
1076             {\r
1077                 for (MidiDeviceInfo info : deviceInfos)\r
1078                 {\r
1079                     int localIndex = 0;\r
1080                     if (info.getId() == path.deviceId)\r
1081                     {\r
1082                         for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())\r
1083                         {\r
1084                             int portType = portInfo.getType();\r
1085                             if (portType == portTypeToFind)\r
1086                             {\r
1087                                 int portIndex = portInfo.getPortNumber();\r
1088                                 if (portIndex == path.portIndex)\r
1089                                 {\r
1090                                     String portName = portInfo.getName();\r
1091                                     if (portName.isEmpty())\r
1092                                         portName = (String) info.getProperties().get(info.PROPERTY_NAME);\r
1093 \r
1094                                     return portName;\r
1095                                 }\r
1096                             }\r
1097                         }\r
1098                     }\r
1099                 }\r
1100             }\r
1101 \r
1102             return "";\r
1103         }\r
1104 \r
1105         public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)\r
1106         {\r
1107             int portIdx = 0;\r
1108             for (MidiDeviceInfo info : deviceInfos)\r
1109             {\r
1110                 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())\r
1111                 {\r
1112                     if (portInfo.getType() == portType)\r
1113                     {\r
1114                         if (portIdx == juceIndex)\r
1115                             return new MidiPortPath (info.getId(),\r
1116                                     (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),\r
1117                                     portInfo.getPortNumber());\r
1118 \r
1119                         portIdx++;\r
1120                     }\r
1121                 }\r
1122             }\r
1123 \r
1124             return null;\r
1125         }\r
1126 \r
1127         private MidiDeviceInfo[] getDeviceInfos()\r
1128         {\r
1129             synchronized (MidiDeviceManager.class)\r
1130             {\r
1131                 MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];\r
1132 \r
1133                 int idx = 0;\r
1134                 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
1135                     infos[idx++] = midiDevice.first.getInfo();\r
1136 \r
1137                 return infos;\r
1138             }\r
1139         }\r
1140 \r
1141         private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)\r
1142         {\r
1143             synchronized (MidiDeviceManager.class)\r
1144             {\r
1145                 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)\r
1146                     if (midiDevice.first.getInfo().getId() == deviceId)\r
1147                         return midiDevice;\r
1148             }\r
1149 \r
1150             return null;\r
1151         }\r
1152 \r
1153         private MidiManager manager;\r
1154         private HashMap<String, BluetoothGatt> btDevicesPairing;\r
1155         private HashMap<Integer, MidiDeviceOpenTask> openTasks;\r
1156         private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;\r
1157         private MidiDeviceInfo[] deviceInfos;\r
1158         private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;\r
1159     }\r
1160 \r
1161     public MidiDeviceManager getAndroidMidiDeviceManager()\r
1162     {\r
1163         if (getSystemService (MIDI_SERVICE) == null)\r
1164             return null;\r
1165 \r
1166         synchronized (AudioPerformanceTest.class)\r
1167         {\r
1168             if (midiDeviceManager == null)\r
1169                 midiDeviceManager = new MidiDeviceManager();\r
1170         }\r
1171 \r
1172         return midiDeviceManager;\r
1173     }\r
1174 \r
1175     public BluetoothManager getAndroidBluetoothManager()\r
1176     {\r
1177         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();\r
1178 \r
1179         if (adapter == null)\r
1180             return null;\r
1181 \r
1182         if (adapter.getBluetoothLeScanner() == null)\r
1183             return null;\r
1184 \r
1185         synchronized (AudioPerformanceTest.class)\r
1186         {\r
1187             if (bluetoothManager == null)\r
1188                 bluetoothManager = new BluetoothManager();\r
1189         }\r
1190 \r
1191         return bluetoothManager;\r
1192     }\r
1193 \r
1194     //==============================================================================\r
1195     @Override\r
1196     public void onCreate (Bundle savedInstanceState)\r
1197     {\r
1198         super.onCreate (savedInstanceState);\r
1199 \r
1200         isScreenSaverEnabled = true;\r
1201         hideActionBar();\r
1202         viewHolder = new ViewHolder (this);\r
1203         setContentView (viewHolder);\r
1204 \r
1205         setVolumeControlStream (AudioManager.STREAM_MUSIC);\r
1206 \r
1207         permissionCallbackPtrMap = new HashMap<Integer, Long>();\r
1208     }\r
1209 \r
1210     @Override\r
1211     protected void onDestroy()\r
1212     {\r
1213         quitApp();\r
1214         super.onDestroy();\r
1215 \r
1216         clearDataCache();\r
1217     }\r
1218 \r
1219     @Override\r
1220     protected void onPause()\r
1221     {\r
1222         suspendApp();\r
1223 \r
1224         try\r
1225         {\r
1226             Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down\r
1227                                  // openGL glitches when pausing/resuming apps..\r
1228         } catch (InterruptedException e) {}\r
1229 \r
1230         super.onPause();\r
1231     }\r
1232 \r
1233     @Override\r
1234     protected void onResume()\r
1235     {\r
1236         super.onResume();\r
1237         resumeApp();\r
1238 \r
1239         // Ensure that navigation/status bar visibility is correctly restored.\r
1240         for (int i = 0; i < viewHolder.getChildCount(); ++i)\r
1241         {\r
1242             if (viewHolder.getChildAt (i) instanceof ComponentPeerView)\r
1243                 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();\r
1244         }\r
1245     }\r
1246 \r
1247     @Override\r
1248     public void onConfigurationChanged (Configuration cfg)\r
1249     {\r
1250         super.onConfigurationChanged (cfg);\r
1251         setContentView (viewHolder);\r
1252     }\r
1253 \r
1254     private void callAppLauncher()\r
1255     {\r
1256         launchApp (getApplicationInfo().publicSourceDir,\r
1257                    getApplicationInfo().dataDir);\r
1258     }\r
1259 \r
1260     // Need to override this as the default implementation always finishes the activity.\r
1261     @Override\r
1262     public void onBackPressed()\r
1263     {\r
1264         ComponentPeerView focusedView = getViewWithFocusOrDefaultView();\r
1265 \r
1266         if (focusedView == null)\r
1267             return;\r
1268 \r
1269         focusedView.backButtonPressed();\r
1270     }\r
1271 \r
1272     private ComponentPeerView getViewWithFocusOrDefaultView()\r
1273     {\r
1274         for (int i = 0; i < viewHolder.getChildCount(); ++i)\r
1275         {\r
1276             if (viewHolder.getChildAt (i).hasFocus())\r
1277                 return (ComponentPeerView) viewHolder.getChildAt (i);\r
1278         }\r
1279 \r
1280         if (viewHolder.getChildCount() > 0)\r
1281             return (ComponentPeerView) viewHolder.getChildAt (0);\r
1282 \r
1283         return null;\r
1284     }\r
1285 \r
1286     //==============================================================================\r
1287     private void hideActionBar()\r
1288     {\r
1289         // get "getActionBar" method\r
1290         java.lang.reflect.Method getActionBarMethod = null;\r
1291         try\r
1292         {\r
1293             getActionBarMethod = this.getClass().getMethod ("getActionBar");\r
1294         }\r
1295         catch (SecurityException e)     { return; }\r
1296         catch (NoSuchMethodException e) { return; }\r
1297         if (getActionBarMethod == null) return;\r
1298 \r
1299         // invoke "getActionBar" method\r
1300         Object actionBar = null;\r
1301         try\r
1302         {\r
1303             actionBar = getActionBarMethod.invoke (this);\r
1304         }\r
1305         catch (java.lang.IllegalArgumentException e) { return; }\r
1306         catch (java.lang.IllegalAccessException e) { return; }\r
1307         catch (java.lang.reflect.InvocationTargetException e) { return; }\r
1308         if (actionBar == null) return;\r
1309 \r
1310         // get "hide" method\r
1311         java.lang.reflect.Method actionBarHideMethod = null;\r
1312         try\r
1313         {\r
1314             actionBarHideMethod = actionBar.getClass().getMethod ("hide");\r
1315         }\r
1316         catch (SecurityException e)     { return; }\r
1317         catch (NoSuchMethodException e) { return; }\r
1318         if (actionBarHideMethod == null) return;\r
1319 \r
1320         // invoke "hide" method\r
1321         try\r
1322         {\r
1323             actionBarHideMethod.invoke (actionBar);\r
1324         }\r
1325         catch (java.lang.IllegalArgumentException e) {}\r
1326         catch (java.lang.IllegalAccessException e) {}\r
1327         catch (java.lang.reflect.InvocationTargetException e) {}\r
1328     }\r
1329 \r
1330     void requestPermissionsCompat (String[] permissions, int requestCode)\r
1331     {\r
1332         Method requestPermissionsMethod = null;\r
1333         try\r
1334         {\r
1335             requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",\r
1336                                                                   String[].class, int.class);\r
1337         }\r
1338         catch (SecurityException e)     { return; }\r
1339         catch (NoSuchMethodException e) { return; }\r
1340         if (requestPermissionsMethod == null) return;\r
1341 \r
1342         try\r
1343         {\r
1344             requestPermissionsMethod.invoke (this, permissions, requestCode);\r
1345         }\r
1346         catch (java.lang.IllegalArgumentException e) {}\r
1347         catch (java.lang.IllegalAccessException e) {}\r
1348         catch (java.lang.reflect.InvocationTargetException e) {}\r
1349     }\r
1350 \r
1351     //==============================================================================\r
1352     private native void launchApp (String appFile, String appDataDir);\r
1353     private native void quitApp();\r
1354     private native void suspendApp();\r
1355     private native void resumeApp();\r
1356     private native void setScreenSize (int screenWidth, int screenHeight, int dpi);\r
1357     private native void appActivityResult (int requestCode, int resultCode, Intent data);\r
1358     private native void appNewIntent (Intent intent);\r
1359 \r
1360     //==============================================================================\r
1361     private ViewHolder viewHolder;\r
1362     private MidiDeviceManager midiDeviceManager = null;\r
1363     private BluetoothManager bluetoothManager = null;\r
1364     private boolean isScreenSaverEnabled;\r
1365     private java.util.Timer keepAliveTimer;\r
1366 \r
1367     public final ComponentPeerView createNewView (boolean opaque, long host)\r
1368     {\r
1369         ComponentPeerView v = new ComponentPeerView (this, opaque, host);\r
1370         viewHolder.addView (v);\r
1371         return v;\r
1372     }\r
1373 \r
1374     public final void deleteView (ComponentPeerView view)\r
1375     {\r
1376         view.host = 0;\r
1377 \r
1378         ViewGroup group = (ViewGroup) (view.getParent());\r
1379 \r
1380         if (group != null)\r
1381             group.removeView (view);\r
1382     }\r
1383 \r
1384     public final void deleteNativeSurfaceView (NativeSurfaceView view)\r
1385     {\r
1386         ViewGroup group = (ViewGroup) (view.getParent());\r
1387 \r
1388         if (group != null)\r
1389             group.removeView (view);\r
1390     }\r
1391 \r
1392     final class ViewHolder  extends ViewGroup\r
1393     {\r
1394         public ViewHolder (Context context)\r
1395         {\r
1396             super (context);\r
1397             setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);\r
1398             setFocusable (false);\r
1399         }\r
1400 \r
1401         protected final void onLayout (boolean changed, int left, int top, int right, int bottom)\r
1402         {\r
1403             setScreenSize (getWidth(), getHeight(), getDPI());\r
1404 \r
1405             if (isFirstResize)\r
1406             {\r
1407                 isFirstResize = false;\r
1408                 callAppLauncher();\r
1409             }\r
1410         }\r
1411 \r
1412         private final int getDPI()\r
1413         {\r
1414             DisplayMetrics metrics = new DisplayMetrics();\r
1415             getWindowManager().getDefaultDisplay().getMetrics (metrics);\r
1416             return metrics.densityDpi;\r
1417         }\r
1418 \r
1419         private boolean isFirstResize = true;\r
1420     }\r
1421 \r
1422     public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)\r
1423     {\r
1424         canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);\r
1425     }\r
1426 \r
1427     //==============================================================================\r
1428     public final void setScreenSaver (boolean enabled)\r
1429     {\r
1430         if (isScreenSaverEnabled != enabled)\r
1431         {\r
1432             isScreenSaverEnabled = enabled;\r
1433 \r
1434             if (keepAliveTimer != null)\r
1435             {\r
1436                 keepAliveTimer.cancel();\r
1437                 keepAliveTimer = null;\r
1438             }\r
1439 \r
1440             if (enabled)\r
1441             {\r
1442                 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
1443             }\r
1444             else\r
1445             {\r
1446                 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
1447 \r
1448                 // If no user input is received after about 3 seconds, the OS will lower the\r
1449                 // task's priority, so this timer forces it to be kept active.\r
1450                 keepAliveTimer = new java.util.Timer();\r
1451 \r
1452                 keepAliveTimer.scheduleAtFixedRate (new TimerTask()\r
1453                 {\r
1454                     @Override\r
1455                     public void run()\r
1456                     {\r
1457                         android.app.Instrumentation instrumentation = new android.app.Instrumentation();\r
1458 \r
1459                         try\r
1460                         {\r
1461                             instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);\r
1462                         }\r
1463                         catch (Exception e)\r
1464                         {\r
1465                         }\r
1466                     }\r
1467                 }, 2000, 2000);\r
1468             }\r
1469         }\r
1470     }\r
1471 \r
1472     public final boolean getScreenSaver()\r
1473     {\r
1474         return isScreenSaverEnabled;\r
1475     }\r
1476 \r
1477     //==============================================================================\r
1478     public final String getClipboardContent()\r
1479     {\r
1480         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
1481         return clipboard.getText().toString();\r
1482     }\r
1483 \r
1484     public final void setClipboardContent (String newText)\r
1485     {\r
1486         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
1487         clipboard.setText (newText);\r
1488     }\r
1489 \r
1490     //==============================================================================\r
1491     public final void showMessageBox (String title, String message, final long callback)\r
1492     {\r
1493         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1494         builder.setTitle (title)\r
1495                .setMessage (message)\r
1496                .setCancelable (true)\r
1497                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1498                     {\r
1499                         public void onCancel (DialogInterface dialog)\r
1500                         {\r
1501                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1502                         }\r
1503                     })\r
1504                .setPositiveButton ("OK", new DialogInterface.OnClickListener()\r
1505                     {\r
1506                         public void onClick (DialogInterface dialog, int id)\r
1507                         {\r
1508                             dialog.dismiss();\r
1509                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1510                         }\r
1511                     });\r
1512 \r
1513         builder.create().show();\r
1514     }\r
1515 \r
1516     public final void showOkCancelBox (String title, String message, final long callback,\r
1517                                        String okButtonText, String cancelButtonText)\r
1518     {\r
1519         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1520         builder.setTitle (title)\r
1521                .setMessage (message)\r
1522                .setCancelable (true)\r
1523                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1524                     {\r
1525                         public void onCancel (DialogInterface dialog)\r
1526                         {\r
1527                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1528                         }\r
1529                     })\r
1530                .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()\r
1531                     {\r
1532                         public void onClick (DialogInterface dialog, int id)\r
1533                         {\r
1534                             dialog.dismiss();\r
1535                             AudioPerformanceTest.this.alertDismissed (callback, 1);\r
1536                         }\r
1537                     })\r
1538                .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()\r
1539                     {\r
1540                         public void onClick (DialogInterface dialog, int id)\r
1541                         {\r
1542                             dialog.dismiss();\r
1543                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1544                         }\r
1545                     });\r
1546 \r
1547         builder.create().show();\r
1548     }\r
1549 \r
1550     public final void showYesNoCancelBox (String title, String message, final long callback)\r
1551     {\r
1552         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
1553         builder.setTitle (title)\r
1554                .setMessage (message)\r
1555                .setCancelable (true)\r
1556                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
1557                     {\r
1558                         public void onCancel (DialogInterface dialog)\r
1559                         {\r
1560                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1561                         }\r
1562                     })\r
1563                .setPositiveButton ("Yes", new DialogInterface.OnClickListener()\r
1564                     {\r
1565                         public void onClick (DialogInterface dialog, int id)\r
1566                         {\r
1567                             dialog.dismiss();\r
1568                             AudioPerformanceTest.this.alertDismissed (callback, 1);\r
1569                         }\r
1570                     })\r
1571                .setNegativeButton ("No", new DialogInterface.OnClickListener()\r
1572                     {\r
1573                         public void onClick (DialogInterface dialog, int id)\r
1574                         {\r
1575                             dialog.dismiss();\r
1576                             AudioPerformanceTest.this.alertDismissed (callback, 2);\r
1577                         }\r
1578                     })\r
1579                .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()\r
1580                     {\r
1581                         public void onClick (DialogInterface dialog, int id)\r
1582                         {\r
1583                             dialog.dismiss();\r
1584                             AudioPerformanceTest.this.alertDismissed (callback, 0);\r
1585                         }\r
1586                     });\r
1587 \r
1588         builder.create().show();\r
1589     }\r
1590 \r
1591     public native void alertDismissed (long callback, int id);\r
1592 \r
1593     //==============================================================================\r
1594     public final class ComponentPeerView extends ViewGroup\r
1595                                          implements View.OnFocusChangeListener\r
1596     {\r
1597         public ComponentPeerView (Context context, boolean opaque_, long host)\r
1598         {\r
1599             super (context);\r
1600             this.host = host;\r
1601             setWillNotDraw (false);\r
1602             opaque = opaque_;\r
1603 \r
1604             setFocusable (true);\r
1605             setFocusableInTouchMode (true);\r
1606             setOnFocusChangeListener (this);\r
1607 \r
1608             // swap red and blue colours to match internal opengl texture format\r
1609             ColorMatrix colorMatrix = new ColorMatrix();\r
1610 \r
1611             float[] colorTransform = { 0,    0,    1.0f, 0,    0,\r
1612                                        0,    1.0f, 0,    0,    0,\r
1613                                        1.0f, 0,    0,    0,    0,\r
1614                                        0,    0,    0,    1.0f, 0 };\r
1615 \r
1616             colorMatrix.set (colorTransform);\r
1617             paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));\r
1618 \r
1619             java.lang.reflect.Method method = null;\r
1620 \r
1621             try\r
1622             {\r
1623                 method = getClass().getMethod ("setLayerType", int.class, Paint.class);\r
1624             }\r
1625             catch (SecurityException e)     {}\r
1626             catch (NoSuchMethodException e) {}\r
1627 \r
1628             if (method != null)\r
1629             {\r
1630                 try\r
1631                 {\r
1632                     int layerTypeNone = 0;\r
1633                     method.invoke (this, layerTypeNone, null);\r
1634                 }\r
1635                 catch (java.lang.IllegalArgumentException e) {}\r
1636                 catch (java.lang.IllegalAccessException e) {}\r
1637                 catch (java.lang.reflect.InvocationTargetException e) {}\r
1638             }\r
1639         }\r
1640 \r
1641         //==============================================================================\r
1642         private native void handlePaint (long host, Canvas canvas, Paint paint);\r
1643 \r
1644         @Override\r
1645         public void onDraw (Canvas canvas)\r
1646         {\r
1647             if (host == 0)\r
1648                 return;\r
1649 \r
1650             handlePaint (host, canvas, paint);\r
1651         }\r
1652 \r
1653         @Override\r
1654         public boolean isOpaque()\r
1655         {\r
1656             return opaque;\r
1657         }\r
1658 \r
1659         private boolean opaque;\r
1660         private long host;\r
1661         private Paint paint = new Paint();\r
1662 \r
1663         //==============================================================================\r
1664         private native void handleMouseDown (long host, int index, float x, float y, long time);\r
1665         private native void handleMouseDrag (long host, int index, float x, float y, long time);\r
1666         private native void handleMouseUp   (long host, int index, float x, float y, long time);\r
1667 \r
1668         @Override\r
1669         public boolean onTouchEvent (MotionEvent event)\r
1670         {\r
1671             if (host == 0)\r
1672                 return false;\r
1673 \r
1674             int action = event.getAction();\r
1675             long time = event.getEventTime();\r
1676 \r
1677             switch (action & MotionEvent.ACTION_MASK)\r
1678             {\r
1679                 case MotionEvent.ACTION_DOWN:\r
1680                     handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
1681                     return true;\r
1682 \r
1683                 case MotionEvent.ACTION_CANCEL:\r
1684                 case MotionEvent.ACTION_UP:\r
1685                     handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
1686                     return true;\r
1687 \r
1688                 case MotionEvent.ACTION_MOVE:\r
1689                 {\r
1690                     int n = event.getPointerCount();\r
1691                     for (int i = 0; i < n; ++i)\r
1692                         handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1693 \r
1694                     return true;\r
1695                 }\r
1696 \r
1697                 case MotionEvent.ACTION_POINTER_UP:\r
1698                 {\r
1699                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
1700                     handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1701                     return true;\r
1702                 }\r
1703 \r
1704                 case MotionEvent.ACTION_POINTER_DOWN:\r
1705                 {\r
1706                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
1707                     handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
1708                     return true;\r
1709                 }\r
1710 \r
1711                 default:\r
1712                     break;\r
1713             }\r
1714 \r
1715             return false;\r
1716         }\r
1717 \r
1718         //==============================================================================\r
1719         private native void handleKeyDown (long host, int keycode, int textchar);\r
1720         private native void handleKeyUp (long host, int keycode, int textchar);\r
1721         private native void handleBackButton (long host);\r
1722         private native void handleKeyboardHidden (long host);\r
1723 \r
1724         public void showKeyboard (String type)\r
1725         {\r
1726             InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);\r
1727 \r
1728             if (imm != null)\r
1729             {\r
1730                 if (type.length() > 0)\r
1731                 {\r
1732                     imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);\r
1733                     imm.setInputMethod (getWindowToken(), type);\r
1734                     keyboardDismissListener.startListening();\r
1735                 }\r
1736                 else\r
1737                 {\r
1738                     imm.hideSoftInputFromWindow (getWindowToken(), 0);\r
1739                     keyboardDismissListener.stopListening();\r
1740                 }\r
1741             }\r
1742         }\r
1743 \r
1744         public void backButtonPressed()\r
1745         {\r
1746             if (host == 0)\r
1747                 return;\r
1748 \r
1749             handleBackButton (host);\r
1750         }\r
1751 \r
1752         @Override\r
1753         public boolean onKeyDown (int keyCode, KeyEvent event)\r
1754         {\r
1755             if (host == 0)\r
1756                 return false;\r
1757 \r
1758             switch (keyCode)\r
1759             {\r
1760                 case KeyEvent.KEYCODE_VOLUME_UP:\r
1761                 case KeyEvent.KEYCODE_VOLUME_DOWN:\r
1762                     return super.onKeyDown (keyCode, event);\r
1763                 case KeyEvent.KEYCODE_BACK:\r
1764                 {\r
1765                     ((Activity) getContext()).onBackPressed();\r
1766                     return true;\r
1767                 }\r
1768 \r
1769                 default:\r
1770                     break;\r
1771             }\r
1772 \r
1773             handleKeyDown (host, keyCode, event.getUnicodeChar());\r
1774             return true;\r
1775         }\r
1776 \r
1777         @Override\r
1778         public boolean onKeyUp (int keyCode, KeyEvent event)\r
1779         {\r
1780             if (host == 0)\r
1781                 return false;\r
1782 \r
1783             handleKeyUp (host, keyCode, event.getUnicodeChar());\r
1784             return true;\r
1785         }\r
1786 \r
1787         @Override\r
1788         public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)\r
1789         {\r
1790             if (host == 0)\r
1791                 return false;\r
1792 \r
1793             if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)\r
1794                 return super.onKeyMultiple (keyCode, count, event);\r
1795 \r
1796             if (event.getCharacters() != null)\r
1797             {\r
1798                 int utf8Char = event.getCharacters().codePointAt (0);\r
1799                 handleKeyDown (host, utf8Char, utf8Char);\r
1800                 return true;\r
1801             }\r
1802 \r
1803             return false;\r
1804         }\r
1805 \r
1806         //==============================================================================\r
1807         private final class KeyboardDismissListener\r
1808         {\r
1809             public KeyboardDismissListener (ComponentPeerView viewToUse)\r
1810             {\r
1811                 view = viewToUse;\r
1812             }\r
1813 \r
1814             private void startListening()\r
1815             {\r
1816                 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);\r
1817             }\r
1818 \r
1819             private void stopListening()\r
1820             {\r
1821                 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);\r
1822             }\r
1823 \r
1824             private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener\r
1825             {\r
1826                 TreeObserver()\r
1827                 {\r
1828                     keyboardShown = false;\r
1829                 }\r
1830 \r
1831                 @Override\r
1832                 public void onGlobalLayout()\r
1833                 {\r
1834                     Rect r = new Rect();\r
1835 \r
1836                     ViewGroup parentView = (ViewGroup) getParent();\r
1837 \r
1838                     if (parentView == null)\r
1839                         return;\r
1840 \r
1841                     parentView.getWindowVisibleDisplayFrame (r);\r
1842 \r
1843                     int diff = parentView.getHeight() - (r.bottom - r.top);\r
1844 \r
1845                     // Arbitrary threshold, surely keyboard would take more than 20 pix.\r
1846                     if (diff < 20 && keyboardShown)\r
1847                     {\r
1848                         keyboardShown = false;\r
1849                         handleKeyboardHidden (view.host);\r
1850                     }\r
1851 \r
1852                     if (! keyboardShown && diff > 20)\r
1853                         keyboardShown = true;\r
1854                 };\r
1855 \r
1856                 private boolean keyboardShown;\r
1857             };\r
1858 \r
1859             private ComponentPeerView view;\r
1860             private TreeObserver viewTreeObserver = new TreeObserver();\r
1861         }\r
1862 \r
1863         private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);\r
1864 \r
1865         // this is here to make keyboard entry work on a Galaxy Tab2 10.1\r
1866         @Override\r
1867         public InputConnection onCreateInputConnection (EditorInfo outAttrs)\r
1868         {\r
1869             outAttrs.actionLabel = "";\r
1870             outAttrs.hintText = "";\r
1871             outAttrs.initialCapsMode = 0;\r
1872             outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;\r
1873             outAttrs.label = "";\r
1874             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;\r
1875             outAttrs.inputType = InputType.TYPE_NULL;\r
1876 \r
1877             return new BaseInputConnection (this, false);\r
1878         }\r
1879 \r
1880         //==============================================================================\r
1881         @Override\r
1882         protected void onSizeChanged (int w, int h, int oldw, int oldh)\r
1883         {\r
1884             if (host == 0)\r
1885                 return;\r
1886 \r
1887             super.onSizeChanged (w, h, oldw, oldh);\r
1888             viewSizeChanged (host);\r
1889         }\r
1890 \r
1891         @Override\r
1892         protected void onLayout (boolean changed, int left, int top, int right, int bottom)\r
1893         {\r
1894             for (int i = getChildCount(); --i >= 0;)\r
1895                 requestTransparentRegion (getChildAt (i));\r
1896         }\r
1897 \r
1898         private native void viewSizeChanged (long host);\r
1899 \r
1900         @Override\r
1901         public void onFocusChange (View v, boolean hasFocus)\r
1902         {\r
1903             if (host == 0)\r
1904                 return;\r
1905 \r
1906             if (v == this)\r
1907                 focusChanged (host, hasFocus);\r
1908         }\r
1909 \r
1910         private native void focusChanged (long host, boolean hasFocus);\r
1911 \r
1912         public void setViewName (String newName)    {}\r
1913 \r
1914         public void setSystemUiVisibilityCompat (int visibility)\r
1915         {\r
1916             Method systemUIVisibilityMethod = null;\r
1917             try\r
1918             {\r
1919                 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);\r
1920             }\r
1921             catch (SecurityException e)     { return; }\r
1922             catch (NoSuchMethodException e) { return; }\r
1923             if (systemUIVisibilityMethod == null) return;\r
1924 \r
1925             try\r
1926             {\r
1927                 systemUIVisibilityMethod.invoke (this, visibility);\r
1928             }\r
1929             catch (java.lang.IllegalArgumentException e) {}\r
1930             catch (java.lang.IllegalAccessException e) {}\r
1931             catch (java.lang.reflect.InvocationTargetException e) {}\r
1932         }\r
1933 \r
1934         public boolean isVisible()                  { return getVisibility() == VISIBLE; }\r
1935         public void setVisible (boolean b)          { setVisibility (b ? VISIBLE : INVISIBLE); }\r
1936 \r
1937         public boolean containsPoint (int x, int y)\r
1938         {\r
1939             return true; //xxx needs to check overlapping views\r
1940         }\r
1941 \r
1942         //==============================================================================\r
1943         private native void handleAppResumed (long host);\r
1944 \r
1945         public void appResumed()\r
1946         {\r
1947             if (host == 0)\r
1948                 return;\r
1949 \r
1950             handleAppResumed (host);\r
1951         }\r
1952     }\r
1953 \r
1954     //==============================================================================\r
1955     public static class NativeSurfaceView    extends SurfaceView\r
1956                                           implements SurfaceHolder.Callback\r
1957     {\r
1958         private long nativeContext = 0;\r
1959 \r
1960         NativeSurfaceView (Context context, long nativeContextPtr)\r
1961         {\r
1962             super (context);\r
1963             nativeContext = nativeContextPtr;\r
1964         }\r
1965 \r
1966         public Surface getNativeSurface()\r
1967         {\r
1968             Surface retval = null;\r
1969 \r
1970             SurfaceHolder holder = getHolder();\r
1971             if (holder != null)\r
1972                 retval = holder.getSurface();\r
1973 \r
1974             return retval;\r
1975         }\r
1976 \r
1977         //==============================================================================\r
1978         @Override\r
1979         public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)\r
1980         {\r
1981             surfaceChangedNative (nativeContext, holder, format, width, height);\r
1982         }\r
1983 \r
1984         @Override\r
1985         public void surfaceCreated (SurfaceHolder holder)\r
1986         {\r
1987             surfaceCreatedNative (nativeContext, holder);\r
1988         }\r
1989 \r
1990         @Override\r
1991         public void surfaceDestroyed (SurfaceHolder holder)\r
1992         {\r
1993             surfaceDestroyedNative (nativeContext, holder);\r
1994         }\r
1995 \r
1996         @Override\r
1997         protected void dispatchDraw (Canvas canvas)\r
1998         {\r
1999             super.dispatchDraw (canvas);\r
2000             dispatchDrawNative (nativeContext, canvas);\r
2001         }\r
2002 \r
2003         //==============================================================================\r
2004         @Override\r
2005         protected void onAttachedToWindow ()\r
2006         {\r
2007             super.onAttachedToWindow();\r
2008             getHolder().addCallback (this);\r
2009         }\r
2010 \r
2011         @Override\r
2012         protected void onDetachedFromWindow ()\r
2013         {\r
2014             super.onDetachedFromWindow();\r
2015             getHolder().removeCallback (this);\r
2016         }\r
2017 \r
2018         //==============================================================================\r
2019         private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);\r
2020         private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);\r
2021         private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);\r
2022         private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,\r
2023                                                   int format, int width, int height);\r
2024     }\r
2025 \r
2026     public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)\r
2027     {\r
2028         return new NativeSurfaceView (this, nativeSurfacePtr);\r
2029     }\r
2030 \r
2031     //==============================================================================\r
2032     public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)\r
2033     {\r
2034         Path p = new Path();\r
2035 \r
2036         char[] str = { glyph1, glyph2 };\r
2037         paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);\r
2038 \r
2039         RectF boundsF = new RectF();\r
2040         p.computeBounds (boundsF, true);\r
2041         matrix.mapRect (boundsF);\r
2042 \r
2043         boundsF.roundOut (bounds);\r
2044         bounds.left--;\r
2045         bounds.right++;\r
2046 \r
2047         final int w = bounds.width();\r
2048         final int h = Math.max (1, bounds.height());\r
2049 \r
2050         Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);\r
2051 \r
2052         Canvas c = new Canvas (bm);\r
2053         matrix.postTranslate (-bounds.left, -bounds.top);\r
2054         c.setMatrix (matrix);\r
2055         c.drawPath (p, paint);\r
2056 \r
2057         final int sizeNeeded = w * h;\r
2058         if (cachedRenderArray.length < sizeNeeded)\r
2059             cachedRenderArray = new int [sizeNeeded];\r
2060 \r
2061         bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);\r
2062         bm.recycle();\r
2063         return cachedRenderArray;\r
2064     }\r
2065 \r
2066     private int[] cachedRenderArray = new int [256];\r
2067 \r
2068     //==============================================================================\r
2069     public static class NativeInvocationHandler implements InvocationHandler\r
2070     {\r
2071         public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)\r
2072         {\r
2073             activity = activityToUse;\r
2074             nativeContext = nativeContextRef;\r
2075         }\r
2076 \r
2077         public void nativeContextDeleted()\r
2078         {\r
2079             nativeContext = 0;\r
2080         }\r
2081 \r
2082         @Override\r
2083         public void finalize()\r
2084         {\r
2085             activity.runOnUiThread (new Runnable()\r
2086                                     {\r
2087                                         @Override\r
2088                                         public void run()\r
2089                                         {\r
2090                                             if (nativeContext != 0)\r
2091                                                 dispatchFinalize (nativeContext);\r
2092                                         }\r
2093                                     });\r
2094         }\r
2095 \r
2096         @Override\r
2097         public Object invoke (Object proxy, Method method, Object[] args) throws Throwable\r
2098         {\r
2099             return dispatchInvoke (nativeContext, proxy, method, args);\r
2100         }\r
2101 \r
2102         //==============================================================================\r
2103         Activity activity;\r
2104         private long nativeContext = 0;\r
2105 \r
2106         private native void dispatchFinalize (long nativeContextRef);\r
2107         private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);\r
2108     }\r
2109 \r
2110     public InvocationHandler createInvocationHandler (long nativeContextRef)\r
2111     {\r
2112         return new NativeInvocationHandler (this, nativeContextRef);\r
2113     }\r
2114 \r
2115     public void invocationHandlerContextDeleted (InvocationHandler handler)\r
2116     {\r
2117         ((NativeInvocationHandler) handler).nativeContextDeleted();\r
2118     }\r
2119 \r
2120     //==============================================================================\r
2121     public static class HTTPStream\r
2122     {\r
2123         public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,\r
2124                            String headersToUse, int timeOutMsToUse,\r
2125                            int[] statusCodeToUse, StringBuffer responseHeadersToUse,\r
2126                            int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException\r
2127         {\r
2128             isPost = isPostToUse;\r
2129             postData = postDataToUse;\r
2130             headers = headersToUse;\r
2131             timeOutMs = timeOutMsToUse;\r
2132             statusCode = statusCodeToUse;\r
2133             responseHeaders = responseHeadersToUse;\r
2134             totalLength = -1;\r
2135             numRedirectsToFollow = numRedirectsToFollowToUse;\r
2136             httpRequestCmd = httpRequestCmdToUse;\r
2137 \r
2138             connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);\r
2139         }\r
2140 \r
2141         private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,\r
2142                                                           String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException\r
2143         {\r
2144             HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());\r
2145 \r
2146             try\r
2147             {\r
2148                 newConnection.setInstanceFollowRedirects (false);\r
2149                 newConnection.setConnectTimeout (timeOutMs);\r
2150                 newConnection.setReadTimeout (timeOutMs);\r
2151 \r
2152                 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.\r
2153                 // So convert headers string to an array, with an element for each line\r
2154                 String headerLines[] = headers.split("\\n");\r
2155 \r
2156                 // Set request headers\r
2157                 for (int i = 0; i < headerLines.length; ++i)\r
2158                 {\r
2159                     int pos = headerLines[i].indexOf (":");\r
2160 \r
2161                     if (pos > 0 && pos < headerLines[i].length())\r
2162                     {\r
2163                         String field = headerLines[i].substring (0, pos);\r
2164                         String value = headerLines[i].substring (pos + 1);\r
2165 \r
2166                         if (value.length() > 0)\r
2167                             newConnection.setRequestProperty (field, value);\r
2168                     }\r
2169                 }\r
2170 \r
2171                 newConnection.setRequestMethod (httpRequestCmd);\r
2172 \r
2173                 if (isPost)\r
2174                 {\r
2175                     newConnection.setDoOutput (true);\r
2176 \r
2177                     if (postData != null)\r
2178                     {\r
2179                         OutputStream out = newConnection.getOutputStream();\r
2180                         out.write(postData);\r
2181                         out.flush();\r
2182                     }\r
2183                 }\r
2184 \r
2185                 return newConnection;\r
2186             }\r
2187             catch (Throwable e)\r
2188             {\r
2189                 newConnection.disconnect();\r
2190                 throw new IOException ("Connection error");\r
2191             }\r
2192         }\r
2193 \r
2194         private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException\r
2195         {\r
2196             synchronized (createFutureLock)\r
2197             {\r
2198                 if (hasBeenCancelled.get())\r
2199                     return null;\r
2200 \r
2201                 streamFuture = executor.submit (new Callable<BufferedInputStream>()\r
2202                 {\r
2203                     @Override\r
2204                     public BufferedInputStream call() throws IOException\r
2205                     {\r
2206                         return new BufferedInputStream (isInput ? connection.getInputStream()\r
2207                                                                 : connection.getErrorStream());\r
2208                     }\r
2209                 });\r
2210             }\r
2211 \r
2212             try\r
2213             {\r
2214                 return streamFuture.get();\r
2215             }\r
2216             catch (InterruptedException e)\r
2217             {\r
2218                 return null;\r
2219             }\r
2220             catch (CancellationException e)\r
2221             {\r
2222                 return null;\r
2223             }\r
2224         }\r
2225 \r
2226         public final boolean connect()\r
2227         {\r
2228             boolean result = false;\r
2229             int numFollowedRedirects = 0;\r
2230 \r
2231             while (true)\r
2232             {\r
2233                 result = doConnect();\r
2234 \r
2235                 if (! result)\r
2236                     return false;\r
2237 \r
2238                 if (++numFollowedRedirects > numRedirectsToFollow)\r
2239                     break;\r
2240 \r
2241                 int status = statusCode[0];\r
2242 \r
2243                 if (status == 301 || status == 302 || status == 303 || status == 307)\r
2244                 {\r
2245                     // Assumes only one occurrence of "Location"\r
2246                     int pos1 = responseHeaders.indexOf ("Location:") + 10;\r
2247                     int pos2 = responseHeaders.indexOf ("\n", pos1);\r
2248 \r
2249                     if (pos2 > pos1)\r
2250                     {\r
2251                         String currentLocation = connection.getURL().toString();\r
2252                         String newLocation = responseHeaders.substring (pos1, pos2);\r
2253 \r
2254                         try\r
2255                         {\r
2256                             // Handle newLocation whether it's absolute or relative\r
2257                             URL baseUrl = new URL (currentLocation);\r
2258                             URL newUrl  = new URL (baseUrl, newLocation);\r
2259                             String transformedNewLocation = newUrl.toString();\r
2260 \r
2261                             if (transformedNewLocation != currentLocation)\r
2262                             {\r
2263                                 // Clear responseHeaders before next iteration\r
2264                                 responseHeaders.delete (0, responseHeaders.length());\r
2265 \r
2266                                 synchronized (createStreamLock)\r
2267                                 {\r
2268                                     if (hasBeenCancelled.get())\r
2269                                         return false;\r
2270 \r
2271                                     connection.disconnect();\r
2272 \r
2273                                     try\r
2274                                     {\r
2275                                         connection = createConnection (transformedNewLocation, isPost,\r
2276                                                                        postData, headers, timeOutMs,\r
2277                                                                        httpRequestCmd);\r
2278                                     }\r
2279                                     catch (Throwable e)\r
2280                                     {\r
2281                                         return false;\r
2282                                     }\r
2283                                 }\r
2284                             }\r
2285                             else\r
2286                             {\r
2287                                 break;\r
2288                             }\r
2289                         }\r
2290                         catch (Throwable e)\r
2291                         {\r
2292                             return false;\r
2293                         }\r
2294                     }\r
2295                     else\r
2296                     {\r
2297                         break;\r
2298                     }\r
2299                 }\r
2300                 else\r
2301                 {\r
2302                     break;\r
2303                 }\r
2304             }\r
2305 \r
2306             return result;\r
2307         }\r
2308 \r
2309         private final boolean doConnect()\r
2310         {\r
2311             synchronized (createStreamLock)\r
2312             {\r
2313                 if (hasBeenCancelled.get())\r
2314                     return false;\r
2315 \r
2316                 try\r
2317                 {\r
2318                     try\r
2319                     {\r
2320                         inputStream = getCancellableStream (true);\r
2321                     }\r
2322                     catch (ExecutionException e)\r
2323                     {\r
2324                         if (connection.getResponseCode() < 400)\r
2325                         {\r
2326                             statusCode[0] = connection.getResponseCode();\r
2327                             connection.disconnect();\r
2328                             return false;\r
2329                         }\r
2330                     }\r
2331                     finally\r
2332                     {\r
2333                         statusCode[0] = connection.getResponseCode();\r
2334                     }\r
2335 \r
2336                     try\r
2337                     {\r
2338                         if (statusCode[0] >= 400)\r
2339                             inputStream = getCancellableStream (false);\r
2340                         else\r
2341                             inputStream = getCancellableStream (true);\r
2342                     }\r
2343                     catch (ExecutionException e)\r
2344                     {}\r
2345 \r
2346                     for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())\r
2347                     {\r
2348                         if (entry.getKey() != null && entry.getValue() != null)\r
2349                         {\r
2350                             responseHeaders.append(entry.getKey() + ": "\r
2351                                                    + android.text.TextUtils.join(",", entry.getValue()) + "\n");\r
2352 \r
2353                             if (entry.getKey().compareTo ("Content-Length") == 0)\r
2354                                 totalLength = Integer.decode (entry.getValue().get (0));\r
2355                         }\r
2356                     }\r
2357 \r
2358                     return true;\r
2359                 }\r
2360                 catch (IOException e)\r
2361                 {\r
2362                     return false;\r
2363                 }\r
2364             }\r
2365         }\r
2366 \r
2367         static class DisconnectionRunnable implements Runnable\r
2368         {\r
2369             public DisconnectionRunnable (HttpURLConnection theConnection,\r
2370                                           InputStream theInputStream,\r
2371                                           ReentrantLock theCreateStreamLock,\r
2372                                           Object theCreateFutureLock,\r
2373                                           Future<BufferedInputStream> theStreamFuture)\r
2374             {\r
2375                 connectionToDisconnect = theConnection;\r
2376                 inputStream = theInputStream;\r
2377                 createStreamLock = theCreateStreamLock;\r
2378                 createFutureLock = theCreateFutureLock;\r
2379                 streamFuture = theStreamFuture;\r
2380             }\r
2381 \r
2382             public void run()\r
2383             {\r
2384                 try\r
2385                 {\r
2386                     if (! createStreamLock.tryLock())\r
2387                     {\r
2388                         synchronized (createFutureLock)\r
2389                         {\r
2390                             if (streamFuture != null)\r
2391                                 streamFuture.cancel (true);\r
2392                         }\r
2393 \r
2394                         createStreamLock.lock();\r
2395                     }\r
2396 \r
2397                     if (connectionToDisconnect != null)\r
2398                         connectionToDisconnect.disconnect();\r
2399 \r
2400                     if (inputStream != null)\r
2401                         inputStream.close();\r
2402                 }\r
2403                 catch (IOException e)\r
2404                 {}\r
2405                 finally\r
2406                 {\r
2407                     createStreamLock.unlock();\r
2408                 }\r
2409             }\r
2410 \r
2411             private HttpURLConnection connectionToDisconnect;\r
2412             private InputStream inputStream;\r
2413             private ReentrantLock createStreamLock;\r
2414             private Object createFutureLock;\r
2415             Future<BufferedInputStream> streamFuture;\r
2416         }\r
2417 \r
2418         public final void release()\r
2419         {\r
2420             DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,\r
2421                                                                                      inputStream,\r
2422                                                                                      createStreamLock,\r
2423                                                                                      createFutureLock,\r
2424                                                                                      streamFuture);\r
2425 \r
2426             synchronized (createStreamLock)\r
2427             {\r
2428                 hasBeenCancelled.set (true);\r
2429 \r
2430                 connection = null;\r
2431             }\r
2432 \r
2433             Thread disconnectionThread = new Thread(disconnectionRunnable);\r
2434             disconnectionThread.start();\r
2435         }\r
2436 \r
2437         public final int read (byte[] buffer, int numBytes)\r
2438         {\r
2439             int num = 0;\r
2440 \r
2441             try\r
2442             {\r
2443                 synchronized (createStreamLock)\r
2444                 {\r
2445                     if (inputStream != null)\r
2446                         num = inputStream.read (buffer, 0, numBytes);\r
2447                 }\r
2448             }\r
2449             catch (IOException e)\r
2450             {}\r
2451 \r
2452             if (num > 0)\r
2453                 position += num;\r
2454 \r
2455             return num;\r
2456         }\r
2457 \r
2458         public final long getPosition()                 { return position; }\r
2459         public final long getTotalLength()              { return totalLength; }\r
2460         public final boolean isExhausted()              { return false; }\r
2461         public final boolean setPosition (long newPos)  { return false; }\r
2462 \r
2463         private boolean isPost;\r
2464         private byte[] postData;\r
2465         private String headers;\r
2466         private int timeOutMs;\r
2467         String httpRequestCmd;\r
2468         private HttpURLConnection connection;\r
2469         private int[] statusCode;\r
2470         private StringBuffer responseHeaders;\r
2471         private int totalLength;\r
2472         private int numRedirectsToFollow;\r
2473         private InputStream inputStream;\r
2474         private long position;\r
2475         private final ReentrantLock createStreamLock = new ReentrantLock();\r
2476         private final Object createFutureLock = new Object();\r
2477         private AtomicBoolean hasBeenCancelled = new AtomicBoolean();\r
2478 \r
2479         private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());\r
2480         Future<BufferedInputStream> streamFuture;\r
2481     }\r
2482 \r
2483     public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,\r
2484                                                      String headers, int timeOutMs, int[] statusCode,\r
2485                                                      StringBuffer responseHeaders, int numRedirectsToFollow,\r
2486                                                      String httpRequestCmd)\r
2487     {\r
2488         // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)\r
2489         if (timeOutMs < 0)\r
2490             timeOutMs = 0;\r
2491         else if (timeOutMs == 0)\r
2492             timeOutMs = 30000;\r
2493 \r
2494         for (;;)\r
2495         {\r
2496             try\r
2497             {\r
2498                 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,\r
2499                                                         timeOutMs, statusCode, responseHeaders,\r
2500                                                         numRedirectsToFollow, httpRequestCmd);\r
2501 \r
2502                 return httpStream;\r
2503             }\r
2504             catch (Throwable e) {}\r
2505 \r
2506             return null;\r
2507         }\r
2508     }\r
2509 \r
2510     public final void launchURL (String url)\r
2511     {\r
2512         startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));\r
2513     }\r
2514 \r
2515     private native boolean webViewPageLoadStarted (long host, WebView view, String url);\r
2516     private native void webViewPageLoadFinished (long host, WebView view, String url);\r
2517     private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error);    private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);    private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);\r
2518     private native void webViewCloseWindowRequest (long host, WebView view);\r
2519     private native void webViewCreateWindowRequest (long host, WebView view);\r
2520 \r
2521     //==============================================================================\r
2522     public class JuceWebViewClient   extends WebViewClient\r
2523     {\r
2524         public JuceWebViewClient (long hostToUse)\r
2525         {\r
2526             host = hostToUse;\r
2527         }\r
2528 \r
2529         public void hostDeleted()\r
2530         {\r
2531             synchronized (hostLock)\r
2532             {\r
2533                 host = 0;\r
2534             }\r
2535         }\r
2536 \r
2537         @Override\r
2538         public void onPageFinished (WebView view, String url)\r
2539         {\r
2540             if (host == 0)\r
2541                 return;\r
2542 \r
2543             webViewPageLoadFinished (host, view, url);\r
2544         }\r
2545 \r
2546         @Override\r
2547         public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)\r
2548         {\r
2549             if (host == 0)\r
2550                 return;\r
2551 \r
2552             webViewReceivedSslError (host, view, handler, error);\r
2553         }\r
2554 \r
2555         @Override\r
2556         public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)\r
2557         {\r
2558             if (host == 0)\r
2559                 return;\r
2560 \r
2561             webViewReceivedError (host, view, request, error);\r
2562         }\r
2563 \r
2564         @Override\r
2565         public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)\r
2566         {\r
2567             if (host == 0)\r
2568                 return;\r
2569 \r
2570             webViewReceivedHttpError (host, view, request, errorResponse);\r
2571         }\r
2572 \r
2573         @Override\r
2574         public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)\r
2575         {\r
2576             synchronized (hostLock)\r
2577             {\r
2578                 if (host != 0)\r
2579                 {\r
2580                     boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());\r
2581 \r
2582                     if (shouldLoad)\r
2583                         return null;\r
2584                 }\r
2585             }\r
2586 \r
2587             return new WebResourceResponse ("text/html", null, null);\r
2588         }\r
2589 \r
2590         private long host;\r
2591         private final Object hostLock = new Object();\r
2592     }\r
2593 \r
2594     public class JuceWebChromeClient    extends WebChromeClient\r
2595     {\r
2596         public JuceWebChromeClient (long hostToUse)\r
2597         {\r
2598             host = hostToUse;\r
2599         }\r
2600 \r
2601         @Override\r
2602         public void onCloseWindow (WebView window)\r
2603         {\r
2604             webViewCloseWindowRequest (host, window);\r
2605         }\r
2606 \r
2607         @Override\r
2608         public boolean onCreateWindow (WebView view, boolean isDialog,\r
2609                                        boolean isUserGesture, Message resultMsg)\r
2610         {\r
2611             webViewCreateWindowRequest (host, view);\r
2612             return false;\r
2613         }\r
2614 \r
2615         private long host;\r
2616         private final Object hostLock = new Object();\r
2617     }\r
2618 \r
2619     //==============================================================================\r
2620     public static final String getLocaleValue (boolean isRegion)\r
2621     {\r
2622         java.util.Locale locale = java.util.Locale.getDefault();\r
2623 \r
2624         return isRegion ? locale.getCountry()\r
2625                         : locale.getLanguage();\r
2626     }\r
2627 \r
2628     private static final String getFileLocation (String type)\r
2629     {\r
2630         return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();\r
2631     }\r
2632 \r
2633     public static final String getDocumentsFolder()\r
2634     {\r
2635         if (getAndroidSDKVersion() >= 19)\r
2636             return getFileLocation ("Documents");\r
2637 \r
2638         return Environment.getDataDirectory().getAbsolutePath();\r
2639     }\r
2640 \r
2641     public static final String getPicturesFolder()   { return getFileLocation (Environment.DIRECTORY_PICTURES); }\r
2642     public static final String getMusicFolder()      { return getFileLocation (Environment.DIRECTORY_MUSIC); }\r
2643     public static final String getMoviesFolder()     { return getFileLocation (Environment.DIRECTORY_MOVIES); }\r
2644     public static final String getDownloadsFolder()  { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }\r
2645 \r
2646     //==============================================================================\r
2647     @Override\r
2648     protected void onActivityResult (int requestCode, int resultCode, Intent data)\r
2649     {\r
2650         appActivityResult (requestCode, resultCode, data);\r
2651     }\r
2652 \r
2653     @Override\r
2654     protected void onNewIntent (Intent intent)\r
2655     {\r
2656         super.onNewIntent(intent);\r
2657         setIntent(intent);\r
2658 \r
2659         appNewIntent (intent);\r
2660     }\r
2661 \r
2662     //==============================================================================\r
2663     public final Typeface getTypeFaceFromAsset (String assetName)\r
2664     {\r
2665         try\r
2666         {\r
2667             return Typeface.createFromAsset (this.getResources().getAssets(), assetName);\r
2668         }\r
2669         catch (Throwable e) {}\r
2670 \r
2671         return null;\r
2672     }\r
2673 \r
2674     final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();\r
2675 \r
2676     public static String bytesToHex (byte[] bytes)\r
2677     {\r
2678         char[] hexChars = new char[bytes.length * 2];\r
2679 \r
2680         for (int j = 0; j < bytes.length; ++j)\r
2681         {\r
2682             int v = bytes[j] & 0xff;\r
2683             hexChars[j * 2]     = hexArray[v >>> 4];\r
2684             hexChars[j * 2 + 1] = hexArray[v & 0x0f];\r
2685         }\r
2686 \r
2687         return new String (hexChars);\r
2688     }\r
2689 \r
2690     final private java.util.Map dataCache = new java.util.HashMap();\r
2691 \r
2692     synchronized private final File getDataCacheFile (byte[] data)\r
2693     {\r
2694         try\r
2695         {\r
2696             java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");\r
2697             digest.update (data);\r
2698 \r
2699             String key = bytesToHex (digest.digest());\r
2700 \r
2701             if (dataCache.containsKey (key))\r
2702                 return (File) dataCache.get (key);\r
2703 \r
2704             File f = new File (this.getCacheDir(), "bindata_" + key);\r
2705             f.delete();\r
2706             FileOutputStream os = new FileOutputStream (f);\r
2707             os.write (data, 0, data.length);\r
2708             dataCache.put (key, f);\r
2709             return f;\r
2710         }\r
2711         catch (Throwable e) {}\r
2712 \r
2713         return null;\r
2714     }\r
2715 \r
2716     private final void clearDataCache()\r
2717     {\r
2718         java.util.Iterator it = dataCache.values().iterator();\r
2719 \r
2720         while (it.hasNext())\r
2721         {\r
2722             File f = (File) it.next();\r
2723             f.delete();\r
2724         }\r
2725     }\r
2726 \r
2727     public final Typeface getTypeFaceFromByteArray (byte[] data)\r
2728     {\r
2729         try\r
2730         {\r
2731             File f = getDataCacheFile (data);\r
2732 \r
2733             if (f != null)\r
2734                 return Typeface.createFromFile (f);\r
2735         }\r
2736         catch (Exception e)\r
2737         {\r
2738             Log.e ("JUCE", e.toString());\r
2739         }\r
2740 \r
2741         return null;\r
2742     }\r
2743 \r
2744     public static final int getAndroidSDKVersion()\r
2745     {\r
2746         return android.os.Build.VERSION.SDK_INT;\r
2747     }\r
2748 \r
2749     public final String audioManagerGetProperty (String property)\r
2750     {\r
2751         Object obj = getSystemService (AUDIO_SERVICE);\r
2752         if (obj == null)\r
2753             return null;\r
2754 \r
2755         java.lang.reflect.Method method;\r
2756 \r
2757         try\r
2758         {\r
2759             method = obj.getClass().getMethod ("getProperty", String.class);\r
2760         }\r
2761         catch (SecurityException e)     { return null; }\r
2762         catch (NoSuchMethodException e) { return null; }\r
2763 \r
2764         if (method == null)\r
2765             return null;\r
2766 \r
2767         try\r
2768         {\r
2769             return (String) method.invoke (obj, property);\r
2770         }\r
2771         catch (java.lang.IllegalArgumentException e) {}\r
2772         catch (java.lang.IllegalAccessException e) {}\r
2773         catch (java.lang.reflect.InvocationTargetException e) {}\r
2774 \r
2775         return null;\r
2776     }\r
2777 \r
2778     public final boolean hasSystemFeature (String property)\r
2779     {\r
2780         return getPackageManager().hasSystemFeature (property);\r
2781     }\r
2782 }\r