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