2 ==============================================================================
\r
4 This file is part of the JUCE library.
\r
5 Copyright (c) 2017 - ROLI Ltd.
\r
7 JUCE is an open source library subject to commercial or open-source
\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
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
20 ==============================================================================
\r
23 package com.juce.audioperformancetest;
\r
25 import android.app.Activity;
\r
26 import android.app.AlertDialog;
\r
27 import android.content.DialogInterface;
\r
28 import android.content.Context;
\r
29 import android.content.Intent;
\r
30 import android.content.res.Configuration;
\r
31 import android.content.pm.PackageInfo;
\r
32 import android.content.pm.PackageManager;
\r
33 import android.net.http.SslError;
\r
34 import android.net.Uri;
\r
35 import android.os.Bundle;
\r
36 import android.os.Looper;
\r
37 import android.os.Handler;
\r
38 import android.os.Message;
\r
39 import android.os.ParcelUuid;
\r
40 import android.os.Environment;
\r
41 import android.view.*;
\r
42 import android.view.inputmethod.BaseInputConnection;
\r
43 import android.view.inputmethod.EditorInfo;
\r
44 import android.view.inputmethod.InputConnection;
\r
45 import android.view.inputmethod.InputMethodManager;
\r
46 import android.graphics.*;
\r
47 import android.text.ClipboardManager;
\r
48 import android.text.InputType;
\r
49 import android.util.DisplayMetrics;
\r
50 import android.util.Log;
\r
51 import android.util.Pair;
\r
52 import android.webkit.SslErrorHandler;
\r
53 import android.webkit.WebChromeClient;
\r
54 import android.webkit.WebResourceError;
\r
55 import android.webkit.WebResourceRequest;
\r
56 import android.webkit.WebResourceResponse;
\r
57 import android.webkit.WebView;
\r
58 import android.webkit.WebViewClient;
\r
59 import java.lang.Runnable;
\r
60 import java.lang.ref.WeakReference;
\r
61 import java.lang.reflect.*;
\r
64 import java.net.URL;
\r
65 import java.net.HttpURLConnection;
\r
66 import android.media.AudioManager;
\r
67 import android.Manifest;
\r
68 import java.util.concurrent.CancellationException;
\r
69 import java.util.concurrent.Future;
\r
70 import java.util.concurrent.Executors;
\r
71 import java.util.concurrent.ExecutorService;
\r
72 import java.util.concurrent.ExecutionException;
\r
73 import java.util.concurrent.TimeUnit;
\r
74 import java.util.concurrent.Callable;
\r
75 import java.util.concurrent.TimeoutException;
\r
76 import java.util.concurrent.locks.ReentrantLock;
\r
77 import java.util.concurrent.atomic.*;
\r
79 import android.media.midi.*;
\r
80 import android.bluetooth.*;
\r
81 import android.bluetooth.le.*;
\r
84 //==============================================================================
\r
85 public class AudioPerformanceTest extends Activity
\r
87 //==============================================================================
\r
90 System.loadLibrary ("juce_jni");
\r
93 //==============================================================================
\r
94 public boolean isPermissionDeclaredInManifest (int permissionID)
\r
96 String permissionToCheck = getAndroidPermissionName(permissionID);
\r
100 PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
\r
102 if (info.requestedPermissions != null)
\r
103 for (String permission : info.requestedPermissions)
\r
104 if (permission.equals (permissionToCheck))
\r
107 catch (PackageManager.NameNotFoundException e)
\r
109 Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
\r
112 Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
\r
116 //==============================================================================
\r
117 // these have to match the values of enum PermissionID in C++ class RuntimePermissions:
\r
118 private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
\r
119 private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
\r
120 private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
\r
121 private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
\r
123 private static String getAndroidPermissionName (int permissionID)
\r
125 switch (permissionID)
\r
127 case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
\r
128 case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
\r
129 // use string value as this is not defined in SDKs < 16
\r
130 case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
\r
131 case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
\r
134 // unknown permission ID!
\r
136 return new String();
\r
139 public boolean isPermissionGranted (int permissionID)
\r
141 return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
\r
144 private Map<Integer, Long> permissionCallbackPtrMap;
\r
146 public void requestRuntimePermission (int permissionID, long ptrToCallback)
\r
148 String permissionName = getAndroidPermissionName (permissionID);
\r
150 if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)
\r
152 // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
\r
153 permissionCallbackPtrMap.put (permissionID, ptrToCallback);
\r
154 requestPermissionsCompat (new String[]{permissionName}, permissionID);
\r
158 // permissions were already granted before, we can call callback directly
\r
159 androidRuntimePermissionsCallback (true, ptrToCallback);
\r
163 private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
\r
166 public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
\r
168 boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);
\r
170 if (! permissionsGranted)
\r
171 Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));
\r
173 Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);
\r
174 permissionCallbackPtrMap.remove (permissionID);
\r
175 androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);
\r
178 //==============================================================================
\r
179 public interface JuceMidiPort
\r
181 boolean isInputPort();
\r
183 // start, stop does nothing on an output port
\r
189 // send will do nothing on an input port
\r
190 void sendMidi (byte[] msg, int offset, int count);
\r
193 //==============================================================================
\r
194 //==============================================================================
\r
195 public class BluetoothManager extends ScanCallback
\r
201 public String[] getMidiBluetoothAddresses()
\r
203 return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
\r
206 public String getHumanReadableStringForBluetoothAddress (String address)
\r
208 BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
\r
209 return btDevice.getName();
\r
212 public int getBluetoothDeviceStatus (String address)
\r
214 return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);
\r
217 public void startStopScan (boolean shouldStart)
\r
219 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
\r
221 if (bluetoothAdapter == null)
\r
223 Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
\r
227 BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
\r
229 if (bluetoothLeScanner == null)
\r
231 Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
\r
237 ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
\r
238 scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
\r
240 ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
\r
241 scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
\r
242 .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
\r
243 .setScanMode (ScanSettings.MATCH_MODE_STICKY);
\r
245 bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
\r
246 scanSettingsBuilder.build(),
\r
251 bluetoothLeScanner.stopScan (this);
\r
255 public boolean pairBluetoothMidiDevice(String address)
\r
257 BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
\r
259 if (btDevice == null)
\r
261 Log.d ("JUCE", "failed to create buletooth device from address");
\r
265 return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);
\r
268 public void unpairBluetoothMidiDevice (String address)
\r
270 getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
\r
273 public void onScanFailed (int errorCode)
\r
277 public void onScanResult (int callbackType, ScanResult result)
\r
279 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
\r
280 || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
\r
282 BluetoothDevice device = result.getDevice();
\r
284 if (device != null)
\r
285 bluetoothMidiDevices.add (device.getAddress());
\r
288 if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
\r
290 Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
\r
291 BluetoothDevice device = result.getDevice();
\r
293 if (device != null)
\r
295 bluetoothMidiDevices.remove (device.getAddress());
\r
296 unpairBluetoothMidiDevice (device.getAddress());
\r
301 public void onBatchScanResults (List<ScanResult> results)
\r
303 for (ScanResult result : results)
\r
304 onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
\r
307 private BluetoothLeScanner scanner;
\r
308 private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
\r
310 private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
\r
313 public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
\r
315 private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
\r
317 public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)
\r
320 androidPort = actualPort;
\r
321 portPath = portPathToUse;
\r
322 juceHost = hostToUse;
\r
323 isConnected = false;
\r
327 protected void finalize() throws Throwable
\r
334 public boolean isInputPort()
\r
340 public void start()
\r
342 if (owner != null && androidPort != null && ! isConnected) {
\r
343 androidPort.connect(this);
\r
344 isConnected = true;
\r
351 if (owner != null && androidPort != null && isConnected) {
\r
352 androidPort.disconnect(this);
\r
353 isConnected = false;
\r
358 public void close()
\r
360 if (androidPort != null) {
\r
362 androidPort.close();
\r
363 } catch (IOException exception) {
\r
364 Log.d("JUCE", "IO Exception while closing port");
\r
369 owner.removePort (portPath);
\r
372 androidPort = null;
\r
376 public void onSend (byte[] msg, int offset, int count, long timestamp)
\r
379 handleReceive (juceHost, msg, offset, count, timestamp);
\r
383 public void onFlush()
\r
387 public void sendMidi (byte[] msg, int offset, int count)
\r
391 MidiDeviceManager owner;
\r
392 MidiOutputPort androidPort;
\r
393 MidiPortPath portPath;
\r
395 boolean isConnected;
\r
398 public static class JuceMidiOutputPort implements JuceMidiPort
\r
400 public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)
\r
403 androidPort = actualPort;
\r
404 portPath = portPathToUse;
\r
408 protected void finalize() throws Throwable
\r
415 public boolean isInputPort()
\r
421 public void start()
\r
431 public void sendMidi (byte[] msg, int offset, int count)
\r
433 if (androidPort != null)
\r
436 androidPort.send(msg, offset, count);
\r
437 } catch (IOException exception)
\r
439 Log.d ("JUCE", "send midi had IO exception");
\r
445 public void close()
\r
447 if (androidPort != null) {
\r
449 androidPort.close();
\r
450 } catch (IOException exception) {
\r
451 Log.d("JUCE", "IO Exception while closing port");
\r
456 owner.removePort (portPath);
\r
459 androidPort = null;
\r
462 MidiDeviceManager owner;
\r
463 MidiInputPort androidPort;
\r
464 MidiPortPath portPath;
\r
467 private static class MidiPortPath extends Object
\r
469 public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)
\r
471 deviceId = deviceIdToUse;
\r
472 isInput = direction;
\r
473 portIndex = androidIndex;
\r
477 public int deviceId;
\r
478 public int portIndex;
\r
479 public boolean isInput;
\r
482 public int hashCode()
\r
484 Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));
\r
485 return i.hashCode() * (isInput ? -1 : 1);
\r
489 public boolean equals (Object obj)
\r
494 if (getClass() != obj.getClass())
\r
497 MidiPortPath other = (MidiPortPath) obj;
\r
498 return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);
\r
502 //==============================================================================
\r
503 public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener
\r
505 //==============================================================================
\r
506 private class DummyBluetoothGattCallback extends BluetoothGattCallback
\r
508 public DummyBluetoothGattCallback (MidiDeviceManager mm)
\r
514 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
\r
516 if (newState == BluetoothProfile.STATE_CONNECTED)
\r
518 gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
\r
519 owner.pairBluetoothDeviceStepTwo (gatt.getDevice());
\r
522 public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
\r
523 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
\r
524 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
\r
525 public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}
\r
526 public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
\r
527 public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
\r
528 public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}
\r
529 public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}
\r
530 public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
\r
532 private MidiDeviceManager owner;
\r
535 //==============================================================================
\r
536 private class MidiDeviceOpenTask extends java.util.TimerTask
\r
538 public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)
\r
540 owner = deviceManager;
\r
541 midiDevice = device;
\r
542 btGatt = gattToUse;
\r
546 public boolean cancel()
\r
548 synchronized (MidiDeviceOpenTask.class)
\r
551 boolean retval = super.cancel();
\r
553 if (btGatt != null)
\r
555 btGatt.disconnect();
\r
561 if (midiDevice != null)
\r
565 midiDevice.close();
\r
567 catch (IOException e)
\r
577 public String getBluetoothAddress()
\r
579 synchronized (MidiDeviceOpenTask.class)
\r
581 if (midiDevice != null)
\r
583 MidiDeviceInfo info = midiDevice.getInfo();
\r
584 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
586 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
587 if (btDevice != null)
\r
588 return btDevice.getAddress();
\r
596 public BluetoothGatt getGatt() { return btGatt; }
\r
600 return midiDevice.getInfo().getId();
\r
606 synchronized (MidiDeviceOpenTask.class)
\r
608 if (owner != null && midiDevice != null)
\r
609 owner.onDeviceOpenedDelayed (midiDevice);
\r
613 private MidiDeviceManager owner;
\r
614 private MidiDevice midiDevice;
\r
615 private BluetoothGatt btGatt;
\r
618 //==============================================================================
\r
619 public MidiDeviceManager()
\r
621 manager = (MidiManager) getSystemService (MIDI_SERVICE);
\r
623 if (manager == null)
\r
625 Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
\r
629 openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();
\r
630 midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();
\r
631 openTasks = new HashMap<Integer, MidiDeviceOpenTask>();
\r
632 btDevicesPairing = new HashMap<String, BluetoothGatt>();
\r
634 MidiDeviceInfo[] foundDevices = manager.getDevices();
\r
635 for (MidiDeviceInfo info : foundDevices)
\r
636 onDeviceAdded (info);
\r
638 manager.registerDeviceCallback (this, null);
\r
641 protected void finalize() throws Throwable
\r
643 manager.unregisterDeviceCallback (this);
\r
645 synchronized (MidiDeviceManager.class)
\r
647 btDevicesPairing.clear();
\r
649 for (Integer deviceID : openTasks.keySet())
\r
650 openTasks.get (deviceID).cancel();
\r
655 for (MidiPortPath key : openPorts.keySet())
\r
656 openPorts.get (key).get().close();
\r
660 for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)
\r
662 if (device.second != null)
\r
664 device.second.disconnect();
\r
665 device.second.close();
\r
668 device.first.close();
\r
671 midiDevices.clear();
\r
676 public String[] getJuceAndroidMidiInputDevices()
\r
678 return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
\r
681 public String[] getJuceAndroidMidiOutputDevices()
\r
683 return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
\r
686 private String[] getJuceAndroidMidiDevices (int portType)
\r
688 // only update the list when JUCE asks for a new list
\r
689 synchronized (MidiDeviceManager.class)
\r
691 deviceInfos = getDeviceInfos();
\r
694 ArrayList<String> portNames = new ArrayList<String>();
\r
697 for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))
\r
698 portNames.add (getPortName (portInfo));
\r
700 String[] names = new String[portNames.size()];
\r
701 return portNames.toArray (names);
\r
704 private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)
\r
706 synchronized (MidiDeviceManager.class)
\r
708 int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);
\r
709 MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);
\r
711 if (portInfo != null)
\r
713 // ports must be opened exclusively!
\r
714 if (openPorts.containsKey (portInfo))
\r
717 Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);
\r
719 if (devicePair != null)
\r
721 MidiDevice device = devicePair.first;
\r
722 if (device != null)
\r
724 JuceMidiPort juceMidiPort = null;
\r
728 MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);
\r
730 if (outputPort != null)
\r
731 juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);
\r
735 MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);
\r
737 if (inputPort != null)
\r
738 juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);
\r
741 if (juceMidiPort != null)
\r
743 openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));
\r
745 return juceMidiPort;
\r
755 public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
\r
757 return openMidiPortWithJuceIndex (index, host, true);
\r
760 public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
\r
762 return openMidiPortWithJuceIndex (index, 0, false);
\r
765 /* 0: unpaired, 1: paired, 2: pairing */
\r
766 public int getBluetoothDeviceStatus (String address)
\r
768 synchronized (MidiDeviceManager.class)
\r
770 if (! address.isEmpty())
\r
772 if (findMidiDeviceForBluetoothAddress (address) != null)
\r
775 if (btDevicesPairing.containsKey (address))
\r
778 if (findOpenTaskForBluetoothAddress (address) != null)
\r
786 public boolean pairBluetoothDevice (BluetoothDevice btDevice)
\r
788 String btAddress = btDevice.getAddress();
\r
789 if (btAddress.isEmpty())
\r
792 synchronized (MidiDeviceManager.class)
\r
794 if (getBluetoothDeviceStatus (btAddress) != 0)
\r
798 btDevicesPairing.put (btDevice.getAddress(), null);
\r
799 BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));
\r
803 btDevicesPairing.put (btDevice.getAddress(), gatt);
\r
807 pairBluetoothDeviceStepTwo (btDevice);
\r
814 public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)
\r
816 manager.openBluetoothDevice(btDevice, this, null);
\r
819 public void unpairBluetoothDevice (String address)
\r
821 if (address.isEmpty())
\r
824 synchronized (MidiDeviceManager.class)
\r
826 if (btDevicesPairing.containsKey (address))
\r
828 BluetoothGatt gatt = btDevicesPairing.get (address);
\r
835 btDevicesPairing.remove (address);
\r
838 MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);
\r
839 if (openTask != null)
\r
841 int deviceID = openTask.getID();
\r
843 openTasks.remove (deviceID);
\r
846 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);
\r
847 if (midiDevicePair != null)
\r
849 MidiDevice midiDevice = midiDevicePair.first;
\r
850 onDeviceRemoved (midiDevice.getInfo());
\r
853 midiDevice.close();
\r
855 catch (IOException exception)
\r
857 Log.d ("JUCE", "IOException while closing midi device");
\r
863 private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)
\r
865 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
867 MidiDeviceInfo info = midiDevice.first.getInfo();
\r
868 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
870 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
871 if (btDevice != null && btDevice.getAddress().equals (address))
\r
879 private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)
\r
881 for (Integer deviceID : openTasks.keySet())
\r
883 MidiDeviceOpenTask openTask = openTasks.get (deviceID);
\r
884 if (openTask.getBluetoothAddress().equals (address))
\r
891 public void removePort (MidiPortPath path)
\r
893 openPorts.remove (path);
\r
896 public String getInputPortNameForJuceIndex (int index)
\r
898 MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);
\r
899 if (portInfo != null)
\r
900 return getPortName (portInfo);
\r
905 public String getOutputPortNameForJuceIndex (int index)
\r
907 MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);
\r
908 if (portInfo != null)
\r
909 return getPortName (portInfo);
\r
914 public void onDeviceAdded (MidiDeviceInfo info)
\r
916 // only add standard midi devices
\r
917 if (info.getType() == info.TYPE_BLUETOOTH)
\r
920 manager.openDevice (info, this, null);
\r
923 public void onDeviceRemoved (MidiDeviceInfo info)
\r
925 synchronized (MidiDeviceManager.class)
\r
927 Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());
\r
929 if (devicePair != null)
\r
931 MidiDevice midiDevice = devicePair.first;
\r
932 BluetoothGatt gatt = devicePair.second;
\r
934 // close all ports that use this device
\r
935 boolean removedPort = true;
\r
937 while (removedPort == true)
\r
939 removedPort = false;
\r
940 for (MidiPortPath key : openPorts.keySet())
\r
942 if (key.deviceId == info.getId())
\r
944 openPorts.get(key).get().close();
\r
945 removedPort = true;
\r
957 midiDevices.remove (devicePair);
\r
962 public void onDeviceStatusChanged (MidiDeviceStatus status)
\r
967 public void onDeviceOpened (MidiDevice theDevice)
\r
969 synchronized (MidiDeviceManager.class)
\r
971 MidiDeviceInfo info = theDevice.getInfo();
\r
972 int deviceID = info.getId();
\r
973 BluetoothGatt gatt = null;
\r
974 boolean isBluetooth = false;
\r
976 if (! openTasks.containsKey (deviceID))
\r
978 if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
\r
980 isBluetooth = true;
\r
981 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
982 if (btDevice != null)
\r
984 String btAddress = btDevice.getAddress();
\r
985 if (btDevicesPairing.containsKey (btAddress))
\r
987 gatt = btDevicesPairing.get (btAddress);
\r
988 btDevicesPairing.remove (btAddress);
\r
992 // unpair was called in the mean time
\r
995 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
\r
996 if (midiDevicePair != null)
\r
998 gatt = midiDevicePair.second;
\r
1002 gatt.disconnect();
\r
1007 theDevice.close();
\r
1009 catch (IOException e)
\r
1017 MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);
\r
1018 openTasks.put (deviceID, openTask);
\r
1020 new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));
\r
1025 public void onDeviceOpenedDelayed (MidiDevice theDevice)
\r
1027 synchronized (MidiDeviceManager.class)
\r
1029 int deviceID = theDevice.getInfo().getId();
\r
1031 if (openTasks.containsKey (deviceID))
\r
1033 if (! midiDevices.contains(theDevice))
\r
1035 BluetoothGatt gatt = openTasks.get (deviceID).getGatt();
\r
1036 openTasks.remove (deviceID);
\r
1037 midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));
\r
1042 // unpair was called in the mean time
\r
1043 MidiDeviceInfo info = theDevice.getInfo();
\r
1044 BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
\r
1045 if (btDevice != null)
\r
1047 String btAddress = btDevice.getAddress();
\r
1048 Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
\r
1049 if (midiDevicePair != null)
\r
1051 BluetoothGatt gatt = midiDevicePair.second;
\r
1055 gatt.disconnect();
\r
1063 theDevice.close();
\r
1065 catch (IOException e)
\r
1071 public String getPortName(MidiPortPath path)
\r
1073 int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
\r
1075 synchronized (MidiDeviceManager.class)
\r
1077 for (MidiDeviceInfo info : deviceInfos)
\r
1079 int localIndex = 0;
\r
1080 if (info.getId() == path.deviceId)
\r
1082 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
\r
1084 int portType = portInfo.getType();
\r
1085 if (portType == portTypeToFind)
\r
1087 int portIndex = portInfo.getPortNumber();
\r
1088 if (portIndex == path.portIndex)
\r
1090 String portName = portInfo.getName();
\r
1091 if (portName.isEmpty())
\r
1092 portName = (String) info.getProperties().get(info.PROPERTY_NAME);
\r
1105 public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
\r
1108 for (MidiDeviceInfo info : deviceInfos)
\r
1110 for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
\r
1112 if (portInfo.getType() == portType)
\r
1114 if (portIdx == juceIndex)
\r
1115 return new MidiPortPath (info.getId(),
\r
1116 (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
\r
1117 portInfo.getPortNumber());
\r
1127 private MidiDeviceInfo[] getDeviceInfos()
\r
1129 synchronized (MidiDeviceManager.class)
\r
1131 MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];
\r
1134 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
1135 infos[idx++] = midiDevice.first.getInfo();
\r
1141 private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)
\r
1143 synchronized (MidiDeviceManager.class)
\r
1145 for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
\r
1146 if (midiDevice.first.getInfo().getId() == deviceId)
\r
1147 return midiDevice;
\r
1153 private MidiManager manager;
\r
1154 private HashMap<String, BluetoothGatt> btDevicesPairing;
\r
1155 private HashMap<Integer, MidiDeviceOpenTask> openTasks;
\r
1156 private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;
\r
1157 private MidiDeviceInfo[] deviceInfos;
\r
1158 private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;
\r
1161 public MidiDeviceManager getAndroidMidiDeviceManager()
\r
1163 if (getSystemService (MIDI_SERVICE) == null)
\r
1166 synchronized (AudioPerformanceTest.class)
\r
1168 if (midiDeviceManager == null)
\r
1169 midiDeviceManager = new MidiDeviceManager();
\r
1172 return midiDeviceManager;
\r
1175 public BluetoothManager getAndroidBluetoothManager()
\r
1177 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
\r
1179 if (adapter == null)
\r
1182 if (adapter.getBluetoothLeScanner() == null)
\r
1185 synchronized (AudioPerformanceTest.class)
\r
1187 if (bluetoothManager == null)
\r
1188 bluetoothManager = new BluetoothManager();
\r
1191 return bluetoothManager;
\r
1194 //==============================================================================
\r
1196 public void onCreate (Bundle savedInstanceState)
\r
1198 super.onCreate (savedInstanceState);
\r
1200 isScreenSaverEnabled = true;
\r
1202 viewHolder = new ViewHolder (this);
\r
1203 setContentView (viewHolder);
\r
1205 setVolumeControlStream (AudioManager.STREAM_MUSIC);
\r
1207 permissionCallbackPtrMap = new HashMap<Integer, Long>();
\r
1211 protected void onDestroy()
\r
1214 super.onDestroy();
\r
1220 protected void onPause()
\r
1226 Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
\r
1227 // openGL glitches when pausing/resuming apps..
\r
1228 } catch (InterruptedException e) {}
\r
1234 protected void onResume()
\r
1239 // Ensure that navigation/status bar visibility is correctly restored.
\r
1240 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
1242 if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
\r
1243 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
\r
1248 public void onConfigurationChanged (Configuration cfg)
\r
1250 super.onConfigurationChanged (cfg);
\r
1251 setContentView (viewHolder);
\r
1254 private void callAppLauncher()
\r
1256 launchApp (getApplicationInfo().publicSourceDir,
\r
1257 getApplicationInfo().dataDir);
\r
1260 // Need to override this as the default implementation always finishes the activity.
\r
1262 public void onBackPressed()
\r
1264 ComponentPeerView focusedView = getViewWithFocusOrDefaultView();
\r
1266 if (focusedView == null)
\r
1269 focusedView.backButtonPressed();
\r
1272 private ComponentPeerView getViewWithFocusOrDefaultView()
\r
1274 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
1276 if (viewHolder.getChildAt (i).hasFocus())
\r
1277 return (ComponentPeerView) viewHolder.getChildAt (i);
\r
1280 if (viewHolder.getChildCount() > 0)
\r
1281 return (ComponentPeerView) viewHolder.getChildAt (0);
\r
1286 //==============================================================================
\r
1287 private void hideActionBar()
\r
1289 // get "getActionBar" method
\r
1290 java.lang.reflect.Method getActionBarMethod = null;
\r
1293 getActionBarMethod = this.getClass().getMethod ("getActionBar");
\r
1295 catch (SecurityException e) { return; }
\r
1296 catch (NoSuchMethodException e) { return; }
\r
1297 if (getActionBarMethod == null) return;
\r
1299 // invoke "getActionBar" method
\r
1300 Object actionBar = null;
\r
1303 actionBar = getActionBarMethod.invoke (this);
\r
1305 catch (java.lang.IllegalArgumentException e) { return; }
\r
1306 catch (java.lang.IllegalAccessException e) { return; }
\r
1307 catch (java.lang.reflect.InvocationTargetException e) { return; }
\r
1308 if (actionBar == null) return;
\r
1310 // get "hide" method
\r
1311 java.lang.reflect.Method actionBarHideMethod = null;
\r
1314 actionBarHideMethod = actionBar.getClass().getMethod ("hide");
\r
1316 catch (SecurityException e) { return; }
\r
1317 catch (NoSuchMethodException e) { return; }
\r
1318 if (actionBarHideMethod == null) return;
\r
1320 // invoke "hide" method
\r
1323 actionBarHideMethod.invoke (actionBar);
\r
1325 catch (java.lang.IllegalArgumentException e) {}
\r
1326 catch (java.lang.IllegalAccessException e) {}
\r
1327 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1330 void requestPermissionsCompat (String[] permissions, int requestCode)
\r
1332 Method requestPermissionsMethod = null;
\r
1335 requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",
\r
1336 String[].class, int.class);
\r
1338 catch (SecurityException e) { return; }
\r
1339 catch (NoSuchMethodException e) { return; }
\r
1340 if (requestPermissionsMethod == null) return;
\r
1344 requestPermissionsMethod.invoke (this, permissions, requestCode);
\r
1346 catch (java.lang.IllegalArgumentException e) {}
\r
1347 catch (java.lang.IllegalAccessException e) {}
\r
1348 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1351 //==============================================================================
\r
1352 private native void launchApp (String appFile, String appDataDir);
\r
1353 private native void quitApp();
\r
1354 private native void suspendApp();
\r
1355 private native void resumeApp();
\r
1356 private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
\r
1357 private native void appActivityResult (int requestCode, int resultCode, Intent data);
\r
1358 private native void appNewIntent (Intent intent);
\r
1360 //==============================================================================
\r
1361 private ViewHolder viewHolder;
\r
1362 private MidiDeviceManager midiDeviceManager = null;
\r
1363 private BluetoothManager bluetoothManager = null;
\r
1364 private boolean isScreenSaverEnabled;
\r
1365 private java.util.Timer keepAliveTimer;
\r
1367 public final ComponentPeerView createNewView (boolean opaque, long host)
\r
1369 ComponentPeerView v = new ComponentPeerView (this, opaque, host);
\r
1370 viewHolder.addView (v);
\r
1374 public final void deleteView (ComponentPeerView view)
\r
1378 ViewGroup group = (ViewGroup) (view.getParent());
\r
1380 if (group != null)
\r
1381 group.removeView (view);
\r
1384 public final void deleteNativeSurfaceView (NativeSurfaceView view)
\r
1386 ViewGroup group = (ViewGroup) (view.getParent());
\r
1388 if (group != null)
\r
1389 group.removeView (view);
\r
1392 final class ViewHolder extends ViewGroup
\r
1394 public ViewHolder (Context context)
\r
1397 setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
\r
1398 setFocusable (false);
\r
1401 protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1403 setScreenSize (getWidth(), getHeight(), getDPI());
\r
1405 if (isFirstResize)
\r
1407 isFirstResize = false;
\r
1408 callAppLauncher();
\r
1412 private final int getDPI()
\r
1414 DisplayMetrics metrics = new DisplayMetrics();
\r
1415 getWindowManager().getDefaultDisplay().getMetrics (metrics);
\r
1416 return metrics.densityDpi;
\r
1419 private boolean isFirstResize = true;
\r
1422 public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
\r
1424 canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
\r
1427 //==============================================================================
\r
1428 public final void setScreenSaver (boolean enabled)
\r
1430 if (isScreenSaverEnabled != enabled)
\r
1432 isScreenSaverEnabled = enabled;
\r
1434 if (keepAliveTimer != null)
\r
1436 keepAliveTimer.cancel();
\r
1437 keepAliveTimer = null;
\r
1442 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1446 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1448 // If no user input is received after about 3 seconds, the OS will lower the
\r
1449 // task's priority, so this timer forces it to be kept active.
\r
1450 keepAliveTimer = new java.util.Timer();
\r
1452 keepAliveTimer.scheduleAtFixedRate (new TimerTask()
\r
1457 android.app.Instrumentation instrumentation = new android.app.Instrumentation();
\r
1461 instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
\r
1463 catch (Exception e)
\r
1472 public final boolean getScreenSaver()
\r
1474 return isScreenSaverEnabled;
\r
1477 //==============================================================================
\r
1478 public final String getClipboardContent()
\r
1480 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1481 return clipboard.getText().toString();
\r
1484 public final void setClipboardContent (String newText)
\r
1486 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1487 clipboard.setText (newText);
\r
1490 //==============================================================================
\r
1491 public final void showMessageBox (String title, String message, final long callback)
\r
1493 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1494 builder.setTitle (title)
\r
1495 .setMessage (message)
\r
1496 .setCancelable (true)
\r
1497 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1499 public void onCancel (DialogInterface dialog)
\r
1501 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1504 .setPositiveButton ("OK", new DialogInterface.OnClickListener()
\r
1506 public void onClick (DialogInterface dialog, int id)
\r
1509 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1513 builder.create().show();
\r
1516 public final void showOkCancelBox (String title, String message, final long callback,
\r
1517 String okButtonText, String cancelButtonText)
\r
1519 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1520 builder.setTitle (title)
\r
1521 .setMessage (message)
\r
1522 .setCancelable (true)
\r
1523 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1525 public void onCancel (DialogInterface dialog)
\r
1527 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1530 .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()
\r
1532 public void onClick (DialogInterface dialog, int id)
\r
1535 AudioPerformanceTest.this.alertDismissed (callback, 1);
\r
1538 .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()
\r
1540 public void onClick (DialogInterface dialog, int id)
\r
1543 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1547 builder.create().show();
\r
1550 public final void showYesNoCancelBox (String title, String message, final long callback)
\r
1552 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1553 builder.setTitle (title)
\r
1554 .setMessage (message)
\r
1555 .setCancelable (true)
\r
1556 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1558 public void onCancel (DialogInterface dialog)
\r
1560 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1563 .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
\r
1565 public void onClick (DialogInterface dialog, int id)
\r
1568 AudioPerformanceTest.this.alertDismissed (callback, 1);
\r
1571 .setNegativeButton ("No", new DialogInterface.OnClickListener()
\r
1573 public void onClick (DialogInterface dialog, int id)
\r
1576 AudioPerformanceTest.this.alertDismissed (callback, 2);
\r
1579 .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
\r
1581 public void onClick (DialogInterface dialog, int id)
\r
1584 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1588 builder.create().show();
\r
1591 public native void alertDismissed (long callback, int id);
\r
1593 //==============================================================================
\r
1594 public final class ComponentPeerView extends ViewGroup
\r
1595 implements View.OnFocusChangeListener
\r
1597 public ComponentPeerView (Context context, boolean opaque_, long host)
\r
1601 setWillNotDraw (false);
\r
1604 setFocusable (true);
\r
1605 setFocusableInTouchMode (true);
\r
1606 setOnFocusChangeListener (this);
\r
1608 // swap red and blue colours to match internal opengl texture format
\r
1609 ColorMatrix colorMatrix = new ColorMatrix();
\r
1611 float[] colorTransform = { 0, 0, 1.0f, 0, 0,
\r
1614 0, 0, 0, 1.0f, 0 };
\r
1616 colorMatrix.set (colorTransform);
\r
1617 paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
\r
1619 java.lang.reflect.Method method = null;
\r
1623 method = getClass().getMethod ("setLayerType", int.class, Paint.class);
\r
1625 catch (SecurityException e) {}
\r
1626 catch (NoSuchMethodException e) {}
\r
1628 if (method != null)
\r
1632 int layerTypeNone = 0;
\r
1633 method.invoke (this, layerTypeNone, null);
\r
1635 catch (java.lang.IllegalArgumentException e) {}
\r
1636 catch (java.lang.IllegalAccessException e) {}
\r
1637 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1641 //==============================================================================
\r
1642 private native void handlePaint (long host, Canvas canvas, Paint paint);
\r
1645 public void onDraw (Canvas canvas)
\r
1650 handlePaint (host, canvas, paint);
\r
1654 public boolean isOpaque()
\r
1659 private boolean opaque;
\r
1660 private long host;
\r
1661 private Paint paint = new Paint();
\r
1663 //==============================================================================
\r
1664 private native void handleMouseDown (long host, int index, float x, float y, long time);
\r
1665 private native void handleMouseDrag (long host, int index, float x, float y, long time);
\r
1666 private native void handleMouseUp (long host, int index, float x, float y, long time);
\r
1669 public boolean onTouchEvent (MotionEvent event)
\r
1674 int action = event.getAction();
\r
1675 long time = event.getEventTime();
\r
1677 switch (action & MotionEvent.ACTION_MASK)
\r
1679 case MotionEvent.ACTION_DOWN:
\r
1680 handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1683 case MotionEvent.ACTION_CANCEL:
\r
1684 case MotionEvent.ACTION_UP:
\r
1685 handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1688 case MotionEvent.ACTION_MOVE:
\r
1690 int n = event.getPointerCount();
\r
1691 for (int i = 0; i < n; ++i)
\r
1692 handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1697 case MotionEvent.ACTION_POINTER_UP:
\r
1699 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1700 handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1704 case MotionEvent.ACTION_POINTER_DOWN:
\r
1706 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1707 handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1718 //==============================================================================
\r
1719 private native void handleKeyDown (long host, int keycode, int textchar);
\r
1720 private native void handleKeyUp (long host, int keycode, int textchar);
\r
1721 private native void handleBackButton (long host);
\r
1722 private native void handleKeyboardHidden (long host);
\r
1724 public void showKeyboard (String type)
\r
1726 InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
\r
1730 if (type.length() > 0)
\r
1732 imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
\r
1733 imm.setInputMethod (getWindowToken(), type);
\r
1734 keyboardDismissListener.startListening();
\r
1738 imm.hideSoftInputFromWindow (getWindowToken(), 0);
\r
1739 keyboardDismissListener.stopListening();
\r
1744 public void backButtonPressed()
\r
1749 handleBackButton (host);
\r
1753 public boolean onKeyDown (int keyCode, KeyEvent event)
\r
1760 case KeyEvent.KEYCODE_VOLUME_UP:
\r
1761 case KeyEvent.KEYCODE_VOLUME_DOWN:
\r
1762 return super.onKeyDown (keyCode, event);
\r
1763 case KeyEvent.KEYCODE_BACK:
\r
1765 ((Activity) getContext()).onBackPressed();
\r
1773 handleKeyDown (host, keyCode, event.getUnicodeChar());
\r
1778 public boolean onKeyUp (int keyCode, KeyEvent event)
\r
1783 handleKeyUp (host, keyCode, event.getUnicodeChar());
\r
1788 public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
\r
1793 if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
\r
1794 return super.onKeyMultiple (keyCode, count, event);
\r
1796 if (event.getCharacters() != null)
\r
1798 int utf8Char = event.getCharacters().codePointAt (0);
\r
1799 handleKeyDown (host, utf8Char, utf8Char);
\r
1806 //==============================================================================
\r
1807 private final class KeyboardDismissListener
\r
1809 public KeyboardDismissListener (ComponentPeerView viewToUse)
\r
1814 private void startListening()
\r
1816 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);
\r
1819 private void stopListening()
\r
1821 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);
\r
1824 private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
\r
1828 keyboardShown = false;
\r
1832 public void onGlobalLayout()
\r
1834 Rect r = new Rect();
\r
1836 ViewGroup parentView = (ViewGroup) getParent();
\r
1838 if (parentView == null)
\r
1841 parentView.getWindowVisibleDisplayFrame (r);
\r
1843 int diff = parentView.getHeight() - (r.bottom - r.top);
\r
1845 // Arbitrary threshold, surely keyboard would take more than 20 pix.
\r
1846 if (diff < 20 && keyboardShown)
\r
1848 keyboardShown = false;
\r
1849 handleKeyboardHidden (view.host);
\r
1852 if (! keyboardShown && diff > 20)
\r
1853 keyboardShown = true;
\r
1856 private boolean keyboardShown;
\r
1859 private ComponentPeerView view;
\r
1860 private TreeObserver viewTreeObserver = new TreeObserver();
\r
1863 private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);
\r
1865 // this is here to make keyboard entry work on a Galaxy Tab2 10.1
\r
1867 public InputConnection onCreateInputConnection (EditorInfo outAttrs)
\r
1869 outAttrs.actionLabel = "";
\r
1870 outAttrs.hintText = "";
\r
1871 outAttrs.initialCapsMode = 0;
\r
1872 outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
\r
1873 outAttrs.label = "";
\r
1874 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
\r
1875 outAttrs.inputType = InputType.TYPE_NULL;
\r
1877 return new BaseInputConnection (this, false);
\r
1880 //==============================================================================
\r
1882 protected void onSizeChanged (int w, int h, int oldw, int oldh)
\r
1887 super.onSizeChanged (w, h, oldw, oldh);
\r
1888 viewSizeChanged (host);
\r
1892 protected void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1894 for (int i = getChildCount(); --i >= 0;)
\r
1895 requestTransparentRegion (getChildAt (i));
\r
1898 private native void viewSizeChanged (long host);
\r
1901 public void onFocusChange (View v, boolean hasFocus)
\r
1907 focusChanged (host, hasFocus);
\r
1910 private native void focusChanged (long host, boolean hasFocus);
\r
1912 public void setViewName (String newName) {}
\r
1914 public void setSystemUiVisibilityCompat (int visibility)
\r
1916 Method systemUIVisibilityMethod = null;
\r
1919 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
\r
1921 catch (SecurityException e) { return; }
\r
1922 catch (NoSuchMethodException e) { return; }
\r
1923 if (systemUIVisibilityMethod == null) return;
\r
1927 systemUIVisibilityMethod.invoke (this, visibility);
\r
1929 catch (java.lang.IllegalArgumentException e) {}
\r
1930 catch (java.lang.IllegalAccessException e) {}
\r
1931 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1934 public boolean isVisible() { return getVisibility() == VISIBLE; }
\r
1935 public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
\r
1937 public boolean containsPoint (int x, int y)
\r
1939 return true; //xxx needs to check overlapping views
\r
1942 //==============================================================================
\r
1943 private native void handleAppResumed (long host);
\r
1945 public void appResumed()
\r
1950 handleAppResumed (host);
\r
1954 //==============================================================================
\r
1955 public static class NativeSurfaceView extends SurfaceView
\r
1956 implements SurfaceHolder.Callback
\r
1958 private long nativeContext = 0;
\r
1960 NativeSurfaceView (Context context, long nativeContextPtr)
\r
1963 nativeContext = nativeContextPtr;
\r
1966 public Surface getNativeSurface()
\r
1968 Surface retval = null;
\r
1970 SurfaceHolder holder = getHolder();
\r
1971 if (holder != null)
\r
1972 retval = holder.getSurface();
\r
1977 //==============================================================================
\r
1979 public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
\r
1981 surfaceChangedNative (nativeContext, holder, format, width, height);
\r
1985 public void surfaceCreated (SurfaceHolder holder)
\r
1987 surfaceCreatedNative (nativeContext, holder);
\r
1991 public void surfaceDestroyed (SurfaceHolder holder)
\r
1993 surfaceDestroyedNative (nativeContext, holder);
\r
1997 protected void dispatchDraw (Canvas canvas)
\r
1999 super.dispatchDraw (canvas);
\r
2000 dispatchDrawNative (nativeContext, canvas);
\r
2003 //==============================================================================
\r
2005 protected void onAttachedToWindow ()
\r
2007 super.onAttachedToWindow();
\r
2008 getHolder().addCallback (this);
\r
2012 protected void onDetachedFromWindow ()
\r
2014 super.onDetachedFromWindow();
\r
2015 getHolder().removeCallback (this);
\r
2018 //==============================================================================
\r
2019 private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
\r
2020 private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
\r
2021 private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
\r
2022 private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
\r
2023 int format, int width, int height);
\r
2026 public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
\r
2028 return new NativeSurfaceView (this, nativeSurfacePtr);
\r
2031 //==============================================================================
\r
2032 public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)
\r
2034 Path p = new Path();
\r
2036 char[] str = { glyph1, glyph2 };
\r
2037 paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);
\r
2039 RectF boundsF = new RectF();
\r
2040 p.computeBounds (boundsF, true);
\r
2041 matrix.mapRect (boundsF);
\r
2043 boundsF.roundOut (bounds);
\r
2047 final int w = bounds.width();
\r
2048 final int h = Math.max (1, bounds.height());
\r
2050 Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
\r
2052 Canvas c = new Canvas (bm);
\r
2053 matrix.postTranslate (-bounds.left, -bounds.top);
\r
2054 c.setMatrix (matrix);
\r
2055 c.drawPath (p, paint);
\r
2057 final int sizeNeeded = w * h;
\r
2058 if (cachedRenderArray.length < sizeNeeded)
\r
2059 cachedRenderArray = new int [sizeNeeded];
\r
2061 bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
\r
2063 return cachedRenderArray;
\r
2066 private int[] cachedRenderArray = new int [256];
\r
2068 //==============================================================================
\r
2069 public static class NativeInvocationHandler implements InvocationHandler
\r
2071 public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)
\r
2073 activity = activityToUse;
\r
2074 nativeContext = nativeContextRef;
\r
2077 public void nativeContextDeleted()
\r
2079 nativeContext = 0;
\r
2083 public void finalize()
\r
2085 activity.runOnUiThread (new Runnable()
\r
2090 if (nativeContext != 0)
\r
2091 dispatchFinalize (nativeContext);
\r
2097 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
\r
2099 return dispatchInvoke (nativeContext, proxy, method, args);
\r
2102 //==============================================================================
\r
2103 Activity activity;
\r
2104 private long nativeContext = 0;
\r
2106 private native void dispatchFinalize (long nativeContextRef);
\r
2107 private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);
\r
2110 public InvocationHandler createInvocationHandler (long nativeContextRef)
\r
2112 return new NativeInvocationHandler (this, nativeContextRef);
\r
2115 public void invocationHandlerContextDeleted (InvocationHandler handler)
\r
2117 ((NativeInvocationHandler) handler).nativeContextDeleted();
\r
2120 //==============================================================================
\r
2121 public static class HTTPStream
\r
2123 public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,
\r
2124 String headersToUse, int timeOutMsToUse,
\r
2125 int[] statusCodeToUse, StringBuffer responseHeadersToUse,
\r
2126 int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
\r
2128 isPost = isPostToUse;
\r
2129 postData = postDataToUse;
\r
2130 headers = headersToUse;
\r
2131 timeOutMs = timeOutMsToUse;
\r
2132 statusCode = statusCodeToUse;
\r
2133 responseHeaders = responseHeadersToUse;
\r
2135 numRedirectsToFollow = numRedirectsToFollowToUse;
\r
2136 httpRequestCmd = httpRequestCmdToUse;
\r
2138 connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);
\r
2141 private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,
\r
2142 String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
\r
2144 HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
\r
2148 newConnection.setInstanceFollowRedirects (false);
\r
2149 newConnection.setConnectTimeout (timeOutMs);
\r
2150 newConnection.setReadTimeout (timeOutMs);
\r
2152 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.
\r
2153 // So convert headers string to an array, with an element for each line
\r
2154 String headerLines[] = headers.split("\\n");
\r
2156 // Set request headers
\r
2157 for (int i = 0; i < headerLines.length; ++i)
\r
2159 int pos = headerLines[i].indexOf (":");
\r
2161 if (pos > 0 && pos < headerLines[i].length())
\r
2163 String field = headerLines[i].substring (0, pos);
\r
2164 String value = headerLines[i].substring (pos + 1);
\r
2166 if (value.length() > 0)
\r
2167 newConnection.setRequestProperty (field, value);
\r
2171 newConnection.setRequestMethod (httpRequestCmd);
\r
2175 newConnection.setDoOutput (true);
\r
2177 if (postData != null)
\r
2179 OutputStream out = newConnection.getOutputStream();
\r
2180 out.write(postData);
\r
2185 return newConnection;
\r
2187 catch (Throwable e)
\r
2189 newConnection.disconnect();
\r
2190 throw new IOException ("Connection error");
\r
2194 private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException
\r
2196 synchronized (createFutureLock)
\r
2198 if (hasBeenCancelled.get())
\r
2201 streamFuture = executor.submit (new Callable<BufferedInputStream>()
\r
2204 public BufferedInputStream call() throws IOException
\r
2206 return new BufferedInputStream (isInput ? connection.getInputStream()
\r
2207 : connection.getErrorStream());
\r
2214 return streamFuture.get();
\r
2216 catch (InterruptedException e)
\r
2220 catch (CancellationException e)
\r
2226 public final boolean connect()
\r
2228 boolean result = false;
\r
2229 int numFollowedRedirects = 0;
\r
2233 result = doConnect();
\r
2238 if (++numFollowedRedirects > numRedirectsToFollow)
\r
2241 int status = statusCode[0];
\r
2243 if (status == 301 || status == 302 || status == 303 || status == 307)
\r
2245 // Assumes only one occurrence of "Location"
\r
2246 int pos1 = responseHeaders.indexOf ("Location:") + 10;
\r
2247 int pos2 = responseHeaders.indexOf ("\n", pos1);
\r
2251 String currentLocation = connection.getURL().toString();
\r
2252 String newLocation = responseHeaders.substring (pos1, pos2);
\r
2256 // Handle newLocation whether it's absolute or relative
\r
2257 URL baseUrl = new URL (currentLocation);
\r
2258 URL newUrl = new URL (baseUrl, newLocation);
\r
2259 String transformedNewLocation = newUrl.toString();
\r
2261 if (transformedNewLocation != currentLocation)
\r
2263 // Clear responseHeaders before next iteration
\r
2264 responseHeaders.delete (0, responseHeaders.length());
\r
2266 synchronized (createStreamLock)
\r
2268 if (hasBeenCancelled.get())
\r
2271 connection.disconnect();
\r
2275 connection = createConnection (transformedNewLocation, isPost,
\r
2276 postData, headers, timeOutMs,
\r
2279 catch (Throwable e)
\r
2290 catch (Throwable e)
\r
2309 private final boolean doConnect()
\r
2311 synchronized (createStreamLock)
\r
2313 if (hasBeenCancelled.get())
\r
2320 inputStream = getCancellableStream (true);
\r
2322 catch (ExecutionException e)
\r
2324 if (connection.getResponseCode() < 400)
\r
2326 statusCode[0] = connection.getResponseCode();
\r
2327 connection.disconnect();
\r
2333 statusCode[0] = connection.getResponseCode();
\r
2338 if (statusCode[0] >= 400)
\r
2339 inputStream = getCancellableStream (false);
\r
2341 inputStream = getCancellableStream (true);
\r
2343 catch (ExecutionException e)
\r
2346 for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
\r
2348 if (entry.getKey() != null && entry.getValue() != null)
\r
2350 responseHeaders.append(entry.getKey() + ": "
\r
2351 + android.text.TextUtils.join(",", entry.getValue()) + "\n");
\r
2353 if (entry.getKey().compareTo ("Content-Length") == 0)
\r
2354 totalLength = Integer.decode (entry.getValue().get (0));
\r
2360 catch (IOException e)
\r
2367 static class DisconnectionRunnable implements Runnable
\r
2369 public DisconnectionRunnable (HttpURLConnection theConnection,
\r
2370 InputStream theInputStream,
\r
2371 ReentrantLock theCreateStreamLock,
\r
2372 Object theCreateFutureLock,
\r
2373 Future<BufferedInputStream> theStreamFuture)
\r
2375 connectionToDisconnect = theConnection;
\r
2376 inputStream = theInputStream;
\r
2377 createStreamLock = theCreateStreamLock;
\r
2378 createFutureLock = theCreateFutureLock;
\r
2379 streamFuture = theStreamFuture;
\r
2386 if (! createStreamLock.tryLock())
\r
2388 synchronized (createFutureLock)
\r
2390 if (streamFuture != null)
\r
2391 streamFuture.cancel (true);
\r
2394 createStreamLock.lock();
\r
2397 if (connectionToDisconnect != null)
\r
2398 connectionToDisconnect.disconnect();
\r
2400 if (inputStream != null)
\r
2401 inputStream.close();
\r
2403 catch (IOException e)
\r
2407 createStreamLock.unlock();
\r
2411 private HttpURLConnection connectionToDisconnect;
\r
2412 private InputStream inputStream;
\r
2413 private ReentrantLock createStreamLock;
\r
2414 private Object createFutureLock;
\r
2415 Future<BufferedInputStream> streamFuture;
\r
2418 public final void release()
\r
2420 DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,
\r
2426 synchronized (createStreamLock)
\r
2428 hasBeenCancelled.set (true);
\r
2430 connection = null;
\r
2433 Thread disconnectionThread = new Thread(disconnectionRunnable);
\r
2434 disconnectionThread.start();
\r
2437 public final int read (byte[] buffer, int numBytes)
\r
2443 synchronized (createStreamLock)
\r
2445 if (inputStream != null)
\r
2446 num = inputStream.read (buffer, 0, numBytes);
\r
2449 catch (IOException e)
\r
2458 public final long getPosition() { return position; }
\r
2459 public final long getTotalLength() { return totalLength; }
\r
2460 public final boolean isExhausted() { return false; }
\r
2461 public final boolean setPosition (long newPos) { return false; }
\r
2463 private boolean isPost;
\r
2464 private byte[] postData;
\r
2465 private String headers;
\r
2466 private int timeOutMs;
\r
2467 String httpRequestCmd;
\r
2468 private HttpURLConnection connection;
\r
2469 private int[] statusCode;
\r
2470 private StringBuffer responseHeaders;
\r
2471 private int totalLength;
\r
2472 private int numRedirectsToFollow;
\r
2473 private InputStream inputStream;
\r
2474 private long position;
\r
2475 private final ReentrantLock createStreamLock = new ReentrantLock();
\r
2476 private final Object createFutureLock = new Object();
\r
2477 private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
\r
2479 private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());
\r
2480 Future<BufferedInputStream> streamFuture;
\r
2483 public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
\r
2484 String headers, int timeOutMs, int[] statusCode,
\r
2485 StringBuffer responseHeaders, int numRedirectsToFollow,
\r
2486 String httpRequestCmd)
\r
2488 // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
\r
2489 if (timeOutMs < 0)
\r
2491 else if (timeOutMs == 0)
\r
2492 timeOutMs = 30000;
\r
2498 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,
\r
2499 timeOutMs, statusCode, responseHeaders,
\r
2500 numRedirectsToFollow, httpRequestCmd);
\r
2502 return httpStream;
\r
2504 catch (Throwable e) {}
\r
2510 public final void launchURL (String url)
\r
2512 startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
\r
2515 private native boolean webViewPageLoadStarted (long host, WebView view, String url);
\r
2516 private native void webViewPageLoadFinished (long host, WebView view, String url);
\r
2517 private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error); private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse); private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
\r
2518 private native void webViewCloseWindowRequest (long host, WebView view);
\r
2519 private native void webViewCreateWindowRequest (long host, WebView view);
\r
2521 //==============================================================================
\r
2522 public class JuceWebViewClient extends WebViewClient
\r
2524 public JuceWebViewClient (long hostToUse)
\r
2529 public void hostDeleted()
\r
2531 synchronized (hostLock)
\r
2538 public void onPageFinished (WebView view, String url)
\r
2543 webViewPageLoadFinished (host, view, url);
\r
2547 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
\r
2552 webViewReceivedSslError (host, view, handler, error);
\r
2556 public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)
\r
2561 webViewReceivedError (host, view, request, error);
\r
2565 public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
\r
2570 webViewReceivedHttpError (host, view, request, errorResponse);
\r
2574 public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
\r
2576 synchronized (hostLock)
\r
2580 boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());
\r
2587 return new WebResourceResponse ("text/html", null, null);
\r
2590 private long host;
\r
2591 private final Object hostLock = new Object();
\r
2594 public class JuceWebChromeClient extends WebChromeClient
\r
2596 public JuceWebChromeClient (long hostToUse)
\r
2602 public void onCloseWindow (WebView window)
\r
2604 webViewCloseWindowRequest (host, window);
\r
2608 public boolean onCreateWindow (WebView view, boolean isDialog,
\r
2609 boolean isUserGesture, Message resultMsg)
\r
2611 webViewCreateWindowRequest (host, view);
\r
2615 private long host;
\r
2616 private final Object hostLock = new Object();
\r
2619 //==============================================================================
\r
2620 public static final String getLocaleValue (boolean isRegion)
\r
2622 java.util.Locale locale = java.util.Locale.getDefault();
\r
2624 return isRegion ? locale.getCountry()
\r
2625 : locale.getLanguage();
\r
2628 private static final String getFileLocation (String type)
\r
2630 return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
\r
2633 public static final String getDocumentsFolder()
\r
2635 if (getAndroidSDKVersion() >= 19)
\r
2636 return getFileLocation ("Documents");
\r
2638 return Environment.getDataDirectory().getAbsolutePath();
\r
2641 public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
\r
2642 public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
\r
2643 public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
\r
2644 public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
\r
2646 //==============================================================================
\r
2648 protected void onActivityResult (int requestCode, int resultCode, Intent data)
\r
2650 appActivityResult (requestCode, resultCode, data);
\r
2654 protected void onNewIntent (Intent intent)
\r
2656 super.onNewIntent(intent);
\r
2657 setIntent(intent);
\r
2659 appNewIntent (intent);
\r
2662 //==============================================================================
\r
2663 public final Typeface getTypeFaceFromAsset (String assetName)
\r
2667 return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
\r
2669 catch (Throwable e) {}
\r
2674 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
\r
2676 public static String bytesToHex (byte[] bytes)
\r
2678 char[] hexChars = new char[bytes.length * 2];
\r
2680 for (int j = 0; j < bytes.length; ++j)
\r
2682 int v = bytes[j] & 0xff;
\r
2683 hexChars[j * 2] = hexArray[v >>> 4];
\r
2684 hexChars[j * 2 + 1] = hexArray[v & 0x0f];
\r
2687 return new String (hexChars);
\r
2690 final private java.util.Map dataCache = new java.util.HashMap();
\r
2692 synchronized private final File getDataCacheFile (byte[] data)
\r
2696 java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
\r
2697 digest.update (data);
\r
2699 String key = bytesToHex (digest.digest());
\r
2701 if (dataCache.containsKey (key))
\r
2702 return (File) dataCache.get (key);
\r
2704 File f = new File (this.getCacheDir(), "bindata_" + key);
\r
2706 FileOutputStream os = new FileOutputStream (f);
\r
2707 os.write (data, 0, data.length);
\r
2708 dataCache.put (key, f);
\r
2711 catch (Throwable e) {}
\r
2716 private final void clearDataCache()
\r
2718 java.util.Iterator it = dataCache.values().iterator();
\r
2720 while (it.hasNext())
\r
2722 File f = (File) it.next();
\r
2727 public final Typeface getTypeFaceFromByteArray (byte[] data)
\r
2731 File f = getDataCacheFile (data);
\r
2734 return Typeface.createFromFile (f);
\r
2736 catch (Exception e)
\r
2738 Log.e ("JUCE", e.toString());
\r
2744 public static final int getAndroidSDKVersion()
\r
2746 return android.os.Build.VERSION.SDK_INT;
\r
2749 public final String audioManagerGetProperty (String property)
\r
2751 Object obj = getSystemService (AUDIO_SERVICE);
\r
2755 java.lang.reflect.Method method;
\r
2759 method = obj.getClass().getMethod ("getProperty", String.class);
\r
2761 catch (SecurityException e) { return null; }
\r
2762 catch (NoSuchMethodException e) { return null; }
\r
2764 if (method == null)
\r
2769 return (String) method.invoke (obj, property);
\r
2771 catch (java.lang.IllegalArgumentException e) {}
\r
2772 catch (java.lang.IllegalAccessException e) {}
\r
2773 catch (java.lang.reflect.InvocationTargetException e) {}
\r
2778 public final boolean hasSystemFeature (String property)
\r
2780 return getPackageManager().hasSystemFeature (property);
\r