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
1241 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
\r
1245 public void onConfigurationChanged (Configuration cfg)
\r
1247 super.onConfigurationChanged (cfg);
\r
1248 setContentView (viewHolder);
\r
1251 private void callAppLauncher()
\r
1253 launchApp (getApplicationInfo().publicSourceDir,
\r
1254 getApplicationInfo().dataDir);
\r
1257 // Need to override this as the default implementation always finishes the activity.
\r
1259 public void onBackPressed()
\r
1261 ComponentPeerView focusedView = getViewWithFocusOrDefaultView();
\r
1263 if (focusedView == null)
\r
1266 focusedView.backButtonPressed();
\r
1269 private ComponentPeerView getViewWithFocusOrDefaultView()
\r
1271 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
1273 if (viewHolder.getChildAt (i).hasFocus())
\r
1274 return (ComponentPeerView) viewHolder.getChildAt (i);
\r
1277 if (viewHolder.getChildCount() > 0)
\r
1278 return (ComponentPeerView) viewHolder.getChildAt (0);
\r
1283 //==============================================================================
\r
1284 private void hideActionBar()
\r
1286 // get "getActionBar" method
\r
1287 java.lang.reflect.Method getActionBarMethod = null;
\r
1290 getActionBarMethod = this.getClass().getMethod ("getActionBar");
\r
1292 catch (SecurityException e) { return; }
\r
1293 catch (NoSuchMethodException e) { return; }
\r
1294 if (getActionBarMethod == null) return;
\r
1296 // invoke "getActionBar" method
\r
1297 Object actionBar = null;
\r
1300 actionBar = getActionBarMethod.invoke (this);
\r
1302 catch (java.lang.IllegalArgumentException e) { return; }
\r
1303 catch (java.lang.IllegalAccessException e) { return; }
\r
1304 catch (java.lang.reflect.InvocationTargetException e) { return; }
\r
1305 if (actionBar == null) return;
\r
1307 // get "hide" method
\r
1308 java.lang.reflect.Method actionBarHideMethod = null;
\r
1311 actionBarHideMethod = actionBar.getClass().getMethod ("hide");
\r
1313 catch (SecurityException e) { return; }
\r
1314 catch (NoSuchMethodException e) { return; }
\r
1315 if (actionBarHideMethod == null) return;
\r
1317 // invoke "hide" method
\r
1320 actionBarHideMethod.invoke (actionBar);
\r
1322 catch (java.lang.IllegalArgumentException e) {}
\r
1323 catch (java.lang.IllegalAccessException e) {}
\r
1324 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1327 void requestPermissionsCompat (String[] permissions, int requestCode)
\r
1329 Method requestPermissionsMethod = null;
\r
1332 requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",
\r
1333 String[].class, int.class);
\r
1335 catch (SecurityException e) { return; }
\r
1336 catch (NoSuchMethodException e) { return; }
\r
1337 if (requestPermissionsMethod == null) return;
\r
1341 requestPermissionsMethod.invoke (this, permissions, requestCode);
\r
1343 catch (java.lang.IllegalArgumentException e) {}
\r
1344 catch (java.lang.IllegalAccessException e) {}
\r
1345 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1348 //==============================================================================
\r
1349 private native void launchApp (String appFile, String appDataDir);
\r
1350 private native void quitApp();
\r
1351 private native void suspendApp();
\r
1352 private native void resumeApp();
\r
1353 private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
\r
1354 private native void appActivityResult (int requestCode, int resultCode, Intent data);
\r
1355 private native void appNewIntent (Intent intent);
\r
1357 //==============================================================================
\r
1358 private ViewHolder viewHolder;
\r
1359 private MidiDeviceManager midiDeviceManager = null;
\r
1360 private BluetoothManager bluetoothManager = null;
\r
1361 private boolean isScreenSaverEnabled;
\r
1362 private java.util.Timer keepAliveTimer;
\r
1364 public final ComponentPeerView createNewView (boolean opaque, long host)
\r
1366 ComponentPeerView v = new ComponentPeerView (this, opaque, host);
\r
1367 viewHolder.addView (v);
\r
1371 public final void deleteView (ComponentPeerView view)
\r
1375 ViewGroup group = (ViewGroup) (view.getParent());
\r
1377 if (group != null)
\r
1378 group.removeView (view);
\r
1381 public final void deleteNativeSurfaceView (NativeSurfaceView view)
\r
1383 ViewGroup group = (ViewGroup) (view.getParent());
\r
1385 if (group != null)
\r
1386 group.removeView (view);
\r
1389 final class ViewHolder extends ViewGroup
\r
1391 public ViewHolder (Context context)
\r
1394 setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
\r
1395 setFocusable (false);
\r
1398 protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1400 setScreenSize (getWidth(), getHeight(), getDPI());
\r
1402 if (isFirstResize)
\r
1404 isFirstResize = false;
\r
1405 callAppLauncher();
\r
1409 private final int getDPI()
\r
1411 DisplayMetrics metrics = new DisplayMetrics();
\r
1412 getWindowManager().getDefaultDisplay().getMetrics (metrics);
\r
1413 return metrics.densityDpi;
\r
1416 private boolean isFirstResize = true;
\r
1419 public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
\r
1421 canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
\r
1424 //==============================================================================
\r
1425 public final void setScreenSaver (boolean enabled)
\r
1427 if (isScreenSaverEnabled != enabled)
\r
1429 isScreenSaverEnabled = enabled;
\r
1431 if (keepAliveTimer != null)
\r
1433 keepAliveTimer.cancel();
\r
1434 keepAliveTimer = null;
\r
1439 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1443 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
1445 // If no user input is received after about 3 seconds, the OS will lower the
\r
1446 // task's priority, so this timer forces it to be kept active.
\r
1447 keepAliveTimer = new java.util.Timer();
\r
1449 keepAliveTimer.scheduleAtFixedRate (new TimerTask()
\r
1454 android.app.Instrumentation instrumentation = new android.app.Instrumentation();
\r
1458 instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
\r
1460 catch (Exception e)
\r
1469 public final boolean getScreenSaver()
\r
1471 return isScreenSaverEnabled;
\r
1474 //==============================================================================
\r
1475 public final String getClipboardContent()
\r
1477 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1478 return clipboard.getText().toString();
\r
1481 public final void setClipboardContent (String newText)
\r
1483 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
1484 clipboard.setText (newText);
\r
1487 //==============================================================================
\r
1488 public final void showMessageBox (String title, String message, final long callback)
\r
1490 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1491 builder.setTitle (title)
\r
1492 .setMessage (message)
\r
1493 .setCancelable (true)
\r
1494 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1496 public void onCancel (DialogInterface dialog)
\r
1498 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1501 .setPositiveButton ("OK", new DialogInterface.OnClickListener()
\r
1503 public void onClick (DialogInterface dialog, int id)
\r
1506 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1510 builder.create().show();
\r
1513 public final void showOkCancelBox (String title, String message, final long callback,
\r
1514 String okButtonText, String cancelButtonText)
\r
1516 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1517 builder.setTitle (title)
\r
1518 .setMessage (message)
\r
1519 .setCancelable (true)
\r
1520 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1522 public void onCancel (DialogInterface dialog)
\r
1524 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1527 .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()
\r
1529 public void onClick (DialogInterface dialog, int id)
\r
1532 AudioPerformanceTest.this.alertDismissed (callback, 1);
\r
1535 .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()
\r
1537 public void onClick (DialogInterface dialog, int id)
\r
1540 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1544 builder.create().show();
\r
1547 public final void showYesNoCancelBox (String title, String message, final long callback)
\r
1549 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
1550 builder.setTitle (title)
\r
1551 .setMessage (message)
\r
1552 .setCancelable (true)
\r
1553 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
1555 public void onCancel (DialogInterface dialog)
\r
1557 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1560 .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
\r
1562 public void onClick (DialogInterface dialog, int id)
\r
1565 AudioPerformanceTest.this.alertDismissed (callback, 1);
\r
1568 .setNegativeButton ("No", new DialogInterface.OnClickListener()
\r
1570 public void onClick (DialogInterface dialog, int id)
\r
1573 AudioPerformanceTest.this.alertDismissed (callback, 2);
\r
1576 .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
\r
1578 public void onClick (DialogInterface dialog, int id)
\r
1581 AudioPerformanceTest.this.alertDismissed (callback, 0);
\r
1585 builder.create().show();
\r
1588 public native void alertDismissed (long callback, int id);
\r
1590 //==============================================================================
\r
1591 public final class ComponentPeerView extends ViewGroup
\r
1592 implements View.OnFocusChangeListener
\r
1594 public ComponentPeerView (Context context, boolean opaque_, long host)
\r
1598 setWillNotDraw (false);
\r
1601 setFocusable (true);
\r
1602 setFocusableInTouchMode (true);
\r
1603 setOnFocusChangeListener (this);
\r
1605 // swap red and blue colours to match internal opengl texture format
\r
1606 ColorMatrix colorMatrix = new ColorMatrix();
\r
1608 float[] colorTransform = { 0, 0, 1.0f, 0, 0,
\r
1611 0, 0, 0, 1.0f, 0 };
\r
1613 colorMatrix.set (colorTransform);
\r
1614 paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
\r
1616 java.lang.reflect.Method method = null;
\r
1620 method = getClass().getMethod ("setLayerType", int.class, Paint.class);
\r
1622 catch (SecurityException e) {}
\r
1623 catch (NoSuchMethodException e) {}
\r
1625 if (method != null)
\r
1629 int layerTypeNone = 0;
\r
1630 method.invoke (this, layerTypeNone, null);
\r
1632 catch (java.lang.IllegalArgumentException e) {}
\r
1633 catch (java.lang.IllegalAccessException e) {}
\r
1634 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1638 //==============================================================================
\r
1639 private native void handlePaint (long host, Canvas canvas, Paint paint);
\r
1642 public void onDraw (Canvas canvas)
\r
1647 handlePaint (host, canvas, paint);
\r
1651 public boolean isOpaque()
\r
1656 private boolean opaque;
\r
1657 private long host;
\r
1658 private Paint paint = new Paint();
\r
1660 //==============================================================================
\r
1661 private native void handleMouseDown (long host, int index, float x, float y, long time);
\r
1662 private native void handleMouseDrag (long host, int index, float x, float y, long time);
\r
1663 private native void handleMouseUp (long host, int index, float x, float y, long time);
\r
1666 public boolean onTouchEvent (MotionEvent event)
\r
1671 int action = event.getAction();
\r
1672 long time = event.getEventTime();
\r
1674 switch (action & MotionEvent.ACTION_MASK)
\r
1676 case MotionEvent.ACTION_DOWN:
\r
1677 handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1680 case MotionEvent.ACTION_CANCEL:
\r
1681 case MotionEvent.ACTION_UP:
\r
1682 handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
1685 case MotionEvent.ACTION_MOVE:
\r
1687 int n = event.getPointerCount();
\r
1688 for (int i = 0; i < n; ++i)
\r
1689 handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1694 case MotionEvent.ACTION_POINTER_UP:
\r
1696 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1697 handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1701 case MotionEvent.ACTION_POINTER_DOWN:
\r
1703 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
1704 handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
1715 //==============================================================================
\r
1716 private native void handleKeyDown (long host, int keycode, int textchar);
\r
1717 private native void handleKeyUp (long host, int keycode, int textchar);
\r
1718 private native void handleBackButton (long host);
\r
1719 private native void handleKeyboardHidden (long host);
\r
1721 public void showKeyboard (String type)
\r
1723 InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
\r
1727 if (type.length() > 0)
\r
1729 imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
\r
1730 imm.setInputMethod (getWindowToken(), type);
\r
1731 keyboardDismissListener.startListening();
\r
1735 imm.hideSoftInputFromWindow (getWindowToken(), 0);
\r
1736 keyboardDismissListener.stopListening();
\r
1741 public void backButtonPressed()
\r
1746 handleBackButton (host);
\r
1750 public boolean onKeyDown (int keyCode, KeyEvent event)
\r
1757 case KeyEvent.KEYCODE_VOLUME_UP:
\r
1758 case KeyEvent.KEYCODE_VOLUME_DOWN:
\r
1759 return super.onKeyDown (keyCode, event);
\r
1760 case KeyEvent.KEYCODE_BACK:
\r
1762 ((Activity) getContext()).onBackPressed();
\r
1770 handleKeyDown (host, keyCode, event.getUnicodeChar());
\r
1775 public boolean onKeyUp (int keyCode, KeyEvent event)
\r
1780 handleKeyUp (host, keyCode, event.getUnicodeChar());
\r
1785 public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
\r
1790 if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
\r
1791 return super.onKeyMultiple (keyCode, count, event);
\r
1793 if (event.getCharacters() != null)
\r
1795 int utf8Char = event.getCharacters().codePointAt (0);
\r
1796 handleKeyDown (host, utf8Char, utf8Char);
\r
1803 //==============================================================================
\r
1804 private final class KeyboardDismissListener
\r
1806 public KeyboardDismissListener (ComponentPeerView viewToUse)
\r
1811 private void startListening()
\r
1813 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);
\r
1816 private void stopListening()
\r
1818 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);
\r
1821 private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
\r
1824 public void onGlobalLayout()
\r
1826 Rect r = new Rect();
\r
1828 view.getWindowVisibleDisplayFrame(r);
\r
1830 int diff = view.getHeight() - (r.bottom - r.top);
\r
1832 // Arbitrary threshold, surely keyboard would take more than 20 pix.
\r
1834 handleKeyboardHidden (view.host);
\r
1838 private ComponentPeerView view;
\r
1839 private TreeObserver viewTreeObserver = new TreeObserver();
\r
1842 private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);
\r
1844 // this is here to make keyboard entry work on a Galaxy Tab2 10.1
\r
1846 public InputConnection onCreateInputConnection (EditorInfo outAttrs)
\r
1848 outAttrs.actionLabel = "";
\r
1849 outAttrs.hintText = "";
\r
1850 outAttrs.initialCapsMode = 0;
\r
1851 outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
\r
1852 outAttrs.label = "";
\r
1853 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
\r
1854 outAttrs.inputType = InputType.TYPE_NULL;
\r
1856 return new BaseInputConnection (this, false);
\r
1859 //==============================================================================
\r
1861 protected void onSizeChanged (int w, int h, int oldw, int oldh)
\r
1866 super.onSizeChanged (w, h, oldw, oldh);
\r
1867 viewSizeChanged (host);
\r
1871 protected void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
1873 for (int i = getChildCount(); --i >= 0;)
\r
1874 requestTransparentRegion (getChildAt (i));
\r
1877 private native void viewSizeChanged (long host);
\r
1880 public void onFocusChange (View v, boolean hasFocus)
\r
1886 focusChanged (host, hasFocus);
\r
1889 private native void focusChanged (long host, boolean hasFocus);
\r
1891 public void setViewName (String newName) {}
\r
1893 public void setSystemUiVisibilityCompat (int visibility)
\r
1895 Method systemUIVisibilityMethod = null;
\r
1898 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
\r
1900 catch (SecurityException e) { return; }
\r
1901 catch (NoSuchMethodException e) { return; }
\r
1902 if (systemUIVisibilityMethod == null) return;
\r
1906 systemUIVisibilityMethod.invoke (this, visibility);
\r
1908 catch (java.lang.IllegalArgumentException e) {}
\r
1909 catch (java.lang.IllegalAccessException e) {}
\r
1910 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1913 public boolean isVisible() { return getVisibility() == VISIBLE; }
\r
1914 public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
\r
1916 public boolean containsPoint (int x, int y)
\r
1918 return true; //xxx needs to check overlapping views
\r
1921 //==============================================================================
\r
1922 private native void handleAppResumed (long host);
\r
1924 public void appResumed()
\r
1929 handleAppResumed (host);
\r
1933 //==============================================================================
\r
1934 public static class NativeSurfaceView extends SurfaceView
\r
1935 implements SurfaceHolder.Callback
\r
1937 private long nativeContext = 0;
\r
1939 NativeSurfaceView (Context context, long nativeContextPtr)
\r
1942 nativeContext = nativeContextPtr;
\r
1945 public Surface getNativeSurface()
\r
1947 Surface retval = null;
\r
1949 SurfaceHolder holder = getHolder();
\r
1950 if (holder != null)
\r
1951 retval = holder.getSurface();
\r
1956 //==============================================================================
\r
1958 public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
\r
1960 surfaceChangedNative (nativeContext, holder, format, width, height);
\r
1964 public void surfaceCreated (SurfaceHolder holder)
\r
1966 surfaceCreatedNative (nativeContext, holder);
\r
1970 public void surfaceDestroyed (SurfaceHolder holder)
\r
1972 surfaceDestroyedNative (nativeContext, holder);
\r
1976 protected void dispatchDraw (Canvas canvas)
\r
1978 super.dispatchDraw (canvas);
\r
1979 dispatchDrawNative (nativeContext, canvas);
\r
1982 //==============================================================================
\r
1984 protected void onAttachedToWindow ()
\r
1986 super.onAttachedToWindow();
\r
1987 getHolder().addCallback (this);
\r
1991 protected void onDetachedFromWindow ()
\r
1993 super.onDetachedFromWindow();
\r
1994 getHolder().removeCallback (this);
\r
1997 //==============================================================================
\r
1998 private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
\r
1999 private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
\r
2000 private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
\r
2001 private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
\r
2002 int format, int width, int height);
\r
2005 public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
\r
2007 return new NativeSurfaceView (this, nativeSurfacePtr);
\r
2010 //==============================================================================
\r
2011 public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)
\r
2013 Path p = new Path();
\r
2015 char[] str = { glyph1, glyph2 };
\r
2016 paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);
\r
2018 RectF boundsF = new RectF();
\r
2019 p.computeBounds (boundsF, true);
\r
2020 matrix.mapRect (boundsF);
\r
2022 boundsF.roundOut (bounds);
\r
2026 final int w = bounds.width();
\r
2027 final int h = Math.max (1, bounds.height());
\r
2029 Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
\r
2031 Canvas c = new Canvas (bm);
\r
2032 matrix.postTranslate (-bounds.left, -bounds.top);
\r
2033 c.setMatrix (matrix);
\r
2034 c.drawPath (p, paint);
\r
2036 final int sizeNeeded = w * h;
\r
2037 if (cachedRenderArray.length < sizeNeeded)
\r
2038 cachedRenderArray = new int [sizeNeeded];
\r
2040 bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
\r
2042 return cachedRenderArray;
\r
2045 private int[] cachedRenderArray = new int [256];
\r
2047 //==============================================================================
\r
2048 public static class NativeInvocationHandler implements InvocationHandler
\r
2050 public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)
\r
2052 activity = activityToUse;
\r
2053 nativeContext = nativeContextRef;
\r
2056 public void nativeContextDeleted()
\r
2058 nativeContext = 0;
\r
2062 public void finalize()
\r
2064 activity.runOnUiThread (new Runnable()
\r
2069 if (nativeContext != 0)
\r
2070 dispatchFinalize (nativeContext);
\r
2076 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
\r
2078 return dispatchInvoke (nativeContext, proxy, method, args);
\r
2081 //==============================================================================
\r
2082 Activity activity;
\r
2083 private long nativeContext = 0;
\r
2085 private native void dispatchFinalize (long nativeContextRef);
\r
2086 private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);
\r
2089 public InvocationHandler createInvocationHandler (long nativeContextRef)
\r
2091 return new NativeInvocationHandler (this, nativeContextRef);
\r
2094 public void invocationHandlerContextDeleted (InvocationHandler handler)
\r
2096 ((NativeInvocationHandler) handler).nativeContextDeleted();
\r
2099 //==============================================================================
\r
2100 public static class HTTPStream
\r
2102 public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,
\r
2103 String headersToUse, int timeOutMsToUse,
\r
2104 int[] statusCodeToUse, StringBuffer responseHeadersToUse,
\r
2105 int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
\r
2107 isPost = isPostToUse;
\r
2108 postData = postDataToUse;
\r
2109 headers = headersToUse;
\r
2110 timeOutMs = timeOutMsToUse;
\r
2111 statusCode = statusCodeToUse;
\r
2112 responseHeaders = responseHeadersToUse;
\r
2114 numRedirectsToFollow = numRedirectsToFollowToUse;
\r
2115 httpRequestCmd = httpRequestCmdToUse;
\r
2117 connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);
\r
2120 private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,
\r
2121 String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
\r
2123 HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
\r
2127 newConnection.setInstanceFollowRedirects (false);
\r
2128 newConnection.setConnectTimeout (timeOutMs);
\r
2129 newConnection.setReadTimeout (timeOutMs);
\r
2131 // 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
2132 // So convert headers string to an array, with an element for each line
\r
2133 String headerLines[] = headers.split("\\n");
\r
2135 // Set request headers
\r
2136 for (int i = 0; i < headerLines.length; ++i)
\r
2138 int pos = headerLines[i].indexOf (":");
\r
2140 if (pos > 0 && pos < headerLines[i].length())
\r
2142 String field = headerLines[i].substring (0, pos);
\r
2143 String value = headerLines[i].substring (pos + 1);
\r
2145 if (value.length() > 0)
\r
2146 newConnection.setRequestProperty (field, value);
\r
2150 newConnection.setRequestMethod (httpRequestCmd);
\r
2154 newConnection.setDoOutput (true);
\r
2156 if (postData != null)
\r
2158 OutputStream out = newConnection.getOutputStream();
\r
2159 out.write(postData);
\r
2164 return newConnection;
\r
2166 catch (Throwable e)
\r
2168 newConnection.disconnect();
\r
2169 throw new IOException ("Connection error");
\r
2173 private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException
\r
2175 synchronized (createFutureLock)
\r
2177 if (hasBeenCancelled.get())
\r
2180 streamFuture = executor.submit (new Callable<BufferedInputStream>()
\r
2183 public BufferedInputStream call() throws IOException
\r
2185 return new BufferedInputStream (isInput ? connection.getInputStream()
\r
2186 : connection.getErrorStream());
\r
2193 return streamFuture.get();
\r
2195 catch (InterruptedException e)
\r
2199 catch (CancellationException e)
\r
2205 public final boolean connect()
\r
2207 boolean result = false;
\r
2208 int numFollowedRedirects = 0;
\r
2212 result = doConnect();
\r
2217 if (++numFollowedRedirects > numRedirectsToFollow)
\r
2220 int status = statusCode[0];
\r
2222 if (status == 301 || status == 302 || status == 303 || status == 307)
\r
2224 // Assumes only one occurrence of "Location"
\r
2225 int pos1 = responseHeaders.indexOf ("Location:") + 10;
\r
2226 int pos2 = responseHeaders.indexOf ("\n", pos1);
\r
2230 String currentLocation = connection.getURL().toString();
\r
2231 String newLocation = responseHeaders.substring (pos1, pos2);
\r
2235 // Handle newLocation whether it's absolute or relative
\r
2236 URL baseUrl = new URL (currentLocation);
\r
2237 URL newUrl = new URL (baseUrl, newLocation);
\r
2238 String transformedNewLocation = newUrl.toString();
\r
2240 if (transformedNewLocation != currentLocation)
\r
2242 // Clear responseHeaders before next iteration
\r
2243 responseHeaders.delete (0, responseHeaders.length());
\r
2245 synchronized (createStreamLock)
\r
2247 if (hasBeenCancelled.get())
\r
2250 connection.disconnect();
\r
2254 connection = createConnection (transformedNewLocation, isPost,
\r
2255 postData, headers, timeOutMs,
\r
2258 catch (Throwable e)
\r
2269 catch (Throwable e)
\r
2288 private final boolean doConnect()
\r
2290 synchronized (createStreamLock)
\r
2292 if (hasBeenCancelled.get())
\r
2299 inputStream = getCancellableStream (true);
\r
2301 catch (ExecutionException e)
\r
2303 if (connection.getResponseCode() < 400)
\r
2305 statusCode[0] = connection.getResponseCode();
\r
2306 connection.disconnect();
\r
2312 statusCode[0] = connection.getResponseCode();
\r
2317 if (statusCode[0] >= 400)
\r
2318 inputStream = getCancellableStream (false);
\r
2320 inputStream = getCancellableStream (true);
\r
2322 catch (ExecutionException e)
\r
2325 for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
\r
2327 if (entry.getKey() != null && entry.getValue() != null)
\r
2329 responseHeaders.append(entry.getKey() + ": "
\r
2330 + android.text.TextUtils.join(",", entry.getValue()) + "\n");
\r
2332 if (entry.getKey().compareTo ("Content-Length") == 0)
\r
2333 totalLength = Integer.decode (entry.getValue().get (0));
\r
2339 catch (IOException e)
\r
2346 static class DisconnectionRunnable implements Runnable
\r
2348 public DisconnectionRunnable (HttpURLConnection theConnection,
\r
2349 InputStream theInputStream,
\r
2350 ReentrantLock theCreateStreamLock,
\r
2351 Object theCreateFutureLock,
\r
2352 Future<BufferedInputStream> theStreamFuture)
\r
2354 connectionToDisconnect = theConnection;
\r
2355 inputStream = theInputStream;
\r
2356 createStreamLock = theCreateStreamLock;
\r
2357 createFutureLock = theCreateFutureLock;
\r
2358 streamFuture = theStreamFuture;
\r
2365 if (! createStreamLock.tryLock())
\r
2367 synchronized (createFutureLock)
\r
2369 if (streamFuture != null)
\r
2370 streamFuture.cancel (true);
\r
2373 createStreamLock.lock();
\r
2376 if (connectionToDisconnect != null)
\r
2377 connectionToDisconnect.disconnect();
\r
2379 if (inputStream != null)
\r
2380 inputStream.close();
\r
2382 catch (IOException e)
\r
2386 createStreamLock.unlock();
\r
2390 private HttpURLConnection connectionToDisconnect;
\r
2391 private InputStream inputStream;
\r
2392 private ReentrantLock createStreamLock;
\r
2393 private Object createFutureLock;
\r
2394 Future<BufferedInputStream> streamFuture;
\r
2397 public final void release()
\r
2399 DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,
\r
2405 synchronized (createStreamLock)
\r
2407 hasBeenCancelled.set (true);
\r
2409 connection = null;
\r
2412 Thread disconnectionThread = new Thread(disconnectionRunnable);
\r
2413 disconnectionThread.start();
\r
2416 public final int read (byte[] buffer, int numBytes)
\r
2422 synchronized (createStreamLock)
\r
2424 if (inputStream != null)
\r
2425 num = inputStream.read (buffer, 0, numBytes);
\r
2428 catch (IOException e)
\r
2437 public final long getPosition() { return position; }
\r
2438 public final long getTotalLength() { return totalLength; }
\r
2439 public final boolean isExhausted() { return false; }
\r
2440 public final boolean setPosition (long newPos) { return false; }
\r
2442 private boolean isPost;
\r
2443 private byte[] postData;
\r
2444 private String headers;
\r
2445 private int timeOutMs;
\r
2446 String httpRequestCmd;
\r
2447 private HttpURLConnection connection;
\r
2448 private int[] statusCode;
\r
2449 private StringBuffer responseHeaders;
\r
2450 private int totalLength;
\r
2451 private int numRedirectsToFollow;
\r
2452 private InputStream inputStream;
\r
2453 private long position;
\r
2454 private final ReentrantLock createStreamLock = new ReentrantLock();
\r
2455 private final Object createFutureLock = new Object();
\r
2456 private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
\r
2458 private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());
\r
2459 Future<BufferedInputStream> streamFuture;
\r
2462 public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
\r
2463 String headers, int timeOutMs, int[] statusCode,
\r
2464 StringBuffer responseHeaders, int numRedirectsToFollow,
\r
2465 String httpRequestCmd)
\r
2467 // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
\r
2468 if (timeOutMs < 0)
\r
2470 else if (timeOutMs == 0)
\r
2471 timeOutMs = 30000;
\r
2477 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,
\r
2478 timeOutMs, statusCode, responseHeaders,
\r
2479 numRedirectsToFollow, httpRequestCmd);
\r
2481 return httpStream;
\r
2483 catch (Throwable e) {}
\r
2489 public final void launchURL (String url)
\r
2491 startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
\r
2494 private native boolean webViewPageLoadStarted (long host, WebView view, String url);
\r
2495 private native void webViewPageLoadFinished (long host, WebView view, String url);
\r
2496 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
2497 private native void webViewCloseWindowRequest (long host, WebView view);
\r
2498 private native void webViewCreateWindowRequest (long host, WebView view);
\r
2500 //==============================================================================
\r
2501 public class JuceWebViewClient extends WebViewClient
\r
2503 public JuceWebViewClient (long hostToUse)
\r
2508 public void hostDeleted()
\r
2510 synchronized (hostLock)
\r
2517 public void onPageFinished (WebView view, String url)
\r
2522 webViewPageLoadFinished (host, view, url);
\r
2526 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
\r
2531 webViewReceivedSslError (host, view, handler, error);
\r
2535 public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error)
\r
2540 webViewReceivedError (host, view, request, error);
\r
2544 public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
\r
2549 webViewReceivedHttpError (host, view, request, errorResponse);
\r
2553 public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
\r
2555 synchronized (hostLock)
\r
2559 boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString());
\r
2566 return new WebResourceResponse ("text/html", null, null);
\r
2569 private long host;
\r
2570 private final Object hostLock = new Object();
\r
2573 public class JuceWebChromeClient extends WebChromeClient
\r
2575 public JuceWebChromeClient (long hostToUse)
\r
2581 public void onCloseWindow (WebView window)
\r
2583 webViewCloseWindowRequest (host, window);
\r
2587 public boolean onCreateWindow (WebView view, boolean isDialog,
\r
2588 boolean isUserGesture, Message resultMsg)
\r
2590 webViewCreateWindowRequest (host, view);
\r
2594 private long host;
\r
2595 private final Object hostLock = new Object();
\r
2598 //==============================================================================
\r
2599 public static final String getLocaleValue (boolean isRegion)
\r
2601 java.util.Locale locale = java.util.Locale.getDefault();
\r
2603 return isRegion ? locale.getCountry()
\r
2604 : locale.getLanguage();
\r
2607 private static final String getFileLocation (String type)
\r
2609 return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
\r
2612 public static final String getDocumentsFolder()
\r
2614 if (getAndroidSDKVersion() >= 19)
\r
2615 return getFileLocation ("Documents");
\r
2617 return Environment.getDataDirectory().getAbsolutePath();
\r
2620 public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
\r
2621 public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
\r
2622 public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
\r
2623 public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
\r
2625 //==============================================================================
\r
2627 protected void onActivityResult (int requestCode, int resultCode, Intent data)
\r
2629 appActivityResult (requestCode, resultCode, data);
\r
2633 protected void onNewIntent (Intent intent)
\r
2635 super.onNewIntent(intent);
\r
2636 setIntent(intent);
\r
2638 appNewIntent (intent);
\r
2641 //==============================================================================
\r
2642 public final Typeface getTypeFaceFromAsset (String assetName)
\r
2646 return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
\r
2648 catch (Throwable e) {}
\r
2653 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
\r
2655 public static String bytesToHex (byte[] bytes)
\r
2657 char[] hexChars = new char[bytes.length * 2];
\r
2659 for (int j = 0; j < bytes.length; ++j)
\r
2661 int v = bytes[j] & 0xff;
\r
2662 hexChars[j * 2] = hexArray[v >>> 4];
\r
2663 hexChars[j * 2 + 1] = hexArray[v & 0x0f];
\r
2666 return new String (hexChars);
\r
2669 final private java.util.Map dataCache = new java.util.HashMap();
\r
2671 synchronized private final File getDataCacheFile (byte[] data)
\r
2675 java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
\r
2676 digest.update (data);
\r
2678 String key = bytesToHex (digest.digest());
\r
2680 if (dataCache.containsKey (key))
\r
2681 return (File) dataCache.get (key);
\r
2683 File f = new File (this.getCacheDir(), "bindata_" + key);
\r
2685 FileOutputStream os = new FileOutputStream (f);
\r
2686 os.write (data, 0, data.length);
\r
2687 dataCache.put (key, f);
\r
2690 catch (Throwable e) {}
\r
2695 private final void clearDataCache()
\r
2697 java.util.Iterator it = dataCache.values().iterator();
\r
2699 while (it.hasNext())
\r
2701 File f = (File) it.next();
\r
2706 public final Typeface getTypeFaceFromByteArray (byte[] data)
\r
2710 File f = getDataCacheFile (data);
\r
2713 return Typeface.createFromFile (f);
\r
2715 catch (Exception e)
\r
2717 Log.e ("JUCE", e.toString());
\r
2723 public static final int getAndroidSDKVersion()
\r
2725 return android.os.Build.VERSION.SDK_INT;
\r
2728 public final String audioManagerGetProperty (String property)
\r
2730 Object obj = getSystemService (AUDIO_SERVICE);
\r
2734 java.lang.reflect.Method method;
\r
2738 method = obj.getClass().getMethod ("getProperty", String.class);
\r
2740 catch (SecurityException e) { return null; }
\r
2741 catch (NoSuchMethodException e) { return null; }
\r
2743 if (method == null)
\r
2748 return (String) method.invoke (obj, property);
\r
2750 catch (java.lang.IllegalArgumentException e) {}
\r
2751 catch (java.lang.IllegalAccessException e) {}
\r
2752 catch (java.lang.reflect.InvocationTargetException e) {}
\r
2757 public final boolean hasSystemFeature (String property)
\r
2759 return getPackageManager().hasSystemFeature (property);
\r